xiaoxian521
2 years ago
76 changed files with 4152 additions and 2089 deletions
-
3.env
-
2.env.staging
-
3.stylelintignore
-
2LICENSE
-
2README.en-US.md
-
2README.md
-
1build/index.ts
-
4build/info.ts
-
2build/optimize.ts
-
3build/plugins.ts
-
38index.html
-
123package.json
-
4239pnpm-lock.yaml
-
1postcss.config.js
-
5public/serverConfig.json
-
5src/App.vue
-
29src/components/ReCol/index.ts
-
39src/components/ReDialog/index.ts
-
118src/components/ReDialog/index.vue
-
216src/components/ReDialog/type.ts
-
5src/components/RePureTableBar/index.ts
-
339src/components/RePureTableBar/src/bar.tsx
-
1src/components/RePureTableBar/src/svg/collapse.svg
-
1src/components/RePureTableBar/src/svg/drag.svg
-
1src/components/RePureTableBar/src/svg/expand.svg
-
1src/components/RePureTableBar/src/svg/refresh.svg
-
1src/components/RePureTableBar/src/svg/settings.svg
-
5src/config/index.ts
-
4src/layout/components/appMain.vue
-
16src/layout/components/navbar.vue
-
6src/layout/components/notice/index.vue
-
9src/layout/components/notice/noticeItem.vue
-
48src/layout/components/panel/index.vue
-
4src/layout/components/search/components/SearchResult.vue
-
34src/layout/components/setting/index.vue
-
33src/layout/components/sidebar/breadCrumb.vue
-
6src/layout/components/sidebar/horizontal.vue
-
22src/layout/components/sidebar/logo.vue
-
2src/layout/components/sidebar/mixNav.vue
-
11src/layout/components/sidebar/sidebarItem.vue
-
14src/layout/components/sidebar/vertical.vue
-
99src/layout/components/tag/index.scss
-
48src/layout/components/tag/index.vue
-
4src/layout/frameView.vue
-
7src/layout/hooks/useNav.ts
-
17src/layout/hooks/useTag.ts
-
26src/layout/index.vue
-
31src/layout/types.ts
-
5src/plugins/i18n.ts
-
13src/router/index.ts
-
4src/router/modules/home.ts
-
3src/router/modules/remaining.ts
-
63src/router/utils.ts
-
27src/store/modules/app.ts
-
29src/store/modules/epTheme.ts
-
28src/store/modules/multiTags.ts
-
23src/store/modules/permission.ts
-
15src/store/modules/settings.ts
-
21src/style/dark.scss
-
18src/style/element-plus.scss
-
9src/style/index.scss
-
28src/style/mixin.scss
-
45src/style/reset.scss
-
168src/style/sidebar.scss
-
5src/style/transition.scss
-
2src/utils/http/index.ts
-
6src/utils/responsive.ts
-
5src/views/error/403.vue
-
5src/views/error/404.vue
-
5src/views/error/500.vue
-
4src/views/login/index.vue
-
62stylelint.config.js
-
4tailwind.config.js
-
8tsconfig.json
-
2types/global-components.d.ts
-
2types/global.d.ts
@ -1,2 +1,5 @@ |
|||||
# 平台本地运行端口号 |
# 平台本地运行端口号 |
||||
VITE_PORT = 8848 |
VITE_PORT = 8848 |
||||
|
|
||||
|
# 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置) |
||||
|
VITE_HIDE_HOME = false |
@ -1,3 +1,4 @@ |
|||||
/dist/* |
/dist/* |
||||
/public/* |
/public/* |
||||
public/* |
|
||||
|
public/* |
||||
|
src/style/reset.scss |
4239
pnpm-lock.yaml
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,29 @@ |
|||||
|
import { ElCol } from "element-plus"; |
||||
|
import { h, defineComponent } from "vue"; |
||||
|
|
||||
|
// 封装element-plus的el-col组件
|
||||
|
export default defineComponent({ |
||||
|
name: "ReCol", |
||||
|
props: { |
||||
|
value: { |
||||
|
type: Number, |
||||
|
default: 24 |
||||
|
} |
||||
|
}, |
||||
|
render() { |
||||
|
const attrs = this.$attrs; |
||||
|
const val = this.value; |
||||
|
return h( |
||||
|
ElCol, |
||||
|
{ |
||||
|
xs: val, |
||||
|
sm: val, |
||||
|
md: val, |
||||
|
lg: val, |
||||
|
xl: val, |
||||
|
...attrs |
||||
|
}, |
||||
|
{ default: () => this.$slots.default() } |
||||
|
); |
||||
|
} |
||||
|
}); |
@ -0,0 +1,39 @@ |
|||||
|
import { ref } from "vue"; |
||||
|
import reDialog from "./index.vue"; |
||||
|
import { useTimeoutFn } from "@vueuse/core"; |
||||
|
import { withInstall } from "@pureadmin/utils"; |
||||
|
import type { |
||||
|
EventType, |
||||
|
ArgsType, |
||||
|
DialogProps, |
||||
|
ButtonProps, |
||||
|
DialogOptions |
||||
|
} from "./type"; |
||||
|
|
||||
|
const dialogStore = ref<Array<DialogOptions>>([]); |
||||
|
|
||||
|
const addDialog = (options: DialogOptions) => { |
||||
|
const open = () => |
||||
|
dialogStore.value.push(Object.assign(options, { visible: true })); |
||||
|
if (options?.openDelay) { |
||||
|
useTimeoutFn(() => { |
||||
|
open(); |
||||
|
}, options.openDelay); |
||||
|
} else { |
||||
|
open(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const closeDialog = (options: DialogOptions, index: number, args?: any) => { |
||||
|
dialogStore.value.splice(index, 1); |
||||
|
options.closeCallBack && options.closeCallBack({ options, index, args }); |
||||
|
}; |
||||
|
|
||||
|
const closeAllDialog = () => { |
||||
|
dialogStore.value = []; |
||||
|
}; |
||||
|
|
||||
|
const ReDialog = withInstall(reDialog); |
||||
|
|
||||
|
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; |
||||
|
export { ReDialog, dialogStore, addDialog, closeDialog, closeAllDialog }; |
@ -0,0 +1,118 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { computed } from "vue"; |
||||
|
import { isFunction } from "@pureadmin/utils"; |
||||
|
import { |
||||
|
type DialogOptions, |
||||
|
type ButtonProps, |
||||
|
type EventType, |
||||
|
dialogStore, |
||||
|
closeDialog |
||||
|
} from "./index"; |
||||
|
|
||||
|
const footerButtons = computed(() => { |
||||
|
return (options: DialogOptions) => { |
||||
|
return options?.footerButtons?.length > 0 |
||||
|
? options.footerButtons |
||||
|
: ([ |
||||
|
{ |
||||
|
label: "取消", |
||||
|
text: true, |
||||
|
bg: true, |
||||
|
btnClick: ({ dialog: { options, index } }) => { |
||||
|
const done = () => |
||||
|
closeDialog(options, index, { command: "cancel" }); |
||||
|
if (options?.beforeCancel && isFunction(options?.beforeCancel)) { |
||||
|
options.beforeCancel(done, { options, index }); |
||||
|
} else { |
||||
|
done(); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
label: "确定", |
||||
|
type: "primary", |
||||
|
text: true, |
||||
|
bg: true, |
||||
|
btnClick: ({ dialog: { options, index } }) => { |
||||
|
const done = () => |
||||
|
closeDialog(options, index, { command: "sure" }); |
||||
|
if (options?.beforeSure && isFunction(options?.beforeSure)) { |
||||
|
options.beforeSure(done, { options, index }); |
||||
|
} else { |
||||
|
done(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
] as Array<ButtonProps>); |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
function eventsCallBack( |
||||
|
event: EventType, |
||||
|
options: DialogOptions, |
||||
|
index: number |
||||
|
) { |
||||
|
if (options?.[event] && isFunction(options?.[event])) { |
||||
|
return options?.[event]({ options, index }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleClose( |
||||
|
options: DialogOptions, |
||||
|
index: number, |
||||
|
args = { command: "close" } |
||||
|
) { |
||||
|
closeDialog(options, index, args); |
||||
|
eventsCallBack("close", options, index); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-dialog |
||||
|
v-for="(options, index) in dialogStore" |
||||
|
:key="index" |
||||
|
v-bind="options" |
||||
|
v-model="options.visible" |
||||
|
@opened="eventsCallBack('open', options, index)" |
||||
|
@close="handleClose(options, index)" |
||||
|
@openAutoFocus="eventsCallBack('openAutoFocus', options, index)" |
||||
|
@closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)" |
||||
|
> |
||||
|
<!-- header --> |
||||
|
<template |
||||
|
v-if="options?.headerRenderer" |
||||
|
#header="{ close, titleId, titleClass }" |
||||
|
> |
||||
|
<component |
||||
|
:is="options?.headerRenderer({ close, titleId, titleClass })" |
||||
|
/> |
||||
|
</template> |
||||
|
<!-- default --> |
||||
|
<component |
||||
|
v-bind="options?.props" |
||||
|
:is="options.contentRenderer({ options, index })" |
||||
|
@close="args => handleClose(options, index, args)" |
||||
|
/> |
||||
|
<!-- footer --> |
||||
|
<template v-if="!options?.hideFooter" #footer> |
||||
|
<template v-if="options?.footerRenderer"> |
||||
|
<component :is="options?.footerRenderer({ options, index })" /> |
||||
|
</template> |
||||
|
<span v-else> |
||||
|
<el-button |
||||
|
v-for="(btn, key) in footerButtons(options)" |
||||
|
:key="key" |
||||
|
v-bind="btn" |
||||
|
@click=" |
||||
|
btn.btnClick({ |
||||
|
dialog: { options, index }, |
||||
|
button: { btn, index: key } |
||||
|
}) |
||||
|
" |
||||
|
> |
||||
|
{{ btn?.label }} |
||||
|
</el-button> |
||||
|
</span> |
||||
|
</template> |
||||
|
</el-dialog> |
||||
|
</template> |
@ -0,0 +1,216 @@ |
|||||
|
import type { CSSProperties, VNode, Component } from "vue"; |
||||
|
|
||||
|
type DoneFn = (cancel?: boolean) => void; |
||||
|
type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus"; |
||||
|
type ArgsType = { |
||||
|
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */ |
||||
|
command: "cancel" | "sure" | "close"; |
||||
|
}; |
||||
|
|
||||
|
/** https://element-plus.org/zh-CN/component/dialog.html#attributes */ |
||||
|
type DialogProps = { |
||||
|
/** `Dialog` 的显示与隐藏 */ |
||||
|
visible?: boolean; |
||||
|
/** `Dialog` 的标题 */ |
||||
|
title?: string; |
||||
|
/** `Dialog` 的宽度,默认 `50%` */ |
||||
|
width?: string | number; |
||||
|
/** 是否为全屏 `Dialog`,默认 `false` */ |
||||
|
fullscreen?: boolean; |
||||
|
/** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */ |
||||
|
top?: string; |
||||
|
/** 是否需要遮罩层,默认 `true` */ |
||||
|
modal?: boolean; |
||||
|
/** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */ |
||||
|
appendToBody?: boolean; |
||||
|
/** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */ |
||||
|
lockScroll?: boolean; |
||||
|
/** `Dialog` 的自定义类名 */ |
||||
|
class?: string; |
||||
|
/** `Dialog` 的自定义样式 */ |
||||
|
style?: CSSProperties; |
||||
|
/** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */ |
||||
|
openDelay?: number; |
||||
|
/** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */ |
||||
|
closeDelay?: number; |
||||
|
/** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */ |
||||
|
closeOnClickModal?: boolean; |
||||
|
/** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */ |
||||
|
closeOnPressEscape?: boolean; |
||||
|
/** 是否显示关闭按钮,默认 `true` */ |
||||
|
showClose?: boolean; |
||||
|
/** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */ |
||||
|
beforeClose?: (done: DoneFn) => void; |
||||
|
/** 为 `Dialog` 启用可拖拽功能,默认 `false` */ |
||||
|
draggable?: boolean; |
||||
|
/** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */ |
||||
|
center?: boolean; |
||||
|
/** 是否水平垂直对齐对话框,默认 `false` */ |
||||
|
alignCenter?: boolean; |
||||
|
/** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */ |
||||
|
destroyOnClose?: boolean; |
||||
|
}; |
||||
|
|
||||
|
type BtnClickDialog = { |
||||
|
options?: DialogOptions; |
||||
|
index?: number; |
||||
|
}; |
||||
|
type BtnClickButton = { |
||||
|
btn?: ButtonProps; |
||||
|
index?: number; |
||||
|
}; |
||||
|
/** https://element-plus.org/zh-CN/component/button.html#button-attributes */ |
||||
|
type ButtonProps = { |
||||
|
/** 按钮文字 */ |
||||
|
label: string; |
||||
|
/** 按钮尺寸 */ |
||||
|
size?: "large" | "default" | "small"; |
||||
|
/** 按钮类型 */ |
||||
|
type?: "primary" | "success" | "warning" | "danger" | "info"; |
||||
|
/** 是否为朴素按钮,默认 `false` */ |
||||
|
plain?: boolean; |
||||
|
/** 是否为文字按钮,默认 `false` */ |
||||
|
text?: boolean; |
||||
|
/** 是否显示文字按钮背景颜色,默认 `false` */ |
||||
|
bg?: boolean; |
||||
|
/** 是否为链接按钮,默认 `false` */ |
||||
|
link?: boolean; |
||||
|
/** 是否为圆角按钮,默认 `false` */ |
||||
|
round?: boolean; |
||||
|
/** 是否为圆形按钮,默认 `false` */ |
||||
|
circle?: boolean; |
||||
|
/** 是否为加载中状态,默认 `false` */ |
||||
|
loading?: boolean; |
||||
|
/** 自定义加载中状态图标组件 */ |
||||
|
loadingIcon?: string | Component; |
||||
|
/** 按钮是否为禁用状态,默认 `false` */ |
||||
|
disabled?: boolean; |
||||
|
/** 图标组件 */ |
||||
|
icon?: string | Component; |
||||
|
/** 是否开启原生 `autofocus` 属性,默认 `false` */ |
||||
|
autofocus?: boolean; |
||||
|
/** 原生 `type` 属性,默认 `button` */ |
||||
|
nativeType?: "button" | "submit" | "reset"; |
||||
|
/** 自动在两个中文字符之间插入空格 */ |
||||
|
autoInsertSpace?: boolean; |
||||
|
/** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */ |
||||
|
color?: string; |
||||
|
/** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */ |
||||
|
dark?: boolean; |
||||
|
/** 自定义元素标签 */ |
||||
|
tag?: string | Component; |
||||
|
/** 点击按钮后触发的回调 */ |
||||
|
btnClick?: ({ |
||||
|
dialog, |
||||
|
button |
||||
|
}: { |
||||
|
/** 当前 `Dialog` 信息 */ |
||||
|
dialog: BtnClickDialog; |
||||
|
/** 当前 `button` 信息 */ |
||||
|
button: BtnClickButton; |
||||
|
}) => void; |
||||
|
}; |
||||
|
|
||||
|
interface DialogOptions extends DialogProps { |
||||
|
/** 内容区组件的 `props`,可通过 `defineProps` 接收 */ |
||||
|
props?: any; |
||||
|
/** 是否隐藏 `Dialog` 按钮操作区的内容 */ |
||||
|
hideFooter?: boolean; |
||||
|
/** |
||||
|
* @description 自定义对话框标题的内容渲染器 |
||||
|
* @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
|
||||
|
*/ |
||||
|
headerRenderer?: ({ |
||||
|
close, |
||||
|
titleId, |
||||
|
titleClass |
||||
|
}: { |
||||
|
close: Function; |
||||
|
titleId: string; |
||||
|
titleClass: string; |
||||
|
}) => VNode | Component; |
||||
|
/** 自定义内容渲染器 */ |
||||
|
contentRenderer?: ({ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
}) => VNode | Component; |
||||
|
/** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */ |
||||
|
footerRenderer?: ({ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
}) => VNode | Component; |
||||
|
/** 自定义底部按钮操作 */ |
||||
|
footerButtons?: Array<ButtonProps>; |
||||
|
/** `Dialog` 打开后的回调 */ |
||||
|
open?: ({ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
}) => void; |
||||
|
/** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或者空白页关闭页面时才会触发) */ |
||||
|
close?: ({ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
}) => void; |
||||
|
/** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */ |
||||
|
closeCallBack?: ({ |
||||
|
options, |
||||
|
index, |
||||
|
args |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
args: any; |
||||
|
}) => void; |
||||
|
/** 输入焦点聚焦在 `Dialog` 内容时的回调 */ |
||||
|
openAutoFocus?: ({ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
}) => void; |
||||
|
/** 输入焦点从 `Dialog` 内容失焦时的回调 */ |
||||
|
closeAutoFocus?: ({ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
}) => void; |
||||
|
/** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */ |
||||
|
beforeCancel?: ( |
||||
|
done: Function, |
||||
|
{ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
} |
||||
|
) => void; |
||||
|
/** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */ |
||||
|
beforeSure?: ( |
||||
|
done: Function, |
||||
|
{ |
||||
|
options, |
||||
|
index |
||||
|
}: { |
||||
|
options: DialogOptions; |
||||
|
index: number; |
||||
|
} |
||||
|
) => void; |
||||
|
} |
||||
|
|
||||
|
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; |
@ -0,0 +1,5 @@ |
|||||
|
import pureTableBar from "./src/bar"; |
||||
|
import { withInstall } from "@pureadmin/utils"; |
||||
|
|
||||
|
/** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/pure-admin/pure-admin-table */ |
||||
|
export const PureTableBar = withInstall(pureTableBar); |
@ -0,0 +1,339 @@ |
|||||
|
import { useEpThemeStoreHook } from "@/store/modules/epTheme"; |
||||
|
import { delay, getKeyList, cloneDeep } from "@pureadmin/utils"; |
||||
|
import { defineComponent, ref, computed, type PropType, nextTick } from "vue"; |
||||
|
|
||||
|
import Sortable from "sortablejs"; |
||||
|
import DragIcon from "./svg/drag.svg?component"; |
||||
|
import ExpandIcon from "./svg/expand.svg?component"; |
||||
|
import RefreshIcon from "./svg/refresh.svg?component"; |
||||
|
import SettingIcon from "./svg/settings.svg?component"; |
||||
|
import CollapseIcon from "./svg/collapse.svg?component"; |
||||
|
|
||||
|
const props = { |
||||
|
/** 头部最左边的标题 */ |
||||
|
title: { |
||||
|
type: String, |
||||
|
default: "列表" |
||||
|
}, |
||||
|
/** 对于树形表格,如果想启用展开和折叠功能,传入当前表格的ref即可 */ |
||||
|
tableRef: { |
||||
|
type: Object as PropType<any> |
||||
|
}, |
||||
|
/** 需要展示的列 */ |
||||
|
columns: { |
||||
|
type: Array as PropType<TableColumnList>, |
||||
|
default: () => [] |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default defineComponent({ |
||||
|
name: "PureTableBar", |
||||
|
props, |
||||
|
emits: ["refresh"], |
||||
|
setup(props, { emit, slots, attrs }) { |
||||
|
const buttonRef = ref(); |
||||
|
const size = ref("default"); |
||||
|
const isExpandAll = ref(true); |
||||
|
const loading = ref(false); |
||||
|
const checkAll = ref(true); |
||||
|
const isIndeterminate = ref(false); |
||||
|
let checkColumnList = getKeyList(cloneDeep(props?.columns), "label"); |
||||
|
const checkedColumns = ref(checkColumnList); |
||||
|
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; |
||||
|
toggleRowExpansionAll(props.tableRef.data, isExpandAll.value); |
||||
|
} |
||||
|
|
||||
|
function toggleRowExpansionAll(data, isExpansion) { |
||||
|
data.forEach(item => { |
||||
|
props.tableRef.toggleRowExpansion(item, isExpansion); |
||||
|
if (item.children !== undefined && item.children !== null) { |
||||
|
toggleRowExpansionAll(item.children, isExpansion); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleCheckAllChange(val: boolean) { |
||||
|
checkedColumns.value = val ? checkColumnList : []; |
||||
|
isIndeterminate.value = false; |
||||
|
dynamicColumns.value.map(column => |
||||
|
val ? (column.hide = false) : (column.hide = true) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function handleCheckedColumnsChange(value: string[]) { |
||||
|
const checkedCount = value.length; |
||||
|
checkAll.value = checkedCount === checkColumnList.length; |
||||
|
isIndeterminate.value = |
||||
|
checkedCount > 0 && checkedCount < checkColumnList.length; |
||||
|
} |
||||
|
|
||||
|
function handleCheckColumnListChange(val: boolean, label: string) { |
||||
|
dynamicColumns.value.filter(item => item.label === label)[0].hide = !val; |
||||
|
} |
||||
|
|
||||
|
async function onReset() { |
||||
|
checkAll.value = true; |
||||
|
isIndeterminate.value = false; |
||||
|
dynamicColumns.value = cloneDeep(props?.columns); |
||||
|
checkColumnList = []; |
||||
|
checkColumnList = await getKeyList(cloneDeep(props?.columns), "label"); |
||||
|
checkedColumns.value = checkColumnList; |
||||
|
} |
||||
|
|
||||
|
const dropdown = { |
||||
|
dropdown: () => ( |
||||
|
<el-dropdown-menu class="translation"> |
||||
|
<el-dropdown-item |
||||
|
style={getDropdownItemStyle.value("large")} |
||||
|
onClick={() => (size.value = "large")} |
||||
|
> |
||||
|
宽松 |
||||
|
</el-dropdown-item> |
||||
|
<el-dropdown-item |
||||
|
style={getDropdownItemStyle.value("default")} |
||||
|
onClick={() => (size.value = "default")} |
||||
|
> |
||||
|
默认 |
||||
|
</el-dropdown-item> |
||||
|
<el-dropdown-item |
||||
|
style={getDropdownItemStyle.value("small")} |
||||
|
onClick={() => (size.value = "small")} |
||||
|
> |
||||
|
紧凑 |
||||
|
</el-dropdown-item> |
||||
|
</el-dropdown-menu> |
||||
|
) |
||||
|
}; |
||||
|
|
||||
|
/** 列展示拖拽排序 */ |
||||
|
const rowDrop = (event: { preventDefault: () => void }) => { |
||||
|
event.preventDefault(); |
||||
|
nextTick(() => { |
||||
|
const wrapper: HTMLElement = document.querySelector( |
||||
|
".el-checkbox-group>div" |
||||
|
); |
||||
|
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); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const isFixedColumn = (label: string) => { |
||||
|
return dynamicColumns.value.filter(item => item.label === label)[0].fixed |
||||
|
? true |
||||
|
: false; |
||||
|
}; |
||||
|
|
||||
|
const reference = { |
||||
|
reference: () => ( |
||||
|
<SettingIcon |
||||
|
class={["w-[16px]", iconClass.value]} |
||||
|
onMouseover={e => (buttonRef.value = e.currentTarget)} |
||||
|
/> |
||||
|
) |
||||
|
}; |
||||
|
|
||||
|
return () => ( |
||||
|
<> |
||||
|
<div {...attrs} class="w-[99/100] mt-6 p-2 bg-bg_color"> |
||||
|
<div class="flex justify-between w-full h-[60px] p-4"> |
||||
|
<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.tableRef?.size ? ( |
||||
|
<> |
||||
|
<el-tooltip |
||||
|
effect="dark" |
||||
|
content={isExpandAll.value ? "折叠" : "展开"} |
||||
|
placement="top" |
||||
|
> |
||||
|
<ExpandIcon |
||||
|
class={["w-[16px]", iconClass.value]} |
||||
|
style={{ |
||||
|
transform: isExpandAll.value ? "none" : "rotate(-90deg)" |
||||
|
}} |
||||
|
onClick={() => onExpand()} |
||||
|
/> |
||||
|
</el-tooltip> |
||||
|
<el-divider direction="vertical" /> |
||||
|
</> |
||||
|
) : null} |
||||
|
<el-tooltip effect="dark" content="刷新" placement="top"> |
||||
|
<RefreshIcon |
||||
|
class={[ |
||||
|
"w-[16px]", |
||||
|
iconClass.value, |
||||
|
loading.value ? "animate-spin" : "" |
||||
|
]} |
||||
|
onClick={() => onReFresh()} |
||||
|
/> |
||||
|
</el-tooltip> |
||||
|
<el-divider direction="vertical" /> |
||||
|
<el-tooltip effect="dark" content="密度" placement="top"> |
||||
|
<el-dropdown v-slots={dropdown} trigger="click"> |
||||
|
<CollapseIcon class={["w-[16px]", iconClass.value]} /> |
||||
|
</el-dropdown> |
||||
|
</el-tooltip> |
||||
|
<el-divider direction="vertical" /> |
||||
|
|
||||
|
<el-popover |
||||
|
v-slots={reference} |
||||
|
popper-style={{ padding: 0 }} |
||||
|
width="160" |
||||
|
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-checkbox-group |
||||
|
v-model={checkedColumns.value} |
||||
|
onChange={value => handleCheckedColumnsChange(value)} |
||||
|
> |
||||
|
<el-space |
||||
|
direction="vertical" |
||||
|
alignment="flex-start" |
||||
|
size={0} |
||||
|
> |
||||
|
{checkColumnList.map(item => { |
||||
|
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={item} |
||||
|
label={item} |
||||
|
onChange={value => |
||||
|
handleCheckColumnListChange(value, item) |
||||
|
} |
||||
|
> |
||||
|
<span |
||||
|
title={item} |
||||
|
class="inline-block w-[120px] truncate hover:text-text_color_primary" |
||||
|
> |
||||
|
{item} |
||||
|
</span> |
||||
|
</el-checkbox> |
||||
|
</div> |
||||
|
); |
||||
|
})} |
||||
|
</el-space> |
||||
|
</el-checkbox-group> |
||||
|
</div> |
||||
|
</el-popover> |
||||
|
</div> |
||||
|
|
||||
|
<el-tooltip |
||||
|
popper-options={{ |
||||
|
modifiers: [ |
||||
|
{ |
||||
|
name: "computeStyles", |
||||
|
options: { |
||||
|
adaptive: false, |
||||
|
enabled: false |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
}} |
||||
|
placement="top" |
||||
|
virtual-ref={buttonRef.value} |
||||
|
virtual-triggering |
||||
|
trigger="hover" |
||||
|
content="列设置" |
||||
|
/> |
||||
|
</div> |
||||
|
{slots.default({ |
||||
|
size: size.value, |
||||
|
dynamicColumns: dynamicColumns.value |
||||
|
})} |
||||
|
</div> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
|
}); |
@ -0,0 +1 @@ |
|||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.79 10.21a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42l-2.5-2.5a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21l-2.5 2.5a1 1 0 0 0 1.42 1.42l.79-.8v5.18l-.79-.8a1 1 0 0 0-1.42 1.42l2.5 2.5a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l2.5-2.5a1 1 0 0 0-1.42-1.42l-.79.8V9.41ZM7 4h10a1 1 0 0 0 0-2H7a1 1 0 0 0 0 2Zm10 16H7a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2Z"/></svg> |
@ -0,0 +1 @@ |
|||||
|
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zm0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0zM300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0z"/></svg> |
@ -0,0 +1 @@ |
|||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4h9Z"/></svg> |
@ -0,0 +1 @@ |
|||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"/></svg> |
@ -0,0 +1 @@ |
|||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M3.34 17a10.018 10.018 0 0 1-.978-2.326 3 3 0 0 0 .002-5.347A9.99 9.99 0 0 1 4.865 4.99a3 3 0 0 0 4.631-2.674 9.99 9.99 0 0 1 5.007.002 3 3 0 0 0 4.632 2.672A9.99 9.99 0 0 1 20.66 7c.433.749.757 1.53.978 2.326a3 3 0 0 0-.002 5.347 9.99 9.99 0 0 1-2.501 4.337 3 3 0 0 0-4.631 2.674 9.99 9.99 0 0 1-5.007-.002 3 3 0 0 0-4.632-2.672A10.018 10.018 0 0 1 3.34 17zm5.66.196a4.993 4.993 0 0 1 2.25 2.77c.499.047 1 .048 1.499.001A4.993 4.993 0 0 1 15 17.197a4.993 4.993 0 0 1 3.525-.565c.29-.408.54-.843.748-1.298A4.993 4.993 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8.126 8.126 0 0 0-.75-1.298A4.993 4.993 0 0 1 15 6.804a4.993 4.993 0 0 1-2.25-2.77c-.499-.047-1-.048-1.499-.001A4.993 4.993 0 0 1 9 6.803a4.993 4.993 0 0 1-3.525.565 7.99 7.99 0 0 0-.748 1.298A4.993 4.993 0 0 1 6 12a4.99 4.99 0 0 1-1.273 3.334 8.126 8.126 0 0 0 .75 1.298A4.993 4.993 0 0 1 9 17.196zM12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg> |
@ -1,28 +0,0 @@ |
|||||
@mixin clearfix { |
|
||||
&::after { |
|
||||
content: ""; |
|
||||
display: table; |
|
||||
clear: both; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@mixin relative { |
|
||||
position: relative; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
@mixin scrollBar { |
|
||||
&::-webkit-scrollbar-track-piece { |
|
||||
background: #d3dce6; |
|
||||
} |
|
||||
|
|
||||
&::-webkit-scrollbar { |
|
||||
width: 6px; |
|
||||
} |
|
||||
|
|
||||
&::-webkit-scrollbar-thumb { |
|
||||
background: #99a9bf; |
|
||||
border-radius: 20px; |
|
||||
} |
|
||||
} |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue