特性: 增加 husky

This commit is contained in:
yuanzbz 2023-12-22 17:46:27 +08:00
parent 3c1001dd76
commit 7103c121f3
200 changed files with 9186 additions and 8380 deletions

View File

@ -16,11 +16,51 @@ module.exports = {
ecmaVersion: 'latest', ecmaVersion: 'latest',
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
sourceType: 'module', sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
}, },
plugins: ['vue', '@typescript-eslint'], plugins: ['vue', '@typescript-eslint'],
globals: { globals: {
defineProps: 'readonly', defineProps: 'readonly',
defineEmits: 'readonly', defineEmits: 'readonly',
}, },
rules: {}, rules: {
'no-console': 'off', // 禁止调用console对象的方法。
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-empty-function': ['off'], // 关闭空函数警告
'@typescript-eslint/no-inferrable-types': 'off', // 可以轻松推断的显式类型可能会增加不必要的冗长
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间
'@typescript-eslint/ban-types': 'off', // 禁止使用特定类型
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
'vue/no-v-html': 'off', // 禁止使用 v-html
'no-undef': 'off',
'no-redeclare': 'off',
'no-self-assign': 'off',
'no-sparse-arrays': 'off',
'vue/valid-v-for': 'off',
'vue/no-unused-vars': 'off',
'vue/require-v-for-key': 'off',
'no-useless-escape': 'off',
'vue/require-explicit-emits': 'off',
'no-case-declarations': 'off', // 不允许在 case 子句中使用词法声明
//禁止非空断言非空断言是在变量后面添加一个感叹号(!),表示该变量一定存在,不会为 null 或 undefined
'@typescript-eslint/no-non-null-assertion': 'off',
'vue/require-prop-types': 'off',
'@typescript-eslint/no-this-alias': 'off',
'no-async-promise-executor': 'off',
'vue/no-template-shadow': 'off',
'vue/require-default-prop': 'off', // 此规则要求为每个 prop 为必填时,必须提供默认值
'@typescript-eslint/no-unused-vars': [
// // 禁止定义未使用的变量
'off',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
} }

View File

@ -1,6 +1,8 @@
module.exports = { module.exports = {
// 一行的字符数如果超过会进行换行默认为80 // 一行的字符数如果超过会进行换行默认为80
printWidth: 100, printWidth: 140,
// 缩进制表符宽度 | 空格数
tabWidth: 2,
// 行位是否使用分号默认为true // 行位是否使用分号默认为true
semi: false, semi: false,
vueIndentScriptAndStyle: true, vueIndentScriptAndStyle: true,

View File

@ -2,82 +2,83 @@
// cz.config.js kk // cz.config.js kk
/** @type {import('cz-git').CommitizenGitOptions} */ /** @type {import('cz-git').CommitizenGitOptions} */
module.exports = { module.exports = {
ignores: [commit => commit.includes("init")], ignores: [(commit) => commit.includes('init')],
extends: ["@commitlint/config-conventional"], extends: ['@commitlint/config-conventional'],
// alias: { fd: 'docs: fix typos' }, // alias: { fd: 'docs: fix typos' },
// messages: { // messages: {
// type: 'Select the type of change that you\'re committing:', // type: 'Select the type of change that you\'re committing:',
// scope: 'Denote the SCOPE of this change (optional):', // scope: 'Denote the SCOPE of this change (optional):',
// customScope: 'Denote the SCOPE of this chang e:', // customScope: 'Denote the SCOPE of this chang e:',
// subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n', // subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
// body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n', // body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
// breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n', // breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n',
// footerPrefixsSelect: 'Select the ISSUES type of changeList by this change (optional):', // footerPrefixsSelect: 'Select the ISSUES type of changeList by this change (optional):',
// customFooterPrefixs: 'Input ISSUES prefix:', // customFooterPrefixs: 'Input ISSUES prefix:',
// footer: 'List any ISSUES by this change. E.g.: #31, #34:\n', // footer: 'List any ISSUES by this change. E.g.: #31, #34:\n',
// confirmCommit: 'Are you sure you want to proceed with the commit above?' // confirmCommit: 'Are you sure you want to proceed with the commit above?'
// }, // },
prompt: { prompt: {
// 中英文对照版 // 中英文对照版
messages: { messages: {
type: '选择你要提交的类型 :', type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):', scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :', customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n', subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n', body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n', breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixesSelect: '选择关联issue前缀可选:', footerPrefixesSelect: '选择关联issue前缀可选:',
customFooterPrefix: '输入自定义issue前缀 :', customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?' confirmCommit: '是否提交或修改commit ?',
}, },
types: [ types: [
{ value: '特性', name: '特性: 新增功能' }, { value: '特性', name: '特性: 新增功能' },
{ value: '修复', name: '修复: 修复缺陷' }, { value: '修复', name: '修复: 修复缺陷' },
{ value: '文档', name: '文档: 文档变更' }, { value: '文档', name: '文档: 文档变更' },
{ value: '格式', name: '格式: 代码格式(不影响功能,例如空格、分号等格式修正)' }, { value: '格式', name: '格式: 代码格式(不影响功能,例如空格、分号等格式修正)' },
{ value: '重构', name: '重构: 代码重构(不包括 bug 修复、功能新增)' }, { value: '重构', name: '重构: 代码重构(不包括 bug 修复、功能新增)' },
{ value: '性能', name: '性能: 性能优化' }, { value: '性能', name: '性能: 性能优化' },
{ value: '测试', name: '测试: 添加疏漏测试或已有测试改动' }, { value: '测试', name: '测试: 添加疏漏测试或已有测试改动' },
{ value: '构建', name: '构建: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)' }, {
{ value: '集成', name: '集成: 修改 CI 配置、脚本' }, value: '构建',
{ value: '回退', name: '回退: 回滚 commit' }, name: '构建: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)',
{ value: '其他', name: '其他: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' }, },
{ value: '集成', name: '集成: 修改 CI 配置、脚本' },
], { value: '回退', name: '回退: 回滚 commit' },
// emptyScopesAlias: 'empty: 不填写', { value: '其他', name: '其他: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
// customScopesAlias: 'custom: 自定义', ],
// emptyScopesAlias: 'empty: 不填写',
useEmoji: true, // customScopesAlias: 'custom: 自定义',
// emojiAlign: 'center',
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixs: [{ value: 'closed', name: 'closed: ISSUES has been processed' }],
customIssuePrefixsAlign: 'top',
emptyIssuePrefixsAlias: 'skip',
customIssuePrefixsAlias: 'custom',
allowCustomIssuePrefixs: true,
allowEmptyIssuePrefixs: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
useEmoji: true,
// emojiAlign: 'center',
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixs: [{ value: 'closed', name: 'closed: ISSUES has been processed' }],
customIssuePrefixsAlign: 'top',
emptyIssuePrefixsAlias: 'skip',
customIssuePrefixsAlias: 'custom',
allowCustomIssuePrefixs: true,
allowEmptyIssuePrefixs: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: '',
},
} }

View File

@ -12,7 +12,7 @@
"preview": "vite preview", "preview": "vite preview",
"build:ts": "vue-tsc --noEmit --skipLibCheck && vite build", "build:ts": "vue-tsc --noEmit --skipLibCheck && vite build",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx", "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx",
"lint:fix": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix", "lint:fix": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", "lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"prepare": "husky install" "prepare": "husky install"
}, },
@ -61,6 +61,7 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.3.0", "@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0", "@commitlint/config-conventional": "^17.3.0",
"@element-plus/icons-vue": "^2.3.1",
"@typescript-eslint/eslint-plugin": "^5.32.0", "@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0", "@typescript-eslint/parser": "^5.32.0",
"@vitejs/plugin-vue": "^3.0.0", "@vitejs/plugin-vue": "^3.0.0",
@ -96,6 +97,10 @@
} }
}, },
"lint-staged": { "lint-staged": {
"*.{vue,js,ts,jsx,tsx}": ["npm run lint","git add"] "src/s*.{vue,js,ts,jsx,tsx}": [
"npm run lint:prettier",
"npm run lint:fix",
"git add ."
]
} }
} }

View File

@ -133,6 +133,9 @@ devDependencies:
'@commitlint/config-conventional': '@commitlint/config-conventional':
specifier: ^17.3.0 specifier: ^17.3.0
version: 17.8.1 version: 17.8.1
'@element-plus/icons-vue':
specifier: ^2.3.1
version: 2.3.1(vue@3.3.13)
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^5.32.0 specifier: ^5.32.0
version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.56.0)(typescript@4.9.5) version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.56.0)(typescript@4.9.5)
@ -201,7 +204,7 @@ devDependencies:
version: 0.7.3(vite@3.2.7)(vue@3.3.13) version: 0.7.3(vite@3.2.7)(vue@3.3.13)
vite: vite:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.2.7(@types/node@18.19.3)(sass@1.69.5) version: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite-plugin-compression: vite-plugin-compression:
specifier: ^0.5.1 specifier: ^0.5.1
version: 0.5.1(vite@3.2.7) version: 0.5.1(vite@3.2.7)
@ -397,7 +400,7 @@ packages:
lodash.merge: 4.6.2 lodash.merge: 4.6.2
lodash.uniq: 4.5.0 lodash.uniq: 4.5.0
resolve-from: 5.0.0 resolve-from: 5.0.0
ts-node: 10.9.2(@types/node@18.19.3)(typescript@4.9.5) ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
typescript: 4.9.5 typescript: 4.9.5
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/core' - '@swc/core'
@ -534,7 +537,6 @@ packages:
vue: ^3.2.0 vue: ^3.2.0
dependencies: dependencies:
vue: 3.3.13(typescript@4.9.5) vue: 3.3.13(typescript@4.9.5)
dev: false
/@esbuild/android-arm@0.15.18: /@esbuild/android-arm@0.15.18:
resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
@ -774,6 +776,7 @@ packages:
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
dev: true dev: true
optional: true
/@types/node@20.10.5: /@types/node@20.10.5:
resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==} resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==}
@ -1121,7 +1124,7 @@ packages:
vite: ^3.0.0 vite: ^3.0.0
vue: ^3.2.25 vue: ^3.2.25
dependencies: dependencies:
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5) vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vue: 3.3.13(typescript@4.9.5) vue: 3.3.13(typescript@4.9.5)
dev: true dev: true
@ -2350,7 +2353,7 @@ packages:
dependencies: dependencies:
'@types/node': 20.5.1 '@types/node': 20.5.1
cosmiconfig: 8.3.6(typescript@4.9.5) cosmiconfig: 8.3.6(typescript@4.9.5)
ts-node: 10.9.2(@types/node@18.19.3)(typescript@4.9.5) ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
typescript: 4.9.5 typescript: 4.9.5
dev: true dev: true
@ -6672,7 +6675,7 @@ packages:
typescript: 4.9.5 typescript: 4.9.5
dev: true dev: true
/ts-node@10.9.2(@types/node@18.19.3)(typescript@4.9.5): /ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5):
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -6691,7 +6694,7 @@ packages:
'@tsconfig/node12': 1.0.11 '@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3 '@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4 '@tsconfig/node16': 1.0.4
'@types/node': 18.19.3 '@types/node': 20.5.1
acorn: 8.11.2 acorn: 8.11.2
acorn-walk: 8.3.1 acorn-walk: 8.3.1
arg: 4.1.3 arg: 4.1.3
@ -6937,7 +6940,7 @@ packages:
dependencies: dependencies:
acorn: 8.11.2 acorn: 8.11.2
chokidar: 3.5.3 chokidar: 3.5.3
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5) vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
webpack-sources: 3.2.3 webpack-sources: 3.2.3
webpack-virtual-modules: 0.4.6 webpack-virtual-modules: 0.4.6
dev: true dev: true
@ -6961,7 +6964,7 @@ packages:
dependencies: dependencies:
acorn: 8.11.2 acorn: 8.11.2
chokidar: 3.5.3 chokidar: 3.5.3
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5) vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
webpack-sources: 3.2.3 webpack-sources: 3.2.3
webpack-virtual-modules: 0.4.6 webpack-virtual-modules: 0.4.6
dev: true dev: true
@ -7135,7 +7138,7 @@ packages:
chalk: 4.1.2 chalk: 4.1.2
debug: 4.3.4(supports-color@9.4.0) debug: 4.3.4(supports-color@9.4.0)
fs-extra: 10.1.0 fs-extra: 10.1.0
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5) vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -7152,7 +7155,7 @@ packages:
fs-extra: 10.1.0 fs-extra: 10.1.0
magic-string: 0.25.9 magic-string: 0.25.9
pathe: 0.2.0 pathe: 0.2.0
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5) vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
dev: true dev: true
/vite-plugin-svg-icons@2.0.1(vite@3.2.7): /vite-plugin-svg-icons@2.0.1(vite@3.2.7):
@ -7168,7 +7171,7 @@ packages:
pathe: 0.2.0 pathe: 0.2.0
svg-baker: 1.7.0 svg-baker: 1.7.0
svgo: 2.8.0 svgo: 2.8.0
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5) vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -7180,10 +7183,10 @@ packages:
dependencies: dependencies:
'@vue/compiler-sfc': 3.3.13 '@vue/compiler-sfc': 3.3.13
magic-string: 0.25.9 magic-string: 0.25.9
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5) vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
dev: true dev: true
/vite@3.2.7(@types/node@18.19.3)(sass@1.69.5): /vite@3.2.7(@types/node@20.5.1)(sass@1.69.5):
resolution: {integrity: sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==} resolution: {integrity: sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true hasBin: true
@ -7208,7 +7211,7 @@ packages:
terser: terser:
optional: true optional: true
dependencies: dependencies:
'@types/node': 18.19.3 '@types/node': 20.5.1
esbuild: 0.15.18 esbuild: 0.15.18
postcss: 8.4.32 postcss: 8.4.32
resolve: 1.22.8 resolve: 1.22.8

View File

@ -5,14 +5,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed} from "vue"; import { computed } from 'vue'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
// element // element
import zhCn from 'element-plus/es/locale/lang/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
// //
const globalComSize = computed(():string=>SettingStore.themeConfig.globalComSize) const globalComSize = computed((): string => SettingStore.themeConfig.globalComSize)
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,44 +1,44 @@
export const errorCodeType = function(code:string):string{ export const errorCodeType = function (code: string): string {
let errMessage:string = "未知错误" let errMessage = '未知错误'
switch (code) { switch (code) {
case 400: case 400:
errMessage = '请求失败!请您稍后重试' errMessage = '请求失败!请您稍后重试'
break break
case 401: case 401:
errMessage = '未授权,请重新登录' errMessage = '未授权,请重新登录'
break break
case 403: case 403:
errMessage = '当前账号无权限访问!' errMessage = '当前账号无权限访问!'
break break
case 404: case 404:
errMessage = '你所访问的资源不存在!' errMessage = '你所访问的资源不存在!'
break break
case 405: case 405:
errMessage = '请求方式错误!请您稍后重试' errMessage = '请求方式错误!请您稍后重试'
break break
case 408: case 408:
errMessage = '请求超时!请您稍后重试' errMessage = '请求超时!请您稍后重试'
break break
case 500: case 500:
errMessage = '服务器端出错' errMessage = '服务器端出错'
break break
case 501: case 501:
errMessage = '网络未实现' errMessage = '网络未实现'
break break
case 502: case 502:
errMessage = '网络错误' errMessage = '网络错误'
break break
case 503: case 503:
errMessage = '服务不可用' errMessage = '服务不可用'
break break
case 504: case 504:
errMessage = '网络超时' errMessage = '网络超时'
break break
case 505: case 505:
errMessage = 'http版本不支持该请求' errMessage = 'http版本不支持该请求'
break break
default: default:
errMessage = `其他连接错误 --${code}` errMessage = `其他连接错误 --${code}`
} }
return errMessage return errMessage
} }

View File

@ -1,57 +1,62 @@
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios' import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from "element-plus"; import { ElMessage } from 'element-plus'
import {useUserStore} from "@/store/modules/user" import { useUserStore } from '@/store/modules/user'
// 创建axios实例 进行基本参数配置 // 创建axios实例 进行基本参数配置
const service = axios.create({ const service = axios.create({
// 默认请求地址,根据环境的不同可在.env 文件中进行修改 // 默认请求地址,根据环境的不同可在.env 文件中进行修改
baseURL: import.meta.env.VUE_APP_BASE_API, baseURL: import.meta.env.VUE_APP_BASE_API,
// 设置接口访问超时时间 // 设置接口访问超时时间
timeout: 3000000, // request timeout timeout: 3000000, // request timeout
// 跨域时候允许携带凭证 // 跨域时候允许携带凭证
withCredentials: true withCredentials: true,
}) })
// request interceptor 接口请求拦截 // request interceptor 接口请求拦截
service.interceptors.request.use((config:AxiosRequestConfig)=>{ service.interceptors.request.use(
(config: AxiosRequestConfig) => {
/** /**
* token,token进行JWT校验 * token,token进行JWT校验
* token storagevuexpinia * token storagevuexpinia
*/ */
const userStore = useUserStore(); const userStore = useUserStore()
const token: string = userStore.token; const token: string = userStore.token
// 自定义请求头 // 自定义请求头
if(token){ config.headers['Authorization'] = token} if (token) {
config.headers['Authorization'] = token
}
return config return config
},(error: AxiosError) => { },
(error: AxiosError) => {
// 请求错误,这里可以用全局提示框进行提示 // 请求错误,这里可以用全局提示框进行提示
return Promise.reject(error); return Promise.reject(error)
}) },
)
// response interceptor 接口响应拦截 // response interceptor 接口响应拦截
service.interceptors.response.use((response: AxiosResponse) =>{ service.interceptors.response.use(
(response: AxiosResponse) => {
// 直接返回res当然你也可以只返回res.data // 直接返回res当然你也可以只返回res.data
// 系统如果有自定义code也可以在这里处理 // 系统如果有自定义code也可以在这里处理
return response return response
},
(error: AxiosError) => {
},(error: AxiosError) => {
return Promise.reject(error) return Promise.reject(error)
}) },
)
// /**
/** // * @description 显示错误消息
* @description // * opt 传入参数
* opt // * err 错误信息
* err // * type 消息类型
* type // * duration 消息持续时间
* duration // */
*/ // function showErrMessage(opt, err, type: any = 'error', duration = 5000) {
function showErrMessage (opt, err, type:any= 'error', duration:number = 5000){ // ElMessage({
ElMessage({ // message: err.msg,
message: err.msg, // type: type,
type:type, // duration: duration,
duration: duration // });
}) // }
}
export default service export default service

View File

@ -1,9 +1,9 @@
import request from './request' import request from './request'
export function login(data) { export function login(data) {
return request({ return request({
url: '/vue-element-perfect/user/login', url: '/vue-element-perfect/user/login',
method: 'post', method: 'post',
data data,
}) })
} }

View File

@ -1,12 +1,11 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3641732 */ font-family: 'iconfont'; /* Project id 3641732 */
src: url('./iconfont.woff2?t=1663216428515') format('woff2'), src: url('./iconfont.woff2?t=1663216428515') format('woff2'), url('./iconfont.woff?t=1663216428515') format('woff'),
url('./iconfont.woff?t=1663216428515') format('woff'), url('./iconfont.ttf?t=1663216428515') format('truetype');
url('./iconfont.ttf?t=1663216428515') format('truetype');
} }
.iconfont { .iconfont {
font-family: "iconfont" !important; font-family: 'iconfont' !important;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -14,102 +13,101 @@
} }
.icon-yin:before { .icon-yin:before {
content: "\e6a0"; content: '\e6a0';
} }
.icon-dabaoyu:before { .icon-dabaoyu:before {
content: "\e6a7"; content: '\e6a7';
} }
.icon-shuaxin1:before { .icon-shuaxin1:before {
content: "\e627"; content: '\e627';
} }
.icon-shuaxin:before { .icon-shuaxin:before {
content: "\e629"; content: '\e629';
} }
.icon-duoyun-2-copy:before { .icon-duoyun-2-copy:before {
content: "\e68d"; content: '\e68d';
} }
.icon-duoyun-1:before { .icon-duoyun-1:before {
content: "\e679"; content: '\e679';
} }
.icon-daxue:before { .icon-daxue:before {
content: "\e67a"; content: '\e67a';
} }
.icon-dayu:before { .icon-dayu:before {
content: "\e67b"; content: '\e67b';
} }
.icon-feng:before { .icon-feng:before {
content: "\e67c"; content: '\e67c';
} }
.icon-duoyun-3:before { .icon-duoyun-3:before {
content: "\e67d"; content: '\e67d';
} }
.icon-duoyun-2:before { .icon-duoyun-2:before {
content: "\e67e"; content: '\e67e';
} }
.icon-leiyujiaojia:before { .icon-leiyujiaojia:before {
content: "\e67f"; content: '\e67f';
} }
.icon-duoyun:before { .icon-duoyun:before {
content: "\e680"; content: '\e680';
} }
.icon-zhongyu:before { .icon-zhongyu:before {
content: "\e681"; content: '\e681';
} }
.icon-wu:before { .icon-wu:before {
content: "\e682"; content: '\e682';
} }
.icon-xiaoyu:before { .icon-xiaoyu:before {
content: "\e683"; content: '\e683';
} }
.icon-xiaoxue:before { .icon-xiaoxue:before {
content: "\e684"; content: '\e684';
} }
.icon-shandian:before { .icon-shandian:before {
content: "\e685"; content: '\e685';
} }
.icon-xue:before { .icon-xue:before {
content: "\e686"; content: '\e686';
} }
.icon-zhongxue:before { .icon-zhongxue:before {
content: "\e687"; content: '\e687';
} }
.icon-yangchen:before { .icon-yangchen:before {
content: "\e688"; content: '\e688';
} }
.icon-yueliang:before { .icon-yueliang:before {
content: "\e689"; content: '\e689';
} }
.icon-yujiaxue:before { .icon-yujiaxue:before {
content: "\e68a"; content: '\e68a';
} }
.icon-qing:before { .icon-qing:before {
content: "\e68b"; content: '\e68b';
} }
.icon-mai:before { .icon-mai:before {
content: "\e68c"; content: '\e68c';
} }

File diff suppressed because one or more lines are too long

View File

@ -1,86 +1,85 @@
<template> <template>
<vue-cropper <vue-cropper
ref="cropper" ref="cropper"
:img="avatarUrl" :img="avatarUrl"
:output-size="defaultOptions.outputSize" :output-size="defaultOptions.outputSize"
:output-type="defaultOptions.outputType" :output-type="defaultOptions.outputType"
:info="defaultOptions.info" :info="defaultOptions.info"
:full="defaultOptions.full" :full="defaultOptions.full"
:fixed="defaultOptions.fixed" :fixed="defaultOptions.fixed"
:autoCropWidth="defaultOptions.autoCropWidth" :auto-crop-width="defaultOptions.autoCropWidth"
:autoCropHeight="defaultOptions.autoCropHeight" :auto-crop-height="defaultOptions.autoCropHeight"
:fixed-box="defaultOptions.fixedBox" :fixed-box="defaultOptions.fixedBox"
:auto-crop="defaultOptions.autoCrop" :auto-crop="defaultOptions.autoCrop"
:center-box="defaultOptions.centerBox" :center-box="defaultOptions.centerBox"
@real-time="realTime" @real-time="realTime"
/> />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {defineExpose, reactive, ref} from 'vue' import { defineExpose, reactive, ref } from 'vue'
import { VueCropper } from 'vue-cropper' import { VueCropper } from 'vue-cropper'
import 'vue-cropper/dist/index.css' import 'vue-cropper/dist/index.css'
const emit = defineEmits(['change']) const emit = defineEmits(['change'])
let props = defineProps({ let props = defineProps({
avatarUrl:{ avatarUrl: {
type:String type: String,
}, },
options:{ options: {
type:Object, type: Object,
default:()=>{} default: () => {},
} },
})
let cropper = ref()
const defaultOptions = reactive({
outputSize:0.8, //
outputType: 'png', //
info: true, //
fixed: true, //
autoCrop: true, //
anMoveBox: true, //
original: false, //
autoCropWidth: 300, //
autoCropHeight: 300, //
//
centerBox: false, //
high: true, // dpr
fixedBox: false, //
full: false, //
...props.options
})
const getBase64 = ()=>{
return new Promise(resolve => {
cropper.value.getCropData((data) => {
resolve(data)
})
}) })
}
const rotateLeft = () => { let cropper = ref()
cropper.value.rotateLeft()
}
const rotateRight = () => {
cropper.value.rotateRight()
}
const zoom = (percent) => { const defaultOptions = reactive({
cropper.value.changeScale(percent) outputSize: 0.8, //
} outputType: 'png', //
info: true, //
fixed: true, //
autoCrop: true, //
anMoveBox: true, //
original: false, //
autoCropWidth: 300, //
autoCropHeight: 300, //
//
centerBox: false, //
high: true, // dpr
fixedBox: false, //
full: false, //
...props.options,
})
// const getBase64 = () => {
const realTime = (data)=>{ return new Promise((resolve) => {
emit('change',data) cropper.value.getCropData((data) => {
} resolve(data)
})
})
}
const rotateLeft = () => {
cropper.value.rotateLeft()
}
const rotateRight = () => {
cropper.value.rotateRight()
}
defineExpose({ const zoom = (percent) => {
getBase64, cropper.value.changeScale(percent)
rotateLeft, }
rotateRight,
zoom //
}) const realTime = (data) => {
emit('change', data)
}
defineExpose({
getBase64,
rotateLeft,
rotateRight,
zoom,
})
</script> </script>

View File

@ -1,121 +1,120 @@
<template> <template>
<textarea ref="codeEditor" placeholder="请输入..." ></textarea> <textarea ref="codeEditor" placeholder="请输入..."></textarea>
</template> </template>
<script> <script>
import { defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch } from "vue"; import { defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
// codemirror // codemirror
import _CodeMirror from "codemirror"; import _CodeMirror from 'codemirror'
import "codemirror/lib/codemirror.css"; import 'codemirror/lib/codemirror.css'
// language // language
import 'codemirror/mode/javascript/javascript.js' import 'codemirror/mode/javascript/javascript.js'
// theme css // theme css
import 'codemirror/theme/monokai.css' import 'codemirror/theme/monokai.css'
// : // :
import "codemirror/addon/fold/foldgutter.css"; import 'codemirror/addon/fold/foldgutter.css'
import "codemirror/addon/fold/foldcode.js"; import 'codemirror/addon/fold/foldcode.js'
import "codemirror/addon/fold/brace-fold.js"; import 'codemirror/addon/fold/brace-fold.js'
import "codemirror/addon/fold/comment-fold.js"; import 'codemirror/addon/fold/comment-fold.js'
import "codemirror/addon/fold/indent-fold.js"; import 'codemirror/addon/fold/indent-fold.js'
import "codemirror/addon/fold/foldgutter.js"; import 'codemirror/addon/fold/foldgutter.js'
// : // :
// : // :
import "codemirror/addon/scroll/annotatescrollbar.js"; import 'codemirror/addon/scroll/annotatescrollbar.js'
import "codemirror/addon/search/matchesonscrollbar.js"; import 'codemirror/addon/search/matchesonscrollbar.js'
import "codemirror/addon/search/match-highlighter.js"; import 'codemirror/addon/search/match-highlighter.js'
import "codemirror/addon/search/jump-to-line.js"; import 'codemirror/addon/search/jump-to-line.js'
import "codemirror/addon/dialog/dialog.js"; import 'codemirror/addon/dialog/dialog.js'
import "codemirror/addon/dialog/dialog.css"; import 'codemirror/addon/dialog/dialog.css'
import "codemirror/addon/search/searchcursor.js"; import 'codemirror/addon/search/searchcursor.js'
import "codemirror/addon/search/search.js"; import 'codemirror/addon/search/search.js'
// : // :
// placeholder // placeholder
import "codemirror/addon/display/placeholder.js"; import 'codemirror/addon/display/placeholder.js'
import "codemirror/addon/selection/active-line.js"; //styleActiveLinetrue import 'codemirror/addon/selection/active-line.js' //styleActiveLinetrue
const CodeMirror = window.CodeMirror || _CodeMirror; const CodeMirror = window.CodeMirror || _CodeMirror
export default defineComponent({ export default defineComponent({
props: { props: {
modelValue: String, modelValue: String,
defaultValue: String, defaultValue: String,
readOnly: { readOnly: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}, },
setup(props, context) { setup(props, context) {
const { modelValue, defaultValue, readOnly } = toRefs(props); const { modelValue, defaultValue, readOnly } = toRefs(props)
const codeEditor = ref(); const codeEditor = ref()
let editor; let editor
watch(modelValue, () => { watch(modelValue, () => {
if (null != editor && modelValue.value && modelValue.value !== editor.getValue()) { if (null != editor && modelValue.value && modelValue.value !== editor.getValue()) {
// v-model // v-model
editor.setValue(modelValue.value); editor.setValue(modelValue.value)
} }
}); })
watch(readOnly, () => { watch(readOnly, () => {
if (null != editor) { if (null != editor) {
editor.setOption("readOnly", readOnly.value); editor.setOption('readOnly', readOnly.value)
} }
}); })
onMounted(() => { onMounted(() => {
editor = CodeMirror.fromTextArea(codeEditor.value, { editor = CodeMirror.fromTextArea(codeEditor.value, {
value: modelValue.value, value: modelValue.value,
// mime: "text/javascript", // mime: "text/javascript",
mode: "application/json", mode: 'application/json',
indentWithTabs: false, // n*tabntabfalse indentWithTabs: false, // n*tabntabfalse
smartIndent: true, // true smartIndent: true, // true
lineNumbers: true, // lineNumbers: true, //
matchBrackets: true, // matchBrackets: true, //
readOnly: readOnly.value, readOnly: readOnly.value,
// : // :
foldGutter: true, foldGutter: true,
lineWrapping: true, // lineWrapping: true, //
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
// : // :
styleActiveLine: false // styleActiveLine: false, //
}); })
// change // change
editor.on("change", () => { editor.on('change', () => {
// v-model // v-model
context.emit("update:modelValue", editor.getValue()); context.emit('update:modelValue', editor.getValue())
}); })
if (defaultValue.value) { if (defaultValue.value) {
editor.setValue(defaultValue.value); editor.setValue(defaultValue.value)
} }
}); })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (null !== editor) { if (null !== editor) {
editor.toTextArea(); editor.toTextArea()
editor = null; editor = null
} }
}); })
return { codeEditor }; return { codeEditor }
} },
}); })
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss"> <style lang="scss">
.CodeMirror-wrap{ .CodeMirror-wrap {
height: 100%; height: 100%;
} }
.CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
white-space: nowrap;
background-color: transparent;
border-right: 1px solid #ddd;
}
.CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
white-space: nowrap;
background-color: transparent;
border-right: 1px solid #ddd;
}
</style> </style>

View File

@ -103,6 +103,9 @@
} }
this.$emit('mountedCallback') this.$emit('mountedCallback')
}, },
unmounted() {
cancelAnimationFrame(this.rAF)
},
methods: { methods: {
start() { start() {
this.localStartVal = this.startVal this.localStartVal = this.startVal
@ -142,26 +145,15 @@
if (this.useEasing) { if (this.useEasing) {
if (this.countDown) { if (this.countDown) {
this.printVal = this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
this.localStartVal -
this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
} else { } else {
this.printVal = this.easingFn( this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
progress,
this.localStartVal,
this.endVal - this.localStartVal,
this.localDuration,
)
} }
} else { } else {
if (this.countDown) { if (this.countDown) {
this.printVal = this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
this.localStartVal -
(this.localStartVal - this.endVal) * (progress / this.localDuration)
} else { } else {
this.printVal = this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
this.localStartVal +
(this.endVal - this.localStartVal) * (progress / this.localDuration)
} }
} }
if (this.countDown) { if (this.countDown) {
@ -195,8 +187,5 @@
return this.prefix + x1 + x2 + this.suffix return this.prefix + x1 + x2 + this.suffix
}, },
}, },
destroyed() {
cancelAnimationFrame(this.rAF)
},
} }
</script> </script>

View File

@ -23,10 +23,7 @@ if (isServer) {
} }
prefix = prefixes[i] prefix = prefixes[i]
requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame'] requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame']
cancelAnimationFrame = cancelAnimationFrame = cancelAnimationFrame || window[prefix + 'CancelAnimationFrame'] || window[prefix + 'CancelRequestAnimationFrame']
cancelAnimationFrame ||
window[prefix + 'CancelAnimationFrame'] ||
window[prefix + 'CancelRequestAnimationFrame']
} }
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame则会退到setTimeout // 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame则会退到setTimeout

