dev #1

Merged
bunny merged 4 commits from dev into master 2024-05-13 14:19:29 +08:00
17 changed files with 708 additions and 361 deletions
Showing only changes of commit 7ab9f084ed - Show all commits

View File

@ -12,7 +12,7 @@ export default {
"subject-empty": [2, "never"], "subject-empty": [2, "never"],
"type-empty": [2, "never"], "type-empty": [2, "never"],
"subject-case": [0], "subject-case": [0],
"type-enum": [2, "always", ["feat", "fix", "media", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert", "wip", "workflow", "types", "release"]] "type-enum": [2, "always", ["init", "optimize", "feat", "fix", "media", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert", "wip", "workflow", "types", "release"]]
}, },
prompt: { prompt: {
messages: { messages: {
@ -28,6 +28,8 @@ export default {
confirmCommit: "是否提交或修改commit ?" confirmCommit: "是否提交或修改commit ?"
}, },
types: [ types: [
{ value: "init", name: "初始化: ⏳ 初始化项目", emoji: "⏳" },
{ value: "optimize", name: "优化代码: ♻️ 优化项目代码", emoji: "♻️" },
{ value: "feat", name: "特性: 🚀 新增功能", emoji: "🚀" }, { value: "feat", name: "特性: 🚀 新增功能", emoji: "🚀" },
{ value: "media", name: "媒体: 🎁 新增媒体资源", emoji: "🎁" }, { value: "media", name: "媒体: 🎁 新增媒体资源", emoji: "🎁" },
{ value: "fix", name: "修复: 🧩 修复缺陷", emoji: "🧩" }, { value: "fix", name: "修复: 🧩 修复缺陷", emoji: "🧩" },

View File

@ -180,10 +180,7 @@ menus:
pureMindMap: Mind Map pureMindMap: Mind Map
pureMenuOverflow: Menu Overflow Show Tooltip Text pureMenuOverflow: Menu Overflow Show Tooltip Text
pureChildMenuOverflow: Child Menu Overflow Show Tooltip Text pureChildMenuOverflow: Child Menu Overflow Show Tooltip Text
bills: Ledger management systemctlTest: Systemctl lTest
billRecord: Billing record
billHistory: Billing History
billCount: Billing Count
status: status:
pureLoad: Loading... pureLoad: Loading...
pureMessage: Message pureMessage: Message
@ -234,3 +231,5 @@ login:
purePassWordSureReg: Please enter confirm password purePassWordSureReg: Please enter confirm password
purePassWordDifferentReg: The two passwords do not match! purePassWordDifferentReg: The two passwords do not match!
purePassWordUpdateReg: Password has been updated purePassWordUpdateReg: Password has been updated
table:
tableNumber: Table Number

View File

@ -180,10 +180,7 @@ menus:
pureMindMap: 思维导图 pureMindMap: 思维导图
pureMenuOverflow: 目录超出显示 Tooltip 文字提示 pureMenuOverflow: 目录超出显示 Tooltip 文字提示
pureChildMenuOverflow: 菜单超出显示 Tooltip 文字提示 pureChildMenuOverflow: 菜单超出显示 Tooltip 文字提示
bills: 台账管理 systemctlTest: 系统测试
billRecord: 账单记录
billHistory: 历史账单
billCount: 账单统计
status: status:
pureLoad: 加载中... pureLoad: 加载中...
pureMessage: 消息 pureMessage: 消息
@ -234,3 +231,5 @@ login:
purePassWordSureReg: 请输入确认密码 purePassWordSureReg: 请输入确认密码
purePassWordDifferentReg: 两次密码不一致! purePassWordDifferentReg: 两次密码不一致!
purePassWordUpdateReg: 修改密码成功 purePassWordUpdateReg: 修改密码成功
table:
tableNumber: 序号

View File

@ -1,6 +1,6 @@
// 模拟后端动态生成路由 // 模拟后端动态生成路由
import { defineFakeRoute } from "vite-plugin-fake-server/client"; import { defineFakeRoute } from "vite-plugin-fake-server/client";
import { bills, frame, monitor, permission, system, tabs } from "@/router/enums"; import { frame, monitor, permission, system } from "@/router/enums";
/** /**
* roles "admin""common" * roles "admin""common"
@ -102,6 +102,16 @@ const systemMonitorRouter = {
title: "menus.pureSystemLog", title: "menus.pureSystemLog",
roles: ["admin"] roles: ["admin"]
} }
},
{
path: "/monitor/system-test",
component: "monitor/test/index",
name: "SystemTest",
meta: {
icon: "ri:file-search-line",
title: "menus.systemctlTest",
roles: ["admin"]
}
} }
] ]
}; };
@ -257,48 +267,6 @@ const frameRouter = {
] ]
}; };
const tabsRouter = {
path: "/tabs",
meta: {
icon: "ri:bookmark-2-line",
title: "menus.pureTabs",
rank: tabs
},
children: [
{
path: "/tabs/index",
name: "Tabs",
meta: {
title: "menus.pureTabs",
roles: ["admin", "common"]
}
},
// query 传参模式
{
path: "/tabs/query-detail",
name: "TabQueryDetail",
meta: {
// 不在menu菜单中显示
showLink: false,
activePath: "/tabs/index",
roles: ["admin", "common"]
}
},
// params 传参模式
{
path: "/tabs/params-detail/:id",
component: "params-detail",
name: "TabParamsDetail",
meta: {
// 不在menu菜单中显示
showLink: false,
activePath: "/tabs/index",
roles: ["admin", "common"]
}
}
]
};
const about = { const about = {
path: "/about", path: "/about",
meta: { meta: {
@ -324,7 +292,7 @@ export default defineFakeRoute([
response: () => { response: () => {
return { return {
success: true, success: true,
data: [systemManagementRouter, systemMonitorRouter, permissionRouter, frameRouter, about, /*tabsRouter*/] data: [systemManagementRouter, systemMonitorRouter, permissionRouter, frameRouter, about]
}; };
} }
} }

View File

@ -1,5 +0,0 @@
import vxeTableBar from "./src/bar";
import { withInstall } from "@pureadmin/utils";
/** 配合 `vxe-table` 实现快速便捷的表格操作 */
export const VxeTableBar = withInstall(vxeTableBar);

View File

@ -1,251 +0,0 @@
import Sortable from "sortablejs";
import { transformI18n } from "@/plugins/i18n";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { cloneDeep, delay, getKeyList } from "@pureadmin/utils";
import { computed, defineComponent, getCurrentInstance, nextTick, type PropType, ref, unref } from "vue";
import DragIcon from "@/assets/table-bar/drag.svg?component";
import ExpandIcon from "@/assets/table-bar/expand.svg?component";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
import SettingIcon from "@/assets/table-bar/settings.svg?component";
import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
const props = {
/** 头部最左边的标题 */
title: {
type: String,
default: "列表"
},
vxeTableRef: {
type: Object as PropType<any>
},
/** 需要展示的列 */
columns: {
type: Array as PropType<any>,
default: () => []
},
/** 是否为树列表 */
tree: {
type: Boolean,
default: false
},
isExpandAll: {
type: Boolean,
default: true
},
tableKey: {
type: [String, Number] as PropType<string | number>,
default: "0"
}
};
export default defineComponent({
name: "VxeTableBar",
props,
emits: ["refresh"],
setup(props, { emit, slots, attrs }) {
const size = ref("small");
const loading = ref(false);
const checkAll = ref(true);
const isIndeterminate = ref(false);
const instance = getCurrentInstance()!;
const isExpandAll = ref(props.isExpandAll);
let checkColumnList = getKeyList(cloneDeep(props?.columns), "title");
const checkedColumns = ref(getKeyList(cloneDeep(props?.columns), "title"));
const dynamicColumns = ref(cloneDeep(props?.columns));
const getDropdownItemStyle = computed(() => {
return s => {
return {
background: s === size.value ? useEpThemeStoreHook().epThemeColor : "",
color: s === size.value ? "#fff" : "var(--el-text-color-primary)"
};
};
});
const iconClass = computed(() => {
return ["text-black", "dark:text-white", "duration-100", "hover:!text-primary", "cursor-pointer", "outline-none"];
});
const topClass = computed(() => {
return ["flex", "justify-between", "pt-[3px]", "px-[11px]", "border-b-[1px]", "border-solid", "border-[#dcdfe6]", "dark:border-[#303030]"];
});
function onReFresh() {
loading.value = true;
emit("refresh");
delay(500).then(() => (loading.value = false));
}
function onExpand() {
isExpandAll.value = !isExpandAll.value;
isExpandAll.value ? props.vxeTableRef.setAllTreeExpand(true) : props.vxeTableRef.clearTreeExpand();
props.vxeTableRef.refreshColumn();
}
function reloadColumn() {
const curCheckedColumns = cloneDeep(dynamicColumns.value).filter(item => checkedColumns.value.includes(item.title));
props.vxeTableRef.reloadColumn(curCheckedColumns);
}
function handleCheckAllChange(val: boolean) {
checkedColumns.value = val ? checkColumnList : [];
isIndeterminate.value = false;
reloadColumn();
}
function handleCheckedColumnsChange(value: string[]) {
checkedColumns.value = value;
const checkedCount = value.length;
checkAll.value = checkedCount === checkColumnList.length;
isIndeterminate.value = checkedCount > 0 && checkedCount < checkColumnList.length;
}
async function onReset() {
checkAll.value = true;
isIndeterminate.value = false;
dynamicColumns.value = cloneDeep(props?.columns);
checkColumnList = [];
checkColumnList = await getKeyList(cloneDeep(props?.columns), "title");
checkedColumns.value = getKeyList(cloneDeep(props?.columns), "title");
props.vxeTableRef.refreshColumn();
}
function changeSize(curSize: string) {
size.value = curSize;
props.vxeTableRef.refreshColumn();
}
const dropdown = {
dropdown: () => (
<el-dropdown-menu class="translation">
<el-dropdown-item style={getDropdownItemStyle.value("medium")} onClick={() => changeSize("medium")}>
</el-dropdown-item>
<el-dropdown-item style={getDropdownItemStyle.value("small")} onClick={() => changeSize("small")}>
</el-dropdown-item>
<el-dropdown-item style={getDropdownItemStyle.value("mini")} onClick={() => changeSize("mini")}>
</el-dropdown-item>
</el-dropdown-menu>
)
};
/** 列展示拖拽排序 */
const rowDrop = (event: { preventDefault: () => void }) => {
event.preventDefault();
nextTick(() => {
const wrapper: HTMLElement = (instance?.proxy?.$refs[`VxeGroupRef${unref(props.tableKey)}`] as any).$el.firstElementChild;
Sortable.create(wrapper, {
animation: 300,
handle: ".drag-btn",
onEnd: ({ newIndex, oldIndex, item }) => {
const targetThElem = item;
const wrapperElem = targetThElem.parentNode as HTMLElement;
const oldColumn = dynamicColumns.value[oldIndex];
const newColumn = dynamicColumns.value[newIndex];
if (oldColumn?.fixed || newColumn?.fixed) {
// 当前列存在fixed属性 则不可拖拽
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
if (newIndex > oldIndex) {
wrapperElem.insertBefore(targetThElem, oldThElem);
} else {
wrapperElem.insertBefore(targetThElem, oldThElem ? oldThElem.nextElementSibling : oldThElem);
}
return;
}
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
dynamicColumns.value.splice(newIndex, 0, currentRow);
reloadColumn();
}
});
});
};
const isFixedColumn = (title: string) => {
return dynamicColumns.value.filter(item => transformI18n(item.title) === transformI18n(title))[0].fixed ? true : false;
};
const rendTippyProps = (content: string) => {
// https://vue-tippy.netlify.app/props
return {
content,
offset: [0, 18],
duration: [300, 0],
followCursor: true,
hideOnClick: "toggle"
};
};
const reference = {
reference: () => <SettingIcon class={["w-[16px]", iconClass.value]} v-tippy={rendTippyProps("列设置")} />
};
return () => (
<>
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
{slots?.title ? slots.title() : <p class="font-bold truncate">{props.title}</p>}
<div class="flex items-center justify-around">
{slots?.buttons ? <div class="flex mr-4">{slots.buttons()}</div> : null}
{props.tree ? (
<>
<ExpandIcon
class={["w-[16px]", iconClass.value]}
style={{
transform: isExpandAll.value ? "none" : "rotate(-90deg)"
}}
v-tippy={rendTippyProps(isExpandAll.value ? "折叠" : "展开")}
onClick={() => onExpand()}
/>
<el-divider direction="vertical" />
</>
) : null}
<RefreshIcon class={["w-[16px]", iconClass.value, loading.value ? "animate-spin" : ""]} v-tippy={rendTippyProps("刷新")} onClick={() => onReFresh()} />
<el-divider direction="vertical" />
<el-dropdown v-slots={dropdown} trigger="click" v-tippy={rendTippyProps("密度")}>
<CollapseIcon class={["w-[16px]", iconClass.value]} />
</el-dropdown>
<el-divider direction="vertical" />
<el-popover v-slots={reference} placement="bottom-start" popper-style={{ padding: 0 }} width="200" trigger="click">
<div class={[topClass.value]}>
<el-checkbox class="!-mr-1" label="列展示" v-model={checkAll.value} indeterminate={isIndeterminate.value} onChange={value => handleCheckAllChange(value)} />
<el-button type="primary" link onClick={() => onReset()}>
</el-button>
</div>
<div class="pt-[6px] pl-[11px]">
<el-scrollbar max-height="36vh">
<el-checkbox-group ref={`VxeGroupRef${unref(props.tableKey)}`} modelValue={checkedColumns.value} onChange={value => handleCheckedColumnsChange(value)}>
<el-space direction="vertical" alignment="flex-start" size={0}>
{checkColumnList.map((item, index) => {
return (
<div class="flex items-center">
<DragIcon class={["drag-btn w-[16px] mr-2", isFixedColumn(item) ? "!cursor-no-drop" : "!cursor-grab"]} onMouseenter={(event: { preventDefault: () => void }) => rowDrop(event)} />
<el-checkbox key={index} label={item} value={item} onChange={reloadColumn}>
<span title={transformI18n(item)} class="inline-block w-[120px] truncate hover:text-text_color_primary">
{transformI18n(item)}
</span>
</el-checkbox>
</div>
);
})}
</el-space>
</el-checkbox-group>
</el-scrollbar>
</div>
</el-popover>
</div>
</div>
{slots.default({
size: size.value,
dynamicColumns: dynamicColumns.value
})}
</div>
</>
);
}
});

