xiaoxian521
2 years ago
76 changed files with 4152 additions and 2089 deletions
-
3.env
-
2.env.staging
-
1.stylelintignore
-
2LICENSE
-
2README.en-US.md
-
2README.md
-
1build/index.ts
-
4build/info.ts
-
2build/optimize.ts
-
3build/plugins.ts
-
38index.html
-
123package.json
-
4253pnpm-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
-
15src/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 |
|||
|
|||
# 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置) |
|||
VITE_HIDE_HOME = false |
@ -1,3 +1,4 @@ |
|||
/dist/* |
|||
/public/* |
|||
public/* |
|||
src/style/reset.scss |
@ -1,6 +1,6 @@ |
|||
{ |
|||
"name": "pure-admin-thin", |
|||
"version": "3.9.7", |
|||
"version": "4.1.0", |
|||
"private": true, |
|||
"scripts": { |
|||
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite", |
|||
@ -15,8 +15,8 @@ |
|||
"cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML", |
|||
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install", |
|||
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix", |
|||
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"", |
|||
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/", |
|||
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"", |
|||
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", |
|||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", |
|||
"lint:pretty": "pretty-quick --staged", |
|||
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint", |
|||
@ -29,98 +29,109 @@ |
|||
"not op_mini all" |
|||
], |
|||
"dependencies": { |
|||
"@pureadmin/descriptions": "^1.1.0", |
|||
"@pureadmin/table": "^2.0.0", |
|||
"@pureadmin/utils": "^1.8.5", |
|||
"@vueuse/core": "^9.13.0", |
|||
"@pureadmin/descriptions": "^1.1.1", |
|||
"@pureadmin/table": "^2.1.0", |
|||
"@pureadmin/utils": "^1.8.9", |
|||
"@vueuse/core": "^10.1.2", |
|||
"@vueuse/motion": "2.0.0-beta.12", |
|||
"animate.css": "^4.1.1", |
|||
"axios": "1.2.2", |
|||
"axios": "^1.4.0", |
|||
"dayjs": "^1.11.7", |
|||
"echarts": "^5.4.1", |
|||
"element-plus": "^2.2.32", |
|||
"echarts": "^5.4.2", |
|||
"element-plus": "^2.3.4", |
|||
"element-resize-detector": "^1.2.4", |
|||
"js-cookie": "^3.0.1", |
|||
"js-cookie": "^3.0.5", |
|||
"mitt": "^3.0.0", |
|||
"mockjs": "^1.1.0", |
|||
"nprogress": "^0.2.0", |
|||
"path": "^0.12.7", |
|||
"pinia": "^2.0.32", |
|||
"qs": "^6.11.0", |
|||
"pinia": "^2.0.36", |
|||
"qs": "^6.11.1", |
|||
"responsive-storage": "^2.2.0", |
|||
"vue": "^3.2.47", |
|||
"sortablejs": "^1.15.0", |
|||
"vue": "^3.3.1", |
|||
"vue-i18n": "^9.2.2", |
|||
"vue-router": "^4.1.6", |
|||
"vue-types": "^5.0.2" |
|||
}, |
|||
"devDependencies": { |
|||
"@commitlint/cli": "13.1.0", |
|||
"@commitlint/config-conventional": "13.1.0", |
|||
"@iconify-icons/ep": "^1.2.10", |
|||
"@iconify-icons/ri": "^1.2.4", |
|||
"@iconify/vue": "^4.1.0", |
|||
"@intlify/unplugin-vue-i18n": "^0.8.2", |
|||
"@commitlint/cli": "^17.6.3", |
|||
"@commitlint/config-conventional": "^17.6.3", |
|||
"@iconify-icons/ep": "^1.2.11", |
|||
"@iconify-icons/ri": "^1.2.7", |
|||
"@iconify/vue": "^4.1.1", |
|||
"@intlify/unplugin-vue-i18n": "^0.10.0", |
|||
"@pureadmin/theme": "^3.0.0", |
|||
"@types/element-resize-detector": "1.1.3", |
|||
"@types/js-cookie": "^3.0.1", |
|||
"@types/js-cookie": "^3.0.3", |
|||
"@types/mockjs": "^1.0.7", |
|||
"@types/node": "^18.11.9", |
|||
"@types/node": "^18.15.12", |
|||
"@types/nprogress": "0.2.0", |
|||
"@types/qs": "^6.9.7", |
|||
"@typescript-eslint/eslint-plugin": "^5.43.0", |
|||
"@typescript-eslint/parser": "^5.43.0", |
|||
"@vitejs/plugin-vue": "^4.0.0", |
|||
"@vitejs/plugin-vue-jsx": "^3.0.0", |
|||
"@vue/eslint-config-prettier": "^7.0.0", |
|||
"@vue/eslint-config-typescript": "^11.0.2", |
|||
"autoprefixer": "^10.4.13", |
|||
"@types/sortablejs": "^1.15.1", |
|||
"@typescript-eslint/eslint-plugin": "^5.59.5", |
|||
"@typescript-eslint/parser": "^5.59.5", |
|||
"@vitejs/plugin-vue": "^4.2.2", |
|||
"@vitejs/plugin-vue-jsx": "^3.0.1", |
|||
"@vue/eslint-config-prettier": "^7.1.0", |
|||
"@vue/eslint-config-typescript": "^11.0.3", |
|||
"autoprefixer": "^10.4.14", |
|||
"cloc": "^2.11.0", |
|||
"cssnano": "^5.1.14", |
|||
"eslint": "^8.8.0", |
|||
"eslint-plugin-prettier": "^4.0.0", |
|||
"eslint-plugin-vue": "^9.9.0", |
|||
"husky": "^7.0.4", |
|||
"lint-staged": "11.1.2", |
|||
"cssnano": "^6.0.1", |
|||
"eslint": "^8.40.0", |
|||
"eslint-plugin-prettier": "^4.2.1", |
|||
"eslint-plugin-vue": "^9.12.0", |
|||
"husky": "^8.0.3", |
|||
"lint-staged": "^13.2.2", |
|||
"picocolors": "^1.0.0", |
|||
"postcss": "^8.4.21", |
|||
"postcss": "^8.4.23", |
|||
"postcss-html": "^1.5.0", |
|||
"postcss-import": "^15.1.0", |
|||
"postcss-scss": "^4.0.6", |
|||
"prettier": "^2.5.1", |
|||
"prettier": "^2.8.7", |
|||
"pretty-quick": "3.1.1", |
|||
"rimraf": "3.0.2", |
|||
"rimraf": "^5.0.0", |
|||
"rollup-plugin-visualizer": "^5.9.0", |
|||
"sass": "^1.57.1", |
|||
"sass-loader": "^13.2.0", |
|||
"stylelint": "^14.3.0", |
|||
"stylelint-config-html": "^1.0.0", |
|||
"stylelint-config-prettier": "^9.0.3", |
|||
"stylelint-config-recommended": "^9.0.0", |
|||
"stylelint-config-standard": "^29.0.0", |
|||
"stylelint-order": "^5.0.0", |
|||
"sass": "^1.62.1", |
|||
"sass-loader": "^13.2.2", |
|||
"stylelint": "^15.6.1", |
|||
"stylelint-config-html": "^1.1.0", |
|||
"stylelint-config-recess-order": "^4.0.0", |
|||
"stylelint-config-recommended": "^12.0.0", |
|||
"stylelint-config-recommended-scss": "^11.0.0", |
|||
"stylelint-config-recommended-vue": "^1.4.0", |
|||
"stylelint-config-standard": "^33.0.0", |
|||
"stylelint-config-standard-scss": "^9.0.0", |
|||
"stylelint-order": "^6.0.3", |
|||
"stylelint-prettier": "^3.0.0", |
|||
"stylelint-scss": "^5.0.0", |
|||
"svgo": "^3.0.2", |
|||
"tailwindcss": "^3.2.7", |
|||
"terser": "^5.16.1", |
|||
"typescript": "^4.9.5", |
|||
"unplugin-vue-define-options": "^1.0.0", |
|||
"vite": "^4.1.4", |
|||
"tailwindcss": "^3.3.2", |
|||
"terser": "^5.17.1", |
|||
"typescript": "^5.0.4", |
|||
"vite": "^4.3.5", |
|||
"vite-plugin-cdn-import": "^0.3.5", |
|||
"vite-plugin-compression": "^0.5.1", |
|||
"vite-plugin-mock": "^2.9.6", |
|||
"vite-plugin-remove-console": "^2.1.0", |
|||
"vite-plugin-remove-console": "^2.1.1", |
|||
"vite-svg-loader": "^4.0.0", |
|||
"vue-eslint-parser": "^9.1.0", |
|||
"vue-tsc": "^1.2.0" |
|||
"vue-eslint-parser": "^9.2.1", |
|||
"vue-tsc": "^1.6.4" |
|||
}, |
|||
"pnpm": { |
|||
"peerDependencyRules": { |
|||
"ignoreMissing": [ |
|||
"rollup", |
|||
"webpack" |
|||
"webpack", |
|||
"core-js" |
|||
] |
|||
}, |
|||
"allowedDeprecatedVersions": { |
|||
"sourcemap-codec": "*", |
|||
"stable": "*" |
|||
} |
|||
}, |
|||
"repository": "[email protected]:pure-admin/pure-admin-thin.git", |
|||
"repository": "[email protected]:pure-admin/vue-pure-admin.git", |
|||
"author": "xiaoxian521", |
|||
"license": "MIT" |
|||
} |
4253
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