View File

@ -1,269 +1,269 @@
<template> <template>
<div ref="chartsRef" class="echarts"/> <div ref="chartsRef" class="echarts" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core' import { EChartsType } from 'echarts/core'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
const chartsRef = ref<HTMLElement | null>() const chartsRef = ref<HTMLElement | null>()
let colorList = ['#46ea91', '#2ba0ff', '#ed593b', '#7357ff', '#f2d750']; let colorList = ['#46ea91', '#2ba0ff', '#ed593b', '#7357ff', '#f2d750']
const options = { const options = {
legend: { legend: {
icon: 'circle', icon: 'circle',
top: '5%', top: '5%',
right: '5%', right: '5%',
itemWidth: 6, itemWidth: 6,
itemGap: 5, itemGap: 5,
textStyle: { textStyle: {
color: '#fff', color: '#fff',
padding: [3, 0, 0, 0], padding: [3, 0, 0, 0],
},
}, },
}, tooltip: {
tooltip: { trigger: 'axis',
trigger: 'axis', },
}, grid: {
grid: { top: '14%',
top: '14%', left: '3%',
left: '3%', right: '4%',
right: '4%', bottom: '10%',
bottom: '10%', containLabel: true,
containLabel: true, },
}, xAxis: [
xAxis: [ {
{ type: 'category',
type: 'category', data: ['1', '2', '3', '4', '5', '6', '7', '8'],
data: ['1', '2', '3', '4', '5', '6', '7', '8'], axisLine: {
axisLine: { lineStyle: {
lineStyle: { color: '#33BBFF',
color: '#33BBFF', },
}, },
}, axisTick: {
axisTick: { show: false,
show: false,
},
axisLabel: {
interval: 0,
textStyle: {
color: '#5FBBEB',
}, },
// x axisLabel: {
fontSize: 12, interval: 0,
// margin:x textStyle: {
margin: 10, color: '#5FBBEB',
}, },
axisPointer: { // x
label: {
// padding: [11, 5, 7],
padding: [0, 0, 0, 0],
// marginaxisLabelmargin!
margin: 10,
//
fontSize: 12, fontSize: 12,
backgroundColor: 'rgba(0,0,0,0)', // margin:x
margin: 10,
},
axisPointer: {
label: {
// padding: [11, 5, 7],
padding: [0, 0, 0, 0],
// marginaxisLabelmargin!
margin: 10,
//
fontSize: 12,
backgroundColor: 'rgba(0,0,0,0)',
},
},
boundaryGap: false,
},
],
yAxis: [
{
name: '单位/件',
axisTick: {
show: false,
},
axisLine: {
show: true,
lineStyle: {
color: '#05D5FF',
},
},
axisLabel: {
textStyle: {
color: '#5FBBEB',
},
},
splitLine: {
show: false,
}, },
}, },
boundaryGap: false, ],
}, series: [
], {
yAxis: [ name: '咨询',
{ type: 'line',
name: '单位/件', data: [100, 20, 30, 102, 15, 30, 20, 18],
axisTick: { symbolSize: 1,
show: false, symbol: 'circle',
}, smooth: true,
axisLine: { showSymbol: false,
show: true,
lineStyle: { lineStyle: {
color: '#05D5FF', width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#90ffc6',
},
{
offset: 1,
color: '#46ea91',
},
]),
shadowColor: 'rgba(144, 255, 198, .3)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[0],
borderColor: colorList[0],
},
}, },
}, },
axisLabel: { {
textStyle: { name: '求助',
color: '#5FBBEB', type: 'line',
data: [20, 12, 11, 14, 25, 16, 10, 20],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#67bcfc',
},
{
offset: 1,
color: '#2ba0ff',
},
]),
shadowColor: 'rgba(105, 188, 252,.3)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[1],
borderColor: colorList[1],
},
}, },
}, },
splitLine: { {
show: false, name: '无效',
}, type: 'line',
}, data: [150, 120, 170, 140, 100, 160, 110, 110],
], symbolSize: 1,
series: [ symbol: 'circle',
{ smooth: true,
name: '咨询', showSymbol: false,
type: 'line', lineStyle: {
data: [100, 20, 30, 102, 15, 30, 20, 18], width: 2,
symbolSize: 1, color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
symbol: 'circle', {
smooth: true, offset: 0,
showSymbol: false, color: '#fc937e ',
lineStyle: { },
width: 2, {
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [ offset: 1,
{ color: '#ed593b',
offset: 0, },
color: '#90ffc6', ]),
shadowColor: 'rgb(252, 147, 126,.3)',
shadowBlur: 2,
shadowOffsetY: 2,
},
itemStyle: {
normal: {
color: colorList[2],
borderColor: colorList[2],
}, },
{
offset: 1,
color: '#46ea91',
},
]),
shadowColor: 'rgba(144, 255, 198, .3)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[0],
borderColor: colorList[0],
}, },
}, },
}, {
{ name: '投诉举报',
name: '求助', type: 'line',
type: 'line', data: [200, 80, 100, 30, 60, 50, 110, 20],
data: [20, 12, 11, 14, 25, 16, 10, 20], symbolSize: 1,
symbolSize: 1, symbol: 'circle',
symbol: 'circle', smooth: true,
smooth: true, showSymbol: false,
showSymbol: false, lineStyle: {
lineStyle: { width: 2,
width: 2, color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [ {
{ offset: 0,
offset: 0, color: '#a390ff',
color: '#67bcfc', },
{
offset: 1,
color: '#7357ff',
},
]),
shadowColor: 'rgba(115, 87, 255, .1)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[3],
borderColor: colorList[3],
}, },
{
offset: 1,
color: '#2ba0ff',
},
]),
shadowColor: 'rgba(105, 188, 252,.3)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[1],
borderColor: colorList[1],
}, },
}, },
}, {
{ name: '建议',
name: '无效', type: 'line',
type: 'line', data: [20, 80, 150, 30, 60, 50, 50, 20],
data: [150, 120, 170, 140, 100, 160, 110, 110], symbolSize: 1,
symbolSize: 1, symbol: 'circle',
symbol: 'circle', smooth: true,
smooth: true, showSymbol: false,
showSymbol: false, lineStyle: {
lineStyle: { width: 2,
width: 2, color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [ {
{ offset: 0,
offset: 0, color: '#ffeb86',
color: '#fc937e ', },
{
offset: 1,
color: '#f2d750',
},
]),
shadowColor: 'rgba(255, 235, 134, .5)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[4],
borderColor: colorList[4],
}, },
{
offset: 1,
color: '#ed593b',
},
]),
shadowColor: 'rgb(252, 147, 126,.3)',
shadowBlur: 2,
shadowOffsetY: 2,
},
itemStyle: {
normal: {
color: colorList[2],
borderColor: colorList[2],
}, },
}, },
}, ],
{ }
name: '投诉举报',
type: 'line',
data: [200, 80, 100, 30, 60, 50, 110, 20],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#a390ff',
},
{
offset: 1,
color: '#7357ff',
},
]),
shadowColor: 'rgba(115, 87, 255, .1)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[3],
borderColor: colorList[3],
},
},
},
{
name: '建议',
type: 'line',
data: [20, 80, 150, 30, 60, 50, 50, 20],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#ffeb86',
},
{
offset: 1,
color: '#f2d750',
},
]),
shadowColor: 'rgba(255, 235, 134, .5)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[4],
borderColor: colorList[4],
},
},
},
],
};
let chart: EChartsType let chart: EChartsType
const initChart = () => { const initChart = () => {
const chart = echarts.init(chartsRef.value) const chart = echarts.init(chartsRef.value)
chart.setOption(options) chart.setOption(options)
return chart return chart
} }
onMounted(() => { onMounted(() => {
chart = initChart() chart = initChart()
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
chart && chart.resize() chart && chart.resize()
})
}) })
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.echarts{ .echarts {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
</style> </style>

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="echarts" ref="chartsRef" /> <div ref="chartsRef" class="echarts" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import BarCharts from './components/bar.vue' import BarCharts from './components/bar.vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core' import { EChartsType } from 'echarts/core'
import { onMounted, ref,reactive } from 'vue' import { onMounted, ref, reactive } from 'vue'
const chartsRef = ref<HTMLElement | null>() const chartsRef = ref<HTMLElement | null>()
const data = [154, 230, 224, 218, 135, 147, 260] const data = [154, 230, 224, 218, 135, 147, 260]
const color = ['#fa796f', '#54c1fb', '#ca6cd4', '#59dcc1', '#09a4ea', '#e98f4d', '#ea8e49'] const color = ['#fa796f', '#54c1fb', '#ca6cd4', '#59dcc1', '#09a4ea', '#e98f4d', '#ea8e49']
@ -21,7 +21,6 @@
dataOptions.push(obj) dataOptions.push(obj)
}) })
const options = { const options = {
color, color,
grid: { grid: {
@ -95,7 +94,6 @@
}, },
}, },
], ],
} }
let chart: EChartsType let chart: EChartsType

View File