View File

@ -0,0 +1,48 @@
<template>
<div class="main">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:size="size"
adaptive
align-whole="center"
border
row-key="id"
table-layout="auto"
/>
</div>
</template>
<script lang="ts" setup>
import { cellHeaderStyle } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
import type { PropType } from "vue";
// *
defineProps({
//
dataList: {
type: Array<any>,
default: []
},
//
column: {
type: Array<any>,
default: []
},
loading: {
type: Boolean,
default: false
},
size: {
type: String as PropType<any>,
default: "default"
}
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,282 @@
<template>
<div class="main main-content">
<!-- 表单设置外加插槽 -->
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<slot name="tableForm" />
</el-form>
<!-- 表格头部设置 -->
<div class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
<!-- 自定义左边头部内容 -->
<slot name="tableTitle">
<p class="font-bold truncate">{{ tableTitle ? tableTitle : t(route.meta.title) }}</p>
</slot>
<!-- 自定义表格操作内容 -->
<slot name="tableOperation">
<div class="flex items-center justify-around">
<!-- 插槽内容 -->
<div class="mr-4">
<slot name="tableButtons" />
</div>
<!-- 右边操作按钮 -->
<RefreshIcon v-tippy="rendTipProps('刷新')" :class="`w-[16px] ${iconClass()}} ${loading ? 'animate-spin' : ''}`" @click="onReFresh" />
<el-divider direction="vertical" />
<el-dropdown trigger="click">
<CollapseIcon :class="`w-[16px] ${iconClass()}`" />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item :style="getDropdownItemStyle(size, 'large')" @click="handleTableSizeClick('large')"> 宽松 </el-dropdown-item>
<el-dropdown-item :style="getDropdownItemStyle(size, 'default')" @click="handleTableSizeClick('default')"> 默认 </el-dropdown-item>
<el-dropdown-item :style="getDropdownItemStyle(size, 'small')" @click="handleTableSizeClick('small')"> 紧凑 </el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-divider direction="vertical" />
<el-popover :popper-style="{ padding: 0 }" placement="bottom-start" trigger="click" width="200">
<template #reference>
<SettingIcon v-tippy="rendTipProps('列设置')" :class="`w-[16px] ${iconClass()}`" />
</template>
<div :class="topClass()">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" class="!-mr-1" label="列展示" @change="handleCheckAllChange" />
<el-button link type="primary" @click="onReset"> 重置</el-button>
</div>
<div class="pt-[6px] pl-[11px]">
<el-scrollbar max-height="36vh">
<el-checkbox-group :ref="`GroupRef${unref(props.tableKey)}`" :modelValue="checkedColumns" @change="handleCheckedColumnsChange">
<el-space :alignment="'flex-start'" :size="0" direction="vertical">
<div v-for="(item, index) in checkColumnList" :key="index" class="flex items-center">
<DragIcon :class="`drag-btn w-[16px] mr-2 ${isFixedColumn(item) ? '!cursor-no-drop' : '!cursor-grab'}`" @mouseenter="rowDrop" />
<el-checkbox :key="index" :label="item" :value="item" @change="handleCheckColumnListChange(true, item)">
<span class="inline-block w-[120px] truncate hover:text-text_color_primary" title="{transformI18n(item)}"> {{ transformI18n(item) }} </span>
</el-checkbox>
</div>
</el-space>
</el-checkbox-group>
</el-scrollbar>
</div>
</el-popover>
</div>
</slot>
</div>
<slot name="tableSelect" />
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
border
table-layout="auto"
v-bind="$attrs"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<template v-for="(item, index) in column" :key="index" v-slot:[item.slot]="slotProps">
<slot :name="item.slot" v-bind="slotProps" />
</template>
</pure-table>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, getCurrentInstance, nextTick, onMounted, PropType, ref, unref, watch } from "vue";
import { rendTipProps } from "@/components/TableBar/utils/tableConfig";
import { cellHeaderStyle, iconClass, topClass } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import SettingIcon from "@/assets/table-bar/settings.svg?component";
import { cloneDeep, getKeyList, isBoolean, isFunction } from "@pureadmin/utils";
import { transformI18n } from "@/plugins/i18n";
import DragIcon from "@/assets/table-bar/drag.svg?component";
import Sortable from "sortablejs";
// *
const props = defineProps({
//
dataList: { type: Array<any>, default: [] },
//
column: { type: Array<any>, default: [] },
//
loading: { type: Boolean, default: false },
// small | default | large
size: { type: String as PropType<any>, default: "default" },
//
pagination: { type: Object, default: Object },
//
handleSelectionChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleSizeChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleCurrentChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
form: {
type: Object as PropType<any>,
default: Object
},
onReFresh: {
type: Function as PropType<Function>,
default: () => {}
},
tableKey: {
type: [String, Number] as PropType<string | number>,
default: "0"
},
tableTitle: { type: String, default: "" },
class: { type: String, default: "" }
});
const emit = defineEmits(["changeColumn"]);
const { t } = useI18n();
const router = useRouter();
const route = useRoute();
const checkAll = ref(true);
const size = ref(props.size);
const isIndeterminate = ref(false);
const dynamicColumns = ref(props.column);
const filterColumns = cloneDeep(props.column).filter(column => (isBoolean(column?.hide) ? !column.hide : !(isFunction(column?.hide) && column?.hide())));
const checkedColumns = ref(getKeyList(cloneDeep(filterColumns), "label"));
let checkColumnList = getKeyList(cloneDeep(props.column), "label");
const instance = getCurrentInstance()!;
const getDropdownItemStyle = computed(() => {
return (size: string, s: string) => {
return {
background: s === size ? useEpThemeStoreHook().epThemeColor : "",
color: s === size ? "#fff" : "var(--el-text-color-primary)"
};
};
});
/**
* * 修改表格样式大小
* @param value 修改样式大小 larger | default | small
*/
const handleTableSizeClick = (value: string) => {
size.value = value;
};
/**
* 设置表格列是否全部显示
* @param val 是否全部显示
*/
const handleCheckAllChange = (val: boolean) => {
checkedColumns.value = val ? checkColumnList : [];
isIndeterminate.value = false;
dynamicColumns.value.map(column => (val ? (column.hide = false) : (column.hide = true)));
};
const handleCheckedColumnsChange = (value: string[]) => {
checkedColumns.value = value;
const checkedCount = value.length;
checkAll.value = checkedCount === checkColumnList.length;
isIndeterminate.value = checkedCount > 0 && checkedCount < checkColumnList.length;
};
const handleCheckColumnListChange = (val: boolean, label: string) => {
dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].hide = !val;
};
const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].fixed;
};
const onReset = () => {
checkAll.value = true;
isIndeterminate.value = false;
// dynamicColumns.value = cloneDeep(props.column);
// checkColumnList = [];
// checkColumnList = getKeyList(cloneDeep(props?.column), "label");
checkedColumns.value = getKeyList(cloneDeep(filterColumns), "label");
const list = [];
checkedColumns.value.forEach(item => {
dynamicColumns.value.forEach(column => {
if (column.label == item) {
list.push(column);
}
});
});
emit("changeColumn", list);
};
/** 列展示拖拽排序 */
const rowDrop = (event: any) => {
event.preventDefault();
nextTick(() => {
const wrapper: HTMLElement = (instance?.proxy?.$refs[`GroupRef${unref(props.tableKey)}`] as any).$el.firstElementChild;
Sortable.create(wrapper, {
animation: 300,
handle: ".drag-btn",
onEnd: ({ newIndex, oldIndex, item }) => {
const targetThElem = item;
const wrapperElem = targetThElem.parentNode as HTMLElement;
const oldColumn = dynamicColumns.value[oldIndex];
const newColumn = dynamicColumns.value[newIndex];
if (oldColumn?.fixed || newColumn?.fixed) {
// fixed
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
if (newIndex > oldIndex) {
wrapperElem.insertBefore(targetThElem, oldThElem);
} else {
wrapperElem.insertBefore(targetThElem, oldThElem ? oldThElem.nextElementSibling : oldThElem);
}
return;
}
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
dynamicColumns.value.splice(newIndex, 0, currentRow);
emit("changeColumn", props.column);
}
});
}).then();
};
onMounted(() => {
watch([() => props.column], () => {
dynamicColumns.value = props.column;
});
});
</script>
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
.main-content {
margin: 24px 24px 0 !important;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<div class="main mt-2 p-2 bg-bg_color">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
border
row-key="id"
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
/>
</div>
</template>
<script lang="ts" setup>
import { cellHeaderStyle } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
import type { PropType } from "vue";
// *
defineProps({
//
dataList: {
type: Array<any>,
default: []
},
//
column: {
type: Array<any>,
default: []
},
//
loading: {
type: Boolean,
default: false
},
// small | default | large
size: {
type: String as PropType<any>,
default: "default"
},
//
pagination: {
type: Object,
default: Object
},
//
handleSelectionChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleSizeChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleCurrentChange: {
type: Function as PropType<Function>,
default: () => {}
}
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,100 @@
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<slot name="tableForm" />
</el-form>
<div class="mt-2 p-2 bg-bg_color">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
border
row-key="id"
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { cellHeaderStyle } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
// *
defineProps({
//
dataList: {
type: Array<any>,
default: []
},
//
column: {
type: Array<any>,
default: []
},
//
loading: {
type: Boolean,
default: false
},
// small | default | large
size: {
type: String as PropType<any>,
default: "default"
},
//
pagination: {
type: Object,
default: Object
},
//
handleSelectionChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleSizeChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleCurrentChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
form: {
type: Object as PropType<any>,
default: Object
}
});
</script>
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
.main-content {
margin: 24px 24px 0 !important;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -188,7 +188,7 @@ export default defineComponent({
}; };
const isFixedColumn = (label: string) => { const isFixedColumn = (label: string) => {
return !!dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].fixed; return dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].fixed;
}; };
const rendTippyProps = (content: string) => { const rendTippyProps = (content: string) => {
@ -244,7 +244,7 @@ export default defineComponent({
<div class="pt-[6px] pl-[11px]"> <div class="pt-[6px] pl-[11px]">
<el-scrollbar max-height="36vh"> <el-scrollbar max-height="36vh">
<el-checkbox-group ref={`GroupRef${unref(props.tableKey)}`} modelValue={checkedColumns.value} onChange={value => handleCheckedColumnsChange(value)}> <el-checkbox-group ref={`GroupRef${unref(props.tableKey)}`} modelValue={checkedColumns.value} onChange={value => handleCheckedColumnsChange(value)}>
<el-space direction="vertical" alignment="flex-start" size={0}> <el-space direction="vertical" alignment={"flex-start"} size={0}>
{checkColumnList.map((item, index) => { {checkColumnList.map((item, index) => {
return ( return (
<div class="flex items-center"> <div class="flex items-center">
@ -271,24 +271,6 @@ export default defineComponent({
size: size.value, size: size.value,
dynamicColumns: dynamicColumns.value dynamicColumns: dynamicColumns.value
})} })}
{/*<PureTable*/}
{/* ref="tableRef"*/}
{/* columns={dynamicColumns.value}*/}
{/* adaptiveConfig={{ offsetBottom: 108 }}*/}
{/* headerCellStyle={{*/}
{/* background: "var(--el-fill-color-light)",*/}
{/* color: "var(--el-text-color-primary)"*/}
{/* }}*/}
{/* adaptive*/}
{/* align-whole="center"*/}
{/* row-key="id"*/}
{/* table-layout="auto"*/}
{/* data={props.dataList}*/}
{/* loading={props.loading}*/}
{/* pagination={props.pagination}*/}
{/* paginationSmall={props.size == "small"}*/}
{/* {...props}*/}
{/*/>*/}
</div> </div>
</> </>
); );

View File

@ -0,0 +1,7 @@
export const rendTipProps = (content: string) => ({
content,
offset: [0, 18],
duration: [300, 0],
followCursor: true,
hideOnClick: "toggle"
});

View File

@ -0,0 +1,11 @@
/**
* *
*/
export const cellHeaderStyle = () => ({
background: "var(--el-fill-color-light)",
color: "var(--el-text-color-primary)"
});
export const iconClass = () => "text-black dark:text-white duration-100 hover:!text-primary cursor-pointer outline-none";
export const topClass = () => "flex justify-between pt-[3px] px-[11px] border-b-[1px] border-solid border-[#dcdfe6] dark:border-[#303030]";

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { ref, computed } from "vue"; import { computed, ref } from "vue";
import { noticesData } from "./data"; import { noticesData } from "./data";
import NoticeList from "./components/NoticeList.vue"; import NoticeList from "./components/NoticeList.vue";
import BellIcon from "@iconify-icons/ep/bell"; import BellIcon from "@iconify-icons/ep/bell";
@ -12,23 +12,13 @@ const activeKey = ref(noticesData[0]?.key);
notices.value.map(v => (noticesNum.value += v.list.length)); notices.value.map(v => (noticesNum.value += v.list.length));
const getLabel = computed( const getLabel = computed(() => item => t(item.name) + (item.list.length > 0 ? `(${item.list.length})` : ""));
() => item =>
t(item.name) + (item.list.length > 0 ? `(${item.list.length})` : "")
);
</script> </script>
<template> <template>
<el-dropdown trigger="click" placement="bottom-end"> <el-dropdown placement="bottom-end" trigger="click">
<span <span :class="['dropdown-badge', 'navbar-bg-hover', 'select-none', Number(noticesNum) !== 0 && 'mr-[10px]']">
:class="[ <el-badge :max="99" :value="Number(noticesNum) === 0 ? '' : noticesNum">
'dropdown-badge',
'navbar-bg-hover',
'select-none',
Number(noticesNum) !== 0 && 'mr-[10px]'
]"
>
<el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99">
<span class="header-notice-icon"> <span class="header-notice-icon">
<IconifyIconOffline :icon="BellIcon" /> <IconifyIconOffline :icon="BellIcon" />
</span> </span>
@ -36,23 +26,14 @@ const getLabel = computed(
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-tabs <el-tabs v-model="activeKey" :stretch="true" :style="{ width: notices.length === 0 ? '200px' : '330px' }" class="dropdown-tabs">
v-model="activeKey" <el-empty v-if="notices.length === 0" :description="t('status.pureNoMessage')" :image-size="60" />
:stretch="true"
class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }"
>
<el-empty
v-if="notices.length === 0"
:description="t('status.pureNoMessage')"
:image-size="60"
/>
<span v-else> <span v-else>
<template v-for="item in notices" :key="item.key"> <template v-for="item in notices" :key="item.key">
<el-tab-pane :label="getLabel(item)" :name="`${item.key}`"> <el-tab-pane :label="getLabel(item)" :name="`${item.key}`">
<el-scrollbar max-height="330px"> <el-scrollbar max-height="330px">
<div class="noticeList-container"> <div class="noticeList-container">
<NoticeList :list="item.list" :emptyText="item.emptyText" /> <NoticeList :emptyText="item.emptyText" :list="item.list" />
</div> </div>
</el-scrollbar> </el-scrollbar>
</el-tab-pane> </el-tab-pane>

View File

@ -13,7 +13,6 @@ import { injectResponsiveStorage } from "@/utils/responsive";
import { FontIcon, IconifyIconOffline, IconifyIconOnline } from "./components/ReIcon"; import { FontIcon, IconifyIconOffline, IconifyIconOnline } from "./components/ReIcon";
import Table from "@pureadmin/table"; import Table from "@pureadmin/table";
import PureDescriptions from "@pureadmin/descriptions"; import PureDescriptions from "@pureadmin/descriptions";
// 引入重置样式 // 引入重置样式
import "./style/reset.scss"; import "./style/reset.scss";
// 导入公共样式 // 导入公共样式
@ -42,7 +41,6 @@ Object.keys(directives).forEach(key => {
app.component("IconifyIconOffline", IconifyIconOffline); app.component("IconifyIconOffline", IconifyIconOffline);
app.component("IconifyIconOnline", IconifyIconOnline); app.component("IconifyIconOnline", IconifyIconOnline);
app.component("FontIcon", FontIcon); app.component("FontIcon", FontIcon);
app.component("Auth", Auth); app.component("Auth", Auth);
app.use(VueTippy); app.use(VueTippy);

View File

@ -82,7 +82,7 @@ const {
}" }"
:loading="loading" :loading="loading"
:pagination="pagination" :pagination="pagination"
:paginationSmall="size === 'small' ? true : false" :paginationSmall="size === 'small'"
:size="size" :size="size"
adaptive adaptive
align-whole="center" align-whole="center"

View File

@ -0,0 +1,151 @@
<script lang="ts" setup>
import { onMounted, reactive, ref } from "vue";
import { PaginationProps } from "@pureadmin/table";
import TablePlusBar from "@/components/TableBar/src/TablePlusBar.vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Refresh from "@iconify-icons/ep/refresh";
import { getPickerShortcuts } from "@/views/monitor/utils";
import { delay } from "@pureadmin/utils";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const loading = ref(true);
const form = reactive({});
const selectedNum = ref(0);
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true
});
let columns = ref([
{ label: t("table.tableNumber"), prop: "id", minWidth: 60 },
{ label: "用户名", prop: "username", minWidth: 100 },
{ label: "登录 IP", prop: "ip", minWidth: 140 },
{ label: "登录地点", prop: "address", minWidth: 140 },
{ label: "操作系统", prop: "system", minWidth: 100 },
{ label: "浏览器类型", prop: "browser", minWidth: 100 },
{ label: "登录时间", prop: "loginTime", minWidth: 180 },
{ label: "操作", fixed: "right", slot: "operation" }
]);
const dataList = [
{
id: 1,
username: "admin",
ip: "163.108.28.226",
address: "中国河南省信阳市",
system: "macOS",
browser: "Chrome",
loginTime: "2024-05-12T07:33:12.080Z"
},
{
id: 2,
username: "common",
ip: "106.62.252.181",
address: "中国广东省深圳市",
system: "Windows",
browser: "Firefox",
loginTime: "2024-05-12T07:33:12.080Z"
}
];
const onSearch = () => {
console.log(form);
console.log("搜索。。。");
};
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
function handleSizeChange(val: number) {
console.log(`${val} items per page`);
}
function handleCurrentChange(val: number) {
console.log(`current page: ${val}`);
}
function handleSelectionChange(val) {
console.log("handleSelectionChange", val);
}
function onReFresh() {
loading.value = true;
delay(500).then(() => (loading.value = false));
}
function handleChangeColumn(value: Object[]) {
columns.value = value;
}
const onSelectionCancel = () => {};
const onbatchDel = () => {};
const handleCellDblclick = val => {
console.log(val);
};
onMounted(() => {
setTimeout(() => (loading.value = false), 1000);
});
</script>
<template>
<TablePlusBar
:column="columns"
:data-list="dataList"
:form="form"
:handle-current-change="handleCurrentChange"
:handle-selection-change="handleSelectionChange"
:handle-size-change="handleSizeChange"
:loading="loading"
:onReFresh="onReFresh"
:pagination="pagination"
row-key="id"
tableTitle="系统测试(仅演示,操作后不生效)"
@changeColumn="handleChangeColumn"
@cell-dblclick="handleCellDblclick"
>
<template #tableForm>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" class="!w-[150px]" clearable placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="登录状态" prop="status">
<el-select v-model="form.status" class="!w-[150px]" clearable placeholder="请选择">
<el-option label="成功" value="1" />
<el-option label="失败" value="0" />
</el-select>
</el-form-item>
<el-form-item label="登录时间" prop="loginTime">
<el-date-picker v-model="form.loginTime" :shortcuts="getPickerShortcuts()" end-placeholder="结束日期时间" range-separator="" start-placeholder="开始日期时间" type="datetimerange" />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置</el-button>
</el-form-item>
</template>
<template #tableButtons>
<el-button type="danger">按钮</el-button>
</template>
<template #tableSelect>
<div v-if="selectedNum > 0" v-motion-fade class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center">
<div class="flex-auto">
<span class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" style="font-size: var(--el-font-size-base)"> 已选 {{ selectedNum }} </span>
<el-button text type="primary" @click="onSelectionCancel"> 取消选择</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<template #reference>
<el-button class="mr-1" text type="danger"> 批量删除</el-button>
</template>
</el-popconfirm>
</div>
</template>
<template #operation> 111</template>
</TablePlusBar>
</template>