Compare commits

..

No commits in common. "9caa00855197d04c6defcc7da0a4c07a40a78967" and "607b73a7b3f323c17af92757cee1928fdd47dea2" have entirely different histories.

45 changed files with 531 additions and 887 deletions

View File

@ -1,5 +1,5 @@
# 环境 # 环境
VITE_NODE_ENV=development NODE_ENV=development
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH= VITE_API_BASE_PATH=
@ -9,12 +9,3 @@ VITE_BASE_PATH=/
# 标题 # 标题
VITE_APP_TITLE=ElementAdmin VITE_APP_TITLE=ElementAdmin
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=true
# 是否开启mock
VITE_USE_MOCK=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true

View File

@ -1,5 +1,5 @@
# 环境 # 环境
VITE_NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH= VITE_API_BASE_PATH=
@ -21,18 +21,3 @@ VITE_OUT_DIR=dist-dev
# 标题 # 标题
VITE_APP_TITLE=ElementAdmin VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true

View File

@ -1,5 +1,5 @@
# 环境 # 环境
VITE_NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH= VITE_API_BASE_PATH=
@ -21,18 +21,3 @@ VITE_OUT_DIR=dist-pro
# 标题 # 标题
VITE_APP_TITLE=ElementAdmin VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true

View File

@ -1,5 +1,5 @@
# 环境 # 环境
VITE_NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH= VITE_API_BASE_PATH=
@ -21,18 +21,3 @@ VITE_OUT_DIR=dist-pro
# 标题 # 标题
VITE_APP_TITLE=ElementAdmin VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=true
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true

View File

@ -1,8 +1,8 @@
# 环境 # 环境
VITE_NODE_ENV=production NODE_ENV=production
# 接口前缀 # 接口前缀
VITE_API_BASE_PATH= VITE_API_BASE_PATH=test
# 打包路径 # 打包路径
VITE_BASE_PATH=/dist-test/ VITE_BASE_PATH=/dist-test/
@ -21,18 +21,3 @@ VITE_OUT_DIR=dist-test
# 标题 # 标题
VITE_APP_TITLE=ElementAdmin VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=false
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true

1
.gitignore vendored
View File

@ -6,4 +6,3 @@ dist-ssr
/dist* /dist*
*-lock.* *-lock.*
pnpm-debug pnpm-debug
stats.html

View File