@ -1,113 +1,111 @@
<template> <template>
<div class="echarts" ref="chartsRef" /> <div ref="chartsRef" class="echarts" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import BarCharts from './components/bar.vue' import BarCharts from './components/bar.vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core' import { EChartsType } from 'echarts/core'
import { onMounted, ref,reactive } from 'vue' import { onMounted, ref, reactive } from 'vue'
const chartsRef = ref<HTMLElement | null>() const chartsRef = ref<HTMLElement | null>()
const options = {
const options = { grid: {
grid: { top: '10%',
top: '10%', left: '3%',
left: '3%', right: '4%',
right: '4%', bottom: '10%',
bottom: '10%', containLabel: true,
containLabel: true, },
}, tooltip: {
tooltip: { trigger: 'axis',
trigger: 'axis', backgroundColor: 'rgba(0,0,0,0.7)',
backgroundColor: 'rgba(0,0,0,0.7)', borderWidth: 0,
borderWidth: 0, borderColor: 'rgba(0,0,0,0.7)',
borderColor: 'rgba(0,0,0,0.7)', formatter: (name, val) => {
formatter: (name, val) => { const tipHtml = `
const tipHtml = `
<div class="m-info" style=" opacity: 0.95;font-size: 12px; color: white;" > <div class="m-info" style=" opacity: 0.95;font-size: 12px; color: white;" >
<div class="title" ></div> <div class="title" ></div>
<div class="title" >完成占比${name[0].data}</div> <div class="title" >完成占比${name[0].data}</div>
</div>` </div>`
return tipHtml return tipHtml
},
},
yAxis: {
//
axisLabel: {
color: '#bbdaff',
margin: 20, // 线
},
// 线
splitLine: {
lineStyle: {
color: '#2d5baf',
}, },
}, },
}, yAxis: {
xAxis: {
splitLine: {
show: false,
},
// 线
axisLine: {
lineStyle: {
color: '#2d5baf',
},
},
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisLabel: {
// //
color: '#bbdaff', axisLabel: {
margin: 20, // 线 color: '#bbdaff',
}, margin: 20, // 线
boundaryGap: false, //
axisTick: {
// 线
show: false,
},
},
series: [
{
data: [154, 230, 224, 218, 135, 147, 260],
type: 'line',
// smooth:false, //true线线true
symbolSize: 12, //
symbol: 'circle',
markLine: {
silent: true,
}, },
itemStyle: { // 线
normal: { splitLine: {
color: '#920783', // symbol lineStyle: {
lineStyle: { color: '#2d5baf',
width: 3,
color: '#920783',
type: 'solid', // 'dotted'线 'solid'线
},
}, },
}, },
}, },
], xAxis: {
splitLine: {
show: false,
},
// 线
axisLine: {
lineStyle: {
color: '#2d5baf',
},
},
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisLabel: {
//
color: '#bbdaff',
margin: 20, // 线
},
boundaryGap: false, //
axisTick: {
// 线
show: false,
},
},
series: [
{
data: [154, 230, 224, 218, 135, 147, 260],
type: 'line',
// smooth:false, //true线线true
symbolSize: 12, //
symbol: 'circle',
markLine: {
silent: true,
},
itemStyle: {
normal: {
color: '#920783', // symbol
lineStyle: {
width: 3,
color: '#920783',
type: 'solid', // 'dotted'线 'solid'线
},
},
},
},
],
}
} let chart: EChartsType
const initChart = () => {
let chart: EChartsType const chart = echarts.init(chartsRef.value)
const initChart = () => { chart.setOption(options)
const chart = echarts.init(chartsRef.value) return chart
chart.setOption(options) }
return chart onMounted(() => {
} chart = initChart()
onMounted(() => { window.addEventListener('resize', function () {
chart = initChart() chart && chart.resize()
window.addEventListener('resize', function () { })
chart && chart.resize()
}) })
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.echarts { .echarts {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
</style> </style>

View File

@ -5,7 +5,7 @@
import { geoJson } from './map.js' import { geoJson } from './map.js'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core' import { EChartsType } from 'echarts/core'
import { onMounted, onUnmounted } from "vue"; import { onMounted, onUnmounted } from 'vue'
import { cityIconData } from './data.js' import { cityIconData } from './data.js'
import logo from '@/assets/image/logo.png' import logo from '@/assets/image/logo.png'
const props = defineProps({ const props = defineProps({
@ -319,8 +319,8 @@
return chart return chart
} }
onUnmounted(()=>{ onUnmounted(() => {
chart&&chart.dispose() chart && chart.dispose()
}) })
onMounted(() => { onMounted(() => {

View File

@ -1,36 +1,36 @@
<template> <template>
<div ref="chartsRef" class="echarts"/> <div ref="chartsRef" class="echarts" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core' import { EChartsType } from 'echarts/core'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
const chartsRef = ref<HTMLElement | null>() const chartsRef = ref<HTMLElement | null>()
var trafficWay = [ var trafficWay = [
{ {
name: 'Ⅰ类', name: 'Ⅰ类',
value: 20, value: 20,
}, },
{ {
name: 'Ⅱ类', name: 'Ⅱ类',
value: 20, value: 20,
}, },
{ {
name: 'Ⅲ类', name: 'Ⅲ类',
value: 20, value: 20,
}, },
{ {
name: 'Ⅳ类', name: 'Ⅳ类',
value: 20, value: 20,
}, },
{ name: 'Ⅴ类', value: 20 }, { name: 'Ⅴ类', value: 20 },
{ name: '劣Ⅴ类', value: 20 }, { name: '劣Ⅴ类', value: 20 },
]; ]
var data = []; var data = []
var color = ['#fd566a', '#9787ff', '#fdb36a', '#fdd56a', '#6da7ff', '#63e1f2', '#ff3000']; var color = ['#fd566a', '#9787ff', '#fdb36a', '#fdd56a', '#6da7ff', '#63e1f2', '#ff3000']
for (var i = 0; i < trafficWay.length; i++) { for (var i = 0; i < trafficWay.length; i++) {
data.push( data.push(
{ {
value: trafficWay[i].value, value: trafficWay[i].value,
name: trafficWay[i].name, name: trafficWay[i].name,
@ -59,121 +59,125 @@ for (var i = 0; i < trafficWay.length; i++) {
borderWidth: 0, borderWidth: 0,
}, },
}, },
} },
); )
} }
var seriesOption = [ var seriesOption = [
{ {
name: '', name: '',
type: 'pie', type: 'pie',
clockWise: false, clockWise: false,
radius: [105, 109], radius: [105, 109],
hoverAnimation: false, hoverAnimation: false,
itemStyle: { itemStyle: {
normal: { normal: {
label: { label: {
show: true, show: true,
position: 'outside', position: 'outside',
formatter: function (params) { formatter: function (params) {
var percent = 0; var percent = 0
var total = 0; var total = 0
for (var i = 0; i < trafficWay.length; i++) { for (var i = 0; i < trafficWay.length; i++) {
total += trafficWay[i].value; total += trafficWay[i].value
} }
percent = ((params.value / total) * 100).toFixed(0); percent = ((params.value / total) * 100).toFixed(0)
if (params.name !== '') { if (params.name !== '') {
return params.name + '\t' + percent + '%'; return params.name + '\t' + percent + '%'
} else { } else {
return ''; return ''
} }
},
},
labelLine: {
length: 10,
length2: 20,
show: true,
color: '#00ffff',
}, },
}, },
labelLine: { },
length: 10, data: data,
length2: 20, },
show: true, ]
color: '#00ffff', let options = {
color: color,
title: [
{
text: '水质监测',
top: '44%',
textAlign: 'center',
left: '49.50%',
backgroundColor: '#163253',
borderRadius: 100,
textStyle: {
color: '#fff',
fontSize: 20,
fontWeight: '400',
}, },
}, },
{
text: '水环境监测站',
top: '49%',
textAlign: 'center',
left: '49.50%',
textStyle: {
color: '#fff',
fontSize: 20,
fontWeight: '400',
},
},
{
text: '9',
top: '53%',
textAlign: 'center',
left: '48%',
textStyle: {
color: '#f6ea2f',
fontSize: 25,
fontWeight: '800',
fontStyle: 'italic',
},
},
{
text: '个',
top: '53.5%',
textAlign: 'center',
left: '50.5%',
textStyle: {
color: '#fff',
fontSize: 16,
fontWeight: '400',
},
},
],
tooltip: {
show: false,
}, },
data: data,
},
];
let options = {
color: color,
title: [{
text: '水质监测',
top: '44%',
textAlign: 'center',
left: '49.50%',
backgroundColor: '#163253',
borderRadius: 100,
textStyle: {
color: '#fff',
fontSize: 20,
fontWeight: '400',
},
}, {
text: '水环境监测站',
top: '49%',
textAlign: 'center',
left: '49.50%',
textStyle: {
color: '#fff',
fontSize: 20,
fontWeight: '400',
},
}, {
text: '9',
top: '53%',
textAlign: 'center',
left: '48%',
textStyle: {
color: '#f6ea2f',
fontSize: 25,
fontWeight: '800',
fontStyle: 'italic'
},
}, {
text: '个',
top: '53.5%',
textAlign: 'center',
left: '50.5%',
textStyle: {
color: '#fff',
fontSize: 16,
fontWeight: '400',
},
}],
tooltip: {
show: false,
},
toolbox: { toolbox: {
show: false, show: false,
}, },
series: seriesOption, series: seriesOption,
}; }
let chart: EChartsType let chart: EChartsType
const initChart = () => { const initChart = () => {
const chart = echarts.init(chartsRef.value) const chart = echarts.init(chartsRef.value)
chart.setOption(options) chart.setOption(options)
return chart return chart
} }
onMounted(() => { onMounted(() => {
chart = initChart() chart = initChart()
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
chart && chart.resize() chart && chart.resize()
})
}) })
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.echarts{ .echarts {
width: 100%; width: 100%;
height: 100%; height: 100%;
}
}
</style> </style>

View File

@ -9,5 +9,5 @@
<script lang="ts" setup></script> <script lang="ts" setup></script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./index.scss"; @import './index.scss';
</style> </style>

View File

@ -1,12 +1,8 @@
<template> <template>
<div class="g-right-click-menu" :style="style" v-if="isShow" ref="rightMenu"> <div v-if="isShow" ref="rightMenu" class="g-right-click-menu" :style="style">
<div <div v-for="(item, index) in data" :key="index" class="operating" @click.stop="operatingRightAction($event, item)">{{
v-for="(item, index) in data" item.label
:key="index" }}</div>
class="operating"
@click.stop="operatingRightAction($event, item)"
>{{ item.label }}</div
>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -37,16 +33,10 @@
default: true, default: true,
}, },
}, },
watch: { data() {
left: { return {
handler(newName, oldName) { isShow: false,
if (newName) { }
this.isShow = true
}
},
// wacthfirstNamehandler
// immediate: true
},
}, },
computed: { computed: {
style() { style() {
@ -59,19 +49,15 @@
} }
}, },
}, },
data() { watch: {
return { left: {
isShow: false, handler(newName, oldName) {
} if (newName) {
}, this.isShow = true
methods: { }
/** },
* @func 点击操作 // wacthfirstNamehandler
* @param val 1置顶/取消置顶 2开启/关闭免打扰 3开启/关闭星标 4删除会话 // immediate: true
*/
operatingRightAction($event, val) {
this.$emit('ok', $event, val)
this.isShow = false
}, },
}, },
mounted() { mounted() {
@ -85,6 +71,16 @@
} }
}) })
}, },
methods: {
/**
* @func 点击操作
* @param val 1置顶/取消置顶 2开启/关闭免打扰 3开启/关闭星标 4删除会话
*/
operatingRightAction($event, val) {
this.$emit('ok', $event, val)
this.isShow = false
},
},
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,24 +1,19 @@
<template> <template>
<el-form-item :label="config?.label" v-if="config.valueType === 'input'" style="width: 100%"> <el-form-item v-if="config.valueType === 'input'" :label="config?.label" style="width: 100%">
<el-input v-model="value" v-bind="$attrs" /> <el-input v-model="value" v-bind="$attrs" />
</el-form-item> </el-form-item>
<el-form-item :label="config?.label" v-if="config.valueType === 'select'" style="width: 100%"> <el-form-item v-if="config.valueType === 'select'" :label="config?.label" style="width: 100%">
<el-select v-model="value" v-bind="$attrs" style="width: 100%"> <el-select v-model="value" v-bind="$attrs" style="width: 100%">
<el-option <el-option v-for="item in config.options" :key="item.value" :label="item.label" :value="item.value" />
v-for="item in config.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="config?.label" v-if="config.valueType === 'date-picker'" style="width: 100%"> <el-form-item v-if="config.valueType === 'date-picker'" :label="config?.label" style="width: 100%">
<el-date-picker v-model="value" v-bind="$attrs" style="width: 100%" /> <el-date-picker v-model="value" v-bind="$attrs" style="width: 100%" />
</el-form-item> </el-form-item>
<el-form-item :label="config?.label" v-if="config.valueType === 'cascader'" style="width: 100%"> <el-form-item v-if="config.valueType === 'cascader'" :label="config?.label" style="width: 100%">
<el-cascader v-model="value" v-bind="$attrs" style="width: 100%" /> <el-cascader v-model="value" v-bind="$attrs" style="width: 100%" />
</el-form-item> </el-form-item>
<el-form-item :label="config?.label" v-if="config.valueType === 'time-select'" style="width: 100%"> <el-form-item v-if="config.valueType === 'time-select'" :label="config?.label" style="width: 100%">
<el-time-select v-model="value" v-bind="$attrs" style="width: 100%" /> <el-time-select v-model="value" v-bind="$attrs" style="width: 100%" />
</el-form-item> </el-form-item>
</template> </template>
@ -36,7 +31,6 @@
(e: 'update:modelValue', value: any): void (e: 'update:modelValue', value: any): void
}>() }>()
const value = computed({ const value = computed({
get() { get() {
return props?.modelValue return props?.modelValue

View File

@ -1,20 +1,10 @@
<template> <template>
<div class="advancedForm"> <div class="advancedForm">
<el-form <el-form ref="ruleFormRef" :inline="true" :label-position="'right'" :model="formParams" class="form-inline">
ref="ruleFormRef"
:inline="true"
:label-position="'right'"
:model="formParams"
class="form-inline"
>
<el-row :class="{ 'not-show': byHeight && !isExpand }" :gutter="gutterWidth"> <el-row :class="{ 'not-show': byHeight && !isExpand }" :gutter="gutterWidth">
<template v-for="(item, index) in columns"> <template v-for="(item, index) in columns">
<el-col <el-col v-if="item.valueType" v-show="byHeight ? true : index < showRow * 3 || isExpand" :span="item.span">
v-if="item.valueType" <BaseFormItem :key="index" v-bind="item.attrs" v-model="item.value" :config="item" />
:span="item.span"
v-show="byHeight ? true : index < showRow * 3 || isExpand"
>
<BaseFormItem :key="index" :config="item" v-bind="item.attrs" v-model="item.value" />
</el-col> </el-col>
</template> </template>
</el-row> </el-row>
@ -22,7 +12,7 @@
<div class="search-btn"> <div class="search-btn">
<el-button type="primary" @click="onSubmit">查询</el-button> <el-button type="primary" @click="onSubmit">查询</el-button>
<el-button @click="resetForm(ruleFormRef)">重置</el-button> <el-button @click="resetForm(ruleFormRef)">重置</el-button>
<el-button link type="primary" @click="isExpand = !isExpand" v-if="columns.length > 3"> <el-button v-if="columns.length > 3" link type="primary" @click="isExpand = !isExpand">
{{ isExpand ? '合并' : '展开' }} {{ isExpand ? '合并' : '展开' }}
<el-icon> <el-icon>
<arrow-down v-if="!isExpand" /> <arrow-down v-if="!isExpand" />

View File

@ -7,7 +7,7 @@
<script lang="ts"> <script lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
export default { export default {
name: 'baseSvgIcon', name: 'BaseSvgIcon',
props: { props: {
iconClass: { type: String }, iconClass: { type: String },
className: { type: String }, className: { type: String },

View File

@ -1,26 +1,20 @@
<template> <template>
<el-switch <el-switch v-model="themeConfig.isDark" inline-prompt :active-icon="Sunny" :inactive-icon="Moon" @change="switchDark" />
@change="switchDark"
inline-prompt
v-model="themeConfig.isDark"
:active-icon="Sunny"
:inactive-icon="Moon"
/>
</template> </template>
<script setup lang="ts" name="switchDark"> <script setup lang="ts" name="switchDark">
import { Sunny, Moon } from "@element-plus/icons-vue"; import { Sunny, Moon } from '@element-plus/icons-vue'
import {computed, ref} from "vue"; import { computed, ref } from 'vue'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
// //
const themeConfig = computed(()=>SettingStore.themeConfig) const themeConfig = computed(() => SettingStore.themeConfig)
// //
const switchDark = () => { const switchDark = () => {
const body = document.documentElement as HTMLElement; const body = document.documentElement as HTMLElement
if (themeConfig.value.isDark) body.setAttribute("class", "dark"); if (themeConfig.value.isDark) body.setAttribute('class', 'dark')
else body.setAttribute("class", ""); else body.setAttribute('class', '')
}; }
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="m-edit-table"> <div class="m-edit-table">
<div style="margin-top: 15px; margin-bottom: 15px" v-if="mode !== 'hide' && mode !== 'bottom'"> <div v-if="mode !== 'hide' && mode !== 'bottom'" style="margin-top: 15px; margin-bottom: 15px">
<el-button style="width: 100%" @click="add"> <el-button style="width: 100%" @click="add">
<el-icon style="margin-right: 4px"><plus /></el-icon> </el-button <el-icon style="margin-right: 4px"><plus /></el-icon> </el-button
> >
@ -8,61 +8,39 @@
<el-table :data="transData" style="width: 100%" row-key="id" border> <el-table :data="transData" style="width: 100%" row-key="id" border>
<template v-for="item in columns"> <template v-for="item in columns">
<el-table-column <el-table-column
v-if="item.type" v-if="item.type"
:type="item.type" :type="item.type"
:width="item.width" :width="item.width"
:align="item.align" :align="item.align"
:fixed="item.fixed" :fixed="item.fixed"
:label="item.label" :label="item.label"
/> />
<el-table-column <el-table-column v-else :prop="item.name" :width="item.width" :align="item.align" :fixed="item.fixed" :label="item.label">
v-else
:prop="item.name"
:width="item.width"
:align="item.align"
:fixed="item.fixed"
:label="item.label"
>
<template #default="scope"> <template #default="scope">
<template v-if="!item.slot"> <template v-if="!item.slot">
<template v-if="item.readonly"> <template v-if="item.readonly">
{{ scope.row[item.name] }} {{ scope.row[item.name] }}
</template> </template>
<template v-else-if="item.valueType === 'select'"> <template v-else-if="item.valueType === 'select'">
<el-select <el-select v-if="scope.row.edit" v-model="scope.row[item.name]" clearable :placeholder="`请选择`">
clearable <el-option v-for="ite in item.options" :key="ite.value" :label="ite.label" :value="ite.value" />
:placeholder="`请选择`"
v-model="scope.row[item.name]"
v-if="scope.row.edit"
>
<el-option
v-for="ite in item.options"
:key="ite.value"
:label="ite.label"
:value="ite.value"
/>
</el-select> </el-select>
<span v-else>{{ filterOption(item, scope) }}</span> <span v-else>{{ filterOption(item, scope) }}</span>
</template> </template>
<template v-else-if="item.valueType === 'date'"> <template v-else-if="item.valueType === 'date'">
<el-date-picker <el-date-picker
v-model="scope.row[item.name]" v-if="scope.row.edit"
type="date" v-model="scope.row[item.name]"
value-format="YYYY-MM-DD" type="date"
clearable value-format="YYYY-MM-DD"
placeholder="请选择" clearable
v-if="scope.row.edit" placeholder="请选择"
/> />
<span v-else>{{ scope.row[item.name] || '--' }}</span> <span v-else>{{ scope.row[item.name] || '--' }}</span>
</template> </template>
<template v-else> <template v-else>
<el-input <el-input v-if="scope.row.edit" v-model="scope.row[item.name]" clearable placeholder="请输入"></el-input>
clearable
placeholder="请输入"
v-model="scope.row[item.name]"
v-if="scope.row.edit"
></el-input>
<span v-else>{{ scope.row[item.name] || '--' }}</span> <span v-else>{{ scope.row[item.name] || '--' }}</span>
</template> </template>
</template> </template>
@ -72,59 +50,28 @@
</template> </template>
<el-table-column prop="operator" label="操作" width="250px" fixed="right"> <el-table-column prop="operator" label="操作" width="250px" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button <el-button v-if="scope.row.edit" type="primary" size="small" icon="CircleCheckFilled" @click="confirmEdit(scope.row)">
v-if="scope.row.edit"
type="primary"
size="small"
icon="CircleCheckFilled"
@click="confirmEdit(scope.row)"
>
保存 保存
</el-button> </el-button>
<el-button <el-button v-else type="primary" size="small" icon="Edit" @click="scope.row.edit = !scope.row.edit"> 编辑 </el-button>
v-else <el-popover v-model:visible="scope.row.visible" trigger="click" placement="top" :width="160">
type="primary"
size="small"
icon="Edit"
@click="scope.row.edit = !scope.row.edit"
>
编辑
</el-button>
<el-popover
trigger="click"
v-model:visible="scope.row.visible"
placement="top"
:width="160"
>
<p style="display: flex; align-items: center; margin-bottom: 10px"> <p style="display: flex; align-items: center; margin-bottom: 10px">
<el-icon color="#faad14" style="margin-right: 10px"><warning-filled /></el-icon> <el-icon color="#faad14" style="margin-right: 10px"><warning-filled /></el-icon>
删除此行</p 删除此行</p
> >
<div style="text-align: right; margin: 0"> <div style="text-align: right; margin: 0">
<el-button size="small" @click="scope.row.visible = false">取消</el-button> <el-button size="small" @click="scope.row.visible = false">取消</el-button>
<el-button size="small" type="primary" @click="deleteAction(scope.row)" <el-button size="small" type="primary" @click="deleteAction(scope.row)">确定</el-button>
>确定</el-button
>
</div> </div>
<template #reference> <template #reference>
<el-button icon="Delete" @click="deleteCurrent(scope.row)" type="danger" size="small" <el-button icon="Delete" type="danger" size="small" @click="deleteCurrent(scope.row)">删除</el-button>
>删除</el-button
>
</template> </template>
</el-popover> </el-popover>
<el-button <el-button v-if="scope.row.edit" type="primary" size="small" icon="Edit" @click="cancelEdit(scope.row)"> 取消 </el-button>
v-if="scope.row.edit"
type="primary"
size="small"
icon="Edit"
@click="cancelEdit(scope.row)"
>
取消
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div style="margin-top: 15px" v-if="mode !== 'hide' && mode !== 'top'"> <div v-if="mode !== 'hide' && mode !== 'top'" style="margin-top: 15px">
<el-button style="width: 100%" @click="add"> <el-button style="width: 100%" @click="add">
<el-icon style="margin-right: 4px"><plus /></el-icon> </el-button <el-icon style="margin-right: 4px"><plus /></el-icon> </el-button
> >
@ -132,72 +79,72 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import { deepObjClone } from '@/utils/index' import { deepObjClone } from '@/utils/index'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { reactive } from 'vue' import { reactive } from 'vue'
const emit = defineEmits(['del', 'add', 'onChange']) const emit = defineEmits(['del', 'add', 'onChange'])
let transData = ref([]) let transData = ref([])
let props = defineProps({ let props = defineProps({
columns: { columns: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
data: { data: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
editableKeys: { editableKeys: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
mode: { mode: {
type: String, type: String,
default: 'bottom', default: 'bottom',
}, },
}) })
// //
const deleteCurrent = (row)=>{ const deleteCurrent = (row) => {
// console.log('----------',row) // console.log('----------',row)
// row.visible = true // row.visible = true
} }
const getData = () => { const getData = () => {
let arr = deepObjClone(transData.value) let arr = deepObjClone(transData.value)
for (let item of arr) { for (let item of arr) {
for (let attr in item) { for (let attr in item) {
if (attr.includes('te__mp')) { if (attr.includes('te__mp')) {
delete item[attr] delete item[attr]
}
} }
} }
emit('onChange', arr)
} }
emit('onChange', arr)
}
let obj = {} let obj = {}
for (let item of props.columns) { for (let item of props.columns) {
props.data.forEach((it) => { props.data.forEach((it) => {
if (!obj[item.name]) { if (!obj[item.name]) {
obj[item.name] = null obj[item.name] = null
} }
}) })
}
//
const reset = () => {
transData.value = props.data
for (let item of transData.value) {
if (props.editableKeys.includes(item.id)) {
item.edit = true
}
} }
getData()
}
onMounted(() => { //
watch( const reset = () => {
transData.value = props.data
for (let item of transData.value) {
if (props.editableKeys.includes(item.id)) {
item.edit = true
}
}
getData()
}
onMounted(() => {
watch(
() => props.data, () => props.data,
(val) => { (val) => {
// // // //
@ -217,88 +164,88 @@ onMounted(() => {
immediate: true, immediate: true,
deep: true, deep: true,
}, },
) )
}) })
const visible = ref(false) const visible = ref(false)
const handleSizeChange = (val: number) => { const handleSizeChange = (val: number) => {
console.log(`${val} items per page`) console.log(`${val} items per page`)
}
const listLoading = ref(false)
//
const confirmEdit = (row) => {
row.edit = false
for (let attr in row) {
if (attr.includes('te__mp')) {
row[attr] = row[attr.replace('te__mp', '')]
}
} }
getData()
} const listLoading = ref(false)
//
const cancelEdit = (row) => { //
row.edit = !row.edit const confirmEdit = (row) => {
for (let attr in row) { row.edit = false
if (attr !== 'edit') { for (let attr in row) {
if (!attr.includes('te__mp')) { if (attr.includes('te__mp')) {
row[attr] = row[attr + 'te__mp'] row[attr] = row[attr.replace('te__mp', '')]
}
}
getData()
}
//
const cancelEdit = (row) => {
row.edit = !row.edit
for (let attr in row) {
if (attr !== 'edit') {
if (!attr.includes('te__mp')) {
row[attr] = row[attr + 'te__mp']
}
} }
} }
} }
}
const deleteAction = (row) => { const deleteAction = (row) => {
row.visible = false row.visible = false
transData.value = transData.value.filter((item) => item.id !== row.id) transData.value = transData.value.filter((item) => item.id !== row.id)
emit('del', row) emit('del', row)
} }
// //
const add = () => { const add = () => {
let id = ~~(Math.random() * 1000000).toFixed(0) let id = ~~(Math.random() * 1000000).toFixed(0)
let obj1 = Object.assign({}, obj, { let obj1 = Object.assign({}, obj, {
id: id, id: id,
edit: true, edit: true,
visible: false, visible: false,
})
for (let attr in obj1) {
let temp = `${attr}te__mp`
obj1[temp] = obj1[attr]
}
if (props.mode === 'bottom') {
transData.value.push(obj1)
}
if (props.mode === 'top') {
transData.value.unshift(obj1)
}
}
const filterOption = (item, scope) => {
let obj = item.options.find((ite) => ite.value === scope.row[item.name])
if (obj) {
return obj.label
}
return '--'
}
defineExpose({
reset,
}) })
for (let attr in obj1) {
let temp = `${attr}te__mp`
obj1[temp] = obj1[attr]
}
if (props.mode === 'bottom') {
transData.value.push(obj1)
}
if (props.mode === 'top') {
transData.value.unshift(obj1)
}
}
const filterOption = (item, scope) => {
let obj = item.options.find((ite) => ite.value === scope.row[item.name])
if (obj) {
return obj.label
}
return '--'
}
defineExpose({
reset,
})
</script> </script>
<style scoped> <style scoped>
.edit-input { .edit-input {
padding-right: 100px; padding-right: 100px;
} }
.cancel-btn { .cancel-btn {
position: absolute; position: absolute;
right: 15px; right: 15px;
top: 10px; top: 10px;
} }
.inline-edit-table { .inline-edit-table {
width: 100%; width: 100%;
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="zb-pro-table"> <div class="zb-pro-table">
<div class="header"> <div class="header">
<SearchForm @submit="onSubmit" :columns="baseFormColumns" /> <SearchForm :columns="baseFormColumns" @submit="onSubmit" />
</div> </div>
<!----------底部----------------------> <!----------底部---------------------->
@ -14,14 +14,14 @@
<!-- ------------表格---------------> <!-- ------------表格--------------->
<div class="table"> <div class="table">
<el-table <el-table
class="zb-table"
v-loading="loading" v-loading="loading"
@selection-change="(val) => emit('selection-change', val)" class="zb-table"
:data="list" :data="list"
:border="true" :border="true"
@selection-change="(val) => emit('selection-change', val)"
> >
<template v-for="item in columns"> <template v-for="item in columns">
<el-table-column v-bind="{ ...item, ...{ prop: item.name } }" v-if="item.slot"> <el-table-column v-if="item.slot" v-bind="{ ...item, ...{ prop: item.name } }">
<template #default="scope"> <template #default="scope">
<slot :name="item.name" :item="item" :row="scope.row"></slot> <slot :name="item.name" :item="item" :row="scope.row"></slot>
</template> </template>

View File

@ -12,16 +12,10 @@
</div> </div>
</div> </div>
</div> </div>
<el-drawer <el-drawer v-model="drawer" title="主题配置" size="300px">
v-model="drawer" title="主题配置" size="300px">
<div class="theme-item"> <div class="theme-item">
<label>导航栏布局</label> <label>导航栏布局</label>
<el-select <el-select v-model="layout" placeholder="请选择" style="width: 150px" @change="(val) => changeSwitch('mode', val)">
v-model="layout"
placeholder="请选择"
style="width: 150px"
@change="(val) => changeSwitch('mode',val)"
>
<el-option label="纵向" value="vertical"></el-option> <el-option label="纵向" value="vertical"></el-option>
<el-option label="横向" value="horizontal"></el-option> <el-option label="横向" value="horizontal"></el-option>
<el-option label="分栏" value="columns"></el-option> <el-option label="分栏" value="columns"></el-option>
@ -37,39 +31,39 @@
</div> </div>
<div class="theme-item"> <div class="theme-item">
<label>灰色模式</label> <label>灰色模式</label>
<el-switch v-model="gray" @change="(val) => changeGrayWeak('gray',val)" /> <el-switch v-model="gray" @change="(val) => changeGrayWeak('gray', val)" />
</div> </div>
<div class="theme-item"> <div class="theme-item">
<label>色弱模式</label> <label>色弱模式</label>
<el-switch v-model="weak" @change="(val) => changeGrayWeak('weak',val)" /> <el-switch v-model="weak" @change="(val) => changeGrayWeak('weak', val)" />
</div> </div>
<div class="theme-item"> <div class="theme-item">
<label>标签栏</label> <label>标签栏</label>
<el-switch v-model="showTag" @change="(val) => changeSwitch('showTag',val)" /> <el-switch v-model="showTag" @change="(val) => changeSwitch('showTag', val)" />
</div> </div>
<div class="theme-item"> <div class="theme-item">
<label>侧边栏 Logo</label> <label>侧边栏 Logo</label>
<el-switch v-model="showLogo" @change="(val) => changeSwitch('showLogo',val)" /> <el-switch v-model="showLogo" @change="(val) => changeSwitch('showLogo', val)" />
</div> </div>
<div class="theme-item"> <div class="theme-item">
<label>保持一个子菜单的展开</label> <label>保持一个子菜单的展开</label>
<el-switch v-model="uniqueOpened" @change="(val) => changeSwitch('uniqueOpened',val)" /> <el-switch v-model="uniqueOpened" @change="(val) => changeSwitch('uniqueOpened', val)" />
</div> </div>
<div class="theme-item"> <div class="theme-item">
<label>固定header</label> <label>固定header</label>
<el-switch v-model="fixedHeader" @change="(val) => changeSwitch('fixedHeader',val)" /> <el-switch v-model="fixedHeader" @change="(val) => changeSwitch('fixedHeader', val)" />
</div> </div>
</el-drawer> </el-drawer>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, ref,watch} from 'vue' import { computed, ref, watch } from 'vue'
import {ElMessage} from "element-plus"; import { ElMessage } from 'element-plus'
import {openLoading,closeLoading} from "@/utils/element" import { openLoading, closeLoading } from '@/utils/element'
import SwitchDark from '@/components/SwitchDark/index.vue' import SwitchDark from '@/components/SwitchDark/index.vue'
import {PRIMARY_COLOR} from "@/config/index"; import { PRIMARY_COLOR } from '@/config/index'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const layout = ref(SettingStore.themeConfig.mode) const layout = ref(SettingStore.themeConfig.mode)
@ -83,17 +77,15 @@
const drawer = computed({ const drawer = computed({
get() { get() {
return SettingStore.themeConfig.showSetting; return SettingStore.themeConfig.showSetting
}, },
set() { set() {
changeSwitch('showSetting',!SettingStore.themeConfig.showSetting) changeSwitch('showSetting', !SettingStore.themeConfig.showSetting)
} },
}) })
// //
const predefineColor = [ const predefineColor = ['#409EFF', '#1890ff', '#304156', '#212121', '#11a983', '#13c2c2', '#6959CD', '#f5222d']
'#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d'
];
const operator = (type) => { const operator = (type) => {
switch (type) { switch (type) {
@ -107,53 +99,52 @@
} }
// //
const changeSwitch = (key,val) => { const changeSwitch = (key, val) => {
SettingStore.setThemeConfig({key, val}) SettingStore.setThemeConfig({ key, val })
if(key==='mode'){ if (key === 'mode') {
openLoading() openLoading()
setTimeout(()=>{ setTimeout(() => {
closeLoading() closeLoading()
},600) }, 600)
} }
} }
// //
watch( watch(
() => layout.value, () => layout.value,
() => { () => {
const body = document.body as HTMLElement; const body = document.body as HTMLElement
body.setAttribute("class", `layout-${layout.value}`); body.setAttribute('class', `layout-${layout.value}`)
}, },
{ immediate: true } { immediate: true },
); )
// //
const changePrimary = (val)=>{ const changePrimary = (val) => {
if (!val) { if (!val) {
primary.value = val = PRIMARY_COLOR; primary.value = val = PRIMARY_COLOR
ElMessage({ type: "success", message: `主题颜色已重置为 ${PRIMARY_COLOR}` }); ElMessage({ type: 'success', message: `主题颜色已重置为 ${PRIMARY_COLOR}` })
} }
document.documentElement.style.setProperty("--el-color-primary", val); document.documentElement.style.setProperty('--el-color-primary', val)
changeSwitch('primary',val) changeSwitch('primary', val)
} }
// //
const changeGrayWeak = (type,val)=>{ const changeGrayWeak = (type, val) => {
const body = document.documentElement as HTMLElement; const body = document.documentElement as HTMLElement
if (!val) return body.setAttribute("style", ""); if (!val) return body.setAttribute('style', '')
if (type === "gray") body.setAttribute("style", "filter: grayscale(1)"); if (type === 'gray') body.setAttribute('style', 'filter: grayscale(1)')
if (type === "weak") body.setAttribute("style", "filter: invert(80%)"); if (type === 'weak') body.setAttribute('style', 'filter: invert(80%)')
changeSwitch(type,val) changeSwitch(type, val)
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep(.el-drawer__header){ ::v-deep(.el-drawer__header) {
border-bottom: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5;
padding: 15px 20px 14px; padding: 15px 20px 14px;
margin-bottom: 0; margin-bottom: 0;
} }
.m-setting-fix { .m-setting-fix {
position: fixed; position: fixed;
top: 50%; top: 50%;
@ -193,19 +184,17 @@
border-radius: 5.5px; border-radius: 5.5px;
font-size: 12px; font-size: 12px;
background: #ebf5ff; background: #ebf5ff;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
box-shadow 0.15s ease;
} }
.item-child2 { .item-child2 {
margin-top: 10px; margin-top: 10px;
color: #b37feb; color: #b37feb;
background: #f7f2fd; background: #f7f2fd;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
box-shadow 0.15s ease;
} }
} }
:deep(.el-drawer__title) { :deep(.el-drawer__title) {
font-weight: bold; font-weight: bold;
color: black; color: black;
} }
@ -217,6 +206,5 @@
font-size: 14px; font-size: 14px;
color: black; color: black;
justify-content: space-between; justify-content: space-between;
} }
</style> </style>

View File

@ -1,26 +1,21 @@
<template> <template>
<div class="m-wangEditor"> <div class="m-wangEditor">
<Toolbar <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor <Editor
class="editor-content'" v-model="valueHtml"
style="height: 300px; overflow-y: hidden;" class="editor-content'"
v-model="valueHtml" style="height: 300px; overflow-y: hidden"
:defaultConfig="editorConfig" :default-config="editorConfig"
:mode="mode" :mode="mode"
@onCreated="handleCreated" @on-created="handleCreated"
/> />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// wangEditor // wangEditor
import { Editor, Toolbar } from "@wangeditor/editor-for-vue"; import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css' // css import '@wangeditor/editor/dist/css/style.css' // css
import {onBeforeUnmount, onMounted, watch, shallowRef, ref, computed} from 'vue' import { onBeforeUnmount, onMounted, watch, shallowRef, ref, computed } from 'vue'
let editors = null let editors = null
// shallowRef // shallowRef
const editorRef = shallowRef() const editorRef = shallowRef()
@ -45,18 +40,18 @@
} }
const valueHtml = computed({ const valueHtml = computed({
get(){ get() {
return props.modelValue return props.modelValue
}, },
set(val){ set(val) {
// //
if (editorRef.value.isEmpty()) val = ""; if (editorRef.value.isEmpty()) val = ''
emit('update:modelValue', val) emit('update:modelValue', val)
} },
}) })
// //
onBeforeUnmount(()=>{ onBeforeUnmount(() => {
// API // API
const editor = editorRef.value const editor = editorRef.value
if (editor == null) { if (editor == null) {
@ -66,15 +61,15 @@
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-wangEditor{ .m-wangEditor {
z-index: 99; z-index: 99;
width: 100%; width: 100%;
border: 1px solid #cccccc; border: 1px solid #cccccc;
.editor-toolbar { .editor-toolbar {
border-bottom: 1px solid #cccccc; border-bottom: 1px solid #cccccc;
}
.editor-content {
overflow-y: hidden;
}
} }
.editor-content {
overflow-y: hidden;
}
}
</style> </style>

View File

@ -1,10 +1,6 @@
<template> <template>
<div class="zb-pipeline-start-wrapper"> <div class="zb-pipeline-start-wrapper">
<div <div class="zb-pipeline-start" :class="control === value ? 'active' : ''" @click="handleClick">
class="zb-pipeline-start"
v-bind:class="control === value ? 'active' : ''"
v-on:click="handleClick"
>
<div class="zb-pipeline-start-header"> <div class="zb-pipeline-start-header">
<zb-icon type="play-filled" /> <zb-icon type="play-filled" />
</div> </div>

View File

@ -1,2 +1,2 @@
// * 默认主题颜色 // * 默认主题颜色
export const PRIMARY_COLOR: string = "#409eff"; export const PRIMARY_COLOR = '#409eff'

View File

@ -4,120 +4,109 @@
* *
*/ */
import { ElMessage } from "element-plus"; import { ElMessage } from 'element-plus'
import {onMounted} from "vue"; import { onMounted } from 'vue'
export const useFullscreen = () => { export const useFullscreen = () => {
/** /**
* @description: + * @description: +
*/ */
const isFullscreen = ()=> { const isFullscreen = () => {
let prefixName = ""; // 浏览器前缀 let prefixName = '' // 浏览器前缀
let fullscreenEnabled = false; let fullscreenEnabled = false
// 判断浏览器前缀 // 判断浏览器前缀
if (document.fullscreenEnabled) { if (document.fullscreenEnabled) {
fullscreenEnabled = document.fullscreenEnabled; fullscreenEnabled = document.fullscreenEnabled
// webkit // webkit
} else if (document.webkitFullscreenEnabled) { } else if (document.webkitFullscreenEnabled) {
fullscreenEnabled = document.webkitFullscreenEnabled; fullscreenEnabled = document.webkitFullscreenEnabled
prefixName = "webkit"; prefixName = 'webkit'
// moz // moz
} else if (document.mozFullScreenEnabled) { } else if (document.mozFullScreenEnabled) {
fullscreenEnabled = document.mozFullScreenEnabled; fullscreenEnabled = document.mozFullScreenEnabled
prefixName = "moz"; prefixName = 'moz'
// ms // ms
} else if (document.msFullscreenEnabled) { } else if (document.msFullscreenEnabled) {
fullscreenEnabled = document.msFullscreenEnabled; fullscreenEnabled = document.msFullscreenEnabled
prefixName = "ms"; prefixName = 'ms'
}
return {
fullscreenEnabled,
prefixName
}
} }
return {
fullscreenEnabled,
/** prefixName,
* @description:
* @return
*/
const isElementFullScreen = ()=> {
const fullscreenElement =
document.fullscreenElement ||
document.msFullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement;
if (fullscreenElement === null) {
return false; // 当前没有元素在全屏状态
} else {
return true; // 有元素在全屏状态
}
} }
}
/** /**
* @description: * @description:
* @param {String} domName dom名称 * @return
*/ */
const Fullscreen = (target)=> { const isElementFullScreen = () => {
const targetRef = target || (document == null ? void 0 : document.querySelector("html")); const fullscreenElement =
const {prefixName} = isFullscreen() document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement
const methodName = if (fullscreenElement === null) {
prefixName === "" return false // 当前没有元素在全屏状态
? "requestFullscreen" } else {
: `${prefixName}RequestFullScreen`; return true // 有元素在全屏状态
targetRef[methodName]();
} }
}
// 退出全屏 /**
const exitFullscreen =()=> { * @description:
const {prefixName} = isFullscreen() * @param {String} domName dom名称
const methodName = */
prefixName === "" const Fullscreen = (target) => {
? "exitFullscreen" const targetRef = target || (document == null ? void 0 : document.querySelector('html'))
: `${prefixName}ExitFullscreen`; const { prefixName } = isFullscreen()
document[methodName](); const methodName = prefixName === '' ? 'requestFullscreen' : `${prefixName}RequestFullScreen`
targetRef[methodName]()
}
// 退出全屏
const exitFullscreen = () => {
const { prefixName } = isFullscreen()
const methodName = prefixName === '' ? 'exitFullscreen' : `${prefixName}ExitFullscreen`
document[methodName]()
}
/**
* @description: ,,
* @param {Function} enterErrorFn
*/
const screenError = () => {
const { prefixName } = isFullscreen()
const methodName = `on${prefixName}fullscreenerror`
document[methodName] = (e) => {
ElMessage.error('进入全屏失败')
} }
}
/** /**
* @description: ,, * @description: /
* @param {Function} enterErrorFn * @param {Function} enter
*/ * @param {Function} quit
const screenError = ()=> { */
const {prefixName} = isFullscreen() const screenChange = (enter, quit) => {
const methodName = `on${prefixName}fullscreenerror`; const { fullscreenEnabled, prefixName } = isFullscreen()
document[methodName] = e => { if (!fullscreenEnabled) return
ElMessage.error('进入全屏失败') const methodName = `on${prefixName}fullscreenchange`
}; document[methodName] = (e) => {
if (isElementFullScreen()) {
enter && enter(e) // 进入全屏回调
} else {
quit && quit(e) // 离开全屏的回调
}
} }
}
/** onMounted(() => {
* @description: / screenError()
* @param {Function} enter })
* @param {Function} quit
*/
const screenChange = (enter, quit)=> {
const {fullscreenEnabled,prefixName} = isFullscreen()
if (!fullscreenEnabled) return;
const methodName = `on${prefixName}fullscreenchange`;
document[methodName] = e => {
if (isElementFullScreen()) {
enter && enter(e); // 进入全屏回调
} else {
quit && quit(e); // 离开全屏的回调
}
};
}
onMounted(()=>{ return {
screenError() isFullscreen,
}) isElementFullScreen,
Fullscreen,
exitFullscreen,
return{ screenChange,
isFullscreen, }
isElementFullScreen,
Fullscreen,
exitFullscreen,
screenChange
}
} }

View File

@ -1,39 +1,39 @@
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from 'resize-observer-polyfill'
import { onBeforeUnmount } from "vue"; import { onBeforeUnmount } from 'vue'
import requestAnimationFrameThrottle from "@/utils/requestAnimationFrameThrottle" import requestAnimationFrameThrottle from '@/utils/requestAnimationFrameThrottle'
export const useResizeElement = (chart, chartsRef)=>{ export const useResizeElement = (chart, chartsRef) => {
let observer = null ; let observer = null
let widthW = 0; let widthW = 0
let heightW = 0; let heightW = 0
const handleResize = (entries )=>{ const handleResize = (entries) => {
const { contentRect } = entries[0]; const { contentRect } = entries[0]
let {width,height} = contentRect let { width, height } = contentRect
width = Math.floor(width); width = Math.floor(width)
height = Math.floor(height); height = Math.floor(height)
if(widthW!==width|| heightW !== height){ if (widthW !== width || heightW !== height) {
widthW = width widthW = width
heightW = height heightW = height
chart && chart.resize() chart && chart.resize()
} }
} }
const addObserver = ()=>{ const addObserver = () => {
observer = new ResizeObserver(requestAnimationFrameThrottle(handleResize)) observer = new ResizeObserver(requestAnimationFrameThrottle(handleResize))
observer.observe(chartsRef) observer.observe(chartsRef)
} }
const removeObserver = ()=>{ const removeObserver = () => {
if (observer) { if (observer) {
observer.disconnect(); observer.disconnect()
observer = null; observer = null
} }
chart&&chart.dispose() chart && chart.dispose()
} }
onBeforeUnmount(()=>{ onBeforeUnmount(() => {
removeObserver() removeObserver()
}) })
return{ return {
addObserver addObserver,
} }
} }

View File

@ -1,70 +1,66 @@
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
import {computed, onMounted, onUnmounted, watch} from "vue"; import { computed, onMounted, onUnmounted, watch } from 'vue'
import {useRoute} from "vue-router"; import { useRoute } from 'vue-router'
const { body } = document const { body } = document
const WIDTH = 800 // refer to Bootstrap's responsive design const WIDTH = 800 // refer to Bootstrap's responsive design
const MAX_WIDTH = 1200 const MAX_WIDTH = 1200
export const useResizeHandler = ()=>{ export const useResizeHandler = () => {
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const route = useRoute() const route = useRoute()
const device = computed(()=>{ const device = computed(() => {
return SettingStore.device return SettingStore.device
}) })
function $_isMobile(){ function $_isMobile() {
const rect = body.getBoundingClientRect() const rect = body.getBoundingClientRect()
return rect.width - 1 < WIDTH return rect.width - 1 < WIDTH
}
function collapse() {
const rect = body.getBoundingClientRect()
if (rect.width - 1 > MAX_WIDTH) {
return true
} else {
return false
} }
}
function collapse(){ function $_resizeHandler() {
const rect = body.getBoundingClientRect() if (!document.hidden) {
if(rect.width - 1 > MAX_WIDTH){ // bool型表示页面是否处于隐藏状态。页面隐藏包括页面在后台标签页或者浏览器最小化
return true const isMobile = $_isMobile()
}else { const isCollapse = collapse()
return false SettingStore.toggleDevice(isMobile ? 'mobile' : 'desktop')
}
if (isMobile) {
SettingStore.closeSideBar({ withoutAnimation: true })
}
if (!isMobile) {
SettingStore.setCollapse(isCollapse)
}
} }
}
function $_resizeHandler(){ onMounted(() => {
if (!document.hidden) { // bool型表示页面是否处于隐藏状态。页面隐藏包括页面在后台标签页或者浏览器最小化 const isMobile = $_isMobile()
const isMobile = $_isMobile() if (isMobile) {
const isCollapse = collapse() SettingStore.toggleDevice('mobile')
SettingStore.toggleDevice(isMobile ? 'mobile' : 'desktop') SettingStore.closeSideBar({ withoutAnimation: true })
if (isMobile) {
SettingStore.closeSideBar({ withoutAnimation: true })
}
if(!isMobile){
SettingStore.setCollapse(isCollapse)
}
}
} }
onMounted(()=>{ window.addEventListener('resize', $_resizeHandler)
const isMobile = $_isMobile()
if (isMobile) {
SettingStore.toggleDevice('mobile')
SettingStore.closeSideBar({ withoutAnimation: true })
}
window.addEventListener('resize', $_resizeHandler)
watch(route,()=>{ watch(route, () => {
if (device.value === 'mobile' && SettingStore.isCollapse) { if (device.value === 'mobile' && SettingStore.isCollapse) {
SettingStore.closeSideBar({ withoutAnimation: false }) SettingStore.closeSideBar({ withoutAnimation: false })
} }
})
}) })
})
onUnmounted(()=>{ onUnmounted(() => {
window.removeEventListener('resize', $_resizeHandler) window.removeEventListener('resize', $_resizeHandler)
}) })
return { device }
return {device}
} }

View File

@ -1,23 +1,23 @@
// 自定义name的壳的集合 // 自定义name的壳的集合
import {h} from "vue"; import { h } from 'vue'
const wrapperMap = new Map() const wrapperMap = new Map()
export const useWrapComponents = (Component,route)=>{ export const useWrapComponents = (Component, route) => {
let wrapper let wrapper
if (Component) { if (Component) {
const wrapperName = route.name const wrapperName = route.name
if (wrapperMap.has(wrapperName)) { if (wrapperMap.has(wrapperName)) {
wrapper = wrapperMap.get(wrapperName) wrapper = wrapperMap.get(wrapperName)
} else { } else {
wrapper = { wrapper = {
name: wrapperName, name: wrapperName,
render() { render() {
return h("div", {className: "app-main-inner"}, Component) return h('div', { className: 'app-main-inner' }, Component)
}, },
} }
wrapperMap.set(wrapperName, wrapper) wrapperMap.set(wrapperName, wrapper)
}
return h(wrapper)
} }
return h(wrapper)
}
} }

View File

@ -1,7 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon' // svg component import SvgIcon from '@/components/SvgIcon' // svg component
// const req = require.context('./svg', false, /\.svg$/) // const req = require.context('./svg', false, /\.svg$/)
const req = import.meta.globEager('./svg/*.svg') const req = import.meta.globEager('./svg/*.svg')

View File

@ -7,12 +7,12 @@
<el-scrollbar> <el-scrollbar>
<div class="menu-wrap"> <div class="menu-wrap">
<div <div
class="item-menu-wrap"
:class="{
'active-menu':activeCurrentMenu===item.path
}"
v-for="item in menusRoutes" v-for="item in menusRoutes"
:key="item.path" :key="item.path"
class="item-menu-wrap"
:class="{
'active-menu': activeCurrentMenu === item.path,
}"
@click="handleChangeMenu(item)" @click="handleChangeMenu(item)"
> >
<el-icon :size="20"> <el-icon :size="20">
@ -26,9 +26,9 @@
<div class="layout-columns-sub" :style="{ width: isCollapse ? '60px' : '210px' }"> <div class="layout-columns-sub" :style="{ width: isCollapse ? '60px' : '210px' }">
<div class="logo flex-center"> <div class="logo flex-center">
<span v-show="subMenus.length">{{ isCollapse ? "Vue" : "Vue Admin Perfect" }}</span> <span v-show="subMenus.length">{{ isCollapse ? 'Vue' : 'Vue Admin Perfect' }}</span>
</div> </div>
<el-scrollbar > <el-scrollbar>
<el-menu <el-menu
:collapse="isCollapse" :collapse="isCollapse"
:router="false" :router="false"
@ -37,7 +37,7 @@
:collapse-transition="false" :collapse-transition="false"
class="menu-columns" class="menu-columns"
> >
<SubMenu :menuList="subMenus" /> <SubMenu :menu-list="subMenus" />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
</div> </div>
@ -45,171 +45,174 @@
<div class="container"> <div class="container">
<div class="layout-header"> <div class="layout-header">
<div class="header-tool"> <div class="header-tool">
<HeaderToolLeft/> <HeaderToolLeft />
<HeaderToolRight/> <HeaderToolRight />
</div> </div>
<TagsView v-if="themeConfig.showTag"/> <TagsView v-if="themeConfig.showTag" />
</div> </div>
<Main/> <Main />
<Footer/> <Footer />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from "vue"; import { ref, computed, watch } from 'vue'
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from 'vue-router'
import {usePermissionStore} from "@/store/modules/permission" import { usePermissionStore } from '@/store/modules/permission'
import { useSettingStore } from "@/store/modules/setting"; import { useSettingStore } from '@/store/modules/setting'
import Footer from '../components/Footer/index.vue' import Footer from '../components/Footer/index.vue'
import SubMenu from '../components/SubMenu/SubMenu.vue' import SubMenu from '../components/SubMenu/SubMenu.vue'
import TagsView from '../components/TagsView/index.vue' import TagsView from '../components/TagsView/index.vue'
const PermissionStore = usePermissionStore() const PermissionStore = usePermissionStore()
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const route = useRoute() const route = useRoute()
const router = useRouter(); const router = useRouter()
import HeaderToolRight from '../components/Header/ToolRight.vue' import HeaderToolRight from '../components/Header/ToolRight.vue'
import HeaderToolLeft from '../components/Header/ToolLeft.vue' import HeaderToolLeft from '../components/Header/ToolLeft.vue'
import Main from '../components/Main/index.vue' import Main from '../components/Main/index.vue'
// //
const permission_routes = computed(() => PermissionStore.permission_routes) const permission_routes = computed(() => PermissionStore.permission_routes)
// //
const menusRoutes = computed(()=>{ const menusRoutes = computed(() => {
return PermissionStore.permission_routes.filter(item=>!item.hidden) return PermissionStore.permission_routes.filter((item) => !item.hidden)
}) })
const activeCurrentMenu = ref('') const activeCurrentMenu = ref('')
// //
const themeConfig = computed(() =>SettingStore.themeConfig) const themeConfig = computed(() => SettingStore.themeConfig)
const isCollapse = computed(() =>!SettingStore.isCollapse) const isCollapse = computed(() => !SettingStore.isCollapse)
const activeMenu = computed(() => { const activeMenu = computed(() => {
const { meta, path } = route const { meta, path } = route
return path return path
}) })
const basePath = ref<string>('/') const basePath = ref<string>('/')
const subMenus = ref([]) const subMenus = ref([])
watch(()=>[route],()=>{ watch(
if (!menusRoutes.value.length) return; () => [route],
const [firstMenu] = route.matched () => {
activeCurrentMenu.value = firstMenu.path; if (!menusRoutes.value.length) return
let menuItem = menusRoutes.value.find(item=>firstMenu.path === item.path) const [firstMenu] = route.matched
if(menuItem&&menuItem.children?.length) { activeCurrentMenu.value = firstMenu.path
subMenus.value = menuItem.children let menuItem = menusRoutes.value.find((item) => firstMenu.path === item.path)
}else { if (menuItem && menuItem.children?.length) {
subMenus.value = [] subMenus.value = menuItem.children
} } else {
basePath.value = firstMenu.path subMenus.value = []
},{ }
deep: true, basePath.value = firstMenu.path
immediate:true },
}) {
deep: true,
immediate: true,
},
)
const handleChangeMenu = (item)=>{ const handleChangeMenu = (item) => {
router.push(item.path); router.push(item.path)
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.main-columns{ .main-columns {
display: flex;
flex-direction: row!important;
height: 100%;
width: 100%;
}
.layout-columns-aside{
flex-shrink: 0;
width: 80px;
height: 100%;
background-color: #304156;
.el-scrollbar{
height: calc(100% - 55px);
}
.logo {
box-sizing: border-box;
height: 50px;
img {
width: 32px;
object-fit: contain;
}
}
.menu-wrap{
display: flex; display: flex;
flex-direction: column; flex-direction: row !important;
align-items: center; height: 100%;
justify-content: center; width: 100%;
.item-menu-wrap{ }
.layout-columns-aside {
flex-shrink: 0;
width: 80px;
height: 100%;
background-color: #304156;
.el-scrollbar {
height: calc(100% - 55px);
}
.logo {
box-sizing: border-box;
height: 50px;
img {
width: 32px;
object-fit: contain;
}
}
.menu-wrap {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 70px; .item-menu-wrap {
width: 70px; display: flex;
cursor: pointer; flex-direction: column;
transition: all .3s ease; align-items: center;
} justify-content: center;
.active-menu{ height: 70px;
background: $primaryColor; width: 70px;
border-radius: 5px; cursor: pointer;
} transition: all 0.3s ease;
.title{ }
color: #e5eaf3; .active-menu {
} background: $primaryColor;
.el-icon{ border-radius: 5px;
color: #e5eaf3; }
.title {
color: #e5eaf3;
}
.el-icon {
color: #e5eaf3;
}
} }
} }
}
.layout-columns-sub{ .layout-columns-sub {
flex-shrink: 0; flex-shrink: 0;
width:200px; width: 200px;
box-sizing: border-box;
flex-direction: column;
overflow: hidden;
transition: all 0.3s ease;
background: white;
border-right: 1px solid #eee;
.el-scrollbar{
height: calc(100vh - 50px);
}
.logo {
width: 100%;
box-sizing: border-box; box-sizing: border-box;
height: 50px; flex-direction: column;
border-bottom: 1px solid #eee; overflow: hidden;
span{ transition: all 0.3s ease;
font-weight: bold; background: white;
white-space: nowrap; border-right: 1px solid #eee;
.el-scrollbar {
height: calc(100vh - 50px);
}
.logo {
width: 100%;
box-sizing: border-box;
height: 50px;
border-bottom: 1px solid #eee;
span {
font-weight: bold;
white-space: nowrap;
}
}
::v-deep(.menu-columns) {
border-right: none;
} }
} }
::v-deep(.menu-columns){ .container {
border-right: none; flex: 1;
} overflow: hidden;
}
.container{
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.layout-header{
background: white;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
.header-tool{
height: 50px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex; display: flex;
align-items: center; flex-direction: column;
padding: 0 10px 0 0; }
box-sizing: border-box; .layout-header {
justify-content: space-between; background: white;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
.header-tool {
height: 50px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
padding: 0 10px 0 0;
box-sizing: border-box;
justify-content: space-between;
}
} }
}
</style> </style>

View File

@ -9,29 +9,26 @@
width: 100%; width: 100%;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
display: flex; display: flex;
background-color:$menuBg; background-color: $menuBg;
align-items: center; align-items: center;
padding: 0 10px 0 0; padding: 0 10px 0 0;
box-sizing: border-box; box-sizing: border-box;
justify-content: space-between; justify-content: space-between;
} }
} }
.fixed-header{ .fixed-header {
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; right: 0;
z-index: 9; z-index: 9;
} }
.menu-horizontal{ .menu-horizontal {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
:deep(.el-menu-item){ :deep(.el-menu-item) {
height: 100%; height: 100%;
} }
} }

View File

@ -1,69 +1,61 @@
<template> <template>
<!--纵向布局--> <!--纵向布局-->
<Height/> <Height />
<div <div
class="m-layout-header" class="m-layout-header"
:class="{ :class="{
'fixed-header':themeConfig.fixedHeader, 'fixed-header': themeConfig.fixedHeader,
}"> }"
<div class="header-inner"> >
<el-menu <div class="header-inner">
mode="horizontal" <el-menu
:default-active="activeMenu" mode="horizontal"
background-color="#304156" :default-active="activeMenu"
text-color="#bfcbd9" background-color="#304156"
:unique-opened="SettingStore.themeConfig.uniqueOpened" text-color="#bfcbd9"
:collapse-transition="false" :unique-opened="SettingStore.themeConfig.uniqueOpened"
class="menu-horizontal" :collapse-transition="false"
> class="menu-horizontal"
<SubItem >
v-for="route in permission_routes" <SubItem v-for="route in permission_routes" :key="route.path" :item="route" />
:key="route.path" </el-menu>
:item="route" <HeaderToolRight />
/>
</el-menu>
<HeaderToolRight/>
</div>
<TagsView v-if="themeConfig.showTag"/>
</div> </div>
<TagsView v-if="themeConfig.showTag" />
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// //
import Height from '../../components/Header/components/Height.vue' import Height from '../../components/Header/components/Height.vue'
import HeaderToolRight from '../../components/Header/ToolRight.vue' import HeaderToolRight from '../../components/Header/ToolRight.vue'
import TagsView from '../../components/TagsView/index.vue' import TagsView from '../../components/TagsView/index.vue'
import SubItem from '../../components/SubMenu/SubItem.vue' import SubItem from '../../components/SubMenu/SubItem.vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import {usePermissionStore} from "@/store/modules/permission" import { usePermissionStore } from '@/store/modules/permission'
const PermissionStore = usePermissionStore() const PermissionStore = usePermissionStore()
const route = useRoute() const route = useRoute()
// //
const permission_routes = computed(() => PermissionStore.permission_routes) const permission_routes = computed(() => PermissionStore.permission_routes)
import {computed} from "vue"; import { computed } from 'vue'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const activeMenu = computed(() => { const activeMenu = computed(() => {
const { meta, path } = route const { meta, path } = route
if (meta.activeMenu) { if (meta.activeMenu) {
return meta.activeMenu return meta.activeMenu
} }
return path return path
}) })
//
// const themeConfig = computed(() => SettingStore.themeConfig)
const themeConfig = computed(() =>SettingStore.themeConfig) const isCollapse = computed(() => !SettingStore.isCollapse)
const isCollapse = computed(() =>!SettingStore.isCollapse)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./index.scss"; @import './index.scss';
</style> </style>

View File

@ -1,24 +1,24 @@
<template> <template>
<div class="main-container"> <div class="main-container">
<UHeader/> <UHeader />
<Main/> <Main />
<Footer/> <Footer />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import Sidebar from '../components/Sidebar/index.vue' import Sidebar from '../components/Sidebar/index.vue'
import UHeader from './HeaderHorizontal/index.vue' import UHeader from './HeaderHorizontal/index.vue'
import Main from '../components/Main/index.vue' import Main from '../components/Main/index.vue'
import Footer from '../components/Footer/index.vue' import Footer from '../components/Footer/index.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.main-container{ .main-container {
display: flex; display: flex;
flex: 1; flex: 1;
box-sizing: border-box; box-sizing: border-box;
flex-direction: column; flex-direction: column;
min-height: 100%; min-height: 100%;
} }
</style> </style>

View File

@ -1,16 +1,15 @@
.mobile { .mobile {
.m-layout-header { .m-layout-header {
left: 0 !important; left: 0 !important;
width: 100%!important; width: 100% !important;
} }
} }
.show-tag{ .show-tag {
height: 90px; height: 90px;
} }
.zb-no-fixed-header {
.zb-no-fixed-header{ width: 100% !important;
width: 100%!important;;
} }
.m-layout-header { .m-layout-header {
@ -32,39 +31,37 @@
justify-content: space-between; justify-content: space-between;
} }
} }
.fixed-header{ .fixed-header {
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; right: 0;
z-index: 9; z-index: 9;
} }
.collapse{ .collapse {
width: calc(100% - 60px); width: calc(100% - 60px);
} }
.no-collapse{ .no-collapse {
width: calc(100% - 210px); width: calc(100% - 210px);
} }
.el-dropdown { .el-dropdown {
display: flex; display: flex;
height: 100%; height: 100%;
align-items: center; align-items: center;
} }
.transverseMenu{ .transverseMenu {
display: flex; display: flex;
.el-menu{ .el-menu {
overflow: hidden; overflow: hidden;
} }
:deep(.el-menu-item){ :deep(.el-menu-item) {
height: 100% !important; height: 100% !important;
} }
.tool-bar-right{ .tool-bar-right {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
min-width:300px ; min-width: 300px;
flex-shrink: 0; flex-shrink: 0;
} }
} }

View File

@ -1,39 +1,38 @@
<template> <template>
<!--纵向布局--> <!--纵向布局-->
<Height/> <Height />
<div <div
class="m-layout-header" class="m-layout-header"
:class="{ :class="{
'fixed-header':themeConfig.fixedHeader, 'fixed-header': themeConfig.fixedHeader,
'collapse':themeConfig.fixedHeader&&isCollapse, collapse: themeConfig.fixedHeader && isCollapse,
'no-collapse':themeConfig.fixedHeader&&!isCollapse 'no-collapse': themeConfig.fixedHeader && !isCollapse,
}"> }"
>
<div class="header-inner"> <div class="header-inner">
<HeaderToolLeft/> <HeaderToolLeft />
<HeaderToolRight/> <HeaderToolRight />
</div> </div>
<TagsView v-if="themeConfig.showTag"/> <TagsView v-if="themeConfig.showTag" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// //
import Height from '../../components/Header/components/Height.vue' import Height from '../../components/Header/components/Height.vue'
import HeaderToolRight from '../../components/Header/ToolRight.vue' import HeaderToolRight from '../../components/Header/ToolRight.vue'
import HeaderToolLeft from '../../components/Header/ToolLeft.vue' import HeaderToolLeft from '../../components/Header/ToolLeft.vue'
import TagsView from '../../components/TagsView/index.vue' import TagsView from '../../components/TagsView/index.vue'
import {computed} from "vue"; import { computed } from 'vue'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
// //
const themeConfig = computed(() =>SettingStore.themeConfig) const themeConfig = computed(() => SettingStore.themeConfig)
const isCollapse = computed(() =>!SettingStore.isCollapse) const isCollapse = computed(() => !SettingStore.isCollapse)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./index.scss"; @import './index.scss';
</style> </style>

View File

@ -1,46 +1,46 @@
<template> <template>
<!--纵向布局--> <!--纵向布局-->
<Sidebar/> <Sidebar />
<div class="main-container"> <div class="main-container">
<HeaderVertical/> <HeaderVertical />
<Main/> <Main />
<Footer/> <Footer />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import Sidebar from '../components/Sidebar/index.vue' import Sidebar from '../components/Sidebar/index.vue'
import HeaderVertical from './HeaderVertical/index.vue' import HeaderVertical from './HeaderVertical/index.vue'
import Main from '../components/Main/index.vue' import Main from '../components/Main/index.vue'
import Footer from '../components/Footer/index.vue' import Footer from '../components/Footer/index.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.g-container-layout { .g-container-layout {
height: 100%; height: 100%;
width: 100%; width: 100%;
.main-container { .main-container {
display: flex;
flex: 1;
box-sizing: border-box;
flex-direction: column;
}
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.sidebar-container {
display: flex; display: flex;
flex: 1;
box-sizing: border-box;
flex-direction: column; flex-direction: column;
} }
&.mobile.openSidebar { .drawer-bg {
position: fixed; background: #000;
opacity: 0.3;
width: 100%;
top: 0; top: 0;
height: 100%;
position: absolute;
z-index: 90;
} }
}
.sidebar-container {
display: flex;
flex-direction: column;
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 90;
}
</style> </style>

View File

@ -1,19 +1,19 @@
<template> <template>
<div class="footer-layout"> <div class="footer-layout">
<span href="/" target="_blank"> 2022 © VUE-ADMIN-PERFECT By ZB Technology. </span> <span href="/" target="_blank"> 2022 © VUE-ADMIN-PERFECT By ZB Technology. </span>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.footer-layout{ .footer-layout {
height: 40px; height: 40px;
font-size: 12px; font-size: 12px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #ffffff; background: #ffffff;
border-top: 1px solid #e4e7ed; border-top: 1px solid #e4e7ed;
flex-shrink: 0; flex-shrink: 0;
color: rgba(0, 0, 0, 0.45); color: rgba(0, 0, 0, 0.45);
} }
</style> </style>

View File

@ -1,19 +1,19 @@
<template> <template>
<div class="m-tool-left"> <div class="m-tool-left">
<CollapseIcon/> <CollapseIcon />
<Hamburger /> <Hamburger />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import CollapseIcon from './components/CollapseIcon.vue' import CollapseIcon from './components/CollapseIcon.vue'
import Hamburger from './components/Hamburger.vue' import Hamburger from './components/Hamburger.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-tool-left{ .m-tool-left {
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%; height: 100%;
} }
</style> </style>

View File

@ -1,29 +1,29 @@
<template> <template>
<div class="m-tool-right"> <div class="m-tool-right">
<GlobalComSize class="item-children"/> <GlobalComSize class="item-children" />
<HeaderSearch class="item-children"/> <HeaderSearch class="item-children" />
<Remind class="item-children"/> <Remind class="item-children" />
<ScreenFull class="item-children"/> <ScreenFull class="item-children" />
<Setting class="item-children"/> <Setting class="item-children" />
<Avatar/> <Avatar />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import GlobalComSize from './components/globalComSize.vue' import GlobalComSize from './components/globalComSize.vue'
import HeaderSearch from './components/HeaderSearch.vue' import HeaderSearch from './components/HeaderSearch.vue'
import Remind from './components/Remind.vue' import Remind from './components/Remind.vue'
import ScreenFull from './components/ScreenFull.vue' import ScreenFull from './components/ScreenFull.vue'
import Setting from './components/Setting.vue' import Setting from './components/Setting.vue'
import Avatar from './components/Avatar.vue' import Avatar from './components/Avatar.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-tool-right{ .m-tool-right {
display: flex; display: flex;
align-items: center; align-items: center;
.item-children{ .item-children {
margin-right: 22px; margin-right: 22px;
}
} }
}
</style> </style>

View File

@ -1,104 +1,104 @@
<template> <template>
<el-dropdown > <el-dropdown>
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<el-avatar :size="30" class="avatar" :src="AvatarLogo"/> <el-avatar :size="30" class="avatar" :src="AvatarLogo" />
{{userInfo.username}} {{ userInfo.username }}
<el-icon class="header-icon el-icon--right"> <el-icon class="header-icon el-icon--right">
<arrow-down /> <arrow-down />
</el-icon> </el-icon>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item :command="0" @click="switchRolesAction('admin')"> <el-dropdown-item :command="0" @click="switchRolesAction('admin')">
{{currentRoles==='admin'?'当前角色':'切换角色'}}管理员 {{ currentRoles === 'admin' ? '当前角色' : '切换角色' }}管理员
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item :command="0" divided @click="switchRolesAction('other')"> <el-dropdown-item :command="0" divided @click="switchRolesAction('other')">
{{currentRoles==='other'?'当前角色':'切换角色'}}普通用户 {{ currentRoles === 'other' ? '当前角色' : '切换角色' }}普通用户
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item :command="3" divided @click="modifyPassword"> <el-dropdown-item :command="3" divided @click="modifyPassword">
<el-icon><Edit /></el-icon> <el-icon><Edit /></el-icon>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item :command="4" divided @click="logOut" > <el-dropdown-item :command="4" divided @click="logOut">
<el-icon><SwitchButton /></el-icon>退 <el-icon><SwitchButton /></el-icon>退
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<PersonalDialog ref="person"/> <PersonalDialog ref="person" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import {ElMessage, ElMessageBox} from "element-plus"; import { ElMessage, ElMessageBox } from 'element-plus'
import {computed, ref} from "vue"; import { computed, ref } from 'vue'
import AvatarLogo from '@/assets/image/avatar.png' import AvatarLogo from '@/assets/image/avatar.png'
import {useUserStore} from "@/store/modules/user" import { useUserStore } from '@/store/modules/user'
import {useTagsViewStore} from "@/store/modules/tagsView" import { useTagsViewStore } from '@/store/modules/tagsView'
import {usePermissionStore} from "@/store/modules/permission" import { usePermissionStore } from '@/store/modules/permission'
import PersonalDialog from './PersonalDialog.vue' import PersonalDialog from './PersonalDialog.vue'
const router = useRouter() const router = useRouter()
const UserStore = useUserStore() const UserStore = useUserStore()
const TagsViewStore = useTagsViewStore() const TagsViewStore = useTagsViewStore()
const PermissionStore = usePermissionStore() const PermissionStore = usePermissionStore()
const currentRoles = computed({ const currentRoles = computed({
get() { get() {
return UserStore.roles[0] return UserStore.roles[0]
}, },
set(val) { set(val) {
;(async () => { ;(async () => {
await UserStore.getInfo([val]) await UserStore.getInfo([val])
router.push({ router.push({
path:'/' path: '/',
}) })
location.reload() location.reload()
})() })()
}, },
}) })
const switchRolesAction = (type:string)=>{ const switchRolesAction = (type: string) => {
if(type===currentRoles.value) return if (type === currentRoles.value) return
currentRoles.value = currentRoles.value==='admin'?'other':'admin' currentRoles.value = currentRoles.value === 'admin' ? 'other' : 'admin'
} }
// //
const userInfo = computed(() => UserStore.userInfo) const userInfo = computed(() => UserStore.userInfo)
const person = ref() const person = ref()
const logOut = async () => { const logOut = async () => {
ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', { ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}) })
.then(async () => { .then(async () => {
await UserStore.logout() await UserStore.logout()
await router.push({path: '/login'}) await router.push({ path: '/login' })
TagsViewStore.clearVisitedView() TagsViewStore.clearVisitedView()
PermissionStore.clearRoutes() PermissionStore.clearRoutes()
ElMessage({ ElMessage({
type: "success", type: 'success',
message: "退出登录成功!" message: '退出登录成功!',
}); })
}) })
.catch(() => {}) .catch(() => {})
} }
const modifyPassword = ()=>{ const modifyPassword = () => {
person.value.show() person.value.show()
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.avatar{ .avatar {
margin-right: 6px margin-right: 6px;
} }
.el-dropdown-link { .el-dropdown-link {
cursor: pointer; cursor: pointer;
//color: var(--el-color-primary); //color: var(--el-color-primary);
display: flex; display: flex;
align-items: center; align-items: center;
} }
</style> </style>

View File

@ -1,30 +1,30 @@
<template> <template>
<div class="hamburger-container" @click="handleCollapse"> <div class="hamburger-container" @click="handleCollapse">
<el-icon class="icon" v-if="isCollapse" ><expand /></el-icon> <el-icon v-if="isCollapse" class="icon"><expand /></el-icon>
<el-icon class="icon" v-else><fold /></el-icon> <el-icon v-else class="icon"><fold /></el-icon>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
import {computed} from "vue"; import { computed } from 'vue'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const isCollapse = computed(() =>!SettingStore.isCollapse) const isCollapse = computed(() => !SettingStore.isCollapse)
const handleCollapse = () => { const handleCollapse = () => {
SettingStore.setCollapse(isCollapse.value) SettingStore.setCollapse(isCollapse.value)
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.hamburger-container{ .hamburger-container {
padding: 0px 15px; padding: 0px 15px;
height: 100%; height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
&:hover { &:hover {
background: rgba(0, 0, 0, .025) background: rgba(0, 0, 0, 0.025);
}
.icon {
font-size: 24px;
cursor: pointer;
}
} }
.icon {
font-size: 24px;
cursor: pointer;
}
}
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<el-breadcrumb class="app-breadcrumb" separator="/"> <el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb" > <transition-group name="breadcrumb">
<el-breadcrumb-item :to="{ path: '/' }" key="home" v-if="matched[0].meta.title !== '首页'"> <el-breadcrumb-item v-if="matched[0].meta.title !== '首页'" key="home" :to="{ path: '/' }">
<div class="breadcrumb-item"> <div class="breadcrumb-item">
<span class="breadcrumb-title">首页</span> <span class="breadcrumb-title">首页</span>
</div> </div>
@ -15,20 +15,17 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute ,useRouter} from 'vue-router' import { useRoute, useRouter } from 'vue-router'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const handleLink = (item)=>{
router.push({
path:item.path
})
}
const matched = computed(() => route.matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false));
const handleLink = (item) => {
router.push({
path: item.path,
})
}
const matched = computed(() => route.matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false))
</script> </script>

View File

@ -1,23 +1,27 @@
<template> <template>
<div class="m-headerSearch"> <div class="m-headerSearch">
<el-tooltip effect="dark" content="菜单搜索" placement="bottom"> <el-tooltip effect="dark" content="菜单搜索" placement="bottom">
<el-icon class="bell header-icon" style="font-size: 22px;" @click="handleSearch"><Search /></el-icon> <el-icon class="bell header-icon" style="font-size: 22px" @click="handleSearch"><Search /></el-icon>
</el-tooltip> </el-tooltip>
<el-dialog v-model="isShowSearch" width="600px" destroy-on-close :show-close="false"> <el-dialog v-model="isShowSearch" width="600px" destroy-on-close :show-close="false">
<el-select <el-select
style="width: 100%" ref="headerSearchSelect"
ref="headerSearchSelect" v-model="search"
v-model="search" style="width: 100%"
:remote-method="querySearch" :remote-method="querySearch"
filterable filterable
default-first-option default-first-option
remote remote
placeholder="菜单搜索 :支持菜单名称、路径" placeholder="菜单搜索 :支持菜单名称、路径"
class="header-search-select" class="header-search-select"
@change="change" @change="change"
> >
<el-option v-for="item in options" :key="item.item.path" :value="item.item.path" :label="item&&item.item.title&&item.item.title.length&&item.item.title.join(' > ') " > <el-option
v-for="item in options"
:key="item.item.path"
:value="item.item.path"
:label="item && item.item.title && item.item.title.length && item.item.title.join(' > ')"
>
</el-option> </el-option>
</el-select> </el-select>
</el-dialog> </el-dialog>
@ -25,147 +29,149 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, onMounted, ref, watch} from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import path from 'path-browserify' import path from 'path-browserify'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { useVueFuse } from 'vue-fuse' import { useVueFuse } from 'vue-fuse'
import {useRouter} from "vue-router"; import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const isShowSearch = ref(false); const isShowSearch = ref(false)
const options = ref([]); const options = ref([])
const searchPool = ref([]); const searchPool = ref([])
const search = ref(''); const search = ref('')
const fuse = ref(null); const fuse = ref(null)
import {usePermissionStore} from "@/store/modules/permission" import { usePermissionStore } from '@/store/modules/permission'
const PermissionStore = usePermissionStore() const PermissionStore = usePermissionStore()
const routes = computed(() => PermissionStore.routes) const routes = computed(() => PermissionStore.routes)
const handleSearch = () => { const handleSearch = () => {
isShowSearch.value = true isShowSearch.value = true
}
const initFuse = (list)=> {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
}
watch(searchPool,(list)=>{
initFuse(list)
})
// 线
const generateRoutes = (routes, basePath = '/', prefixTitle = [])=>{
let res = []
for (const router of routes) {
//
if (router.hidden) { continue }
const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle]
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
if (router.redirect !== 'noRedirect') {
//
//
res.push(data)
}
}
//
if (router.children) {
const tempRoutes = generateRoutes(router.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
} }
return res
}
const change=(val)=> { const initFuse = (list) => {
if(val){ fuse.value = new Fuse(list, {
router.push({ shouldSort: true,
path: val, threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [
{
name: 'title',
weight: 0.7,
},
{
name: 'path',
weight: 0.3,
},
],
}) })
} }
options.value = []
search.value = ''
isShowSearch.value = false
}
onMounted(()=>{
searchPool.value = generateRoutes(JSON.parse(JSON.stringify(routes.value)))
})
const querySearch=(query)=> { watch(searchPool, (list) => {
if (query !== '') { initFuse(list)
options.value = fuse.value.search(query) })
} else {
options.value = [] // 线
const generateRoutes = (routes, basePath = '/', prefixTitle = []) => {
let res = []
for (const router of routes) {
//
if (router.hidden) {
continue
}
const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle],
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
if (router.redirect !== 'noRedirect') {
//
//
res.push(data)
}
}
//
if (router.children) {
const tempRoutes = generateRoutes(router.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
}
const change = (val) => {
if (val) {
router.push({
path: val,
})
}
options.value = []
search.value = ''
isShowSearch.value = false
}
onMounted(() => {
searchPool.value = generateRoutes(JSON.parse(JSON.stringify(routes.value)))
})
const querySearch = (query) => {
if (query !== '') {
options.value = fuse.value.search(query)
} else {
options.value = []
}
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-headerSearch { .m-headerSearch {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.item-info-pop {
display: flex; display: flex;
align-items: center; align-items: center;
} justify-content: center;
.bell{ cursor: pointer;
color: black; transition: all 0.3s;
} .item-info-pop {
.item-child { display: flex;
display: flex; align-items: center;
align-items: center;
font-size: 13px;
}
}
.transverseMenu {
.bell {
color: white;
}
}
.header-search-select{
}
/* 菜单搜索样式 */
.m-headerSearch {
:deep(.el-dialog) {
.el-dialog__header {
display: none;
} }
.el-dialog__body{ .bell {
padding: 0; color: black;
}
.item-child {
display: flex;
align-items: center;
font-size: 13px;
} }
} }
.header-search-select{ .transverseMenu {
height: 50px; .bell {
:deep(.el-input__wrapper){ color: white;
}
}
.header-search-select {
}
/* 菜单搜索样式 */
.m-headerSearch {
:deep(.el-dialog) {
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0;
}
}
.header-search-select {
height: 50px; height: 50px;
:deep(.el-input__wrapper) {
height: 50px;
}
} }
} }
}
</style> </style>

View File

@ -1,14 +1,10 @@
<template> <template>
<div <div v-if="themeConfig.fixedHeader" :style="{ height: `${themeConfig.showTag ? 90 : 50}px` }"> </div>
:style="{ height:`${themeConfig.showTag?90:50}px`}"
v-if="themeConfig.fixedHeader">
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed} from "vue"; import { computed } from 'vue'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const themeConfig = computed(()=>SettingStore.themeConfig) const themeConfig = computed(() => SettingStore.themeConfig)
</script> </script>

View File

@ -1,13 +1,6 @@
<template> <template>
<el-dialog v-model="dialogVisible" title="修改密码" width="40%"> <el-dialog v-model="dialogVisible" title="修改密码" width="40%">
<el-form <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm" :size="formSize">
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
class="demo-ruleForm"
:size="formSize"
>
<el-form-item label="姓名"> <el-form-item label="姓名">
<el-input v-model="ruleForm.name" disabled></el-input> <el-input v-model="ruleForm.name" disabled></el-input>
</el-form-item> </el-form-item>
@ -31,7 +24,7 @@
import { ref, defineExpose, reactive } from 'vue' import { ref, defineExpose, reactive } from 'vue'
import type { ElForm } from 'element-plus' import type { ElForm } from 'element-plus'
const dialogVisible = ref(false) const dialogVisible = ref(false)
import {useUserStore} from "@/store/modules/user" import { useUserStore } from '@/store/modules/user'
const UserStore = useUserStore() const UserStore = useUserStore()
const show = () => { const show = () => {
dialogVisible.value = true dialogVisible.value = true

View File

@ -3,22 +3,28 @@
<el-popover width="200px" placement="bottom"> <el-popover width="200px" placement="bottom">
<template #reference> <template #reference>
<el-badge :value="3" class="item-info-pop"> <el-badge :value="3" class="item-info-pop">
<el-icon class="bell header-icon" style="font-size: 20px;"><Bell /></el-icon> <el-icon class="bell header-icon" style="font-size: 20px"><Bell /></el-icon>
</el-badge> </el-badge>
</template> </template>
<div> <div>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"> <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="通知" name="first"> <el-tab-pane label="通知" name="first">
<div class="item-child"> <div class="item-child">
GitHub开源地址<el-button type="primary" link @click="toGitHub('https://github.com/zouzhibin/vue-admin-perfect')">点我</el-button></div GitHub开源地址<el-button type="primary" link @click="toGitHub('https://github.com/zouzhibin/vue-admin-perfect')"
>点我</el-button
></div
> >
<el-divider style="margin-bottom: 15px"/> <el-divider style="margin-bottom: 15px" />
<div class="item-child"> <div class="item-child">
Gitee开源地址<el-button type="primary" link @click="toGitHub('https://gitee.com/yuanzbz/vue-admin-perfect')">点我</el-button></div Gitee开源地址<el-button type="primary" link @click="toGitHub('https://gitee.com/yuanzbz/vue-admin-perfect')"
>点我</el-button
></div
> >
<el-divider /> <el-divider />
<div class="item-child"> <div class="item-child">
github开源地址<el-button type="primary" link @click="toGitHub('https://github.com/zouzhibin/vue-admin-perfect')">点我</el-button></div github开源地址<el-button type="primary" link @click="toGitHub('https://github.com/zouzhibin/vue-admin-perfect')"
>点我</el-button
></div
> >
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -28,45 +34,45 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus' import type { TabsPaneContext } from 'element-plus'
const activeName = ref('first') const activeName = ref('first')
const toGitHub = (link) => { const toGitHub = (link) => {
window.open(link) window.open(link)
} }
const handleClick = (tab: TabsPaneContext, event: Event) => { const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event) console.log(tab, event)
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-info { .m-info {
display: inline-flex; display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.item-info-pop {
display: flex;
align-items: center; align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.item-info-pop {
display: flex;
align-items: center;
}
.bell {
color: black;
}
.item-child {
display: flex;
align-items: center;
font-size: 13px;
}
} }
.bell{ ::v-deep(.el-divider--horizontal) {
color: black; margin-bottom: 10px;
margin-top: 10px;
} }
.item-child { .transverseMenu {
display: flex; .bell {
align-items: center; color: white;
font-size: 13px; }
} }
}
::v-deep(.el-divider--horizontal){
margin-bottom: 10px;
margin-top: 10px;
}
.transverseMenu {
.bell {
color: white;
}
}
</style> </style>

View File

@ -1,25 +1,23 @@
<template> <template>
<div class="m-screenful"> <div class="m-screenful">
<el-tooltip effect="dark" content="全屏" placement="bottom"> <el-tooltip effect="dark" content="全屏" placement="bottom">
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="toggle" <svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" class-name="header-icon" @click="toggle" />
className="header-icon"
/>
</el-tooltip> </el-tooltip>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useFullscreen } from "@vueuse/core"; import { useFullscreen } from '@vueuse/core'
const { toggle, isFullscreen } = useFullscreen(); const { toggle, isFullscreen } = useFullscreen()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-screenful { .m-screenful {
display: flex; display: flex;
align-items: center; align-items: center;
padding-right: 0; padding-right: 0;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
} }
</style> </style>

View File

@ -1,45 +1,42 @@
<template> <template>
<div class="m-setting"> <div class="m-setting">
<el-tooltip effect="dark" content="主题设置" placement="bottom"> <el-tooltip effect="dark" content="主题设置" placement="bottom">
<el-icon style="font-size: 20px;" class="bell header-icon"><Setting @click="changeSwitch('showSetting',true)"/></el-icon> <el-icon style="font-size: 20px" class="bell header-icon"><Setting @click="changeSwitch('showSetting', true)" /></el-icon>
</el-tooltip> </el-tooltip>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const changeSwitch = (key,val) => { const changeSwitch = (key, val) => {
SettingStore.setThemeConfig({key, val}) SettingStore.setThemeConfig({ key, val })
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-setting { .m-setting {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.item-info-pop {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.item-info-pop {
display: flex;
align-items: center;
}
.bell {
color: black;
}
.item-child {
display: flex;
align-items: center;
font-size: 13px;
}
} }
.bell{ .transverseMenu {
color: black; .bell {
color: white;
}
} }
.item-child {
display: flex;
align-items: center;
font-size: 13px;
}
}
.transverseMenu {
.bell {
color: white;
}
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<el-dropdown trigger="hover" @command="setAssemblySize"> <el-dropdown trigger="hover" @command="setAssemblySize">
<svg-icon class-name="size-icon header-icon" icon-class="size" style="font-size: 20px;cursor: pointer"/> <svg-icon class-name="size-icon header-icon" icon-class="size" style="font-size: 20px; cursor: pointer" />
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item v-for="item in assemblySizeList" :key="item" :disabled="globalComSize === item" :command="item"> <el-dropdown-item v-for="item in assemblySizeList" :key="item" :disabled="globalComSize === item" :command="item">
@ -12,30 +12,30 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, computed } from "vue"; import { reactive, computed } from 'vue'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const globalComSize = computed(():string=>SettingStore.themeConfig.globalComSize) const globalComSize = computed((): string => SettingStore.themeConfig.globalComSize)
const assemblySizeListCh = reactive<{ [key: string]: any }>({ const assemblySizeListCh = reactive<{ [key: string]: any }>({
default: "默认", default: '默认',
large: "大型", large: '大型',
small: "小型" small: '小型',
}); })
const assemblySizeList = reactive<string[]>(["default", "large", "small"]); const assemblySizeList = reactive<string[]>(['default', 'large', 'small'])
const setAssemblySize = (item: string) => { const setAssemblySize = (item: string) => {
if (globalComSize.value === item) return; if (globalComSize.value === item) return
SettingStore.setThemeConfig({key:'globalComSize', val:item}) SettingStore.setThemeConfig({ key: 'globalComSize', val: item })
}; }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.transverseMenu { .transverseMenu {
.size-icon { .size-icon {
color: white; color: white;
}
} }
}
</style> </style>

View File

@ -2,7 +2,7 @@
<div class="app-main"> <div class="app-main">
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition name="fade-slide" mode="out-in" appear> <transition name="fade-slide" mode="out-in" appear>
<keep-alive :include="cacheRoutes" v-if="isReload"> <keep-alive v-if="isReload" :include="cacheRoutes">
<component :is="useWrapComponents(Component, route)" :key="route.path" /> <component :is="useWrapComponents(Component, route)" :key="route.path" />
</keep-alive> </keep-alive>
</transition> </transition>

View File

@ -3,22 +3,20 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed} from "vue"; import { computed } from 'vue'
import { useResizeHandler } from '@/hooks/useResizeHandler' import { useResizeHandler } from '@/hooks/useResizeHandler'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
let { device } = useResizeHandler() let { device } = useResizeHandler()
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
// //
const isCollapse = computed(() => !SettingStore.isCollapse) const isCollapse = computed(() => !SettingStore.isCollapse)
// //
const handleClickOutside = () => { const handleClickOutside = () => {
SettingStore.closeSideBar({ withoutAnimation: false }) SettingStore.closeSideBar({ withoutAnimation: false })
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -2,10 +2,10 @@
<div class="sidebar-logo-container"> <div class="sidebar-logo-container">
<transition name="sidebarLogoFadeCl"> <transition name="sidebarLogoFadeCl">
<router-link v-if="isCollapse" key="collapse" class="sidebar-logo-link" to="/"> <router-link v-if="isCollapse" key="collapse" class="sidebar-logo-link" to="/">
<img src="@/assets/image/logo.png" class="sidebar-logo"> <img src="@/assets/image/logo.png" class="sidebar-logo" />
</router-link> </router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/"> <router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img src="@/assets/image/logo.png" class="sidebar-logo"> <img src="@/assets/image/logo.png" class="sidebar-logo" />
<h1 class="sidebar-title">Vue Admin Perfect</h1> <h1 class="sidebar-title">Vue Admin Perfect</h1>
</router-link> </router-link>
</transition> </transition>
@ -13,7 +13,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
defineProps<{ isCollapse: boolean }>(); defineProps<{ isCollapse: boolean }>()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -41,7 +41,6 @@ defineProps<{ isCollapse: boolean }>();
width: 32px; width: 32px;
height: 32px; height: 32px;
vertical-align: middle; vertical-align: middle;
} }
& .sidebar-title { & .sidebar-title {
display: inline-block; display: inline-block;

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="sidebar-container" :class="{ 'has-logo': themeConfig.showLogo }"> <div class="sidebar-container" :class="{ 'has-logo': themeConfig.showLogo }">
<Logo :isCollapse="isCollapse" v-if="themeConfig.showLogo" /> <Logo v-if="themeConfig.showLogo" :is-collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper"> <el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu <el-menu
:default-active="activeMenu" :default-active="activeMenu"

View File

@ -1,7 +1,5 @@
<template> <template>
<el-menu-item <el-menu-item :index="subItem.path" @click="handleClickMenu(subItem)">
:index="subItem.path"
@click="handleClickMenu(subItem)">
<el-icon> <el-icon>
<component :is="subItem?.meta?.icon"></component> <component :is="subItem?.meta?.icon"></component>
</el-icon> </el-icon>
@ -12,24 +10,24 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from 'vue'
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router'
import { isExternal } from "@/utils/validate"; import { isExternal } from '@/utils/validate'
const router = useRouter(); const router = useRouter()
let props = defineProps({ let props = defineProps({
menuList:{ menuList: {
type:Array, type: Array,
default:()=>[] default: () => [],
}, },
subItem:{ subItem: {
type:Object, type: Object,
default:()=>{} default: () => {},
}, },
}) })
const handleClickMenu = (subItem) => { const handleClickMenu = (subItem) => {
if (isExternal(subItem.path)) return window.open(subItem.path, "_blank"); if (isExternal(subItem.path)) return window.open(subItem.path, '_blank')
router.push(subItem.path); router.push(subItem.path)
}; }
</script> </script>

View File

@ -10,70 +10,65 @@
</el-menu-item> </el-menu-item>
</app-link> </app-link>
</template> </template>
<el-sub-menu :index="item.path" v-else teleported > <el-sub-menu v-else :index="item.path" teleported>
<template #title> <template #title>
<el-icon :size="20"> <component :is="item.meta?.icon"></component></el-icon> <el-icon :size="20"> <component :is="item.meta?.icon"></component></el-icon>
<span>{{ item.meta && item.meta.title }}</span> <span>{{ item.meta && item.meta.title }}</span>
</template> </template>
<sub-item <sub-item v-for="child in item.children" :key="child.path" :item="child" />
v-for="child in item.children"
:key="child.path"
:item="child"
/>
</el-sub-menu> </el-sub-menu>
</template> </template>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { isExternal } from '@/utils/validate.js' import { isExternal } from '@/utils/validate.js'
import AppLink from './Link.vue' import AppLink from './Link.vue'
import path from 'path-browserify' import path from 'path-browserify'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
required: true, required: true,
}, },
basePath: { basePath: {
type: String, type: String,
default: '', default: '',
}, },
})
const onlyOneChild = ref(null)
const hasOneShowingChild = (children = [], parent) => {
const showingChildren = children.filter((item) => {
//
if (item.hidden) {
return false
} else {
// 使
onlyOneChild.value = item
return true
}
}) })
// const onlyOneChild = ref(null)
if (showingChildren.length === 1) { const hasOneShowingChild = (children = [], parent) => {
return true const showingChildren = children.filter((item) => {
} //
// if (item.hidden) {
if (showingChildren.length === 0) { return false
onlyOneChild.value = { ...parent, noShowingChildren: true } } else {
return true // 使
onlyOneChild.value = item
return true
}
})
//
if (showingChildren.length === 1) {
return true
}
//
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, noShowingChildren: true }
return true
}
return false
} }
return false const resolvePath = (routePath) => {
} if (isExternal(routePath)) {
return routePath
const resolvePath = (routePath) => { }
if (isExternal(routePath)) { if (isExternal(props.basePath)) {
return routePath return props.basePath
}
return path.resolve(props.basePath, routePath)
} }
if (isExternal(props.basePath)) {
return props.basePath
}
return path.resolve(props.basePath, routePath)
}
</script> </script>

View File

@ -1,10 +1,8 @@
<template> <template>
<template v-for="subItem in menuList" :key="subItem.path"> <template v-for="subItem in menuList" :key="subItem.path">
<template v-if="!subItem.hidden"> <template v-if="!subItem.hidden">
<template v-if="!subItem.alwaysShow&&hasOneChild(subItem.children, subItem)"> <template v-if="!subItem.alwaysShow && hasOneChild(subItem.children, subItem)">
<MenuItem <MenuItem :sub-item="hasOneChild(subItem.children, subItem)" />
:subItem="hasOneChild(subItem.children, subItem)"
/>
</template> </template>
<el-sub-menu v-else :index="subItem.path"> <el-sub-menu v-else :index="subItem.path">
<template #title> <template #title>
@ -13,42 +11,41 @@
</el-icon> </el-icon>
<span>{{ subItem?.meta?.title }}</span> <span>{{ subItem?.meta?.title }}</span>
</template> </template>
<SubMenu :menuList="subItem.children" /> <SubMenu :menu-list="subItem.children" />
</el-sub-menu> </el-sub-menu>
</template> </template>
</template> </template>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from 'vue'
import MenuItem from './MenuItem.vue' import MenuItem from './MenuItem.vue'
let props = defineProps({ let props = defineProps({
menuList:{ menuList: {
type:Array, type: Array,
default:()=>[] default: () => [],
}, },
})
const hasOneChild = (children = [], parent) => {
const showingChildren = children.filter((item) => {
//
if (item.hidden) {
return false
} else {
return true
}
}) })
//
if (showingChildren.length === 1) {
// 使
return showingChildren[0]
}
//
if (showingChildren.length === 0) {
return parent
}
return false
}
const hasOneChild = (children = [], parent) => {
const showingChildren = children.filter((item) => {
//
if (item.hidden) {
return false
} else {
return true
}
})
//
if (showingChildren.length === 1) {
// 使
return showingChildren[0]
}
//
if (showingChildren.length === 0) {
return parent
}
return false
}
</script> </script>

View File

@ -6,51 +6,58 @@
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item @click="refresh"><el-icon :size="14"><Refresh /></el-icon> </el-dropdown-item> <el-dropdown-item @click="refresh"
<el-dropdown-item @click="closeCurrentTab"><el-icon :size="14"><FolderRemove/></el-icon> </el-dropdown-item> ><el-icon :size="14"><Refresh /></el-icon> </el-dropdown-item
<el-dropdown-item @click="closeOtherTab"><el-icon :size="14"><Close /></el-icon></el-dropdown-item> >
<el-dropdown-item @click="closeAllTab"><el-icon :size="14"><FolderDelete /></el-icon></el-dropdown-item> <el-dropdown-item @click="closeCurrentTab"
><el-icon :size="14"><FolderRemove /></el-icon> </el-dropdown-item
>
<el-dropdown-item @click="closeOtherTab"
><el-icon :size="14"><Close /></el-icon></el-dropdown-item
>
<el-dropdown-item @click="closeAllTab"
><el-icon :size="14"><FolderDelete /></el-icon></el-dropdown-item
>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed} from "vue"; import { computed } from 'vue'
import {useSettingStore} from "@/store/modules/setting" import { useSettingStore } from '@/store/modules/setting'
import {useTagsViewStore} from "@/store/modules/tagsView" import { useTagsViewStore } from '@/store/modules/tagsView'
import { useRouter,useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const TagsViewStore = useTagsViewStore() const TagsViewStore = useTagsViewStore()
const visitedViews = computed(() => TagsViewStore.visitedViews) const visitedViews = computed(() => TagsViewStore.visitedViews)
const refresh = () => { const refresh = () => {
SettingStore.setReload() SettingStore.setReload()
} }
// //
const closeCurrentTab = (event)=>{ const closeCurrentTab = (event) => {
TagsViewStore.toLastView(route.path) TagsViewStore.toLastView(route.path)
TagsViewStore.delView(route.path) TagsViewStore.delView(route.path)
}
//
const closeOtherTab = async () => {
TagsViewStore.delOtherViews(route.path)
}
} //
// const closeAllTab = async () => {
const closeOtherTab= async ()=>{ await TagsViewStore.delAllViews()
TagsViewStore.delOtherViews(route.path) TagsViewStore.goHome()
} }
//
const closeAllTab = async ()=>{
await TagsViewStore.delAllViews()
TagsViewStore.goHome()
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.more{ .more {
background-color: $primaryColor; background-color: $primaryColor;
color: white; color: white;
.tags-view-item{ .tags-view-item {
display: flex; display: flex;
align-items: center; align-items: center;
}
} }
}
</style> </style>

View File

@ -1,22 +1,17 @@
<template> <template>
<div class="m-tags-view"> <div class="m-tags-view">
<div class="tags-view"> <div class="tags-view">
<el-tabs <el-tabs v-model="activeTabsValue" type="card" @tab-click="tabClick" @tab-remove="removeTab">
v-model="activeTabsValue"
type="card"
@tab-click="tabClick"
@tab-remove="removeTab"
>
<el-tab-pane <el-tab-pane
v-for="item in visitedViews" v-for="item in visitedViews"
:key="item.path" :key="item.path"
:path="item.path" :path="item.path"
:label="item.title" :label="item.title"
:name="item.path" :name="item.path"
:closable="!(item.meta&&item.meta.affix)" :closable="!(item.meta && item.meta.affix)"
> >
<template #label> <template #label>
<el-icon class="tabs-icon" v-if="item.icon"> <el-icon v-if="item.icon" class="tabs-icon">
<component :is="item.icon"></component> <component :is="item.icon"></component>
</el-icon> </el-icon>
{{ item.title }} {{ item.title }}
@ -25,143 +20,143 @@
</el-tabs> </el-tabs>
</div> </div>
<div class="right-btn"> <div class="right-btn">
<MoreButton/> <MoreButton />
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, watch, ref, onMounted} from "vue"; import { computed, watch, ref, onMounted } from 'vue'
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from 'vue-router'
import { TabsPaneContext } from "element-plus"; import { TabsPaneContext } from 'element-plus'
import MoreButton from './components/MoreButton' import MoreButton from './components/MoreButton'
import path from 'path-browserify' import path from 'path-browserify'
import {useTagsViewStore} from "@/store/modules/tagsView" import { useTagsViewStore } from '@/store/modules/tagsView'
import {usePermissionStore} from "@/store/modules/permission" import { usePermissionStore } from '@/store/modules/permission'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const TagsViewStore = useTagsViewStore() const TagsViewStore = useTagsViewStore()
const PermissionStore = usePermissionStore() const PermissionStore = usePermissionStore()
const visitedViews = computed(() => TagsViewStore.visitedViews) const visitedViews = computed(() => TagsViewStore.visitedViews)
const routes = computed(() => PermissionStore.routes) const routes = computed(() => PermissionStore.routes)
const addTags = () => { const addTags = () => {
const { name } = route const { name } = route
if (name === 'Login') { if (name === 'Login') {
return return
}
if (name) {
TagsViewStore.addView(route)
}
return false
}
let affixTags = ref([])
function filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta },
})
} }
if (route.children) { if (name) {
const tempTags = filterAffixTags(route.children, route.path) TagsViewStore.addView(route)
if (tempTags.length >= 1) { }
tags = [...tags, ...tempTags] return false
}
let affixTags = ref([])
function filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta },
})
}
if (route.children) {
const tempTags = filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
}
const initTags = () => {
let routesNew = routes.value
let affixTag = (affixTags.value = filterAffixTags(routesNew))
for (const tag of affixTag) {
if (tag.name) {
TagsViewStore.addVisitedView(tag)
} }
} }
}
onMounted(() => {
initTags()
addTags()
}) })
return tags watch(route, () => {
} addTags()
const initTags = () => { })
let routesNew = routes.value let tabIndex = 2
let affixTag = (affixTags.value = filterAffixTags(routesNew)) const activeTabsValue = computed({
for (const tag of affixTag) { get: () => {
if (tag.name) { return TagsViewStore.activeTabsValue
TagsViewStore.addVisitedView(tag) },
} set: (val) => {
TagsViewStore.setTabsMenuValue(val)
},
})
function toLastView(activeTabPath) {
let index = visitedViews.value.findIndex((item) => item.path === activeTabPath)
const nextTab = visitedViews.value[index + 1] || visitedViews.value[index - 1]
if (!nextTab) return
router.push(nextTab.path)
TagsViewStore.addVisitedView(nextTab)
} }
} const tabClick = (tabItem: TabsPaneContext) => {
onMounted(()=>{ let path = tabItem.props.name as string
initTags() router.push(path)
addTags()
})
watch(route, () => {
addTags()
})
let tabIndex = 2
const activeTabsValue = computed({
get: () => {
return TagsViewStore.activeTabsValue;
},
set: val => {
TagsViewStore.setTabsMenuValue(val);
} }
});
function toLastView(activeTabPath) {
let index = visitedViews.value.findIndex(item=>item.path===activeTabPath)
const nextTab = visitedViews.value[index + 1] || visitedViews.value[index - 1];
if (!nextTab) return;
router.push(nextTab.path);
TagsViewStore.addVisitedView(nextTab)
}
const tabClick = (tabItem: TabsPaneContext) => {
let path = tabItem.props.name as string;
router.push(path);
};
const isActive = (path) => { const isActive = (path) => {
return path === route.path return path === route.path
} }
const removeTab = async (activeTabPath: string) => { const removeTab = async (activeTabPath: string) => {
if (isActive(activeTabPath)) { if (isActive(activeTabPath)) {
toLastView(activeTabPath) toLastView(activeTabPath)
}
await TagsViewStore.delView(activeTabPath)
} }
await TagsViewStore.delView(activeTabPath)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.m-tags-view{ .m-tags-view {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
background: white; background: white;
.right-btn{ .right-btn {
height: 100%; height: 100%;
flex-shrink: 0; flex-shrink: 0;
}
} }
} .tags-view {
.tags-view { flex: 1;
flex: 1; overflow: hidden;
overflow: hidden;
box-sizing: border-box;
}
.tags-view{
.el-tabs--card :deep(.el-tabs__header){
box-sizing: border-box; box-sizing: border-box;
height: 40px;
padding: 0 10px;
margin: 0;
} }
:deep(.el-tabs){
.el-tabs__nav { .tags-view {
border: none; .el-tabs--card :deep(.el-tabs__header) {
box-sizing: border-box;
height: 40px;
padding: 0 10px;
margin: 0;
} }
.el-tabs__header .el-tabs__item { :deep(.el-tabs) {
border: none; .el-tabs__nav {
color: #cccccc; border: none;
} }
.el-tabs__header .el-tabs__item.is-active { .el-tabs__header .el-tabs__item {
color: $primaryColor; border: none;
border-bottom:2px solid $primaryColor; color: #cccccc;
}
.el-tabs__header .el-tabs__item.is-active {
color: $primaryColor;
border-bottom: 2px solid $primaryColor;
}
} }
} }
}
</style> </style>

View File

@ -1,32 +1,32 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './routers' import router from './routers'
import pinia from "./store"; import pinia from './store'
import {registerElIcons} from "@/plugins/ElIcons" import { registerElIcons } from '@/plugins/ElIcons'
// 引入全局组件布局 // 引入全局组件布局
import PageWrapLayout from '@/components/PageWrapLayout/index.vue' import PageWrapLayout from '@/components/PageWrapLayout/index.vue'
// 权限路由 // 权限路由
import './permission' import './permission'
// svg-icons注册导入 // svg-icons注册导入
import 'virtual:svg-icons-register' import 'virtual:svg-icons-register'
import SvgIcon from '@/components/SvgIcon/index.vue'// svg component import SvgIcon from '@/components/SvgIcon/index.vue' // svg component
// UI框架 element-plus // UI框架 element-plus
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
// 引入暗黑模式 element-plus 2.2 内置暗黑模式 // 引入暗黑模式 element-plus 2.2 内置暗黑模式
import 'element-plus/theme-chalk/dark/css-vars.css' import 'element-plus/theme-chalk/dark/css-vars.css'
// 自定义暗黑模式 // 自定义暗黑模式
import "@/styles/element-dark.scss"; import '@/styles/element-dark.scss'
// 引入阿里图标库 // 引入阿里图标库
import "@/assets/iconfont/iconfont.css"; import '@/assets/iconfont/iconfont.css'
import "@/assets/iconfont/iconfont.js"; import '@/assets/iconfont/iconfont.js'
const app = createApp(App) const app = createApp(App)
registerElIcons(app) registerElIcons(app)
app.component('svg-icon',SvgIcon) app.component('SvgIcon', SvgIcon)
app.component('PageWrapLayout',PageWrapLayout) app.component('PageWrapLayout', PageWrapLayout)
app.use(pinia) app.use(pinia)
app.use(router) app.use(router)

View File

@ -1,476 +1,487 @@
export const userData =[ export const userData = [
{ {
username:'zzb', username: 'zzb',
nickname:'林峰', nickname: '林峰',
sex:'男', sex: '男',
role:'超级管理员', role: '超级管理员',
status:true, status: true,
photo:'15333333333', photo: '15333333333',
describe:'超级管理员不可删除', describe: '超级管理员不可删除',
createTime:'2022-09-02 15:30:20', createTime: '2022-09-02 15:30:20',
}, },
{ {
username:'zhangsan', username: 'zhangsan',
nickname:'张三', nickname: '张三',
sex:'女', sex: '女',
role:'管理员', role: '管理员',
status:true, status: true,
photo:'15311111111', photo: '15311111111',
describe:'管理员不可删除', describe: '管理员不可删除',
createTime:'2022-09-02 15:30:20', createTime: '2022-09-02 15:30:20',
}, },
{ {
username:'lisi', username: 'lisi',
nickname:'李四', nickname: '李四',
sex:'男', sex: '男',
role:'管理员', role: '管理员',
status:true, status: true,
photo:'13823456789', photo: '13823456789',
describe:'测试账户', describe: '测试账户',
createTime:'2022-09-02 15:30:20', createTime: '2022-09-02 15:30:20',
}, },
{ {
username:'wangwu', username: 'wangwu',
nickname:'王五', nickname: '王五',
sex:'男', sex: '男',
role:'超级管理员', role: '超级管理员',
status:false, status: false,
photo:'13923456789', photo: '13923456789',
describe:'超级管理员不可删除', describe: '超级管理员不可删除',
createTime:'2022-09-02 15:30:20', createTime: '2022-09-02 15:30:20',
}, },
{ {
username:'zhaoliu', username: 'zhaoliu',
nickname:'赵柳', nickname: '赵柳',
sex:'男', sex: '男',
role:'普通用户', role: '普通用户',
status:false, status: false,
photo:'14523456789', photo: '14523456789',
describe:'普通测试用户', describe: '普通测试用户',
createTime:'2022-09-02 15:30:20', createTime: '2022-09-02 15:30:20',
}, },
] ]
export const deptData = [
export const deptData =[ {
{ id: '0',
"id": "0", deptName: '华东分部',
"deptName": "华东分部", orderNo: 1,
"orderNo": 1, createTime: '2011-02-25 18:37:39',
"createTime": "2011-02-25 18:37:39", remark: '世上无难事,只要肯登攀',
"remark": "世上无难事,只要肯登攀", status: false,
"status": false, children: [
"children": [{ {
"id": "0-0", id: '0-0',
"deptName": "研发部", deptName: '研发部',
"orderNo": 1, orderNo: 1,
"createTime": "1995-10-07 03:22:40", createTime: '1995-10-07 03:22:40',
"remark": "不曾扬帆,何以至远方", remark: '不曾扬帆,何以至远方',
"status": true, status: true,
"parentDept": "0" parentDept: '0',
}, { },
"id": "0-1", {
"deptName": "市场部", id: '0-1',
"orderNo": 2, deptName: '市场部',
"createTime": "1972-06-20 09:33:40", orderNo: 2,
"remark": "努力到无能为力,拼搏到感动自己", createTime: '1972-06-20 09:33:40',
"status": false, remark: '努力到无能为力,拼搏到感动自己',
"parentDept": "0" status: false,
}, { parentDept: '0',
"id": "0-2", },
"deptName": "商务部", {
"orderNo": 3, id: '0-2',
"createTime": "1992-10-31 02:54:45", deptName: '商务部',
"remark": "没有过不了的坎,就怕自己不奋斗", orderNo: 3,
"status": false, createTime: '1992-10-31 02:54:45',
"parentDept": "0" remark: '没有过不了的坎,就怕自己不奋斗',
}, { status: false,
"id": "0-3", parentDept: '0',
"deptName": "财务部", },
"orderNo": 4, {
"createTime": "1971-07-06 13:01:49", id: '0-3',
"remark": "没有口水与汗水,就没有成功的泪水。", deptName: '财务部',
"status": true, orderNo: 4,
"parentDept": "0" createTime: '1971-07-06 13:01:49',
}] remark: '没有口水与汗水,就没有成功的泪水。',
}, { status: true,
"id": "1", parentDept: '0',
"deptName": "华南分部", },
"orderNo": 2, ],
"createTime": "1995-12-24 06:36:26", },
"remark": "这个世界从来不缺乏机遇,而是缺少抓住机遇的手。", {
"status": false, id: '1',
"children": [{ deptName: '华南分部',
"id": "1-0", orderNo: 2,
"deptName": "研发部", createTime: '1995-12-24 06:36:26',
"orderNo": 1, remark: '这个世界从来不缺乏机遇,而是缺少抓住机遇的手。',
"createTime": "2022-05-10 12:44:05", status: false,
"remark": "奋斗令我们的生活充满生机,责任让我们的生命充满意义!", children: [
"status": true, {
"parentDept": "1" id: '1-0',
}, { deptName: '研发部',
"id": "1-1", orderNo: 1,
"deptName": "市场部", createTime: '2022-05-10 12:44:05',
"orderNo": 2, remark: '奋斗令我们的生活充满生机,责任让我们的生命充满意义!',
"createTime": "2022-07-15 02:53:29", status: true,
"remark": "学习之心不可无,懒惰之心不可有。", parentDept: '1',
"status": true, },
"parentDept": "1" {
}, id: '1-1',
{ deptName: '市场部',
"id": "1-2", orderNo: 2,
"deptName": "商务部", createTime: '2022-07-15 02:53:29',
"orderNo": 3, remark: '学习之心不可无,懒惰之心不可有。',
"createTime": "2022-08-11 22:44:55", status: true,
"remark": "学会等待,学会坚持,成功是一个循序渐进的过程。", parentDept: '1',
"status": true, },
"parentDept": "1" {
}, { id: '1-2',
"id": "1-3", deptName: '商务部',
"deptName": "财务部", orderNo: 3,
"orderNo": 4, createTime: '2022-08-11 22:44:55',
"createTime": "2022-10-26 19:38:29", remark: '学会等待,学会坚持,成功是一个循序渐进的过程。',
"remark": "能克服困难的人,可使困难化为良机", status: true,
"status": false, parentDept: '1',
"parentDept": "1" },
} {
] id: '1-3',
}, { deptName: '财务部',
"id": "2", orderNo: 4,
"deptName": "西北分部", createTime: '2022-10-26 19:38:29',
"orderNo": 3, remark: '能克服困难的人,可使困难化为良机',
"createTime": "2022-08-27 16:49:21", status: false,
"remark": "行为决定性格,性格决定命运", parentDept: '1',
"status": false, },
"children": [{ ],
"id": "2-0", },
"deptName": "研发部", {
"orderNo": 1, id: '2',
"createTime": "2022-12-11 03:49:33", deptName: '西北分部',
"remark": "带着自己的梦,以一种骄傲的姿态走下去", orderNo: 3,
"status": false, createTime: '2022-08-27 16:49:21',
"parentDept": "2" remark: '行为决定性格,性格决定命运',
}, { status: false,
"id": "2-1", children: [
"deptName": "市场部", {
"orderNo": 2, id: '2-0',
"createTime": "2022-06-18 20:15:34", deptName: '研发部',
"remark": "当世界都在说放弃的时候,轻轻的告诉自己:再试一次", orderNo: 1,
"status": true, createTime: '2022-12-11 03:49:33',
"parentDept": "2" remark: '带着自己的梦,以一种骄傲的姿态走下去',
}, { status: false,
"id": "2-2", parentDept: '2',
"deptName": "商务部", },
"orderNo": 3, {
"createTime": "2022-07-17 09:37:41", id: '2-1',
"remark": "不怕万人阻挡在前方,只怕自己先行投降", deptName: '市场部',
"status": true, orderNo: 2,
"parentDept": "2" createTime: '2022-06-18 20:15:34',
}, { remark: '当世界都在说放弃的时候,轻轻的告诉自己:再试一次',
"id": "2-3", status: true,
"deptName": "财务部", parentDept: '2',
"orderNo": 4, },
"createTime": "2022-11-23 04:34:33", {
"remark": "胸怀临云志,莫负少年时", id: '2-2',
"status": false, deptName: '商务部',
"parentDept": "2" orderNo: 3,
}] createTime: '2022-07-17 09:37:41',
}] remark: '不怕万人阻挡在前方,只怕自己先行投降',
status: true,
parentDept: '2',
export const roleData =[ },
{ {
roleName:'超级管理员', id: '2-3',
roleId:'admin', deptName: '财务部',
roleIdentification:'admin', orderNo: 4,
describe:'这是超级管理员,拥有一切权限', createTime: '2022-11-23 04:34:33',
createTime:'2022-09-02 15:30:20', remark: '胸怀临云志,莫负少年时',
}, status: false,
{ parentDept: '2',
roleName:'管理员', },
roleId:'role', ],
roleIdentification:'admin', },
describe:'普通管理员',
createTime:'2022-09-02 15:30:20',
},
{
roleName:'普通用户',
roleId:'other',
describe:'测试用户',
roleIdentification:'other',
createTime:'2022-09-02 15:30:20',
},
] ]
export const roleData = [
{
roleName: '超级管理员',
roleId: 'admin',
roleIdentification: 'admin',
describe: '这是超级管理员,拥有一切权限',
createTime: '2022-09-02 15:30:20',
},
{
roleName: '管理员',
roleId: 'role',
roleIdentification: 'admin',
describe: '普通管理员',
createTime: '2022-09-02 15:30:20',
},
{
roleName: '普通用户',
roleId: 'other',
describe: '测试用户',
roleIdentification: 'other',
createTime: '2022-09-02 15:30:20',
},
]
export const menuData =[ export const menuData = [
{ {
menuName:'首页', menuName: '首页',
menuType:'菜单', menuType: '菜单',
menuRouter:'/home', menuRouter: '/home',
identification:'menu:home', identification: 'menu:home',
parentId:0, parentId: 0,
level:1, level: 1,
id:0, id: 0,
createTime:'2022-09-02', createTime: '2022-09-02',
}, },
{ {
menuName:'表格', menuName: '表格',
menuType:'目录', menuType: '目录',
menuRouter:'/table', menuRouter: '/table',
identification:'menu:table', identification: 'menu:table',
parentId:0, parentId: 0,
level:1, level: 1,
id:1, id: 1,
createTime:'2022-09-02', createTime: '2022-09-02',
children:[ children: [
{ {
menuName:'菜单1', menuName: '菜单1',
menuType:'菜单', menuType: '菜单',
menuRouter:'/table', menuRouter: '/table',
identification:'menu1:view', identification: 'menu1:view',
parentId:1, parentId: 1,
level:2, level: 2,
id:10, id: 10,
createTime:'2022-09-02', createTime: '2022-09-02',
children:[ children: [
{ {
menuName:'按钮1', menuName: '按钮1',
menuType:'按钮', menuType: '按钮',
menuRouter:'/table', menuRouter: '/table',
identification:'menu1:view:btn1', identification: 'menu1:view:btn1',
parentId:10, parentId: 10,
id:20, id: 20,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
}, },
{ {
menuName:'按钮2', menuName: '按钮2',
menuType:'按钮', menuType: '按钮',
menuRouter:'/table', menuRouter: '/table',
identification:'menu1:view:btn2', identification: 'menu1:view:btn2',
parentId:10, parentId: 10,
id:21, id: 21,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
}, },
{ {
menuName:'按钮3', menuName: '按钮3',
menuType:'按钮', menuType: '按钮',
menuRouter:'/table', menuRouter: '/table',
identification:'menu1:view:btn2', identification: 'menu1:view:btn2',
parentId:10, parentId: 10,
id:22, id: 22,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
} },
] ],
} },
] ],
}, },
{ {
menuName:'可视化图表', menuName: '可视化图表',
menuType:'目录', menuType: '目录',
menuRouter:'/charts', menuRouter: '/charts',
identification:'menu:charts', identification: 'menu:charts',
parentId:0, parentId: 0,
level:1, level: 1,
id:1, id: 1,
createTime:'2022-09-02', createTime: '2022-09-02',
children:[ children: [
{ {
menuName:'菜单1', menuName: '菜单1',
menuType:'菜单', menuType: '菜单',
menuRouter:'/charts', menuRouter: '/charts',
identification:'menu1:view', identification: 'menu1:view',
parentId:1, parentId: 1,
level:2, level: 2,
id:10, id: 10,
createTime:'2022-09-02', createTime: '2022-09-02',
children:[ children: [
{ {
menuName:'按钮1', menuName: '按钮1',
menuType:'按钮', menuType: '按钮',
menuRouter:'/charts', menuRouter: '/charts',
identification:'menu1:view:btn1', identification: 'menu1:view:btn1',
parentId:10, parentId: 10,
id:20, id: 20,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
}, },
{ {
menuName:'按钮2', menuName: '按钮2',
menuType:'按钮', menuType: '按钮',
menuRouter:'/charts', menuRouter: '/charts',
identification:'menu1:view:btn2', identification: 'menu1:view:btn2',
parentId:10, parentId: 10,
id:21, id: 21,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
}, },
{ {
menuName:'按钮3', menuName: '按钮3',
menuType:'按钮', menuType: '按钮',
menuRouter:'/charts', menuRouter: '/charts',
identification:'menu1:view:btn2', identification: 'menu1:view:btn2',
parentId:10, parentId: 10,
id:22, id: 22,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
} },
] ],
} },
] ],
}, },
{ {
menuName:'基础组件', menuName: '基础组件',
menuType:'目录', menuType: '目录',
menuRouter:'/components', menuRouter: '/components',
identification:'menu:components', identification: 'menu:components',
parentId:0, parentId: 0,
level:1, level: 1,
id:1, id: 1,
createTime:'2022-09-02', createTime: '2022-09-02',
children:[ children: [
{ {
menuName:'菜单1', menuName: '菜单1',
menuType:'菜单', menuType: '菜单',
menuRouter:'/components', menuRouter: '/components',
identification:'menu1:view', identification: 'menu1:view',
parentId:1, parentId: 1,
level:2, level: 2,
id:10, id: 10,
createTime:'2022-09-02', createTime: '2022-09-02',
children:[ children: [
{ {
menuName:'按钮1', menuName: '按钮1',
menuType:'按钮', menuType: '按钮',
menuRouter:'/components', menuRouter: '/components',
identification:'menu1:view:btn1', identification: 'menu1:view:btn1',
parentId:10, parentId: 10,
id:20, id: 20,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
}, },
{ {
menuName:'按钮2', menuName: '按钮2',
menuType:'按钮', menuType: '按钮',
menuRouter:'/components', menuRouter: '/components',
identification:'menu1:view:btn2', identification: 'menu1:view:btn2',
parentId:10, parentId: 10,
id:21, id: 21,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
}, },
{ {
menuName:'按钮3', menuName: '按钮3',
menuType:'按钮', menuType: '按钮',
menuRouter:'/components', menuRouter: '/components',
identification:'menu1:view:btn2', identification: 'menu1:view:btn2',
parentId:10, parentId: 10,
id:22, id: 22,
level:3, level: 3,
createTime:'2022-09-02', createTime: '2022-09-02',
} },
] ],
} },
] ],
}, },
] ]
export const dictionaryData = [ export const dictionaryData = [
{ {
"id": 1, id: 1,
"name": "性别", name: '性别',
"keyCode":'sex', keyCode: 'sex',
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "性别", remark: '性别',
}, },
{ {
"id": 2, id: 2,
"name": "证件类型", name: '证件类型',
"keyCode":'idType', keyCode: 'idType',
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "证件类型", remark: '证件类型',
} },
] ]
export const dictionaryDetailData = [ export const dictionaryDetailData = [
{ {
"id": 1, id: 1,
"keyCode":'sex', keyCode: 'sex',
"name": "性别", name: '性别',
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "性别", remark: '性别',
"children":[ children: [
{ {
"id": 11, id: 11,
"name": "男", name: '男',
"key":1, key: 1,
"pid":1, pid: 1,
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "男", remark: '男',
}, },
{ {
"id": 12, id: 12,
"name": "女", name: '女',
"key":0, key: 0,
"pid":1, pid: 1,
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "女", remark: '女',
} },
] ],
}, },
{ {
"id": 2, id: 2,
"keyCode":'idType', keyCode: 'idType',
"name": "证件类型", name: '证件类型',
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "证件类型", remark: '证件类型',
"children":[ children: [
{ {
"id": 21, id: 21,
"name": "身份证", name: '身份证',
"key":1, key: 1,
"pid":2, pid: 2,
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "身份证", remark: '身份证',
}, },
{ {
"id": 22, id: 22,
"name": "社保卡", name: '社保卡',
"key":2, key: 2,
"pid":2, pid: 2,
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "社保卡", remark: '社保卡',
}, },
{ {
"id": 23, id: 23,
"name": "驾驶证", name: '驾驶证',
"key":3, key: 3,
"pid":2, pid: 2,
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "驾驶证", remark: '驾驶证',
}, },
{ {
"id": 24, id: 24,
"name": "护照", name: '护照',
"key":4, key: 4,
"pid":2, pid: 2,
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "护照", remark: '护照',
}, },
{ {
"id": 25, id: 25,
"name": "工作证", name: '工作证',
"key":5, key: 5,
"pid":2, pid: 2,
"createTime": "2011-02-25 18:37:39", createTime: '2011-02-25 18:37:39',
"remark": "工作证", remark: '工作证',
} },
] ],
} },
] ]

View File

@ -1,132 +1,132 @@
export const tableList = [ export const tableList = [
{ {
date: '2016-05-02', date: '2016-05-02',
name: '王五', name: '王五',
price: 20, price: 20,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 1, sex: 1,
id: 1, id: 1,
age: 18, age: 18,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄', address: '上海市普陀区金沙江路 1518 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2018-06-11', date: '2018-06-11',
name: '梦琪', name: '梦琪',
price: 20, price: 20,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 1, sex: 1,
id: 2, id: 2,
age: 22, age: 22,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄', address: '上海市普陀区金沙江路 1519 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2022-05-23', date: '2022-05-23',
name: '忆柳', name: '忆柳',
price: 22, price: 22,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 0, sex: 0,
id: 3, id: 3,
age: 23, age: 23,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 1520 弄', address: '上海市普陀区金沙江路 1520 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2022-01-24', date: '2022-01-24',
name: '之桃', name: '之桃',
price: 33, price: 33,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 1, sex: 1,
id: 4, id: 4,
age: 24, age: 24,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 2222 弄', address: '上海市普陀区金沙江路 2222 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2022-07-22', date: '2022-07-22',
name: '慕青', name: '慕青',
price: 45, price: 45,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 0, sex: 0,
id: 5, id: 5,
age: 25, age: 25,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 2223 弄', address: '上海市普陀区金沙江路 2223 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2016-05-02', date: '2016-05-02',
name: '问兰', name: '问兰',
price: 47, price: 47,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 1, sex: 1,
id: 6, id: 6,
age: 26, age: 26,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 2224 弄', address: '上海市普陀区金沙江路 2224 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2016-08-02', date: '2016-08-02',
name: '元香', name: '元香',
price: 45, price: 45,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 0, sex: 0,
id: 7, id: 7,
age: 27, age: 27,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 2225 弄', address: '上海市普陀区金沙江路 2225 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2019-11-11', date: '2019-11-11',
name: '初夏', name: '初夏',
price: 23, price: 23,
province: '上海', province: '上海',
admin: 'admin', admin: 'admin',
sex: 1, sex: 1,
id: 8, id: 8,
age: 28, age: 28,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 2226 弄', address: '上海市普陀区金沙江路 2226 弄',
zip: 200333, zip: 200333,
}, },
{ {
date: '2018-08-02', date: '2018-08-02',
name: '沛菡', name: '沛菡',
price: 33, price: 33,
province: '上海', province: '上海',
admin: 'other', admin: 'other',
sex: 0, sex: 0,
id: 9, id: 9,
age: 29, age: 29,
city: '普陀区', city: '普陀区',
address: '上海市普陀区金沙江路 2227 弄', address: '上海市普陀区金沙江路 2227 弄',
zip: 200339, zip: 200339,
}, },
{ {
date: '2022-11-03', date: '2022-11-03',
name: '傲珊', name: '傲珊',
price: 222, price: 222,
province: '浙江', province: '浙江',
admin: 'admin', admin: 'admin',
sex: 1, sex: 1,
id: 10, id: 10,
age: 30, age: 30,
city: '杭州', city: '杭州',
address: '杭州市滨江区建业路228号', address: '杭州市滨江区建业路228号',
zip: 200433, zip: 200433,
} },
] ]

View File

@ -1,8 +1,8 @@
import router from '@/routers/index' import router from '@/routers/index'
import NProgress from 'nprogress' import NProgress from 'nprogress'
import 'nprogress/nprogress.css' import 'nprogress/nprogress.css'
import {useUserStore} from "@/store/modules/user" import { useUserStore } from '@/store/modules/user'
import {usePermissionStore} from "@/store/modules/permission" import { usePermissionStore } from '@/store/modules/permission'
NProgress.configure({ showSpinner: false }) // NProgress Configuration NProgress.configure({ showSpinner: false }) // NProgress Configuration
@ -10,51 +10,48 @@ const whiteList = ['/login', '/auth-redirect'] // 设置白名单
// 记录路由 // 记录路由
let hasRoles = true let hasRoles = true
router.beforeEach(async(to, from, next) => { router.beforeEach(async (to, from, next) => {
// 开启进度条 // 开启进度条
NProgress.start() NProgress.start()
// 设置标题 // 设置标题
if(typeof(to.meta.title) === 'string'){ if (typeof to.meta.title === 'string') {
document.title = to.meta.title ||'vue-admin-perfect' document.title = to.meta.title || 'vue-admin-perfect'
} }
const UserStore = useUserStore(); const UserStore = useUserStore()
// 确定用户是否已登录过存在Token // 确定用户是否已登录过存在Token
const hasToken = UserStore.token const hasToken = UserStore.token
if (hasToken) { if (hasToken) {
if (to.path === '/login') { if (to.path === '/login') {
// 如果已登录,请重定向到主页 // 如果已登录,请重定向到主页
next({ path: '/' }) next({ path: '/' })
} else { } else {
try { try {
const PermissionStore = usePermissionStore() const PermissionStore = usePermissionStore()
// 路由添加进去了没有及时更新 需要重新进去一次拦截 // 路由添加进去了没有及时更新 需要重新进去一次拦截
if(!PermissionStore.routes.length){ if (!PermissionStore.routes.length) {
// 获取权限列表进行接口访问 因为这里页面要切换权限 // 获取权限列表进行接口访问 因为这里页面要切换权限
const accessRoutes = await PermissionStore.generateRoutes(UserStore.roles) const accessRoutes = await PermissionStore.generateRoutes(UserStore.roles)
hasRoles = false hasRoles = false
accessRoutes.forEach(item => router.addRoute(item)) // 动态添加访问路由表 accessRoutes.forEach((item) => router.addRoute(item)) // 动态添加访问路由表
next({ ...to, replace: true }) // // 这里相当于push到一个页面 不在进入路由拦截 next({ ...to, replace: true }) // // 这里相当于push到一个页面 不在进入路由拦截
}else { } else {
next() // // 如果不传参数就会重新执行路由拦截,重新进到这里 next() // // 如果不传参数就会重新执行路由拦截,重新进到这里
}
} catch (error) {
next(`/login?redirect=${to.path}`)
} }
} } catch (error) {
}else{ next(`/login?redirect=${to.path}`)
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
} }
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
} }
}) })
router.afterEach(() => { router.afterEach(() => {
NProgress.done(); NProgress.done()
}); })

View File

@ -1,16 +1,15 @@
// 注册icon组件 // 注册icon组件
import * as ElIconsModules from '@element-plus/icons-vue' import * as ElIconsModules from '@element-plus/icons-vue'
export const registerElIcons = (app) => {
export const registerElIcons = (app)=>{ // 全局注册element-plus icon图标组件
// 全局注册element-plus icon图标组件 Object.keys(ElIconsModules).forEach((key) => {
Object.keys(ElIconsModules).forEach((key) => {//循环遍历组件名称 //循环遍历组件名称
if ("Menu" !== key) {//如果不是图标组件不是Menu就跳过否则加上ICon的后缀 if ('Menu' !== key) {
app.component(key, ElIconsModules[key]); //如果不是图标组件不是Menu就跳过否则加上ICon的后缀
} else { app.component(key, ElIconsModules[key])
app.component(key + "Icon", ElIconsModules[key]); } else {
} app.component(key + 'Icon', ElIconsModules[key])
}); }
})
} }

View File

@ -1,10 +1,4 @@
import { import { createRouter, createWebHistory, RouteRecordRaw, createWebHashHistory, Router } from 'vue-router'
createRouter,
createWebHistory,
RouteRecordRaw,
createWebHashHistory,
Router,
} from 'vue-router'
import Layout from '@/layout/index.vue' import Layout from '@/layout/index.vue'
// 扩展继承属性 // 扩展继承属性
interface extendRoute { interface extendRoute {

View File

@ -1,24 +1,26 @@
/** When your routing table is too long, you can split it into small modules**/ /** When your routing table is too long, you can split it into small modules**/
import Layout from "@/layout/index.vue"; import Layout from '@/layout/index.vue'
const chartsRouter = [{ const chartsRouter = [
{
path: '/chat', path: '/chat',
component: Layout, component: Layout,
redirect: '/chat/index', redirect: '/chat/index',
name: 'chat', name: 'chat',
meta: { meta: {
title: '聊天框', title: '聊天框',
icon: 'chat-square' icon: 'chat-square',
}, },
children: [ children: [
{ {
path: '/chat/index', path: '/chat/index',
component: () => import('@/views/chat/index.vue'), component: () => import('@/views/chat/index.vue'),
name: 'chatBox', name: 'chatBox',
meta: { title: '聊天框', icon: 'chat-square' } meta: { title: '聊天框', icon: 'chat-square' },
} },
] ],
}] },
]
export default chartsRouter export default chartsRouter

View File

@ -1,12 +1,12 @@
const dataScreenRouter= [{ const dataScreenRouter = [
path: "/dataScreen", {
name: "dataScreen", path: '/dataScreen',
meta: { name: 'dataScreen',
title: "可视化大屏", meta: {
icon:'Histogram' title: '可视化大屏',
}, icon: 'Histogram',
component: () => import("@/views/dataScreen/index.vue") },
}] component: () => import('@/views/dataScreen/index.vue'),
; },
]
export default dataScreenRouter; export default dataScreenRouter

View File

@ -1,52 +1,53 @@
/** When your routing table is too long, you can split it into small modules**/ /** When your routing table is too long, you can split it into small modules**/
import Layout from "@/layout/index.vue"; import Layout from '@/layout/index.vue'
const echartsRouter = [{ const echartsRouter = [
{
path: '/echarts', path: '/echarts',
component: Layout, component: Layout,
redirect: '/echarts/migration', redirect: '/echarts/migration',
name: 'echarts', name: 'echarts',
meta: { meta: {
title: 'Echarts', title: 'Echarts',
icon: 'trend-charts', icon: 'trend-charts',
roles:['other'] roles: ['other'],
}, },
children: [ children: [
{ {
path: '/echarts/migration', path: '/echarts/migration',
component: () => import('@/views/echarts/migrationMap/index.vue'), component: () => import('@/views/echarts/migrationMap/index.vue'),
name: 'migration', name: 'migration',
meta: { title: '迁徙图', roles:['other'] , icon: 'MenuIcon' } meta: { title: '迁徙图', roles: ['other'], icon: 'MenuIcon' },
}, },
{ {
path: '/echarts/bar', path: '/echarts/bar',
component: () => import('@/views/echarts/barEcharts/index.vue'), component: () => import('@/views/echarts/barEcharts/index.vue'),
name: 'bar', name: 'bar',
meta: { title: '柱状图', roles:['other'] , icon: 'MenuIcon'} meta: { title: '柱状图', roles: ['other'], icon: 'MenuIcon' },
}, },
{ {
path: '/echarts/graph', path: '/echarts/graph',
component: () => import('@/views/echarts/graphEcharts/index.vue'), component: () => import('@/views/echarts/graphEcharts/index.vue'),
name: 'graph', name: 'graph',
meta: { title: '雷达图', roles:['other'] , icon: 'MenuIcon'} meta: { title: '雷达图', roles: ['other'], icon: 'MenuIcon' },
}, },
{ {
path: '/echarts/pie', path: '/echarts/pie',
component: () => import('@/views/echarts/pieEcharts/index.vue'), component: () => import('@/views/echarts/pieEcharts/index.vue'),
name: 'pie', name: 'pie',
meta: { title: '饼图', roles:['other'] , icon: 'MenuIcon'} meta: { title: '饼图', roles: ['other'], icon: 'MenuIcon' },
}, },
{ {
path: '/echarts/simple', path: '/echarts/simple',
component: () => import('@/views/echarts/simple/index.vue'), component: () => import('@/views/echarts/simple/index.vue'),
name: 'echarts-simple', name: 'echarts-simple',
meta: { title: '简单图表', roles:['other'] , icon: 'MenuIcon'} meta: { title: '简单图表', roles: ['other'], icon: 'MenuIcon' },
}, },
],
] },
}] ]
export default echartsRouter export default echartsRouter

View File

@ -1,48 +1,48 @@
import Layout from '@/layout/index.vue'
const excelRouter = [
import Layout from "@/layout/index.vue"; {
const excelRouter = [{
path: '/excel', path: '/excel',
component: Layout, component: Layout,
redirect: '/excel/export-excel', redirect: '/excel/export-excel',
name: 'excel', name: 'excel',
meta: { meta: {
title: 'Excel&Zip', title: 'Excel&Zip',
icon: 'School' icon: 'School',
}, },
children: [ children: [
{ {
path: '/excel/export-excel', path: '/excel/export-excel',
component: () => import('@/views/excel/exportExcel/index.vue'), component: () => import('@/views/excel/exportExcel/index.vue'),
name: 'export-excel', name: 'export-excel',
meta: { title: '导出 Excel', icon: 'MenuIcon'} meta: { title: '导出 Excel', icon: 'MenuIcon' },
}, },
{ {
path: '/excel/export-merge-header', path: '/excel/export-merge-header',
component: () => import('@/views/excel/exportMergeHeader/index.vue'), component: () => import('@/views/excel/exportMergeHeader/index.vue'),
name: 'export-merge-header', name: 'export-merge-header',
meta: { title: '导出 多级表头', icon: 'MenuIcon' } meta: { title: '导出 多级表头', icon: 'MenuIcon' },
}, },
{ {
path: '/excel/upload-style-excel', path: '/excel/upload-style-excel',
component: () => import('@/views/excel/exportStyleExcel/index.vue'), component: () => import('@/views/excel/exportStyleExcel/index.vue'),
name: 'upload-style-excel', name: 'upload-style-excel',
meta: { title: '自定义样式导出 Excel', icon: 'MenuIcon' } meta: { title: '自定义样式导出 Excel', icon: 'MenuIcon' },
}, },
{ {
path: '/excel/upload-excel', path: '/excel/upload-excel',
component: () => import('@/views/excel/uploadExcel/index.vue'), component: () => import('@/views/excel/uploadExcel/index.vue'),
name: 'upload-excel', name: 'upload-excel',
meta: { title: '上传 Excel', icon: 'MenuIcon' } meta: { title: '上传 Excel', icon: 'MenuIcon' },
}, },
{ {
path: '/excel/zip', path: '/excel/zip',
component: () => import('@/views/excel/zip/index.vue'), component: () => import('@/views/excel/zip/index.vue'),
name: 'Zip', name: 'Zip',
meta: { title: '导出 Zip', roles:['other'] ,icon: 'MenuIcon',} meta: { title: '导出 Zip', roles: ['other'], icon: 'MenuIcon' },
}, },
] ],
}] },
]
export default excelRouter export default excelRouter

View File

@ -1,46 +1,46 @@
import Layout from '@/layout/index.vue'
const externalLink = [
import Layout from "@/layout/index.vue"; {
const externalLink = [{
path: '/external-link', path: '/external-link',
component: Layout, component: Layout,
redirect: '/external-link/wechat', redirect: '/external-link/wechat',
name: 'external-link', name: 'external-link',
meta: { meta: {
title: '外部链接', title: '外部链接',
icon: 'link' icon: 'link',
}, },
children: [ children: [
// { // {
// path: '/external-link/wechat', // path: '/external-link/wechat',
// name: 'wechat', // name: 'wechat',
// component: () => import('@/views/externalLinks/wechat/index.vue'), // component: () => import('@/views/externalLinks/wechat/index.vue'),
// meta: { title: '加微信群', icon: 'MenuIcon' } // meta: { title: '加微信群', icon: 'MenuIcon' }
// }, // },
{ {
path: 'https://github.com/zouzhibin/vue-admin-perfect', path: 'https://github.com/zouzhibin/vue-admin-perfect',
name: 'github', name: 'github',
meta: { title: 'Github 地址', icon: 'MenuIcon' } meta: { title: 'Github 地址', icon: 'MenuIcon' },
}, },
{ {
path: 'https://gitee.com/yuanzbz/vue-admin-perfect?_from=gitee_search', path: 'https://gitee.com/yuanzbz/vue-admin-perfect?_from=gitee_search',
name: 'github', name: 'github',
meta: { title: 'Gitee 地址', icon: 'MenuIcon' } meta: { title: 'Gitee 地址', icon: 'MenuIcon' },
}, },
{ {
path: 'https://yuanzbz.gitee.io/vue-admin-simple', path: 'https://yuanzbz.gitee.io/vue-admin-simple',
name: 'simple', name: 'simple',
meta: { title: '简易版本', icon: 'MenuIcon' }, meta: { title: '简易版本', icon: 'MenuIcon' },
component: () => import('@/views/externalLinks/simple/index.vue'), component: () => import('@/views/externalLinks/simple/index.vue'),
}, },
{ {
path: '/external-link/iframe', path: '/external-link/iframe',
component: () => import('@/views/externalLinks/iframe/index.vue'), component: () => import('@/views/externalLinks/iframe/index.vue'),
name: 'iframe', name: 'iframe',
meta: { title: '内嵌 iframe', icon: 'MenuIcon' } meta: { title: '内嵌 iframe', icon: 'MenuIcon' },
}, },
] ],
}] },
]
export default externalLink export default externalLink

View File

@ -1,49 +1,50 @@
/** When your routing table is too long, you can split it into small modules**/ /** When your routing table is too long, you can split it into small modules**/
import Layout from "@/layout/index.vue"; import Layout from '@/layout/index.vue'
const functionPageRouter = [{ const functionPageRouter = [
{
path: '/function-page', path: '/function-page',
component: Layout, component: Layout,
redirect: '/function-page/404', redirect: '/function-page/404',
name: 'function-page', name: 'function-page',
meta: { meta: {
title: '功能页面', title: '功能页面',
icon: 'ElementPlus' icon: 'ElementPlus',
}, },
children: [ children: [
{ {
path: '/function-page/tools', path: '/function-page/tools',
component: () => import('@/views/functionPage/tools/index.vue'), component: () => import('@/views/functionPage/tools/index.vue'),
name: 'tools', name: 'tools',
meta: { title: '工具链集合', keepAlive: true , icon: 'MenuIcon'} meta: { title: '工具链集合', keepAlive: true, icon: 'MenuIcon' },
}, },
{ {
path: '/function-page/404', path: '/function-page/404',
component: () => import('@/views/errorPages/404.vue'), component: () => import('@/views/errorPages/404.vue'),
name: 'function-404', name: 'function-404',
meta: { title: '404 页面', keepAlive: true , icon: 'MenuIcon'} meta: { title: '404 页面', keepAlive: true, icon: 'MenuIcon' },
}, },
{ {
path: '/function-page/403', path: '/function-page/403',
component: () => import('@/views/errorPages/403.vue'), component: () => import('@/views/errorPages/403.vue'),
name: 'function-403', name: 'function-403',
meta: { title: '403 页面', keepAlive: true , icon: 'MenuIcon'} meta: { title: '403 页面', keepAlive: true, icon: 'MenuIcon' },
}, },
{ {
path: '/function-page/fullscreen', path: '/function-page/fullscreen',
component: () => import('@/views/functionPage/fullscreen/index.vue'), component: () => import('@/views/functionPage/fullscreen/index.vue'),
name: 'fullscreen', name: 'fullscreen',
meta: { title: '元素 全屏', keepAlive: true , icon: 'MenuIcon'} meta: { title: '元素 全屏', keepAlive: true, icon: 'MenuIcon' },
}, },
{ {
path: '/function-page/information-list', path: '/function-page/information-list',
component: () => import('@/views/functionPage/informationList/index.vue'), component: () => import('@/views/functionPage/informationList/index.vue'),
name: 'informationList', name: 'informationList',
meta: { title: '信息列表', keepAlive: true , icon: 'MenuIcon'} meta: { title: '信息列表', keepAlive: true, icon: 'MenuIcon' },
}, },
] ],
}] },
]
export default functionPageRouter export default functionPageRouter

View File

@ -1,17 +1,16 @@
/** When your routing table is too long, you can split it into small modules**/ /** When your routing table is too long, you can split it into small modules**/
import Layout from "@/layout/index.vue"; import Layout from '@/layout/index.vue'
const nestedRouter = [{ const nestedRouter = [
{
path: '/nested', path: '/nested',
component: Layout, component: Layout,
redirect: '/nested/menu1', redirect: '/nested/menu1',
name: 'nested', name: 'nested',
meta: { meta: {
title: '路由嵌套', title: '路由嵌套',
icon: 'HelpFilled' icon: 'HelpFilled',
}, },
children: [ children: [
{ {
@ -19,52 +18,52 @@ const nestedRouter = [{
component: () => import('@/views/nested/menu1/index.vue'), component: () => import('@/views/nested/menu1/index.vue'),
name: 'menu1', name: 'menu1',
meta: { title: '菜单1', icon: 'MenuIcon' }, meta: { title: '菜单1', icon: 'MenuIcon' },
alwaysShow:true, alwaysShow: true,
redirect: '/nested/menu1/menu1-1', redirect: '/nested/menu1/menu1-1',
children: [ children: [
{ {
path: '/nested/menu1/menu1-1', path: '/nested/menu1/menu1-1',
component: () => import('@/views/nested/menu1/menu1-1/index.vue'), component: () => import('@/views/nested/menu1/menu1-1/index.vue'),
name: 'menu1-1', name: 'menu1-1',
meta: { title: '菜单 1-1' , icon: 'MenuIcon'} meta: { title: '菜单 1-1', icon: 'MenuIcon' },
}, },
{ {
path: '/nested/menu1/menu1-2', path: '/nested/menu1/menu1-2',
component: () => import('@/views/nested/menu1/menu1-2/index.vue'), component: () => import('@/views/nested/menu1/menu1-2/index.vue'),
name: 'menu1-2', name: 'menu1-2',
redirect: '/nested/menu1/menu1-2/menu1-2-1', redirect: '/nested/menu1/menu1-2/menu1-2-1',
meta: { title: '菜单 1-2' , icon: 'MenuIcon'}, meta: { title: '菜单 1-2', icon: 'MenuIcon' },
children: [ children: [
{ {
path: '/nested/menu1/menu1-2/menu1-2-1', path: '/nested/menu1/menu1-2/menu1-2-1',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1/index.vue'), component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1/index.vue'),
name: 'menu1-2-1', name: 'menu1-2-1',
meta: { title: '菜单 1-2-1' , icon: 'MenuIcon'} meta: { title: '菜单 1-2-1', icon: 'MenuIcon' },
}, },
{ {
path: '/nested/menu1/menu1-2/menu1-2-2', path: '/nested/menu1/menu1-2/menu1-2-2',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2/index.vue'), component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2/index.vue'),
name: 'menu1-2-2', name: 'menu1-2-2',
meta: { title: '菜单 1-2-2' , icon: 'MenuIcon'} meta: { title: '菜单 1-2-2', icon: 'MenuIcon' },
} },
] ],
}, },
{ {
path: '/nested/menu1/menu1-3', path: '/nested/menu1/menu1-3',
component: () => import('@/views/nested/menu1/menu1-3/index.vue'), component: () => import('@/views/nested/menu1/menu1-3/index.vue'),
name: 'menu1-3', name: 'menu1-3',
meta: { title: '菜单 1-3' , icon: 'MenuIcon'} meta: { title: '菜单 1-3', icon: 'MenuIcon' },
} },
] ],
}, },
{ {
path: '/nested/menu2', path: '/nested/menu2',
component: () => import('@/views/nested/menu2/index.vue'), component: () => import('@/views/nested/menu2/index.vue'),
name: 'nested-menu2', name: 'nested-menu2',
meta: { title: '菜单2', icon: 'MenuIcon'} meta: { title: '菜单2', icon: 'MenuIcon' },
}, },
],
] },
}] ]
export default nestedRouter export default nestedRouter

View File

@ -2,47 +2,49 @@
import Layout from '@/layout/index.vue' import Layout from '@/layout/index.vue'
const systemRouter = [{ const systemRouter = [
{
path: '/system', path: '/system',
component: Layout, component: Layout,
redirect: '/system/user', redirect: '/system/user',
name: 'system', name: 'system',
meta: { meta: {
title: '系统管理', title: '系统管理',
icon: 'Setting', icon: 'Setting',
}, },
children: [ children: [
{ {
path: '/system/user', path: '/system/user',
component: () => import('@/views/system/user/index.vue'), component: () => import('@/views/system/user/index.vue'),
name: 'user', name: 'user',
meta: { title: '用户管理' , icon: 'MenuIcon'} meta: { title: '用户管理', icon: 'MenuIcon' },
}, },
{ {
path: '/system/dept', path: '/system/dept',
component: () => import('@/views/system/dept/index.vue'), component: () => import('@/views/system/dept/index.vue'),
name: 'dept', name: 'dept',
meta: { title: '部门管理' , icon: 'MenuIcon'} meta: { title: '部门管理', icon: 'MenuIcon' },
}, },
{ {
path: '/system/role', path: '/system/role',
component: () => import('@/views/system/role/index.vue'), component: () => import('@/views/system/role/index.vue'),
name: 'role', name: 'role',
meta: { title: '角色管理', icon: 'MenuIcon' } meta: { title: '角色管理', icon: 'MenuIcon' },
}, },
{ {
path: '/system/menu', path: '/system/menu',
component: () => import('@/views/system/menu/index.vue'), component: () => import('@/views/system/menu/index.vue'),
name: 'menu', name: 'menu',
meta: { title: '菜单管理', icon: 'MenuIcon'} meta: { title: '菜单管理', icon: 'MenuIcon' },
}, },
{ {
path: '/system/dictionary', path: '/system/dictionary',
component: () => import('@/views/system/dictionary/index.vue'), component: () => import('@/views/system/dictionary/index.vue'),
name: 'dictionary', name: 'dictionary',
meta: { title: '字典管理', icon: 'MenuIcon'} meta: { title: '字典管理', icon: 'MenuIcon' },
}, },
] ],
}] },
]
export default systemRouter export default systemRouter

View File

@ -1,26 +1,23 @@
import {defineStore,createPinia} from 'pinia' import { defineStore, createPinia } from 'pinia'
// 引入持久化插件 // 引入持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
export const Store = defineStore({ export const Store = defineStore({
// id: 必须的,在所有 Store 中唯一 // id: 必须的,在所有 Store 中唯一
id:'globalState', id: 'globalState',
// state: 返回对象的函数 // state: 返回对象的函数
state: ()=>({ state: () => ({}),
getters: {},
}), actions: {},
getters: {}, persist: {
actions:{}, // 本地存储的名称
persist: { key: 'globalState',
// 本地存储的名称 //保存的位置
key: "globalState", storage: window.sessionStorage, //localstorage
//保存的位置 },
storage: window.sessionStorage,//localstorage
},
}) })
const pinia = createPinia()
const pinia = createPinia();
//pinia使用 //pinia使用
pinia.use(piniaPluginPersistedstate); pinia.use(piniaPluginPersistedstate)
export default pinia export default pinia

View File

@ -1,58 +1,54 @@
import {defineStore} from 'pinia' import { defineStore } from 'pinia'
import { asyncRoutes, constantRoutes,routerArray,notFoundRouter } from '@/routers/index' import { asyncRoutes, constantRoutes, routerArray, notFoundRouter } from '@/routers/index'
import {hasPermission,filterAsyncRoutes} from "@/utils/routers" import { hasPermission, filterAsyncRoutes } from '@/utils/routers'
import {filterKeepAlive,filterRoutes} from "@/utils/routers"; import { filterKeepAlive, filterRoutes } from '@/utils/routers'
export const usePermissionStore = defineStore({ export const usePermissionStore = defineStore({
// id: 必须的,在所有 Store 中唯一 // id: 必须的,在所有 Store 中唯一
id:'permissionState', id: 'permissionState',
// state: 返回对象的函数 // state: 返回对象的函数
state: ()=>({ state: () => ({
// 路由 // 路由
routes:[], routes: [],
// 动态路由 // 动态路由
addRoutes:[], addRoutes: [],
// 缓存路由 // 缓存路由
cacheRoutes:{}, cacheRoutes: {},
}), }),
getters: { getters: {
permission_routes:state=> { permission_routes: (state) => {
return state.routes return state.routes
},
keepAliveRoutes: state=>{
return filterKeepAlive(asyncRoutes)
}
}, },
// 可以同步 也可以异步 keepAliveRoutes: (state) => {
actions:{ return filterKeepAlive(asyncRoutes)
// 生成路由
generateRoutes(roles){
return new Promise(resolve => {
// 在这判断是否有权限,哪些角色拥有哪些权限
let accessedRoutes
if (roles&&roles.length&&!roles.includes('admin')) {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
} else {
accessedRoutes = asyncRoutes || []
}
accessedRoutes = accessedRoutes.concat(notFoundRouter)
this.routes = constantRoutes.concat(accessedRoutes)
this.addRoutes = accessedRoutes
resolve(accessedRoutes)
})
},
// 清楚路由
clearRoutes(){
this.routes = []
this.addRoutes = []
this.cacheRoutes = []
},
getCacheRoutes(){
this.cacheRoutes = filterKeepAlive(asyncRoutes)
return this.cacheRoutes
}
}, },
},
// 可以同步 也可以异步
actions: {
// 生成路由
generateRoutes(roles) {
return new Promise((resolve) => {
// 在这判断是否有权限,哪些角色拥有哪些权限
let accessedRoutes
if (roles && roles.length && !roles.includes('admin')) {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
} else {
accessedRoutes = asyncRoutes || []
}
accessedRoutes = accessedRoutes.concat(notFoundRouter)
this.routes = constantRoutes.concat(accessedRoutes)
this.addRoutes = accessedRoutes
resolve(accessedRoutes)
})
},
// 清楚路由
clearRoutes() {
this.routes = []
this.addRoutes = []
this.cacheRoutes = []
},
getCacheRoutes() {
this.cacheRoutes = filterKeepAlive(asyncRoutes)
return this.cacheRoutes
},
},
}) })

View File

@ -1,81 +1,79 @@
import {defineStore} from 'pinia' import { defineStore } from 'pinia'
import {PRIMARY_COLOR} from "../../config"; import { PRIMARY_COLOR } from '../../config'
export const useSettingStore = defineStore({ export const useSettingStore = defineStore({
// id: 必须的,在所有 Store 中唯一 // id: 必须的,在所有 Store 中唯一
id:'settingState', id: 'settingState',
// state: 返回对象的函数 // state: 返回对象的函数
state: ()=>({ state: () => ({
// menu 是否收缩 // menu 是否收缩
isCollapse:true, isCollapse: true,
// //
withoutAnimation:false, withoutAnimation: false,
device: 'desktop', device: 'desktop',
// 刷新当前页 // 刷新当前页
isReload:true, isReload: true,
// 主题设置 // 主题设置
themeConfig:{ themeConfig: {
// 显示设置 // 显示设置
showSetting:false, showSetting: false,
// 菜单展示模式 默认 vertical horizontal / vertical /columns // 菜单展示模式 默认 vertical horizontal / vertical /columns
mode: 'vertical', mode: 'vertical',
// tagsView 是否展示 默认展示 // tagsView 是否展示 默认展示
showTag:true, showTag: true,
// 页脚 // 页脚
footer: true, footer: true,
// 深色模式 切换暗黑模式 // 深色模式 切换暗黑模式
isDark: false, isDark: false,
// 显示侧边栏Logo // 显示侧边栏Logo
showLogo:true, showLogo: true,
// 主题颜色 // 主题颜色
primary:PRIMARY_COLOR, primary: PRIMARY_COLOR,
// element组件大小 // element组件大小
globalComSize:'default', globalComSize: 'default',
// 是否只保持一个子菜单的展开 // 是否只保持一个子菜单的展开
uniqueOpened:true, uniqueOpened: true,
// 固定header // 固定header
fixedHeader:true, fixedHeader: true,
// 灰色模式 // 灰色模式
gray:false, gray: false,
// 色弱模式 // 色弱模式
weak:false weak: false,
},
}),
getters: {},
// 可以同步 也可以异步
actions:{
// 设置主题
setThemeConfig({key, val}){
this.themeConfig[key] = val
},
// 切换 Collapse
setCollapse(value){
this.isCollapse = value
this.withoutAnimation = false
},
// 关闭侧边栏
closeSideBar({withoutAnimation}){
this.isCollapse = false
this.withoutAnimation = withoutAnimation
},
toggleDevice(device){
this.device = device
},
// 刷新
setReload(){
this.isReload = false
setTimeout(()=>{
this.isReload = true
},50)
}
}, },
// 这部分数据不需要存储 }),
// persist: { getters: {},
// // 本地存储的名称 // 可以同步 也可以异步
// key: "settingState", actions: {
// //保存的位置 // 设置主题
// storage: window.localStorage,//localstorage setThemeConfig({ key, val }) {
// }, this.themeConfig[key] = val
},
// 切换 Collapse
setCollapse(value) {
this.isCollapse = value
this.withoutAnimation = false
},
// 关闭侧边栏
closeSideBar({ withoutAnimation }) {
this.isCollapse = false
this.withoutAnimation = withoutAnimation
},
toggleDevice(device) {
this.device = device
},
// 刷新
setReload() {
this.isReload = false
setTimeout(() => {
this.isReload = true
}, 50)
},
},
// 这部分数据不需要存储
// persist: {
// // 本地存储的名称
// key: "settingState",
// //保存的位置
// storage: window.localStorage,//localstorage
// },
}) })

View File

@ -1,113 +1,107 @@
import {defineStore} from 'pinia' import { defineStore } from 'pinia'
import router from "@/routers/index"; import router from '@/routers/index'
export const useTagsViewStore = defineStore({ export const useTagsViewStore = defineStore({
// id: 必须的,在所有 Store 中唯一 // id: 必须的,在所有 Store 中唯一
id:'tagsViewState', id: 'tagsViewState',
// state: 返回对象的函数 // state: 返回对象的函数
state: ()=>({ state: () => ({
activeTabsValue:'/home', activeTabsValue: '/home',
visitedViews:[], visitedViews: [],
cachedViews:[], cachedViews: [],
}),
}), getters: {},
getters: {}, // 可以同步 也可以异步
// 可以同步 也可以异步 actions: {
actions:{ setTabsMenuValue(val) {
setTabsMenuValue(val){ this.activeTabsValue = val
this.activeTabsValue = val
},
addView(view){
this.addVisitedView(view)
},
removeView(routes){
return new Promise((resolve, reject) => {
this.visitedViews = this.visitedViews.filter(item=>!routes.includes(item.path))
resolve(null)
})
},
addVisitedView(view){
this.setTabsMenuValue(view.path);
if (this.visitedViews.some(v => v.path === view.path)) return
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
if (view.meta.keepAlive) {
this.cachedViews.push(view.name)
}
},
delView(activeTabPath){
return new Promise(resolve => {
this.delVisitedView(activeTabPath)
this.delCachedView(activeTabPath)
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
})
})
},
toLastView(activeTabPath){
let index = this.visitedViews.findIndex(item=>item.path===activeTabPath)
const nextTab = this.visitedViews[index + 1] || this.visitedViews[index - 1];
if (!nextTab) return;
router.push(nextTab.path);
this.addVisitedView(nextTab)
},
delVisitedView(path){
return new Promise(resolve => {
this.visitedViews = this.visitedViews.filter(v=>{
return (v.path !== path||v.meta.affix)
})
this.cachedViews = this.cachedViews.filter(v=>{
return (v.path !== path||v.meta.affix)
})
resolve([...this.visitedViews])
})
},
delCachedView(view){
return new Promise(resolve => {
const index = this.cachedViews.indexOf(view.name)
index > -1 && this.cachedViews.splice(index, 1)
resolve([...this.cachedViews])
})
},
clearVisitedView(){
this.delAllViews()
},
delAllViews(){
return new Promise((resolve) => {
this.visitedViews = this.visitedViews.filter(v=>v.meta.affix)
this.cachedViews = this.visitedViews.filter(v=>v.meta.affix)
resolve([...this.visitedViews])
})
},
delOtherViews(path){
this.visitedViews = this.visitedViews.filter(item => {
return item.path === path || item.meta.affix;
});
this.cachedViews = this.visitedViews.filter(item => {
return item.path === path || item.meta.affix;
});
},
goHome() {
this.activeTabsValue = '/home';
router.push({path: '/home'});
},
updateVisitedView(view){
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
}, },
addView(view) {
this.addVisitedView(view)
},
removeView(routes) {
return new Promise((resolve, reject) => {
this.visitedViews = this.visitedViews.filter((item) => !routes.includes(item.path))
resolve(null)
})
},
addVisitedView(view) {
this.setTabsMenuValue(view.path)
if (this.visitedViews.some((v) => v.path === view.path)) return
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name',
}),
)
if (view.meta.keepAlive) {
this.cachedViews.push(view.name)
}
},
delView(activeTabPath) {
return new Promise((resolve) => {
this.delVisitedView(activeTabPath)
this.delCachedView(activeTabPath)
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews],
})
})
},
toLastView(activeTabPath) {
const index = this.visitedViews.findIndex((item) => item.path === activeTabPath)
const nextTab = this.visitedViews[index + 1] || this.visitedViews[index - 1]
if (!nextTab) return
router.push(nextTab.path)
this.addVisitedView(nextTab)
},
delVisitedView(path) {
return new Promise((resolve) => {
this.visitedViews = this.visitedViews.filter((v) => {
return v.path !== path || v.meta.affix
})
this.cachedViews = this.cachedViews.filter((v) => {
return v.path !== path || v.meta.affix
})
resolve([...this.visitedViews])
})
},
delCachedView(view) {
return new Promise((resolve) => {
const index = this.cachedViews.indexOf(view.name)
index > -1 && this.cachedViews.splice(index, 1)
resolve([...this.cachedViews])
})
},
clearVisitedView() {
this.delAllViews()
},
delAllViews() {
return new Promise((resolve) => {
this.visitedViews = this.visitedViews.filter((v) => v.meta.affix)
this.cachedViews = this.visitedViews.filter((v) => v.meta.affix)
resolve([...this.visitedViews])
})
},
delOtherViews(path) {
this.visitedViews = this.visitedViews.filter((item) => {
return item.path === path || item.meta.affix
})
this.cachedViews = this.visitedViews.filter((item) => {
return item.path === path || item.meta.affix
})
},
goHome() {
this.activeTabsValue = '/home'
router.push({ path: '/home' })
},
updateVisitedView(view) {
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
},
},
}) })