@ -2,7 +2,7 @@
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"prettier.enable": false, "prettier.enable": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": true
}, },
"[vue]": { "[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint" "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"

View File

@ -1,6 +1,6 @@
{ {
"name": "vue-element-plus-admin", "name": "vue-element-plus-admin",
"version": "2.5.6", "version": "2.0.0",
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。", "description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
"author": "Archer <502431556@qq.com>", "author": "Archer <502431556@qq.com>",
"private": false, "private": false,
@ -11,7 +11,7 @@
"build:pro": "pnpm vite build --mode pro", "build:pro": "pnpm vite build --mode pro",
"build:gitee": "pnpm vite build --mode gitee", "build:gitee": "pnpm vite build --mode gitee",
"build:dev": "pnpm vite build --mode dev", "build:dev": "pnpm vite build --mode dev",
"build:test": "pnpm vite build --mode test", "build:test": "pnpm run ts:check && vite build --mode test",
"serve:pro": "pnpm vite preview --mode pro", "serve:pro": "pnpm vite preview --mode pro",
"serve:dev": "pnpm vite preview --mode dev", "serve:dev": "pnpm vite preview --mode dev",
"serve:test": "pnpm vite preview --mode test", "serve:test": "pnpm vite preview --mode test",
@ -26,95 +26,94 @@
"p": "plop" "p": "plop"
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "^8.4.0", "@faker-js/faker": "^8.3.1",
"@iconify/iconify": "^3.1.1", "@iconify/iconify": "^3.1.1",
"@iconify/vue": "^4.1.1", "@iconify/vue": "^4.1.1",
"@vueuse/core": "^10.7.2", "@vueuse/core": "^10.7.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10", "@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.6.7", "axios": "^1.6.2",
"cropperjs": "^1.6.1", "cropperjs": "^1.6.1",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"driver.js": "^1.3.1", "driver.js": "^1.3.1",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"element-plus": "2.5.5", "element-plus": "^2.4.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.0",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qs": "^6.11.2", "qs": "^6.11.2",
"url": "^0.11.3", "url": "^0.11.3",
"vue": "3.4.15", "vue": "3.3.10",
"vue-draggable-plus": "^0.3.5", "vue-i18n": "9.8.0",
"vue-i18n": "9.9.1", "vue-json-pretty": "^2.2.4",
"vue-json-pretty": "^2.3.0",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue-types": "^5.1.1", "vue-types": "^5.1.1",
"xgplayer": "^3.0.12" "xgplayer": "^3.0.10"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.6.0", "@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.6.0", "@commitlint/config-conventional": "^18.4.3",
"@iconify/json": "^2.2.180", "@iconify/json": "^2.2.153",
"@intlify/unplugin-vue-i18n": "^2.0.0", "@intlify/unplugin-vue-i18n": "^1.5.0",
"@purge-icons/generated": "^0.10.0",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/inquirer": "^9.0.7", "@types/inquirer": "^9.0.7",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.16", "@types/node": "^20.10.3",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.11", "@types/qs": "^6.9.10",
"@types/sortablejs": "^1.15.7", "@types/sortablejs": "^1.15.7",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^6.13.2",
"@unocss/transformer-variant-group": "^0.58.5", "@unocss/transformer-variant-group": "^0.58.0",
"@vitejs/plugin-legacy": "^5.3.0", "@vitejs/plugin-legacy": "^5.2.0",
"@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue": "^4.5.1",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.16",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"consola": "^3.2.3", "consola": "^3.2.3",
"eslint": "^8.56.0", "eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0", "eslint-define-config": "^2.0.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-vue": "^9.21.1", "eslint-plugin-vue": "^9.19.2",
"esno": "^4.0.0", "esno": "^4.0.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"husky": "^9.0.10", "husky": "^8.0.3",
"inquirer": "^9.2.14", "inquirer": "^9.2.12",
"less": "^4.2.0", "less": "^4.2.0",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.0",
"plop": "^4.0.1", "plop": "^4.0.0",
"postcss": "^8.4.34", "postcss": "^8.4.32",
"postcss-html": "^1.6.0", "postcss-html": "^1.5.0",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"prettier": "^3.2.5", "prettier": "^3.1.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "^4.9.6", "rollup": "^4.6.1",
"rollup-plugin-visualizer": "^5.12.0", "stylelint": "^15.11.0",
"stylelint": "^16.2.1",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^14.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-config-standard": "^36.0.0", "stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.4", "stylelint-order": "^6.0.3",
"terser": "^5.27.0", "terser": "^5.25.0",
"typescript": "5.3.3", "typescript": "5.3.3",
"unocss": "^0.58.5", "unocss": "^0.58.0",
"vite": "5.0.12", "vite": "5.0.6",
"vite-plugin-ejs": "^1.7.0", "vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-mock": "2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-progress": "^0.0.7", "vite-plugin-progress": "^0.0.7",
"vite-plugin-purge-icons": "^0.10.0", "vite-plugin-purge-icons": "^0.10.0",
"vite-plugin-style-import": "2.0.0", "vite-plugin-style-import": "2.0.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.8.27" "vue-tsc": "^1.8.25"
}, },
"packageManager": "pnpm@8.1.0", "packageManager": "pnpm@8.1.0",
"engines": { "engines": {

View File

@ -2,7 +2,9 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { ConfigGlobal } from '@/components/ConfigGlobal' import { ConfigGlobal } from '@/components/ConfigGlobal'
import { isDark } from '@/utils/is'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { useStorage } from '@/hooks/web/useStorage'
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -14,7 +16,19 @@ const currentSize = computed(() => appStore.getCurrentSize)
const greyMode = computed(() => appStore.getGreyMode) const greyMode = computed(() => appStore.getGreyMode)
appStore.initTheme() const { getStorage } = useStorage()
//
const setDefaultTheme = () => {
if (getStorage('isDark') !== null) {
appStore.setIsDark(getStorage('isDark'))
return
}
const isDarkTheme = isDark()
appStore.setIsDark(isDarkTheme)
}
setDefaultTheme()
</script> </script>
<template> <template>

View File

@ -1,9 +1,8 @@
import { AxiosResponse, InternalAxiosRequestConfig } from './types' import { AxiosResponse, InternalAxiosRequestConfig } from './types'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import qs from 'qs' import qs from 'qs'
import { SUCCESS_CODE, TRANSFORM_REQUEST_DATA } from '@/constants' import { SUCCESS_CODE } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
import { objToFormData } from '@/utils'
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => { const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
if ( if (
@ -11,12 +10,6 @@ const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
config.headers['Content-Type'] === 'application/x-www-form-urlencoded' config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
) { ) {
config.data = qs.stringify(config.data) config.data = qs.stringify(config.data)
} else if (
TRANSFORM_REQUEST_DATA &&
config.method === 'post' &&
config.headers['Content-Type'] === 'multipart/form-data'
) {
config.data = objToFormData(config.data)
} }
if (config.method === 'get' && config.params) { if (config.method === 'get' && config.params) {
let url = config.url as string let url = config.url as string

View File

@ -15,7 +15,7 @@ const title = computed(() => appStore.getTitle)
<template> <template>
<div <div
:class="prefixCls" :class="prefixCls"
class="shrink-0 text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]" class="text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
> >
Copyright ©2021-present {{ title }} Copyright ©2021-present {{ title }}
</div> </div>

View File

@ -230,7 +230,7 @@ export default defineComponent({
const { schema = [], isCol } = unref(getProps) const { schema = [], isCol } = unref(getProps)
return schema return schema
.filter((v) => !v.remove && !v.hidden) .filter((v) => !v.remove)
.map((item) => { .map((item) => {
// Divider // Divider
const isDivider = item.component === 'Divider' const isDivider = item.component === 'Divider'
@ -406,15 +406,7 @@ export default defineComponent({
margin-left: 0 !important; margin-left: 0 !important;
} }
.@{elNamespace}-form--inline { .@{elNamespace}-form--inline .@{elNamespace}-input {
:deep(.el-form-item__content) { width: 245px;
& > :first-child {
min-width: 229.5px;
}
}
.@{elNamespace}-input-number {
// 229.5pxel-input-number,
min-width: 229.5px;
}
} }
</style> </style>

View File

@ -25,11 +25,6 @@ const symbolId = computed(() => {
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
}) })
// 使线
const isUseOnline = computed(() => {
return import.meta.env.VITE_USE_ONLINE_ICON === 'true'
})
const getIconifyStyle = computed(() => { const getIconifyStyle = computed(() => {
const { color, size } = props const { color, size } = props
return { return {
@ -45,10 +40,7 @@ const getIconifyStyle = computed(() => {
<use :xlink:href="symbolId" /> <use :xlink:href="symbolId" />
</svg> </svg>
<template v-else> <Icon v-else :icon="icon" :style="getIconifyStyle" />
<Icon v-if="isUseOnline" :icon="icon" :style="getIconifyStyle" />
<div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
</template>
</ElIcon> </ElIcon>
</template> </template>
@ -57,18 +49,11 @@ const getIconifyStyle = computed(() => {
.@{prefix-cls}, .@{prefix-cls},
.iconify { .iconify {
:deep(svg) { &:hover {
&:hover { :deep(svg) {
// stylelint-disable-next-line // stylelint-disable-next-line
color: v-bind(hoverColor) !important; color: v-bind(hoverColor) !important;
} }
} }
} }
.iconify {
&:hover {
// stylelint-disable-next-line
color: v-bind(hoverColor) !important;
}
}
</style> </style>

View File

@ -89,16 +89,11 @@ export default defineComponent({
backgroundColor="var(--left-menu-bg-color)" backgroundColor="var(--left-menu-bg-color)"
textColor="var(--left-menu-text-color)" textColor="var(--left-menu-text-color)"
activeTextColor="var(--left-menu-text-active-color)" activeTextColor="var(--left-menu-text-active-color)"
popperClass={
unref(menuMode) === 'vertical'
? `${prefixCls}-popper--vertical`
: `${prefixCls}-popper--horizontal`
}
onSelect={menuSelect} onSelect={menuSelect}
> >
{{ {{
default: () => { default: () => {
const { renderMenuItem } = useRenderMenuItem() const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
return renderMenuItem(unref(routers)) return renderMenuItem(unref(routers))
} }
}} }}
@ -128,10 +123,30 @@ export default defineComponent({
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-menu'; @prefix-cls: ~'@{namespace}-menu';
// .is-active--after {
// position: absolute;
// top: 0;
// right: 0;
// width: 4px;
// height: 100%;
// background-color: var(--el-color-primary);
// content: '';
// }
.@{prefix-cls} { .@{prefix-cls} {
position: relative; position: relative;
transition: width var(--transition-time-02); transition: width var(--transition-time-02);
// &:after {
// position: absolute;
// top: 0;
// right: 0;
// height: 100%;
// width: 1px;
// background-color: var(--el-border-color);
// content: '';
// }
:deep(.@{elNamespace}-menu) { :deep(.@{elNamespace}-menu) {
width: 100% !important; width: 100% !important;
border-right: none; border-right: none;
@ -140,6 +155,7 @@ export default defineComponent({
.is-active { .is-active {
& > .@{elNamespace}-sub-menu__title { & > .@{elNamespace}-sub-menu__title {
color: var(--left-menu-text-active-color) !important; color: var(--left-menu-text-active-color) !important;
// background-color: var(--left-menu-bg-color) !important;
} }
} }
@ -164,6 +180,10 @@ export default defineComponent({
.@{elNamespace}-menu-item.is-active { .@{elNamespace}-menu-item.is-active {
position: relative; position: relative;
// &:after {
// .is-active--after;
// }
} }
// //
@ -183,11 +203,16 @@ export default defineComponent({
& > .is-active > .@{elNamespace}-sub-menu__title { & > .is-active > .@{elNamespace}-sub-menu__title {
position: relative; position: relative;
background-color: var(--left-menu-collapse-bg-active-color) !important; background-color: var(--left-menu-collapse-bg-active-color) !important;
// &:after {
// .is-active--after;
// }
} }
} }
// //
:deep(.horizontal-collapse-transition) { :deep(.horizontal-collapse-transition) {
// transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;
.@{prefix-cls}__title { .@{prefix-cls}__title {
display: none; display: none;
} }
@ -229,12 +254,23 @@ export default defineComponent({
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-menu-popper'; @prefix-cls: ~'@{namespace}-menu-popper';
// .is-active--after {
// position: absolute;
// top: 0;
// right: 0;
// width: 4px;
// height: 100%;
// background-color: var(--el-color-primary);
// content: '';
// }
.@{prefix-cls}--vertical, .@{prefix-cls}--vertical,
.@{prefix-cls}--horizontal { .@{prefix-cls}--horizontal {
// //
.is-active { .is-active {
& > .el-sub-menu__title { & > .el-sub-menu__title {
color: var(--left-menu-text-active-color) !important; color: var(--left-menu-text-active-color) !important;
// background-color: var(--left-menu-bg-color) !important;
} }
} }
@ -255,6 +291,10 @@ export default defineComponent({
&:hover { &:hover {
background-color: var(--left-menu-bg-active-color) !important; background-color: var(--left-menu-bg-active-color) !important;
} }
// &:after {
// .is-active--after;
// }
} }
} }
</style> </style>

View File

@ -2,49 +2,57 @@ import { ElSubMenu, ElMenuItem } from 'element-plus'
import { hasOneShowingChild } from '../helper' import { hasOneShowingChild } from '../helper'
import { isUrl } from '@/utils/is' import { isUrl } from '@/utils/is'
import { useRenderMenuTitle } from './useRenderMenuTitle' import { useRenderMenuTitle } from './useRenderMenuTitle'
import { useDesign } from '@/hooks/web/useDesign'
import { pathResolve } from '@/utils/routerHelper' import { pathResolve } from '@/utils/routerHelper'
const { renderMenuTitle } = useRenderMenuTitle() const { renderMenuTitle } = useRenderMenuTitle()
export const useRenderMenuItem = () => export const useRenderMenuItem = (
// allRouters: AppRouteRecordRaw[] = [], // allRouters: AppRouteRecordRaw[] = [],
{ menuMode: 'vertical' | 'horizontal'
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => { ) => {
return routers const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
.filter((v) => !v.meta?.hidden) return routers
.map((v) => { .filter((v) => !v.meta?.hidden)
const meta = v.meta ?? {} .map((v) => {
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v) const meta = v.meta ?? {}
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/') const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
if ( if (
oneShowingChild && oneShowingChild &&
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) && (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
!meta?.alwaysShow !meta?.alwaysShow
) { ) {
return ( return (
<ElMenuItem <ElMenuItem index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}>
index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath} {{
> default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
{{ }}
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta) </ElMenuItem>
}} )
</ElMenuItem> } else {
) const { getPrefixCls } = useDesign()
} else {
return (
<ElSubMenu index={fullPath}>
{{
title: () => renderMenuTitle(meta),
default: () => renderMenuItem(v.children!, fullPath)
}}
</ElSubMenu>
)
}
})
}
return { const preFixCls = getPrefixCls('menu-popper')
renderMenuItem return (
} <ElSubMenu
index={fullPath}
popperClass={
menuMode === 'vertical' ? `${preFixCls}--vertical` : `${preFixCls}--horizontal`
}
>
{{
title: () => renderMenuTitle(meta),
default: () => renderMenuItem(v.children!, fullPath)
}}
</ElSubMenu>
)
}
})
} }
return {
renderMenuItem
}
}

View File

@ -10,14 +10,10 @@ export const useRenderMenuTitle = () => {
return icon ? ( return icon ? (
<> <>
<Icon icon={meta.icon}></Icon> <Icon icon={meta.icon}></Icon>
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap"> <span class="v-menu__title">{t(title as string)}</span>
{t(title as string)}
</span>
</> </>
) : ( ) : (
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap"> <span class="v-menu__title">{t(title as string)}</span>
{t(title as string)}
</span>
) )
} }

View File

@ -1,3 +1,4 @@
import type { RouteMeta } from 'vue-router'
import { ref, unref } from 'vue' import { ref, unref } from 'vue'
import { findPath } from '@/utils/tree' import { findPath } from '@/utils/tree'
@ -20,7 +21,7 @@ export const hasOneShowingChild = (
const onlyOneChild = ref<OnlyOneChildType>() const onlyOneChild = ref<OnlyOneChildType>()
const showingChildren = children.filter((v) => { const showingChildren = children.filter((v) => {
const meta = v.meta ?? {} const meta = (v.meta ?? {}) as RouteMeta
if (meta.hidden) { if (meta.hidden) {
return false return false
} else { } else {

View File

@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ElDrawer, ElDivider, ElMessage } from 'element-plus' import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
import { ref, unref } from 'vue' import { ref, unref, computed, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { ThemeSwitch } from '@/components/ThemeSwitch' import { ThemeSwitch } from '@/components/ThemeSwitch'
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
import { useCssVar } from '@vueuse/core' import { useCssVar } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { trim, setCssVar, getCssVar } from '@/utils' import { trim, setCssVar } from '@/utils'
import ColorRadioPicker from './components/ColorRadioPicker.vue' import ColorRadioPicker from './components/ColorRadioPicker.vue'
import InterfaceDisplay from './components/InterfaceDisplay.vue' import InterfaceDisplay from './components/InterfaceDisplay.vue'
import LayoutRadioPicker from './components/LayoutRadioPicker.vue' import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
@ -23,6 +24,8 @@ const appStore = useAppStore()
const { t } = useI18n() const { t } = useI18n()
const layout = computed(() => appStore.getLayout)
const drawer = ref(false) const drawer = ref(false)
// //
@ -39,28 +42,70 @@ const setSystemTheme = (color: string) => {
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '') const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
const setHeaderTheme = (color: string) => { const setHeaderTheme = (color: string) => {
appStore.setHeaderTheme(color) const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
const topToolBorderColor = isDarkColor ? color : '#eee'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
appStore.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor,
topToolBorderColor
})
if (unref(layout) === 'top') {
setMenuTheme(color)
}
} }
// //
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '') const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
const setMenuTheme = (color: string) => { const setMenuTheme = (color: string) => {
appStore.setMenuTheme(color) const primaryColor = useCssVar('--el-color-primary', document.documentElement)
const isDarkColor = colorIsDark(color)
const theme: Recordable = {
//
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
//
leftMenuBgColor: color,
//
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
//
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
//
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
//
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
//
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
// logo
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo
logoBorderColor: isDarkColor ? color : '#eee'
}
appStore.setTheme(theme)
appStore.setCssVarTheme()
} }
// layout // layout
// watch( watch(
// () => layout.value, () => layout.value,
// (n) => { (n) => {
// if (n === 'top' && !appStore.getIsDark) { if (n === 'top' && !appStore.getIsDark) {
// headerTheme.value = '#fff' headerTheme.value = '#fff'
// setHeaderTheme('#fff') setHeaderTheme('#fff')
// } else { } else {
// setMenuTheme(unref(menuTheme)) setMenuTheme(unref(menuTheme))
// } }
// } }
// ) )
// //
const copyConfig = async () => { const copyConfig = async () => {
@ -147,12 +192,6 @@ const clear = () => {
storageClear() storageClear()
window.location.reload() window.location.reload()
} }
const themeChange = () => {
const color = getCssVar('--el-bg-color')
setMenuTheme(color)
setHeaderTheme(color)
}
</script> </script>
<template> <template>
@ -172,7 +211,7 @@ const themeChange = () => {
<div class="text-center"> <div class="text-center">
<!-- 主题 --> <!-- 主题 -->
<ElDivider>{{ t('setting.theme') }}</ElDivider> <ElDivider>{{ t('setting.theme') }}</ElDivider>
<ThemeSwitch @change="themeChange" /> <ThemeSwitch />
<!-- 布局 --> <!-- 布局 -->
<ElDivider>{{ t('setting.layout') }}</ElDivider> <ElDivider>{{ t('setting.layout') }}</ElDivider>
@ -213,21 +252,23 @@ const themeChange = () => {
/> />
<!-- 菜单主题 --> <!-- 菜单主题 -->
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider> <template v-if="layout !== 'top'">
<ColorRadioPicker <ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
v-model="menuTheme" <ColorRadioPicker
:schema="[ v-model="menuTheme"
'#fff', :schema="[
'#001529', '#fff',
'#212121', '#001529',
'#273352', '#212121',
'#191b24', '#273352',
'#383f45', '#191b24',
'#001628', '#383f45',
'#344058' '#001628',
]" '#344058'
@change="setMenuTheme" ]"
/> @change="setMenuTheme"
/>
</template>
</div> </div>
<!-- 界面显示 --> <!-- 界面显示 -->

View File

@ -85,9 +85,6 @@ export default defineComponent({
} else { } else {
showTitle.value = !collapse showTitle.value = !collapse
} }
},
{
immediate: true
} }
) )
@ -200,12 +197,11 @@ export default defineComponent({
</div> </div>
<Menu <Menu
class={[ class={[
'!absolute top-0 z-3000', '!absolute top-0',
{ {
'!left-[var(--tab-menu-min-width)]': unref(collapse), '!left-[var(--tab-menu-min-width)]': unref(collapse),
'!left-[var(--tab-menu-max-width)]': !unref(collapse), '!left-[var(--tab-menu-max-width)]': !unref(collapse),
'!w-[var(--left-menu-max-width)] border-r-1 border-r-solid border-[var(--el-border-color)]': '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
unref(showMenu) || unref(fixedMenu),
'!w-0': !unref(showMenu) && !unref(fixedMenu) '!w-0': !unref(showMenu) && !unref(fixedMenu)
} }
]} ]}

View File

@ -17,6 +17,7 @@ import { set, get } from 'lodash-es'
import { CSSProperties } from 'vue' import { CSSProperties } from 'vue'
import { getSlot } from '@/utils/tsxHelper' import { getSlot } from '@/utils/tsxHelper'
import TableActions from './components/TableActions.vue' import TableActions from './components/TableActions.vue'
import { isImgPath } from '@/utils/is'
import { createVideoViewer } from '@/components/VideoPlayer' import { createVideoViewer } from '@/components/VideoPlayer'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { BaseButton } from '@/components/Button' import { BaseButton } from '@/components/Button'
@ -58,13 +59,8 @@ export default defineComponent({
type: Array as PropType<Recordable[]>, type: Array as PropType<Recordable[]>,
default: () => [] default: () => []
}, },
// //
imagePreview: { preview: {
type: Array as PropType<string[]>,
default: () => []
},
//
videoPreview: {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [] default: () => []
}, },
@ -279,10 +275,6 @@ export default defineComponent({
setProps({ size }) setProps({ size })
} }
const confirmSetColumn = (columns: TableColumn[]) => {
setProps({ columns })
}
expose({ expose({
setProps, setProps,
setColumn, setColumn,
@ -339,13 +331,11 @@ export default defineComponent({
const bindValue: Recordable = { ...attrs, ...unref(getProps) } const bindValue: Recordable = { ...attrs, ...unref(getProps) }
delete bindValue.columns delete bindValue.columns
delete bindValue.data delete bindValue.data
delete bindValue.align
return bindValue return bindValue
}) })
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => { const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } = const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
unref(getProps)
return columnsChildren.map((v) => { return columnsChildren.map((v) => {
if (v.hidden) return null if (v.hidden) return null
const props = { ...v } as any const props = { ...v } as any
@ -356,10 +346,10 @@ export default defineComponent({
const slots = { const slots = {
default: (...args: any[]) => { default: (...args: any[]) => {
const data = args[0] const data = args[0]
let isPreview = false let isImageUrl = false
isPreview = if (preview.length) {
imagePreview.some((item) => (item as string) === v.field) || isImageUrl = preview.some((item) => (item as string) === v.field)
videoPreview.some((item) => (item as string) === v.field) }
return children && children.length return children && children.length
? renderTreeTableColumn(children) ? renderTreeTableColumn(children)
@ -367,8 +357,8 @@ export default defineComponent({
? props.slots.default(...args) ? props.slots.default(...args)
: v?.formatter : v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index) ? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isPreview : isImageUrl
? renderPreview(get(data.row, v.field), v.field) ? renderPreview(get(data.row, v.field))
: get(data.row, v.field) : get(data.row, v.field)
} }
} }
@ -390,11 +380,10 @@ export default defineComponent({
}) })
} }
const renderPreview = (url: string, field: string) => { const renderPreview = (url: string) => {
const { imagePreview, videoPreview } = unref(getProps)
return ( return (
<div class="flex items-center"> <div class="flex items-center">
{imagePreview.includes(field) ? ( {isImgPath(url) ? (
<ElImage <ElImage
src={url} src={url}
fit="cover" fit="cover"
@ -403,7 +392,7 @@ export default defineComponent({
preview-src-list={[url]} preview-src-list={[url]}
preview-teleported preview-teleported
/> />
) : videoPreview.includes(field) ? ( ) : (
<BaseButton <BaseButton
type="primary" type="primary"
icon={<Icon icon="ep:video-play" />} icon={<Icon icon="ep:video-play" />}
@ -415,7 +404,7 @@ export default defineComponent({
> >
预览 预览
</BaseButton> </BaseButton>
) : null} )}
</div> </div>
) )
} }
@ -430,8 +419,7 @@ export default defineComponent({
headerAlign, headerAlign,
showOverflowTooltip, showOverflowTooltip,
reserveSelection, reserveSelection,
imagePreview, preview
videoPreview
} = unref(getProps) } = unref(getProps)
return (columnsChildren || columns).map((v) => { return (columnsChildren || columns).map((v) => {
@ -446,7 +434,6 @@ export default defineComponent({
align={v.align || align} align={v.align || align}
headerAlign={v.headerAlign || headerAlign} headerAlign={v.headerAlign || headerAlign}
label={v.label} label={v.label}
fixed={v.fixed}
width="65px" width="65px"
></ElTableColumn> ></ElTableColumn>
) )
@ -471,10 +458,10 @@ export default defineComponent({
default: (...args: any[]) => { default: (...args: any[]) => {
const data = args[0] const data = args[0]
let isPreview = false let isImageUrl = false
isPreview = if (preview.length) {
imagePreview.some((item) => (item as string) === v.field) || isImageUrl = preview.some((item) => (item as string) === v.field)
videoPreview.some((item) => (item as string) === v.field) }
return children && children.length return children && children.length
? renderTreeTableColumn(children) ? renderTreeTableColumn(children)
@ -482,8 +469,8 @@ export default defineComponent({
? props.slots.default(...args) ? props.slots.default(...args)
: v?.formatter : v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index) ? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isPreview : isImageUrl
? renderPreview(get(data.row, v.field), v.field) ? renderPreview(get(data.row, v.field))
: get(data.row, v.field) : get(data.row, v.field)
} }
} }
@ -556,12 +543,11 @@ export default defineComponent({
</div> </div>
) : ( ) : (
<> <>
{unref(getProps).showAction && !unref(getProps).customContent ? ( {unref(getProps).showAction ? (
<TableActions <TableActions
columns={unref(getProps).columns} columns={unref(getProps).columns}
onChangSize={changSize} onChangSize={changSize}
onRefresh={refresh} onRefresh={refresh}
onConfirm={confirmSetColumn}
/> />
) : null} ) : null}
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}> <ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>

View File

@ -1,166 +0,0 @@
<script setup lang="ts">
import {
ElDrawer,
ElCheckbox,
ElCheckboxGroup,
ElText,
ElRadioButton,
ElRadioGroup
} from 'element-plus'
import { TableColumn } from '../types'
import { PropType, ref, watch, unref } from 'vue'
import { cloneDeep } from 'lodash-es'
import { DEFAULT_FILTER_COLUMN } from '@/constants'
import { VueDraggable } from 'vue-draggable-plus'
const modelValue = defineModel<boolean>()
const props = defineProps({
columns: {
type: Array as PropType<TableColumn[]>,
default: () => []
}
})
const emit = defineEmits(['confirm'])
const oldColumns = ref<TableColumn[]>()
const settingColumns = ref<TableColumn[]>()
//
const hiddenColumns = ref<TableColumn[]>([])
const defaultCheckColumns = ref<string[]>([])
const checkColumns = ref<string[]>([])
const checkAll = ref(false)
const isIndeterminate = ref(true)
const handleCheckAllChange = (val: boolean) => {
checkColumns.value = val ? unref(defaultCheckColumns) : []
isIndeterminate.value = false
}
const handleCheckedColumnsChange = (value: string[]) => {
const checkedCount = value.length
checkAll.value = checkedCount === unref(defaultCheckColumns)?.length
isIndeterminate.value = checkedCount > 0 && checkedCount < unref(defaultCheckColumns)?.length
}
const confirm = () => {
const newColumns = cloneDeep(unref(settingColumns))?.map((item) => {
const fixed = unref(settingColumns)?.find((col) => col.field === item.field)?.fixed
item.hidden = !!!unref(checkColumns)?.includes(item.field)
item.fixed = fixed ? fixed : undefined
return item
})
emit('confirm', [...unref(hiddenColumns), ...(newColumns || [])])
modelValue.value = false
}
const restore = () => {
initColumns([...unref(hiddenColumns), ...(unref(oldColumns) || [])], true)
}
const initColumns = (columns: TableColumn[], isReStore = false) => {
const newColumns = columns?.filter((item) => {
if (!isReStore) {
item.fixed = item.fixed !== void 0 ? item.fixed : undefined
}
return (item.type && !DEFAULT_FILTER_COLUMN.includes(item.type)) || !item.type
})
if (!unref(oldColumns)?.length) {
oldColumns.value = cloneDeep(newColumns)
}
settingColumns.value = cloneDeep(newColumns)
hiddenColumns.value = cloneDeep(
columns?.filter((item) => item.type && DEFAULT_FILTER_COLUMN.includes(item.type))
)
defaultCheckColumns.value = unref(settingColumns)?.map((item) => item.field) || []
checkColumns.value =
unref(settingColumns)
?.filter((item) => !item.hidden)
?.map((item) => item.field) || []
if (unref(checkColumns)?.length === unref(defaultCheckColumns)?.length) {
checkAll.value = true
isIndeterminate.value = false
}
}
watch(
() => props.columns,
(columns) => {
initColumns(columns)
},
{
immediate: true,
deep: true
}
)
</script>
<template>
<ElDrawer v-model="modelValue" title="列设置" size="350px">
<div>
<div class="flex items-center justify-between">
<div class="flex items-center justify-between">
<ElCheckbox
v-model="checkAll"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
/>
<ElText class="ml-8px!">{{ checkColumns.length }} / {{ settingColumns?.length }}</ElText>
</div>
<ElText>固定 / 排序</ElText>
</div>
<div v-if="settingColumns?.length">
<VueDraggable
v-model="settingColumns"
target=".el-checkbox-group"
handle=".handle"
:animation="150"
>
<ElCheckboxGroup
ref="draggableWrap"
v-model="checkColumns"
@change="handleCheckedColumnsChange"
>
<div
v-for="item in settingColumns"
:key="item.field"
class="flex items-center justify-between mt-12px"
>
<ElCheckbox :label="item.field">
{{ item.label }}
</ElCheckbox>
<div class="flex items-center">
<ElRadioGroup size="small" v-model="item.fixed">
<ElRadioButton label="left">
<Icon icon="ep:arrow-left" />
</ElRadioButton>
<ElRadioButton :label="undefined">
<Icon icon="ep:close" />
</ElRadioButton>
<ElRadioButton label="right">
<Icon icon="ep:arrow-right" />
</ElRadioButton>
</ElRadioGroup>
<div class="ml-12px cursor-move handle"><Icon icon="ep:rank" /></div>
</div>
</div>
</ElCheckboxGroup>
</VueDraggable>
</div>
</div>
<template #footer>
<div>
<BaseButton @click="restore">还原</BaseButton>
<BaseButton type="primary" @click="confirm">确定</BaseButton>
</div>
</template>
</ElDrawer>
</template>

View File

@ -1,30 +1,36 @@
<script lang="tsx"> <script lang="tsx">
import { defineComponent, unref, computed, PropType, ref } from 'vue' import { defineComponent, unref, computed, PropType, watch } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus' import {
ElTooltip,
ElDropdown,
ElDropdownMenu,
ElDropdownItem,
ComponentSize
// ElPopover,
// ElTree
} from 'element-plus'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { TableColumn } from '../types' import { TableColumn } from '../types'
import ColumnSetting from './ColumnSetting.vue' import { cloneDeep } from 'lodash-es'
// import { eachTree } from '@/utils/tree'
const appStore = useAppStore()
const sizeMap = computed(() => appStore.sizeMap)
const { t } = useI18n()
export default defineComponent({ export default defineComponent({
name: 'TableActions', name: 'TableActions',
components: {
ColumnSetting
},
props: { props: {
columns: { columns: {
type: Array as PropType<TableColumn[]>, type: Array as PropType<TableColumn[]>,
default: () => [] default: () => []
} }
}, },
emits: ['refresh', 'changSize', 'confirm'], emits: ['refresh', 'changSize'],
setup(props, { emit }) { setup(props, { emit }) {
const appStore = useAppStore()
const { t } = useI18n()
const sizeMap = computed(() => appStore.sizeMap)
const showSetting = ref(false)
const refresh = () => { const refresh = () => {
emit('refresh') emit('refresh')
} }
@ -33,71 +39,111 @@ export default defineComponent({
emit('changSize', size) emit('changSize', size)
} }
const confirm = (columns: TableColumn[]) => { const columns = computed(() => {
emit('confirm', columns) return cloneDeep(props.columns).filter((v) => {
} // typeselectionexpand
if (v.type !== 'selection' && v.type !== 'expand') {
return v
}
})
})
const showColumnSetting = () => { watch(
showSetting.value = true () => columns.value,
} (newColumns) => {
console.log('columns change', newColumns)
},
{
deep: true
}
)
return () => ( return () => (
<> <>
<div class="text-right h-28px flex items-center justify-end"> <div class="text-right h-28px flex items-center justify-end">
<div title="刷新" class="w-30px h-20px flex items-center justify-end" onClick={refresh}> <ElTooltip content={t('common.refresh')} placement="top">
<Icon <span onClick={refresh}>
icon="ant-design:sync-outlined" <Icon
class="cursor-pointer" icon="ant-design:sync-outlined"
hover-color="var(--el-color-primary)" class="cursor-pointer"
/> hover-color="var(--el-color-primary)"
</div> />
</span>
</ElTooltip>
<ElDropdown trigger="click" onCommand={changSize}> <ElTooltip content={t('common.size')} placement="top">
<ElDropdown trigger="click" onCommand={changSize}>
{{
default: () => {
return (
<span>
<Icon
icon="ant-design:column-height-outlined"
class="cursor-pointer mr-8px ml-8px"
hover-color="var(--el-color-primary)"
/>
</span>
)
},
dropdown: () => {
return (
<ElDropdownMenu>
{{
default: () => {
return unref(sizeMap).map((v) => {
return (
<ElDropdownItem key={v} command={v}>
{t(`size.${v}`)}
</ElDropdownItem>
)
})
}
}}
</ElDropdownMenu>
)
}
}}
</ElDropdown>
</ElTooltip>
{/* <ElTooltip content={t('common.columnSetting')} placement="top"> */}
{/* <ElPopover trigger="click" placement="left">
{{ {{
default: () => { default: () => {
return ( return (
<div title="尺寸" class="w-30px h-20px flex items-center justify-end"> <div>
<Icon <ElTree
icon="ant-design:column-height-outlined" data={unref(columns)}
class="cursor-pointer" show-checkbox
hover-color="var(--el-color-primary)" default-checked-keys={unref(defaultCheckeds)}
draggable
node-key="field"
allow-drop={(_draggingNode: any, _dropNode: any, type: string) => {
if (type === 'inner') {
return false
} else {
return true
}
}}
onNode-drag-end={onNodeDragEnd}
onCheck-change={onCheckChange}
/> />
</div> </div>
) )
}, },
dropdown: () => { reference: () => {
return ( return (
<ElDropdownMenu> <Icon
{{ icon="ant-design:setting-outlined"
default: () => { class="cursor-pointer"
return unref(sizeMap).map((v) => { hoverColor="var(--el-color-primary)"
return ( />
<ElDropdownItem key={v} command={v}>
{t(`size.${v}`)}
</ElDropdownItem>
)
})
}
}}
</ElDropdownMenu>
) )
} }
}} }}
</ElDropdown> </ElPopover> */}
{/* </ElTooltip> */}
<div
title="列设置"
class="w-30px h-20px flex items-center justify-end"
onClick={showColumnSetting}
>
<Icon
icon="ant-design:setting-outlined"
class="cursor-pointer"
hover-color="var(--el-color-primary)"
/>
</div>
</div> </div>
<ColumnSetting v-model={showSetting.value} columns={props.columns} onConfirm={confirm} />
</> </>
) )
} }

View File

@ -12,8 +12,6 @@ import { useDesign } from '@/hooks/web/useDesign'
import { useTemplateRefsList } from '@vueuse/core' import { useTemplateRefsList } from '@vueuse/core'
import { ElScrollbar } from 'element-plus' import { ElScrollbar } from 'element-plus'
import { useScrollTo } from '@/hooks/event/useScrollTo' import { useScrollTo } from '@/hooks/event/useScrollTo'
import { useTagsView } from '@/hooks/web/useTagsView'
import { cloneDeep } from 'lodash-es'
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -21,9 +19,7 @@ const prefixCls = getPrefixCls('tags-view')
const { t } = useI18n() const { t } = useI18n()
const { currentRoute, push } = useRouter() const { currentRoute, push, replace } = useRouter()
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTagsView()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
@ -35,10 +31,6 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews)
const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([]) const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
const setSelectTag = tagsViewStore.setSelectedTag
const appStore = useAppStore() const appStore = useAppStore()
const tagsViewIcon = computed(() => appStore.getTagsViewIcon) const tagsViewIcon = computed(() => appStore.getTagsViewIcon)
@ -51,30 +43,66 @@ const initTags = () => {
for (const tag of unref(affixTagArr)) { for (const tag of unref(affixTagArr)) {
// Must have tag name // Must have tag name
if (tag.name) { if (tag.name) {
tagsViewStore.addVisitedView(cloneDeep(tag)) tagsViewStore.addVisitedView(tag)
} }
} }
} }
const selectedTag = ref<RouteLocationNormalizedLoaded>()
// tag // tag
const addTags = () => { const addTags = () => {
const { name } = unref(currentRoute) const { name } = unref(currentRoute)
if (name) { if (name) {
setSelectTag(unref(currentRoute)) selectedTag.value = unref(currentRoute)
tagsViewStore.addView(unref(currentRoute)) tagsViewStore.addView(unref(currentRoute))
} }
return false
} }
// tag // tag
const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => { const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
closeCurrent(view, () => { if (view?.meta?.affix) return
if (isActive(view)) { tagsViewStore.delView(view)
toLastView() if (isActive(view)) {
} toLastView()
}
}
//
const closeAllTags = () => {
tagsViewStore.delAllViews()
toLastView()
}
//
const closeOthersTags = () => {
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
}
//
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
if (!view) return
tagsViewStore.delCachedView()
const { path, query } = view
await nextTick()
replace({
path: '/redirect' + path,
query: query
}) })
} }
// //
const closeLeftTags = () => {
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
}
//
const closeRightTags = () => {
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
}
//
const toLastView = () => { const toLastView = () => {
const visitedViews = tagsViewStore.getVisitedViews const visitedViews = tagsViewStore.getVisitedViews
const latestView = visitedViews.slice(-1)[0] const latestView = visitedViews.slice(-1)[0]
@ -93,33 +121,6 @@ const toLastView = () => {
} }
} }
//
const closeAllTags = () => {
closeAll(() => {
toLastView()
})
}
//
const closeOthersTags = () => {
closeOther()
}
//
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
refreshPage(view)
}
//
const closeLeftTags = () => {
closeLeft()
}
//
const closeRightTags = () => {
closeRight()
}
// tag // tag
const moveToCurrentTag = async () => { const moveToCurrentTag = async () => {
await nextTick() await nextTick()
@ -210,14 +211,13 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
// //
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>() const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
// //
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => { const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
if (visible) { if (visible) {
for (const v of unref(itemRefs)) { for (const v of unref(itemRefs)) {
const elDropdownMenuRef = v.elDropdownMenuRef const elDropdownMenuRef = v.elDropdownMenuRef
if (tagItem.fullPath !== v.tagItem.fullPath) { if (tagItem.fullPath !== v.tagItem.fullPath) {
elDropdownMenuRef?.handleClose() elDropdownMenuRef?.handleClose()
setSelectTag(tagItem)
} }
} }
} }

View File

@ -7,8 +7,6 @@ import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
const emit = defineEmits(['change'])
const prefixCls = getPrefixCls('theme-switch') const prefixCls = getPrefixCls('theme-switch')
const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' }) const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
@ -25,7 +23,6 @@ const blackColor = 'var(--el-color-black)'
const themeChange = (val: boolean) => { const themeChange = (val: boolean) => {
appStore.setIsDark(val) appStore.setIsDark(val)
emit('change', val)
} }
</script> </script>

View File

@ -6,7 +6,7 @@ export const SUCCESS_CODE = 0
/** /**
* contentType * contentType
*/ */
export const CONTENT_TYPE: AxiosContentType = 'application/json' export const CONTENT_TYPE = 'application/json'
/** /**
* *
@ -22,13 +22,3 @@ export const NO_REDIRECT_WHITE_LIST = ['/login']
* *
*/ */
export const NO_RESET_WHITE_LIST = ['Redirect', 'Login', 'NoFind', 'Root'] export const NO_RESET_WHITE_LIST = ['Redirect', 'Login', 'NoFind', 'Root']
/**
*
*/
export const DEFAULT_FILTER_COLUMN = ['expand', 'selection']
/**
* headers->content-type自动转换数据格式
*/
export const TRANSFORM_REQUEST_DATA = true

View File

@ -78,14 +78,20 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const schemaItem = crudSchema[i] const schemaItem = crudSchema[i]
const searchSchemaItem = { // 判断是否隐藏
component: schemaItem?.search?.component || 'Input', if (!schemaItem?.search?.hidden) {
...schemaItem.search, const searchSchemaItem = {
field: schemaItem.field, component: schemaItem?.search?.component || 'Input',
label: schemaItem.label ...schemaItem.search,
} field: schemaItem.field,
label: schemaItem.label
}
searchSchema.push(searchSchemaItem) // 删除不必要的字段
delete searchSchemaItem.hidden
searchSchema.push(searchSchemaItem)
}
} }
return searchSchema return searchSchema
@ -121,14 +127,19 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const formItem = crudSchema[i] const formItem = crudSchema[i]
// 判断是否隐藏 // 判断是否隐藏
const formSchemaItem = { if (!formItem?.form?.hidden) {
component: formItem?.form?.component || 'Input', const formSchemaItem = {
...formItem.form, component: formItem?.form?.component || 'Input',
field: formItem.field, ...formItem.form,
label: formItem.label field: formItem.field,
} label: formItem.label
}
formSchema.push(formSchemaItem) // 删除不必要的字段
delete formSchemaItem.hidden
formSchema.push(formSchemaItem)
}
} }
return formSchema return formSchema

View File

@ -1,13 +1,13 @@
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut()
export const usePageLoading = () => { export const usePageLoading = () => {
const loadStart = () => { const loadStart = () => {
const appStore = useAppStoreWithOut()
appStore.setPageLoading(true) appStore.setPageLoading(true)
} }
const loadDone = () => { const loadDone = () => {
const appStore = useAppStoreWithOut()
appStore.setPageLoading(false) appStore.setPageLoading(false)
} }

View File

@ -1,63 +0,0 @@
import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
import { computed, nextTick, unref } from 'vue'
export const useTagsView = () => {
const tagsViewStore = useTagsViewStoreWithOut()
const { replace, currentRoute } = useRouter()
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
const closeAll = (callback?: Fn) => {
tagsViewStore.delAllViews()
callback?.()
}
const closeLeft = (callback?: Fn) => {
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
callback?.()
}
const closeRight = (callback?: Fn) => {
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
callback?.()
}
const closeOther = (callback?: Fn) => {
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
callback?.()
}
const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
if (view?.meta?.affix) return
tagsViewStore.delView(view || unref(currentRoute))
callback?.()
}
const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
tagsViewStore.delCachedView()
const { path, query } = view || unref(currentRoute)
await nextTick()
replace({
path: '/redirect' + path,
query: query
})
callback?.()
}
const setTitle = (title: string, path?: string) => {
tagsViewStore.setTitle(title, path)
}
return {
closeAll,
closeLeft,
closeRight,
closeOther,
closeCurrent,
refreshPage,
setTitle
}
}

View File

@ -3,10 +3,10 @@ import { isString } from '@/utils/is'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
const appStore = useAppStoreWithOut()
export const useTitle = (newTitle?: string) => { export const useTitle = (newTitle?: string) => {
const { t } = useI18n() const { t } = useI18n()
const appStore = useAppStoreWithOut()
const title = ref( const title = ref(
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
) )

View File

@ -71,14 +71,8 @@ export default defineComponent({
.@{prefix-cls} { .@{prefix-cls} {
background-color: var(--app-content-bg-color); background-color: var(--app-content-bg-color);
.@{prefix-cls}-content-scrollbar { :deep(.@{elNamespace}-scrollbar__view) {
& > :deep(.el-scrollbar__wrap) { height: 100% !important;
& > .@{elNamespace}-scrollbar__view {
display: flex;
height: 100% !important;
flex-direction: column;
}
}
} }
} }
</style> </style>

View File

@ -6,6 +6,10 @@ import { computed } from 'vue'
const appStore = useAppStore() const appStore = useAppStore()
const layout = computed(() => appStore.getLayout)
const fixedHeader = computed(() => appStore.getFixedHeader)
const footer = computed(() => appStore.getFooter) const footer = computed(() => appStore.getFooter)
const tagsViewStore = useTagsViewStore() const tagsViewStore = useTagsViewStore()
@ -13,12 +17,39 @@ const tagsViewStore = useTagsViewStore()
const getCaches = computed((): string[] => { const getCaches = computed((): string[] => {
return tagsViewStore.getCachedViews return tagsViewStore.getCachedViews
}) })
const tagsView = computed(() => appStore.getTagsView)
</script> </script>
<template> <template>
<section <section
:class="[ :class="[
'flex-1 p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]' 'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
{
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
(fixedHeader &&
(layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
footer) ||
(!tagsView && layout === 'top' && footer),
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]':
tagsView && layout === 'top' && footer,
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]':
!fixedHeader && layout === 'classic' && footer,
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
!fixedHeader && layout === 'topLeft' && footer,
// '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height)-var(--top-tool-height))]':
// !fixedHeader && layout === 'top' && footer,
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]':
fixedHeader && layout === 'cutMenu' && footer,
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]':
!fixedHeader && layout === 'cutMenu' && footer
}
]" ]"
> >
<router-view> <router-view>

View File

@ -41,7 +41,8 @@ export default defineComponent({
id={`${variables.namespace}-tool-header`} id={`${variables.namespace}-tool-header`}
class={[ class={[
prefixCls, prefixCls,
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between' 'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between',
'dark:bg-[var(--el-bg-color)]'
]} ]}
> >
{layout.value !== 'top' ? ( {layout.value !== 'top' ? (

View File

@ -1,6 +1,4 @@
import 'vue/jsx' // 引入windi css
// 引入unocss
import '@/plugins/unocss' import '@/plugins/unocss'
// 导入全局的svg图标 // 导入全局的svg图标

View File

@ -12,13 +12,7 @@ export const setupElementPlus = (app: App<Element>) => {
app.use(plugin) app.use(plugin)
}) })
// 为了开发环境启动更快,一次性引入所有样式
if (import.meta.env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'true') {
import('element-plus/dist/index.css')
return
}
components.forEach((component) => { components.forEach((component) => {
app.component(component.name!, component) app.component(component.name, component)
}) })
} }

View File

@ -1 +1,3 @@
import 'virtual:svg-icons-register' import 'virtual:svg-icons-register'
import '@purge-icons/generated'

View File

@ -2,10 +2,6 @@ import { defineStore } from 'pinia'
import { store } from '../index' import { store } from '../index'
import { setCssVar, humpToUnderline } from '@/utils' import { setCssVar, humpToUnderline } from '@/utils'
import { ElMessage, ComponentSize } from 'element-plus' import { ElMessage, ComponentSize } from 'element-plus'
import { colorIsDark, hexToRGB, lighten, mix } from '@/utils/color'
import { unref } from 'vue'
import { useCssVar } from '@vueuse/core'
import { useDark } from '@vueuse/core'
interface AppState { interface AppState {
breadcrumb: boolean breadcrumb: boolean
@ -243,7 +239,6 @@ export const useAppStore = defineStore('app', {
document.documentElement.classList.add('light') document.documentElement.classList.add('light')
document.documentElement.classList.remove('dark') document.documentElement.classList.remove('dark')
} }
this.setPrimaryLight()
}, },
setCurrentSize(currentSize: ComponentSize) { setCurrentSize(currentSize: ComponentSize) {
this.currentSize = currentSize this.currentSize = currentSize
@ -258,76 +253,9 @@ export const useAppStore = defineStore('app', {
for (const key in this.theme) { for (const key in this.theme) {
setCssVar(`--${humpToUnderline(key)}`, this.theme[key]) setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
} }
this.setPrimaryLight()
}, },
setFooter(footer: boolean) { setFooter(footer: boolean) {
this.footer = footer this.footer = footer
},
setPrimaryLight() {
if (this.theme.elColorPrimary) {
const elColorPrimary = this.theme.elColorPrimary
const color = this.isDark ? '#000000' : '#ffffff'
const lightList = [3, 5, 7, 8, 9]
lightList.forEach((v) => {
setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
})
setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
}
},
setMenuTheme(color: string) {
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
const isDarkColor = colorIsDark(color)
const theme: Recordable = {
// 左侧菜单边框颜色
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
// 左侧菜单背景颜色
leftMenuBgColor: color,
// 左侧菜单浅色背景颜色
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
// 左侧菜单选中背景颜色
leftMenuBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单收起选中背景颜色
leftMenuCollapseBgActiveColor: isDarkColor
? 'var(--el-color-primary)'
: hexToRGB(unref(primaryColor), 0.1),
// 左侧菜单字体颜色
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
// 左侧菜单选中字体颜色
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
// logo字体颜色
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
// logo边框颜色
logoBorderColor: isDarkColor ? color : '#eee'
}
this.setTheme(theme)
this.setCssVarTheme()
},
setHeaderTheme(color: string) {
const isDarkColor = colorIsDark(color)
const textColor = isDarkColor ? '#fff' : 'inherit'
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
const topToolBorderColor = isDarkColor ? color : '#eee'
setCssVar('--top-header-bg-color', color)
setCssVar('--top-header-text-color', textColor)
setCssVar('--top-header-hover-color', textHoverColor)
this.setTheme({
topHeaderBgColor: color,
topHeaderTextColor: textColor,
topHeaderHoverColor: textHoverColor,
topToolBorderColor
})
if (this.getLayout === 'top') {
this.setMenuTheme(color)
}
},
initTheme() {
const isDark = useDark({
valueDark: 'dark',
valueLight: 'light'
})
isDark.value = this.getIsDark
} }
}, },
persist: true persist: true

View File

@ -5,7 +5,7 @@ import en from 'element-plus/es/locale/lang/en'
import { useStorage } from '@/hooks/web/useStorage' import { useStorage } from '@/hooks/web/useStorage'
import { LocaleDropdownType } from '@/components/LocaleDropdown' import { LocaleDropdownType } from '@/components/LocaleDropdown'
const { getStorage, setStorage } = useStorage('localStorage') const { getStorage, setStorage } = useStorage()
const elLocaleMap = { const elLocaleMap = {
'zh-CN': zhCn, 'zh-CN': zhCn,

View File

@ -151,22 +151,3 @@ const subtractLight = (color: string, amount: number) => {
const c = cc < 0 ? 0 : cc const c = cc < 0 ? 0 : cc
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}` return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
} }
/**
* Mixes two colors.
*
* @param {string} color1 - The first color, should be a 6-digit hexadecimal color code starting with `#`.
* @param {string} color2 - The second color, should be a 6-digit hexadecimal color code starting with `#`.
* @param {number} [weight=0.5] - The weight of color1 in the mix, should be a number between 0 and 1, where 0 represents 100% of color2, and 1 represents 100% of color1.
* @returns {string} The mixed color, a 6-digit hexadecimal color code starting with `#`.
*/
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
let color = '#'
for (let i = 0; i <= 2; i++) {
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16)
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16)
const c = Math.round(c1 * weight + c2 * (1 - weight))
color += c.toString(16).padStart(2, '0')
}
return color
}

View File

@ -47,10 +47,6 @@ export const setCssVar = (prop: string, val: any, dom = document.documentElement
dom.style.setProperty(prop, val) dom.style.setProperty(prop, val)
} }
export const getCssVar = (prop: string, dom = document.documentElement) => {
return getComputedStyle(dom).getPropertyValue(prop)
}
/** /**
* *
* @param {Array} ary * @param {Array} ary
@ -126,14 +122,3 @@ export function toAnyString() {
export function firstUpperCase(str: string) { export function firstUpperCase(str: string) {
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()) return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
} }
/**
* formData
*/
export function objToFormData(obj: Recordable) {
const formData = new FormData()
Object.keys(obj).forEach((key) => {
formData.append(key, obj[key])
})
return formData
}

View File

@ -3,7 +3,7 @@ import { LoginForm, RegisterForm } from './components'
import { ThemeSwitch } from '@/components/ThemeSwitch' import { ThemeSwitch } from '@/components/ThemeSwitch'
import { LocaleDropdown } from '@/components/LocaleDropdown' import { LocaleDropdown } from '@/components/LocaleDropdown'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { getCssVar, underlineToHump } from '@/utils' import { underlineToHump } from '@/utils'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { ref } from 'vue' import { ref } from 'vue'
@ -26,12 +26,6 @@ const toRegister = () => {
const toLogin = () => { const toLogin = () => {
isLogin.value = true isLogin.value = true
} }
const themeChange = () => {
const color = getCssVar('--el-bg-color')
appStore.setMenuTheme(color)
appStore.setHeaderTheme(color)
}
</script> </script>
<template> <template>
@ -72,7 +66,7 @@ const themeChange = () => {
</div> </div>
<div class="flex justify-end items-center space-x-10px"> <div class="flex justify-end items-center space-x-10px">
<ThemeSwitch @change="themeChange" /> <ThemeSwitch />
<LocaleDropdown class="lt-xl:text-white dark:text-white" /> <LocaleDropdown class="lt-xl:text-white dark:text-white" />
</div> </div>
</div> </div>

View File

@ -20,8 +20,12 @@ module.exports = {
'function-no-unknown': null, 'function-no-unknown': null,
'no-empty-source': null, 'no-empty-source': null,
'named-grid-areas-no-invalid': null, 'named-grid-areas-no-invalid': null,
'unicode-bom': 'never',
'no-descending-specificity': null, 'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null, 'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
'declaration-block-trailing-semicolon': null,
'rule-empty-line-before': [ 'rule-empty-line-before': [
'always', 'always',
{ {

View File

@ -9,7 +9,6 @@
"sourceMap": true, "sourceMap": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"jsxImportSource": "vue",
"lib": ["esnext", "dom"], "lib": ["esnext", "dom"],
"baseUrl": "./", "baseUrl": "./",
"allowJs": true, "allowJs": true,

1
types/global.d.ts vendored
View File

@ -30,7 +30,6 @@ declare global {
| 'application/json' | 'application/json'
| 'application/x-www-form-urlencoded' | 'application/x-www-form-urlencoded'
| 'multipart/form-data' | 'multipart/form-data'
| 'text/plain'
declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put' declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put'

View File

@ -1,49 +1,9 @@
import { defineConfig, toEscapedSelector as e, presetUno, presetIcons } from 'unocss' import { defineConfig, toEscapedSelector as e, presetUno } from 'unocss'
import transformerVariantGroup from '@unocss/transformer-variant-group' import transformerVariantGroup from '@unocss/transformer-variant-group'
import { loadEnv } from 'vite'
const root = process.cwd()
const createPresetIcons = () => {
const isBuild = !!process.argv[4]
let env = {} as any
if (!isBuild) {
env = loadEnv(process.argv[3], root)
} else {
env = loadEnv(process.argv[4], root)
}
// @ts-ignore
if (env.VITE_USE_ONLINE_ICON === 'true') {
return []
} else {
return [
presetIcons({
prefix: ''
// 由于默认加载的是所有的图标,启动会非常慢,可以在这里去加载需要的图标,确保启动速度
// collections: {
// carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default),
// mdi: () => import('@iconify-json/mdi/icons.json').then(i => i.default),
// logos: () => import('@iconify-json/logos/icons.json').then(i => i.default),
// }
})
]
}
}
export default defineConfig({ export default defineConfig({
// ...UnoCSS options // ...UnoCSS options
rules: [ rules: [
[
/^overflow-ellipsis$/,
([], { rawSelector }) => {
const selector = e(rawSelector)
return `
${selector} {
text-overflow: ellipsis;
}
`
}
],
[ [
/^custom-hover$/, /^custom-hover$/,
([], { rawSelector }) => { ([], { rawSelector }) => {
@ -140,11 +100,6 @@ ${selector}:after {
} }
] ]
], ],
presets: [presetUno({ dark: 'class', attributify: false }), ...createPresetIcons()], presets: [presetUno({ dark: 'class', attributify: false })],
transformers: [transformerVariantGroup()], transformers: [transformerVariantGroup()]
content: {
pipeline: {
include: [/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html|ts)($|\?)/]
}
}
}) })

View File

@ -12,7 +12,6 @@ import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import' import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
import UnoCSS from 'unocss/vite' import UnoCSS from 'unocss/vite'
import { visualizer } from 'rollup-plugin-visualizer'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
const root = process.cwd() const root = process.cwd()
@ -40,23 +39,19 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
}), }),
VueJsx(), VueJsx(),
progress(), progress(),
env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'false' createStyleImportPlugin({
? createStyleImportPlugin({ resolves: [ElementPlusResolve()],
resolves: [ElementPlusResolve()], libs: [{
libs: [ libraryName: 'element-plus',
{ esModule: true,
libraryName: 'element-plus', resolveStyle: (name) => {
esModule: true, if (name === 'click-outside') {
resolveStyle: (name) => { return ''
if (name === 'click-outside') { }
return '' return `element-plus/es/components/${name.replace(/^el-/, '')}/style/css`
} }
return `element-plus/es/components/${name.replace(/^el-/, '')}/style/css` }]
} }),
}
]
})
: undefined,
EslintPlugin({ EslintPlugin({
cache: false, cache: false,
include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件 include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
@ -72,19 +67,17 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
svgoOptions: true svgoOptions: true
}), }),
PurgeIcons(), PurgeIcons(),
env.VITE_USE_MOCK === 'true' viteMockServe({
? viteMockServe({ ignore: /^\_/,
ignore: /^\_/, mockPath: 'mock',
mockPath: 'mock', localEnabled: !isBuild,
localEnabled: !isBuild, prodEnabled: isBuild,
prodEnabled: isBuild, injectCode: `
injectCode: `
import { setupProdMockServer } from '../mock/_createProductionServer' import { setupProdMockServer } from '../mock/_createProductionServer'
setupProdMockServer() setupProdMockServer()
` `
}) }),
: undefined,
ViteEjsPlugin({ ViteEjsPlugin({
title: env.VITE_APP_TITLE title: env.VITE_APP_TITLE
}), }),
@ -113,30 +106,20 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
} }
] ]
}, },
esbuild: {
pure: env.VITE_DROP_CONSOLE === 'true' ? ['console.log'] : undefined,
drop: env.VITE_DROP_DEBUGGER === 'true' ? ['debugger'] : undefined
},
build: { build: {
target: 'es2015', minify: 'terser',
outDir: env.VITE_OUT_DIR || 'dist', outDir: env.VITE_OUT_DIR || 'dist',
sourcemap: env.VITE_SOURCEMAP === 'true', sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false,
// brotliSize: false, // brotliSize: false,
rollupOptions: { terserOptions: {
plugins: env.VITE_USE_BUNDLE_ANALYZER === 'true' ? [visualizer()] : undefined, compress: {
// 拆包 drop_debugger: env.VITE_DROP_DEBUGGER === 'true',
output: { drop_console: env.VITE_DROP_CONSOLE === 'true'
manualChunks: {
'vue-chunks': ['vue', 'vue-router', 'pinia', 'vue-i18n'],
'element-plus': ['element-plus'],
'wang-editor': ['@wangeditor/editor', '@wangeditor/editor-for-vue']
}
} }
}, }
cssCodeSplit: !(env.VITE_USE_CSS_SPLIT === 'false')
}, },
server: { server: {
port: 3005, port: 4000,
proxy: { proxy: {
// 选项写法 // 选项写法
'/api': { '/api': {