View File

@ -1,65 +1,61 @@
import {defineStore} from 'pinia' import { defineStore } from 'pinia'
export const useUserStore = defineStore({ export const useUserStore = defineStore({
// id: 必须的,在所有 Store 中唯一 // id: 必须的,在所有 Store 中唯一
id:'userState', id: 'userState',
// state: 返回对象的函数 // state: 返回对象的函数
state: ()=>({ state: () => ({
// 登录token // 登录token
token:null, token: null,
// 登录用户信息 // 登录用户信息
userInfo:{}, userInfo: {},
// 角色 // 角色
roles:localStorage.roles?JSON.parse(localStorage.roles):[] roles: localStorage.roles ? JSON.parse(localStorage.roles) : [],
}),
}), getters: {},
getters: {}, // 可以同步 也可以异步
// 可以同步 也可以异步 actions: {
actions:{ // 登录
// 登录 login(userInfo) {
login(userInfo){ const { username, password } = userInfo
const { username, password } = userInfo return new Promise(async (resolve, reject) => {
return new Promise(async (resolve, reject) => { this.token = username
this.token = username this.userInfo = userInfo
this.userInfo = userInfo await this.getRoles()
await this.getRoles() resolve(username)
resolve(username) })
})
},
// 获取用户授权角色信息,实际应用中 可以通过token通过请求接口在这里获取用户信息
getRoles(){
return new Promise((resolve, reject) =>{
// 获取权限列表 默认就是超级管理员,因为没有进行接口请求 写死
this.roles = ['admin']
localStorage.roles = JSON.stringify(this.roles)
resolve(this.roles)
} )
},
// 获取用户信息 ,如实际应用中 可以通过token通过请求接口在这里获取用户信息
getInfo(roles) {
return new Promise((resolve, reject) =>{
this.roles = roles
resolve(roles)
} )
},
// 退出
logout() {
return new Promise((resolve, reject) => {
this.token = null
this.userInfo = {}
this.roles = []
resolve(null)
})
},
}, },
// 进行持久化存储 // 获取用户授权角色信息,实际应用中 可以通过token通过请求接口在这里获取用户信息
persist: { getRoles() {
// 本地存储的名称 return new Promise((resolve, reject) => {
key: "userState", // 获取权限列表 默认就是超级管理员,因为没有进行接口请求 写死
//保存的位置 this.roles = ['admin']
storage: window.localStorage,//localstorage localStorage.roles = JSON.stringify(this.roles)
resolve(this.roles)
})
}, },
// 获取用户信息 ,如实际应用中 可以通过token通过请求接口在这里获取用户信息
getInfo(roles) {
return new Promise((resolve, reject) => {
this.roles = roles
resolve(roles)
})
},
// 退出
logout() {
return new Promise((resolve, reject) => {
this.token = null
this.userInfo = {}
this.roles = []
resolve(null)
})
},
},
// 进行持久化存储
persist: {
// 本地存储的名称
key: 'userState',
//保存的位置
storage: window.localStorage, //localstorage
},
}) })

View File

@ -8,11 +8,10 @@ body {
padding: 0; padding: 0;
font-size: 14px; font-size: 14px;
} }
body{ body {
background: #f0f2f5; background: #f0f2f5;
} }
/* 常用 flex */ /* 常用 flex */
.flex-center { .flex-center {
display: flex; display: flex;
@ -30,20 +29,18 @@ body{
} }
/* 移动端的时候由于屏幕变小隐藏头部导航栏 */ /* 移动端的时候由于屏幕变小隐藏头部导航栏 */
@media screen and ( max-width: 540px ) { @media screen and (max-width: 540px) {
.app-breadcrumb{ .app-breadcrumb {
display: none!important; display: none !important;
} }
} }
/** 设置滚动条 **/ /** 设置滚动条 **/
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 7px; width: 7px;
height: 8px; height: 8px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background-color: rgb(0 0 0 / 5%); background-color: rgb(0 0 0 / 5%);
} }
@ -56,7 +53,6 @@ body{
box-shadow: inset 0 0 6px rgb(0 0 0 / 20%); box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);
} }
/* nprogress样式 */ /* nprogress样式 */
#nprogress .bar { #nprogress .bar {
background: $primaryColor !important; background: $primaryColor !important;
@ -77,7 +73,7 @@ body{
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.app-container-inner{ .app-container-inner {
height: 100%; height: 100%;
width: 100%; width: 100%;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
@ -86,12 +82,12 @@ body{
box-sizing: border-box; box-sizing: border-box;
} }
.layout-horizontal{ .layout-horizontal {
.header-icon{ .header-icon {
color:#bfcbd9!important; color: #bfcbd9 !important;
} }
.el-dropdown-link{ .el-dropdown-link {
color:#bfcbd9!important; color: #bfcbd9 !important;
} }
} }
@ -100,9 +96,9 @@ body{
} }
.el-dropdown:focus { .el-dropdown:focus {
border: none; border: none;
outline: none!important; outline: none !important;
} }
.svg-icon:focus { .svg-icon:focus {
border: none!important; border: none !important;
outline: none!important; outline: none !important;
} }

View File

@ -7,24 +7,24 @@ html.dark {
--zb-border-light: 1px solid #4c4d4f; --zb-border-light: 1px solid #4c4d4f;
body {
body{
background: none; background: none;
} }
// 编辑器 // 编辑器
.w-e-toolbar, .w-e-text-container, .w-e-menu-panel{ .w-e-toolbar,
background: none!important; .w-e-text-container,
.w-e-menu-panel {
background: none !important;
} }
// 富文本 // 富文本
.md{ .md {
background: none!important; background: none !important;
} }
#app{ #app {
.sidebar-container{ .sidebar-container {
background: var(--el-bg-color)!important; background: var(--el-bg-color) !important;
& .el-menu .el-sub-menu > .el-sub-menu__title, & .el-menu .el-sub-menu > .el-sub-menu__title,
& .el-sub-menu .el-menu-item { & .el-sub-menu .el-menu-item {
background-color: var(--el-bg-color) !important; background-color: var(--el-bg-color) !important;
@ -32,8 +32,8 @@ html.dark {
&:hover { &:hover {
background-color: var(--el-bg-color) !important; background-color: var(--el-bg-color) !important;
} }
&.is-active{ &.is-active {
background-color: #060708!important; background-color: #060708 !important;
} }
} }
.el-sub-menu__title { .el-sub-menu__title {
@ -44,114 +44,106 @@ html.dark {
} }
} }
.sidebar-logo-container {
.sidebar-logo-container{
background: none; background: none;
box-sizing: border-box; box-sizing: border-box;
border:var(--zb-border-light); border: var(--zb-border-light);
} }
.el-drawer__header { .el-drawer__header {
span { span {
color: var(--el-text-color-primary) !important; color: var(--el-text-color-primary) !important;
} }
} }
.theme-item{ .theme-item {
color: var(--el-text-color-primary) !important; color: var(--el-text-color-primary) !important;
} }
.el-table__header th { .el-table__header th {
font-weight: bold; font-weight: bold;
color: white; color: white;
} }
.footer-layout{ .footer-layout {
background: none; background: none;
color: white; color: white;
} }
.zb-pro-table {
.zb-pro-table{ .header {
.header{ background: none !important;
background: none!important;
border: var(--zb-border-light); border: var(--zb-border-light);
} }
.footer{ .footer {
background: none!important; background: none !important;
border: var(--zb-border-light); border: var(--zb-border-light);
} }
.el-table__header th{ .el-table__header th {
color: white!important; color: white !important;
} }
} }
// header // header
.m-layout-header{ .m-layout-header {
color: var(--el-text-color-primary) !important; color: var(--el-text-color-primary) !important;
.header-inner{ .header-inner {
background-color: var(--el-bg-color)!important; background-color: var(--el-bg-color) !important;
border-bottom:var(--zb-border-light); border-bottom: var(--zb-border-light);
.header-icon{ .header-icon {
color:#bfcbd9!important; color: #bfcbd9 !important;
} }
} }
// tagviews // tagviews
.m-tags-view{ .m-tags-view {
background: var(--el-bg-color)!important; background: var(--el-bg-color) !important;
border: var(--zb-border-light); border: var(--zb-border-light);
border-top:none ; border-top: none;
border-left: none; border-left: none;
box-sizing: border-box; box-sizing: border-box;
.el-tabs--card>.el-tabs__header{ .el-tabs--card > .el-tabs__header {
border-bottom: none!important; border-bottom: none !important;
} }
} }
} }
// 内容区 // 内容区
.app-main{ .app-main {
.echarts-map{ .echarts-map {
background: var(--el-bg-color)!important; background: var(--el-bg-color) !important;
} }
.app-echarts{ .app-echarts {
background: none; background: none;
} }
.app-container{ .app-container {
.header{ .header {
background: none; background: none;
} }
.footer{ .footer {
background: none; background: none;
} }
} }
.m-container-layout{ .m-container-layout {
.m-container-layout-inner{ .m-container-layout-inner {
background: none; background: none;
color: var(--el-text-color-primary) !important; color: var(--el-text-color-primary) !important;
} }
} }
.item-group-item{ .item-group-item {
background: none!important; background: none !important;
border: var(--zb-border-light); border: var(--zb-border-light);
color: #cccccc; color: #cccccc;
} }
.app-container-inner{ .app-container-inner {
background: none; background: none;
} }
} }
// 底部 // 底部
.footer-layout{ .footer-layout {
border-top:var(--zb-border-light); border-top: var(--zb-border-light);
} }
// 登录 // 登录
.login-container { .login-container {
background-color: #191919 !important; background-color: #191919 !important;
@ -164,13 +156,13 @@ html.dark {
} }
} }
} }
.info-qrcode{ .info-qrcode {
background: white; background: white;
} }
} }
.el-scrollbar{ .el-scrollbar {
border:var(--zb-border-light); border: var(--zb-border-light);
border-top:none ; border-top: none;
} }
} }

View File

@ -5,5 +5,5 @@
} }
.el-table .el-table__header th { .el-table .el-table__header th {
background: var(--el-fill-color-light)!important; background: var(--el-fill-color-light) !important;
} }

View File

@ -1,7 +1,5 @@
@import './variables.scss'; @import './variables.scss';
@import './sidebar.scss'; @import './sidebar.scss';
@import './transition.scss'; @import './transition.scss';
@import "./common.scss"; @import './common.scss';
@import "./element.scss"; @import './element.scss';

View File

@ -1,4 +1,4 @@
.layout-vertical{ .layout-vertical {
.main-container { .main-container {
min-height: 100%; min-height: 100%;
transition: margin-left 0.28s; transition: margin-left 0.28s;
@ -57,7 +57,6 @@
overflow: hidden; overflow: hidden;
} }
.sub-el-icon { .sub-el-icon {
margin-right: 12px; margin-right: 12px;
margin-left: -2px; margin-left: -2px;
@ -225,4 +224,3 @@
} }
} }
} }

View File

@ -31,15 +31,13 @@
.sidebarLogoFade-enter-active { .sidebarLogoFade-enter-active {
transition: opacity 1.5s; transition: opacity 1.5s;
} }
.sidebarLogoFade-enter-from{ .sidebarLogoFade-enter-from {
opacity: 0; opacity: 0;
} }
.sidebarLogoFade-leave-to { .sidebarLogoFade-leave-to {
opacity: 0; opacity: 0;
} }
// 面包屑动画 方案1 // 面包屑动画 方案1
.breadcrumb-enter-active { .breadcrumb-enter-active {
transition: all 0.25s; transition: all 0.25s;

View File

@ -2,60 +2,63 @@
* DH (http://denghao.me) * DH (http://denghao.me)
* 2017-7-14 * 2017-7-14
*/ */
(function (window, document) { ;(function (window, document) {
var Print = function (dom, options) { var Print = function (dom, options) {
if (!(this instanceof Print)) return new Print(dom, options); if (!(this instanceof Print)) return new Print(dom, options)
this.options = this.extend({ this.options = this.extend(
noPrint: '.no-print', {
onStart: function () { }, noPrint: '.no-print',
onEnd: function () { } onStart: function () {},
}, options); onEnd: function () {},
},
options,
)
if ((typeof dom) === "string") { if (typeof dom === 'string') {
this.dom = document.querySelector(dom); this.dom = document.querySelector(dom)
} else { } else {
this.dom = dom; this.dom = dom
} }
this.init(); this.init()
}; }
Print.prototype = { Print.prototype = {
init: function () { init: function () {
var content = this.getStyle() + this.getHtml(); var content = this.getStyle() + this.getHtml()
this.writeIframe(content); this.writeIframe(content)
}, },
extend: function (obj, obj2) { extend: function (obj, obj2) {
for (var k in obj2) { for (var k in obj2) {
obj[k] = obj2[k]; obj[k] = obj2[k]
} }
return obj; return obj
}, },
getStyle: function () { getStyle: function () {
var str = "", var str = '',
styles = document.querySelectorAll('style,link'); styles = document.querySelectorAll('style,link')
for (var i = 0; i < styles.length; i++) { for (var i = 0; i < styles.length; i++) {
str += styles[i].outerHTML; str += styles[i].outerHTML
} }
str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none;}</style>"; str += '<style>' + (this.options.noPrint ? this.options.noPrint : '.no-print') + '{display:none;}</style>'
return str; return str
}, },
getHtml: function () { getHtml: function () {
var inputs = document.querySelectorAll('input'); var inputs = document.querySelectorAll('input')
var textareas = document.querySelectorAll('textarea'); var textareas = document.querySelectorAll('textarea')
var selects = document.querySelectorAll('select'); var selects = document.querySelectorAll('select')
for (var k in inputs) { for (var k in inputs) {
if (inputs[k].type == "checkbox" || inputs[k].type == "radio") { if (inputs[k].type == 'checkbox' || inputs[k].type == 'radio') {
if (inputs[k].checked == true) { if (inputs[k].checked == true) {
inputs[k].setAttribute('checked', "checked") inputs[k].setAttribute('checked', 'checked')
} else { } else {
inputs[k].removeAttribute('checked') inputs[k].removeAttribute('checked')
} }
} else if (inputs[k].type == "text") { } else if (inputs[k].type == 'text') {
inputs[k].setAttribute('value', inputs[k].value) inputs[k].setAttribute('value', inputs[k].value)
} }
} }
@ -68,11 +71,11 @@
for (var k3 in selects) { for (var k3 in selects) {
if (selects[k3].type == 'select-one') { if (selects[k3].type == 'select-one') {
var child = selects[k3].children; var child = selects[k3].children
for (var i in child) { for (var i in child) {
if (child[i].tagName == 'OPTION') { if (child[i].tagName == 'OPTION') {
if (child[i].selected == true) { if (child[i].selected == true) {
child[i].setAttribute('selected', "selected") child[i].setAttribute('selected', 'selected')
} else { } else {
child[i].removeAttribute('selected') child[i].removeAttribute('selected')
} }
@ -81,44 +84,46 @@
} }
} }
return this.dom.outerHTML; return this.dom.outerHTML
}, },
writeIframe: function (content) { writeIframe: function (content) {
var w, doc, iframe = document.createElement('iframe'), var w,
f = document.body.appendChild(iframe); doc,
iframe.id = "myIframe"; iframe = document.createElement('iframe'),
iframe.style = "position:absolute;width:0;height:0;top:-10px;left:-10px;"; f = document.body.appendChild(iframe)
iframe.id = 'myIframe'
iframe.style = 'position:absolute;width:0;height:0;top:-10px;left:-10px;'
w = f.contentWindow || f.contentDocument; w = f.contentWindow || f.contentDocument
doc = f.contentDocument || f.contentWindow.document; doc = f.contentDocument || f.contentWindow.document
doc.open(); doc.open()
doc.write(content); doc.write(content)
doc.close(); doc.close()
this.toPrint(w, function () { this.toPrint(w, function () {
document.body.removeChild(iframe) document.body.removeChild(iframe)
}); })
}, },
toPrint: function (w, cb) { toPrint: function (w, cb) {
var _this = this; var _this = this
w.onload = function () { w.onload = function () {
try { try {
setTimeout(function () { setTimeout(function () {
w.focus(); w.focus()
typeof _this.options.onStart === 'function' && _this.options.onStart(); typeof _this.options.onStart === 'function' && _this.options.onStart()
if (!w.document.execCommand('print', false, null)) { if (!w.document.execCommand('print', false, null)) {
w.print(); w.print()
} }
typeof _this.options.onEnd === 'function' && _this.options.onEnd(); typeof _this.options.onEnd === 'function' && _this.options.onEnd()
w.close(); w.close()
cb && cb() cb && cb()
}); })
} catch (err) { } catch (err) {
console.log('err', err); console.log('err', err)
} }
} }
} },
}; }
window.Print = Print; window.Print = Print
}(window, document)); })(window, document)

View File

@ -1,33 +1,32 @@
import Clipboard from 'clipboard' import Clipboard from 'clipboard'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
function clipboardSuccess() { function clipboardSuccess() {
ElMessage({ ElMessage({
message: '复制成功', message: '复制成功',
type: 'success', type: 'success',
duration: 1500 duration: 1500,
}) })
} }
function clipboardError() { function clipboardError() {
ElMessage({ ElMessage({
message: '复制失败', message: '复制失败',
type: 'error' type: 'error',
}) })
} }
export default function handleClipboard(text, event) { export default function handleClipboard(text, event) {
const clipboard:any = new Clipboard(event.target, { const clipboard: any = new Clipboard(event.target, {
text: () => text text: () => text,
}) })
clipboard.on('success', () => { clipboard.on('success', () => {
clipboardSuccess() clipboardSuccess()
clipboard.destroy() clipboard.destroy()
}) })
clipboard.on('error', () => { clipboard.on('error', () => {
clipboardError() clipboardError()
clipboard.destroy() clipboard.destroy()
}) })
clipboard.onClick(event) clipboard.onClick(event)
} }

View File

@ -1,76 +1,73 @@
// @ts-ignore // @ts-ignore
import {saveAs} from 'file-saver'; import { saveAs } from 'file-saver'
import * as ExcelJs from 'exceljs'; import * as ExcelJs from 'exceljs'
import {Workbook, Worksheet, Row, Cell} from 'exceljs'; import { Workbook, Worksheet, Row, Cell } from 'exceljs'
import JsZip from 'jszip' import JsZip from 'jszip'
export interface IDownloadFiles2Zip { export interface IDownloadFiles2Zip {
// 压缩包的文件名 // 压缩包的文件名
zipName: string; zipName: string
files: IDownloadExcel[]; files: IDownloadExcel[]
} }
export interface IDownloadFiles2ZipWithFolder { export interface IDownloadFiles2ZipWithFolder {
zipName: string; zipName: string
folders: IFolder[]; folders: IFolder[]
} }
export interface IFolder { export interface IFolder {
folderName: string; folderName: string
files: IDownloadExcel[]; files: IDownloadExcel[]
} }
export interface IDownloadExcel { export interface IDownloadExcel {
filename: string; filename: string
sheets: ISheet[]; sheets: ISheet[]
} }
export interface ISheet { export interface ISheet {
// sheet 的名字 // sheet 的名字
sheetName: string; sheetName: string
// 这个 sheet 中表格的 column类型同 antd 的 column // 这个 sheet 中表格的 column类型同 antd 的 column
columns: any[]; columns: any[]
// 表格的数据 // 表格的数据
dataSource: any[]; dataSource: any[]
} }
export interface ITableHeader { export interface ITableHeader {
header: string; header: string
// 用于数据匹配的 key // 用于数据匹配的 key
key: string; key: string
// 列宽 // 列宽
width: number; width: number
// 父级的 key // 父级的 key
parentKey?: string; parentKey?: string
children?: ITableHeader[]; children?: ITableHeader[]
} }
export interface IStyleAttr { export interface IStyleAttr {
color?: string; color?: string
fontSize?: number; fontSize?: number
horizontal?: 'fill' | 'distributed' | 'justify' | 'center' | 'left' | 'right' | 'centerContinuous' | undefined; horizontal?: 'fill' | 'distributed' | 'justify' | 'center' | 'left' | 'right' | 'centerContinuous' | undefined
bold?: boolean; bold?: boolean
} }
// 默认的列宽 // 默认的列宽
export const DEFAULT_COLUMN_WIDTH = 20; export const DEFAULT_COLUMN_WIDTH = 20
// 默认行高 // 默认行高
export const DEFAULT_ROW_HEIGHT = 20; export const DEFAULT_ROW_HEIGHT = 20
/** /**
* zip压缩包 * zip压缩包
*/ */
export async function downloadFiles2Zip(params: IDownloadFiles2Zip) { export async function downloadFiles2Zip(params: IDownloadFiles2Zip) {
const zip = new JsZip(); const zip = new JsZip()
// 待每个文件都写入完之后再生成 zip 文件 // 待每个文件都写入完之后再生成 zip 文件
const promises = params?.files?.map(async param => await handleEachFile(param, zip, '')) const promises = params?.files?.map(async (param) => await handleEachFile(param, zip, ''))
await Promise.all(promises); await Promise.all(promises)
zip.generateAsync({type: "blob"}).then(blob => { zip.generateAsync({ type: 'blob' }).then((blob) => {
saveAs(blob, `${params.zipName}.zip`) saveAs(blob, `${params.zipName}.zip`)
}) })
} }
/** /**
@ -78,61 +75,61 @@ export async function downloadFiles2Zip(params: IDownloadFiles2Zip) {
* @param params * @param params
*/ */
export async function downloadFiles2ZipWithFolder(params: IDownloadFiles2ZipWithFolder) { export async function downloadFiles2ZipWithFolder(params: IDownloadFiles2ZipWithFolder) {
const zip = new JsZip(); const zip = new JsZip()
const outPromises = params?.folders?.map(async folder => await handleFolder(zip, folder)) const outPromises = params?.folders?.map(async (folder) => await handleFolder(zip, folder))
await Promise.all(outPromises); await Promise.all(outPromises)
zip.generateAsync({type: "blob"}).then(blob => { zip.generateAsync({ type: 'blob' }).then((blob) => {
saveAs(blob, `${params.zipName}.zip`) saveAs(blob, `${params.zipName}.zip`)
}) })
} }
async function handleFolder(zip: JsZip, folder: IFolder) { async function handleFolder(zip: JsZip, folder: IFolder) {
console.log({folder}) console.log({ folder })
const folderPromises: Promise<any>[] = []; const folderPromises: Promise<any>[] = []
const promises = folder?.files?.map(async param => await handleEachFile(param, zip, folder.folderName)); const promises = folder?.files?.map(async (param) => await handleEachFile(param, zip, folder.folderName))
await Promise.all([...promises, ...folderPromises]); await Promise.all([...promises, ...folderPromises])
} }
async function handleEachFile(param: IDownloadExcel, zip: JsZip, folderName: string) { async function handleEachFile(param: IDownloadExcel, zip: JsZip, folderName: string) {
// 创建工作簿 // 创建工作簿
const workbook = new ExcelJs.Workbook(); const workbook = new ExcelJs.Workbook()
param?.sheets?.forEach((sheet) => handleEachSheet(workbook, sheet)); param?.sheets?.forEach((sheet) => handleEachSheet(workbook, sheet))
// 生成 blob // 生成 blob
const data = await workbook.xlsx.writeBuffer(); const data = await workbook.xlsx.writeBuffer()
const blob = new Blob([data], {type: ''}); const blob = new Blob([data], { type: '' })
if (folderName) { if (folderName) {
zip.folder(folderName)?.file(`${param.filename}.xlsx`, blob) zip.folder(folderName)?.file(`${param.filename}.xlsx`, blob)
} else { } else {
// 写入 zip 中一个文件 // 写入 zip 中一个文件
zip.file(`${param.filename}.xlsx`, blob); zip.file(`${param.filename}.xlsx`, blob)
} }
} }
function handleEachSheet(workbook: Workbook, sheet: ISheet) { function handleEachSheet(workbook: Workbook, sheet: ISheet) {
// 添加sheet // 添加sheet
const worksheet = workbook.addWorksheet(sheet.sheetName); const worksheet = workbook.addWorksheet(sheet.sheetName)
// 设置 sheet 的默认行高。设置默认行高跟自动撑开单元格冲突 // 设置 sheet 的默认行高。设置默认行高跟自动撑开单元格冲突
// worksheet.properties.defaultRowHeight = 20; // worksheet.properties.defaultRowHeight = 20;
// 设置列 // 设置列
worksheet.columns = generateHeaders(sheet.columns); worksheet.columns = generateHeaders(sheet.columns)
// handleHeader(worksheet); // handleHeader(worksheet);
// handleData(worksheet, sheet); // handleData(worksheet, sheet);
handleDataWithRender(worksheet, sheet); handleDataWithRender(worksheet, sheet)
} }
export function handleHeader(worksheet: Worksheet) { export function handleHeader(worksheet: Worksheet) {
// 给表头添加背景色。因为表头是第一行,可以通过 getRow(1) 来获取表头这一行 // 给表头添加背景色。因为表头是第一行,可以通过 getRow(1) 来获取表头这一行
const headerRow = worksheet.getRow(1); const headerRow = worksheet.getRow(1)
headerRow.height = 22; headerRow.height = 22
// 通过 cell 设置样式,更精准 // 通过 cell 设置样式,更精准
headerRow.eachCell((cell) => addCellStyle(cell, {color: 'dff8ff', fontSize: 12, horizontal: 'left'})); headerRow.eachCell((cell) => addCellStyle(cell, { color: 'dff8ff', fontSize: 12, horizontal: 'left' }))
} }
export function handleData(worksheet: Worksheet, sheet: ISheet) { export function handleData(worksheet: Worksheet, sheet: ISheet) {
// 添加行 // 添加行
const rows = worksheet.addRows(sheet?.dataSource); const rows = worksheet.addRows(sheet?.dataSource)
// 设置每行的样式 // 设置每行的样式
addStyleToData(rows); addStyleToData(rows)
} }
/** /**
@ -141,120 +138,119 @@ export function handleData(worksheet: Worksheet, sheet: ISheet) {
* @param sheet * @param sheet
*/ */
function handleDataWithRender(worksheet: Worksheet, sheet: ISheet) { function handleDataWithRender(worksheet: Worksheet, sheet: ISheet) {
const {dataSource, columns} = sheet; const { dataSource, columns } = sheet
const rowsData = dataSource?.map(data => { const rowsData = dataSource?.map((data) => {
return columns?.map(column => { return columns?.map((column) => {
// @ts-ignore // @ts-ignore
const renderResult = column?.render?.(data[column.name], data); const renderResult = column?.render?.(data[column.name], data)
if (renderResult) { if (renderResult) {
// 如果不是 object 说明没包裹标签,是基本类型直接返回 // 如果不是 object 说明没包裹标签,是基本类型直接返回
if (typeof renderResult !== "object") { if (typeof renderResult !== 'object') {
return renderResult; return renderResult
} }
// 如果是 object 说明包裹了标签,逐级取出值 // 如果是 object 说明包裹了标签,逐级取出值
return getValueFromRender(renderResult); return getValueFromRender(renderResult)
} }
// @ts-ignore // @ts-ignore
return data[column.name]; return data[column.name]
})
}) })
console.log({rowsData}) })
// 添加行 console.log({ rowsData })
const rows = worksheet.addRows(rowsData); // 添加行
// 设置每行的样式 const rows = worksheet.addRows(rowsData)
addStyleToData(rows); // 设置每行的样式
addStyleToData(rows)
} }
// 递归取出 render 里的值 // 递归取出 render 里的值
// @ts-ignore // @ts-ignore
function getValueFromRender(renderResult: any) { function getValueFromRender(renderResult: any) {
if (renderResult?.type) { if (renderResult?.type) {
const children = renderResult?.props?.children; const children = renderResult?.props?.children
if (children?.type) { if (children?.type) {
return getValueFromRender(children); return getValueFromRender(children)
} else { } else {
return children; return children
}
} }
return '' }
return ''
} }
function addStyleToData(rows: Row[]) { function addStyleToData(rows: Row[]) {
// 设置每行的样式 // 设置每行的样式
rows?.forEach((row) => { rows?.forEach((row) => {
// 设置字体 // 设置字体
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
row.font = { row.font = {
size: 11, size: 11,
name: '微软雅黑', name: '微软雅黑',
}; }
// 设置对齐方式 // 设置对齐方式
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
row.alignment = { row.alignment = {
vertical: 'middle', vertical: 'middle',
horizontal: 'left', horizontal: 'left',
wrapText: true, wrapText: true,
}; }
}); })
} }
export function saveWorkbook(workbook: Workbook, fileName: string) { export function saveWorkbook(workbook: Workbook, fileName: string) {
// 导出文件 // 导出文件
workbook.xlsx.writeBuffer().then((data: any) => { workbook.xlsx.writeBuffer().then((data: any) => {
const blob = new Blob([data], {type: ''}); const blob = new Blob([data], { type: '' })
saveAs(blob, fileName); saveAs(blob, fileName)
}); })
} }
// 根据 antd 的 column 生成 exceljs 的 column // 根据 antd 的 column 生成 exceljs 的 column
export function generateHeaders(columns: any[]) { export function generateHeaders(columns: any[]) {
return columns?.map((col) => { return columns?.map((col) => {
const obj: ITableHeader = { const obj: ITableHeader = {
// 显示的 name // 显示的 name
header: col.label, header: col.label,
// 用于数据匹配的 key // 用于数据匹配的 key
key: col.name, key: col.name,
// 列宽 // 列宽
width: col.width / 5 > DEFAULT_COLUMN_WIDTH ? col.width / 5 : DEFAULT_COLUMN_WIDTH, width: col.width / 5 > DEFAULT_COLUMN_WIDTH ? col.width / 5 : DEFAULT_COLUMN_WIDTH,
}; }
if (col.children) { if (col.children) {
obj.children = col.children?.map((item: any) => ({ obj.children = col.children?.map((item: any) => ({
key: item.name, key: item.name,
header: item.label, header: item.label,
width: item.width, width: item.width,
parentKey: col.name, parentKey: col.name,
})); }))
} }
return obj; return obj
}); })
} }
export function getColumnNumber(width: number) { export function getColumnNumber(width: number) {
// 需要的列数,向上取整 // 需要的列数,向上取整
return Math.ceil(width / DEFAULT_COLUMN_WIDTH); return Math.ceil(width / DEFAULT_COLUMN_WIDTH)
} }
export function addCellStyle(cell: Cell, attr?: IStyleAttr) { export function addCellStyle(cell: Cell, attr?: IStyleAttr) {
const {color, fontSize, horizontal, bold} = attr || {}; const { color, fontSize, horizontal, bold } = attr || {}
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
cell.fill = { cell.fill = {
type: 'pattern', type: 'pattern',
pattern: 'solid', pattern: 'solid',
fgColor: {argb: color}, fgColor: { argb: color },
}; }
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
cell.font = { cell.font = {
bold: bold ?? true, bold: bold ?? true,
size: fontSize ?? 11, size: fontSize ?? 11,
name: '微软雅黑', name: '微软雅黑',
}; }
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
cell.alignment = {vertical: 'middle', wrapText: true, horizontal: horizontal ?? 'left'}; cell.alignment = { vertical: 'middle', wrapText: true, horizontal: horizontal ?? 'left' }
} }
export function addHeaderStyle(row: Row, attr?: IStyleAttr) { export function addHeaderStyle(row: Row, attr?: IStyleAttr) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
row.height = DEFAULT_ROW_HEIGHT; row.height = DEFAULT_ROW_HEIGHT
row.eachCell((cell) => addCellStyle(cell, attr)); row.eachCell((cell) => addCellStyle(cell, attr))
} }

View File

@ -1,16 +1,14 @@
import { ElLoading } from 'element-plus' import { ElLoading } from 'element-plus'
import { import { Loading } from '@element-plus/icons-vue'
Loading
} from '@element-plus/icons-vue'
let loading = null let loading = null
export const openLoading = (options={})=>{ export const openLoading = (options = {}) => {
const text = options.text||'加载中' const text = options.text || '加载中'
loading = ElLoading.service({ loading = ElLoading.service({
lock: true, lock: true,
text: text, text: text,
}) })
} }
export const closeLoading = () => { export const closeLoading = () => {
loading&&loading.close() loading && loading.close()
} }

View File

@ -1,35 +1,35 @@
export default { export default {
imgs: [ imgs: [
'爱你', '爱你',
'爱情', '爱情',
'爱心', '爱心',
'傲慢', '傲慢',
'白眼', '白眼',
'抱拳', '抱拳',
'鄙视', '鄙视',
'闭嘴', '闭嘴',
'便便', '便便',
'擦汗', '擦汗',
'菜刀', '菜刀',
'差劲', '差劲',
'呲牙', '呲牙',
'大哭', '大哭',
'蛋糕', '蛋糕',
'刀', '刀',
'得意', '得意',
'凋谢', '凋谢',
'发呆', '发呆',
'NO', 'NO',
'OK', 'OK',
'发抖', '发抖',
'发怒', '发怒',
'饭', '饭',
'飞吻', '飞吻',
'奋斗', '奋斗',
'疯了', '疯了',
'尴尬', '尴尬',
'勾引', '勾引',
'鼓掌', '鼓掌',
'哈欠', '哈欠',
], ],
} }

View File

@ -1,279 +1,252 @@
import ExcelJS from 'exceljs' import ExcelJS from 'exceljs'
const autoWidthAction = (val,width=10)=>{ const autoWidthAction = (val, width = 10) => {
if (val == null) { if (val == null) {
width = 10; width = 10
} else if (val.toString().charCodeAt(0) > 255) { } else if (val.toString().charCodeAt(0) > 255) {
/*if chinese*/ /*if chinese*/
width = val.toString().length * 2; width = val.toString().length * 2
} else { } else {
width = val.toString().length; width = val.toString().length
}
return width
}
export const exportExcel = async ({ column, data, filename, autoWidth, format }) => {
// 创建excel
const workbook = new ExcelJS.Workbook()
// 设置信息
workbook.creator = 'Me'
workbook.title = filename
workbook.created = new Date()
workbook.modified = new Date()
// 创建工作表
const worksheet = workbook.addWorksheet(filename)
// 设置列名
const columnsName = []
column.forEach((item, index) => {
const obj = {
header: item.label,
key: item.name,
width: null,
} }
return width if (autoWidth) {
} const maxArr = [autoWidthAction(item.label)]
export const exportExcel = async ({column,data,filename,autoWidth,format})=>{ data.forEach((ite) => {
// 创建excel const str = ite[item.name] || ''
const workbook = new ExcelJS.Workbook(); if (str) {
// 设置信息 maxArr.push(autoWidthAction(str))
workbook.creator = "Me";
workbook.title = filename;
workbook.created = new Date();
workbook.modified = new Date();
// 创建工作表
const worksheet = workbook.addWorksheet(filename);
// 设置列名
const columnsName = [];
column.forEach((item,index)=>{
const obj = {
header: item.label, key:item.name, width: null
} }
if(autoWidth){ })
const maxArr = [autoWidthAction(item.label)] obj.width = Math.max(...maxArr) + 5
data.forEach(ite=>{
const str = ite[item.name] ||''
if(str){
maxArr.push(autoWidthAction(str))
}
})
obj.width = Math.max(...maxArr)+5
}
// 设置列名、键和宽度
columnsName.push(obj);
})
worksheet.columns = columnsName;
// 添加行
worksheet.addRows(data);
// 写入文件
const uint8Array =
format === "xlsx"
? await workbook.xlsx.writeBuffer()
: await workbook.csv.writeBuffer();
const blob = new Blob([uint8Array], { type: "application/octet-binary" });
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + `.${format}`);
// 本地保存
} else {
const link = document.createElement("a"); // a标签下载
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接
link.download = filename + `.${format}`; // dowload属性指定文件名
link.click(); // click()事件触发下载
window.URL.revokeObjectURL(link.href); // 释放内存
} }
// 设置列名、键和宽度
columnsName.push(obj)
})
worksheet.columns = columnsName
// 添加行
worksheet.addRows(data)
// 写入文件
const uint8Array = format === 'xlsx' ? await workbook.xlsx.writeBuffer() : await workbook.csv.writeBuffer()
const blob = new Blob([uint8Array], { type: 'application/octet-binary' })
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + `.${format}`)
// 本地保存
} else {
const link = document.createElement('a') // a标签下载
link.href = window.URL.createObjectURL(blob) // href属性指定下载链接
link.download = filename + `.${format}` // dowload属性指定文件名
link.click() // click()事件触发下载
window.URL.revokeObjectURL(link.href) // 释放内存
}
} }
export function addCellStyle(cell, attr) { export function addCellStyle(cell, attr) {
const {color, fontSize, horizontal, bold} = attr || {}; const { color, fontSize, horizontal, bold } = attr || {}
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
cell.fill = { cell.fill = {
type: 'pattern', type: 'pattern',
pattern: 'solid', pattern: 'solid',
fgColor: {argb: color}, fgColor: { argb: color },
}; }
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
cell.font = { cell.font = {
bold: bold ?? true, bold: bold ?? true,
size: fontSize ?? 11, size: fontSize ?? 11,
// italic: true, // italic: true,
// name: '微软雅黑', // name: '微软雅黑',
color: {argb: 'ff0000'}, color: { argb: 'ff0000' },
}; }
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
cell.alignment = {vertical: 'middle', wrapText: true, horizontal: horizontal ?? 'left'}; cell.alignment = { vertical: 'middle', wrapText: true, horizontal: horizontal ?? 'left' }
} }
export const exportStyleExcel =async ({column,data,filename,autoWidth,format})=>{ export const exportStyleExcel = async ({ column, data, filename, autoWidth, format }) => {
// 创建excel // 创建excel
const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook()
// 设置信息 // 设置信息
workbook.creator = "Me"; workbook.creator = 'Me'
workbook.title = filename; workbook.title = filename
workbook.created = new Date(); workbook.created = new Date()
workbook.modified = new Date(); workbook.modified = new Date()
// 创建工作表 // 创建工作表
const worksheet = workbook.addWorksheet(filename); const worksheet = workbook.addWorksheet(filename)
// 设置列名 // 设置列名
const columnsName = []; const columnsName = []
column.forEach((item,index)=>{ column.forEach((item, index) => {
const obj = { const obj = {
header: item.label, key:item.name, width: null header: item.label,
} key: item.name,
if(autoWidth){ width: null,
const maxArr = [autoWidthAction(item.label)]
data.forEach(ite=>{
const str = ite[item.name] ||''
if(str){
maxArr.push(autoWidthAction(str))
}
})
obj.width = Math.max(...maxArr)+5
}
// 设置列名、键和宽度
columnsName.push(obj);
})
worksheet.columns = columnsName;
// 添加行
worksheet.addRows(data);
// 写入文件
// 设置表头颜色
// 给表头添加背景色。因为表头是第一行,可以通过 getRow(1) 来获取表头这一行
const headerRow = worksheet.getRow(1);
// 通过 cell 设置样式,更精准
headerRow.eachCell((cell) => addCellStyle(cell, {color: 'dff8ff', fontSize: 12, horizontal: 'left'}));
const uint8Array =
format === "xlsx"
? await workbook.xlsx.writeBuffer()
: await workbook.csv.writeBuffer();
const blob = new Blob([uint8Array], { type: "application/octet-binary" });
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + `.${format}`);
// 本地保存
} else {
const link = document.createElement("a"); // a标签下载
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接
link.download = filename + `.${format}`; // dowload属性指定文件名
link.click(); // click()事件触发下载
window.URL.revokeObjectURL(link.href); // 释放内存
} }
if (autoWidth) {
const maxArr = [autoWidthAction(item.label)]
data.forEach((ite) => {
const str = ite[item.name] || ''
if (str) {
maxArr.push(autoWidthAction(str))
}
})
obj.width = Math.max(...maxArr) + 5
}
// 设置列名、键和宽度
columnsName.push(obj)
})
worksheet.columns = columnsName
// 添加行
worksheet.addRows(data)
// 写入文件
// 设置表头颜色
// 给表头添加背景色。因为表头是第一行,可以通过 getRow(1) 来获取表头这一行
const headerRow = worksheet.getRow(1)
// 通过 cell 设置样式,更精准
headerRow.eachCell((cell) => addCellStyle(cell, { color: 'dff8ff', fontSize: 12, horizontal: 'left' }))
const uint8Array = format === 'xlsx' ? await workbook.xlsx.writeBuffer() : await workbook.csv.writeBuffer()
const blob = new Blob([uint8Array], { type: 'application/octet-binary' })
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + `.${format}`)
// 本地保存
} else {
const link = document.createElement('a') // a标签下载
link.href = window.URL.createObjectURL(blob) // href属性指定下载链接
link.download = filename + `.${format}` // dowload属性指定文件名
link.click() // click()事件触发下载
window.URL.revokeObjectURL(link.href) // 释放内存
}
} }
// 默认的列宽 // 默认的列宽
export const DEFAULT_COLUMN_WIDTH = 20; export const DEFAULT_COLUMN_WIDTH = 20
function getColumnNumber(width: number) { function getColumnNumber(width: number) {
// 需要的列数,四舍五入 // 需要的列数,四舍五入
return Math.round(width / DEFAULT_COLUMN_WIDTH); return Math.round(width / DEFAULT_COLUMN_WIDTH)
} }
function addData(worksheet, headerKeys, headers, data) {}
function addData(worksheet,headerKeys,headers,data){ export const exportMultiHeaderExcel = ({ column, data, filename, autoWidth }) => {
// 创建excel
const workbook = new ExcelJS.Workbook()
// 创建工作表
const sheet = workbook.addWorksheet('sheet1')
} // 添加表头
sheet.getRow(1).values = ['序号', '日期', '地址', '配送消息', , ,]
export const exportMultiHeaderExcel = ({column,data,filename,autoWidth})=>{ sheet.getRow(2).values = ['序号', '日期', '地址', '省份', '城市', '邮编']
// 创建excel const headers = []
const workbook = new ExcelJS.Workbook(); column.forEach((item, index) => {
// 创建工作表 if (item.children) {
const sheet = workbook.addWorksheet("sheet1"); item.children.forEach((itemChild) => {
const obj = {
// 添加表头 key: itemChild.name,
sheet.getRow(1).values = ["序号", "日期","地址" ,"配送消息" ,,, ]; width: null,
sheet.getRow(2).values = [
"序号",
"日期",
"地址",
"省份",
"城市",
"邮编"
];
const headers = [];
column.forEach((item,index)=>{
if(item.children){
item.children.forEach(itemChild=>{
const obj = {
key:itemChild.name, width: null
}
const maxArr = [autoWidthAction(itemChild.label)]
data.forEach(ite=>{
const str = ite[itemChild.name] ||''
if(str){
maxArr.push(autoWidthAction(str))
}
})
obj.width = Math.max(...maxArr)+5
// 设置列名、键和宽度
headers.push(obj);
})
}else {
const obj = {
key:item.name, width: null
}
const maxArr = [autoWidthAction(item.label)]
data.forEach(ite=>{
const str = ite[item.name] ||''
if(str){
maxArr.push(autoWidthAction(str))
}
})
obj.width = Math.max(...maxArr)+5
// 设置列名、键和宽度
headers.push(obj);
} }
}) const maxArr = [autoWidthAction(itemChild.label)]
sheet.columns = headers; data.forEach((ite) => {
sheet.addRows(data); const str = ite[itemChild.name] || ''
if (str) {
// 合并单元格 maxArr.push(autoWidthAction(str))
sheet.mergeCells(`D1:F1`); }
sheet.mergeCells("A1:A2"); })
sheet.mergeCells("B1:B2"); obj.width = Math.max(...maxArr) + 5
sheet.mergeCells("C1:C2"); // 设置列名、键和宽度
// 写入文件 headers.push(obj)
workbook.xlsx.writeBuffer().then((data) => { })
const blob = new Blob([data, { type: "application/vnd.ms-excel" }]); } else {
if (window.navigator.msSaveOrOpenBlob) { const obj = {
// msSaveOrOpenBlob方法返回boolean值 key: item.name,
navigator.msSaveBlob(blob, filename + ".xlsx"); width: null,
// 本地保存 }
} else { const maxArr = [autoWidthAction(item.label)]
const link = document.createElement("a"); // a标签下载 data.forEach((ite) => {
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接 const str = ite[item.name] || ''
link.download = filename + ".xlsx"; // dowload属性指定文件名 if (str) {
link.click(); // click()事件触发下载 maxArr.push(autoWidthAction(str))
window.URL.revokeObjectURL(link.href); // 释放内存
} }
}); })
obj.width = Math.max(...maxArr) + 5
// 设置列名、键和宽度
headers.push(obj)
}
})
sheet.columns = headers
sheet.addRows(data)
// 合并单元格
sheet.mergeCells(`D1:F1`)
sheet.mergeCells('A1:A2')
sheet.mergeCells('B1:B2')
sheet.mergeCells('C1:C2')
// 写入文件
workbook.xlsx.writeBuffer().then((data) => {
const blob = new Blob([data, { type: 'application/vnd.ms-excel' }])
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + '.xlsx')
// 本地保存
} else {
const link = document.createElement('a') // a标签下载
link.href = window.URL.createObjectURL(blob) // href属性指定下载链接
link.download = filename + '.xlsx' // dowload属性指定文件名
link.click() // click()事件触发下载
window.URL.revokeObjectURL(link.href) // 释放内存
}
})
} }
function mergeColumnCell(headers, rowHeader1, rowHeader2, nameRow1, nameRow2, worksheet) {
// 当前 index 的指针
let pointer = -1
nameRow1.forEach((name, index) => {
// 当 index 小于指针时,说明这一列已经被合并过了,不能再合并
if (index <= pointer) return
// 是否应该列合并
const shouldVerticalMerge = name === nameRow2[index]
function mergeColumnCell(headers, rowHeader1, rowHeader2, nameRow1, nameRow2, worksheet){ // 是否应该行合并
// 当前 index 的指针 const shouldHorizontalMerge = index !== nameRow1.lastIndexOf(name)
let pointer = -1;
nameRow1.forEach((name, index) => {
// 当 index 小于指针时,说明这一列已经被合并过了,不能再合并
if (index <= pointer) return;
// 是否应该列合并
const shouldVerticalMerge = name === nameRow2[index];
console.log('==', name, nameRow2[index], index, nameRow1.lastIndexOf(name), shouldVerticalMerge, shouldHorizontalMerge)
// 是否应该行合并 pointer = nameRow1.lastIndexOf(name)
const shouldHorizontalMerge = index !== nameRow1.lastIndexOf(name); if (shouldVerticalMerge && shouldHorizontalMerge) {
// 两个方向都合并
console.log('==',name,nameRow2[index],index,nameRow1.lastIndexOf(name),shouldVerticalMerge,shouldHorizontalMerge) worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader2.number), nameRow1.lastIndexOf(name) + 1)
console.log('==')
pointer = nameRow1.lastIndexOf(name); } else if (shouldVerticalMerge && !shouldHorizontalMerge) {
if (shouldVerticalMerge && shouldHorizontalMerge) { // 只在垂直方向上同一列的两行合并
// 两个方向都合并 worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader2.number), index + 1)
worksheet.mergeCells( } else if (!shouldVerticalMerge && shouldHorizontalMerge) {
Number(rowHeader1.number), // 只有水平方向同一行的多列合并
index + 1, worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader1.number), nameRow1.lastIndexOf(name) + 1)
Number(rowHeader2.number), // eslint-disable-next-line no-param-reassign
nameRow1.lastIndexOf(name) + 1, const cell = rowHeader1.getCell(index + 1)
); cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }
console.log('==') }
} else if (shouldVerticalMerge && !shouldHorizontalMerge) { })
// 只在垂直方向上同一列的两行合并
worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader2.number), index + 1);
} else if (!shouldVerticalMerge && shouldHorizontalMerge) {
// 只有水平方向同一行的多列合并
worksheet.mergeCells(
Number(rowHeader1.number),
index + 1,
Number(rowHeader1.number),
nameRow1.lastIndexOf(name) + 1,
);
// eslint-disable-next-line no-param-reassign
const cell = rowHeader1.getCell(index + 1);
cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
}
});
} }

View File

@ -1,4 +1,3 @@
/** /**
* Parse the time to string * Parse the time to string
* @param {(Object|string|number)} time * @param {(Object|string|number)} time
@ -79,9 +78,7 @@ export function formatTime(time, option) {
if (option) { if (option) {
return parseTime(time, option) return parseTime(time, option)
} else { } else {
return ( return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
)
} }
} }
@ -111,7 +108,7 @@ export function getQueryObject(url) {
export function byteLength(str) { export function byteLength(str) {
// returns the byte length of an utf8 string // returns the byte length of an utf8 string
let s = str.length let s = str.length
for (var i = str.length - 1; i >= 0; i--) { for (let i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i) const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) s++ if (code > 0x7f && code <= 0x7ff) s++
else if (code > 0x7ff && code <= 0xffff) s += 2 else if (code > 0x7ff && code <= 0xffff) s += 2
@ -217,8 +214,7 @@ export function toggleClass(element, className) {
if (nameIndex === -1) { if (nameIndex === -1) {
classString += '' + className classString += '' + className
} else { } else {
classString = classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length)
classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length)
} }
element.className = classString element.className = classString
} }
@ -347,10 +343,10 @@ export function removeClass(ele, cls) {
} }
export function getColor() { export function getColor() {
var str = '#' let str = '#'
var arr = ['1', '2', '3', '4', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'] const arr = ['1', '2', '3', '4', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
for (var i = 0; i < 6; i++) { for (let i = 0; i < 6; i++) {
var num = parseInt(Math.random() * 16) const num = parseInt(Math.random() * 16)
str += arr[num] str += arr[num]
} }
return str return str
@ -360,14 +356,14 @@ export const isArray = function (value) {
return objToString.call(value) === '[object Array]' return objToString.call(value) === '[object Array]'
} }
let funProto = Function.prototype const funProto = Function.prototype
let objProto = Object.prototype const objProto = Object.prototype
let getPrototypeOf = Object.getPrototypeOf const getPrototypeOf = Object.getPrototypeOf
let objToString = objProto.toString const objToString = objProto.toString
let hasOwnProperty = objProto.hasOwnProperty const hasOwnProperty = objProto.hasOwnProperty
let funToString = funProto.toString const funToString = funProto.toString
// 检查给定的值是否是字符串 // 检查给定的值是否是字符串
export const isString = function (value) { export const isString = function (value) {
return objToString.call(value) === '[object String]' return objToString.call(value) === '[object String]'
@ -378,22 +374,20 @@ export const isPlainObject = function (value) {
return false return false
} }
let prototype = getPrototypeOf(value) const prototype = getPrototypeOf(value)
if (prototype === null) { if (prototype === null) {
return true return true
} }
let constructor = hasOwnProperty.call(prototype, 'constructor') && prototype.constructor const constructor = hasOwnProperty.call(prototype, 'constructor') && prototype.constructor
return ( return typeof constructor === 'function' && funToString.call(constructor) === funToString.call(Object)
typeof constructor === 'function' && funToString.call(constructor) === funToString.call(Object)
)
} }
// // 深度克隆 array 数组或 json 对象,返回克隆后的副本 // // 深度克隆 array 数组或 json 对象,返回克隆后的副本
export const deepObjClone = function (obj) { export const deepObjClone = function (obj) {
let weakMap = new WeakMap() const weakMap = new WeakMap()
function clone(obj) { function clone(obj) {
if (obj == null) { if (obj == null) {
return obj return obj
@ -409,11 +403,11 @@ export const deepObjClone = function (obj) {
if (weakMap.get(obj)) { if (weakMap.get(obj)) {
return weakMap.get(obj) return weakMap.get(obj)
} }
let copy = new obj.constructor() const copy = new obj.constructor()
weakMap.set(obj, copy) weakMap.set(obj, copy)
for (let key in obj) { for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) { if (Object.prototype.hasOwnProperty.call(obj, key)) {
let value = obj[key] const value = obj[key]
copy[key] = clone(value) copy[key] = clone(value)
} }
} }
@ -422,13 +416,12 @@ export const deepObjClone = function (obj) {
return clone(obj) return clone(obj)
} }
export function getTimeStateStr() { export function getTimeStateStr() {
let timeNow = new Date(); const timeNow = new Date()
let hours = timeNow.getHours(); const hours = timeNow.getHours()
if (hours >= 6 && hours <= 10) return `早上好`; if (hours >= 6 && hours <= 10) return `早上好`
if (hours >= 10 && hours <= 14) return `中午好`; if (hours >= 10 && hours <= 14) return `中午好`
if (hours >= 14 && hours <= 18) return `下午好`; if (hours >= 14 && hours <= 18) return `下午好`
if (hours >= 18 && hours <= 24) return `晚上好`; if (hours >= 18 && hours <= 24) return `晚上好`
if (hours >= 0 && hours <= 6) return `凌晨好`; if (hours >= 0 && hours <= 6) return `凌晨好`
} }

View File

@ -1,25 +1,25 @@
import raf from "raf"; import raf from 'raf'
/** /**
* requestAnimationFrame 节流 * requestAnimationFrame 节流
* scroll touchmove 等事件触发很频繁比屏幕刷新频率更快将导致无效的渲染和重绘 * scroll touchmove 等事件触发很频繁比屏幕刷新频率更快将导致无效的渲染和重绘
* 这里使用 requestAnimationFrame 来优化滚动处理在一帧中只进行一次有效重绘 * 这里使用 requestAnimationFrame 来优化滚动处理在一帧中只进行一次有效重绘
*/ */
export default function requestAnimationFrameThrottle(callback) { export default function requestAnimationFrameThrottle(callback) {
let id; let id
const factory = args => () => { const factory = (args) => () => {
id = null; id = null
callback(...args); callback(...args)
}; }
const throttled = (...args) => { const throttled = (...args) => {
if (id == null) { if (id == null) {
id = raf(factory(args)); id = raf(factory(args))
} }
}; }
throttled.cancel = () => raf.cancel(id); throttled.cancel = () => raf.cancel(id)
return throttled; return throttled
}; }

View File

@ -40,15 +40,13 @@ export default {
window.addEventListener('resize', this.$_resizeHandler) window.addEventListener('resize', this.$_resizeHandler)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0] this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
}, },
destroyListener() { destroyListener() {
window.removeEventListener('resize', this.$_resizeHandler) window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null this.$_resizeHandler = null
this.$_sidebarElm && this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
}, },
resize() { resize() {
const { chart } = this const { chart } = this

Some files were not shown because too many files have changed in this diff Show More