特性: 增加 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',
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
plugins: ['vue', '@typescript-eslint'],
globals: {
defineProps: '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 = {
// 一行的字符数如果超过会进行换行默认为80
printWidth: 100,
printWidth: 140,
// 缩进制表符宽度 | 空格数
tabWidth: 2,
// 行位是否使用分号默认为true
semi: false,
vueIndentScriptAndStyle: true,

View File

@ -2,8 +2,8 @@
// cz.config.js kk
/** @type {import('cz-git').CommitizenGitOptions} */
module.exports = {
ignores: [commit => commit.includes("init")],
extends: ["@commitlint/config-conventional"],
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
// alias: { fd: 'docs: fix typos' },
// messages: {
// type: 'Select the type of change that you\'re committing:',
@ -29,7 +29,7 @@ module.exports = {
footerPrefixesSelect: '选择关联issue前缀可选:',
customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?'
confirmCommit: '是否提交或修改commit ?',
},
types: [
{ value: '特性', name: '特性: 新增功能' },
@ -39,11 +39,13 @@ module.exports = {
{ value: '重构', name: '重构: 代码重构(不包括 bug 修复、功能新增)' },
{ value: '性能', name: '性能: 性能优化' },
{ value: '测试', name: '测试: 添加疏漏测试或已有测试改动' },
{ value: '构建', name: '构建: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)' },
{
value: '构建',
name: '构建: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)',
},
{ value: '集成', name: '集成: 修改 CI 配置、脚本' },
{ value: '回退', name: '回退: 回滚 commit' },
{ value: '其他', name: '其他: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
@ -77,7 +79,6 @@ module.exports = {
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
defaultSubject: '',
},
}

View File

@ -61,6 +61,7 @@
"devDependencies": {
"@commitlint/cli": "^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/parser": "^5.32.0",
"@vitejs/plugin-vue": "^3.0.0",
@ -96,6 +97,10 @@
}
},
"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':
specifier: ^17.3.0
version: 17.8.1
'@element-plus/icons-vue':
specifier: ^2.3.1
version: 2.3.1(vue@3.3.13)
'@typescript-eslint/eslint-plugin':
specifier: ^5.32.0
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)
vite:
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:
specifier: ^0.5.1
version: 0.5.1(vite@3.2.7)
@ -397,7 +400,7 @@ packages:
lodash.merge: 4.6.2
lodash.uniq: 4.5.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
transitivePeerDependencies:
- '@swc/core'
@ -534,7 +537,6 @@ packages:
vue: ^3.2.0
dependencies:
vue: 3.3.13(typescript@4.9.5)
dev: false
/@esbuild/android-arm@0.15.18:
resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
@ -774,6 +776,7 @@ packages:
dependencies:
undici-types: 5.26.5
dev: true
optional: true
/@types/node@20.10.5:
resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==}
@ -1121,7 +1124,7 @@ packages:
vite: ^3.0.0
vue: ^3.2.25
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)
dev: true
@ -2350,7 +2353,7 @@ packages:
dependencies:
'@types/node': 20.5.1
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
dev: true
@ -6672,7 +6675,7 @@ packages:
typescript: 4.9.5
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==}
hasBin: true
peerDependencies:
@ -6691,7 +6694,7 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 18.19.3
'@types/node': 20.5.1
acorn: 8.11.2
acorn-walk: 8.3.1
arg: 4.1.3
@ -6937,7 +6940,7 @@ packages:
dependencies:
acorn: 8.11.2
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-virtual-modules: 0.4.6
dev: true
@ -6961,7 +6964,7 @@ packages:
dependencies:
acorn: 8.11.2
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-virtual-modules: 0.4.6
dev: true
@ -7135,7 +7138,7 @@ packages:
chalk: 4.1.2
debug: 4.3.4(supports-color@9.4.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:
- supports-color
dev: true
@ -7152,7 +7155,7 @@ packages:
fs-extra: 10.1.0
magic-string: 0.25.9
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
/vite-plugin-svg-icons@2.0.1(vite@3.2.7):
@ -7168,7 +7171,7 @@ packages:
pathe: 0.2.0
svg-baker: 1.7.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:
- supports-color
dev: true
@ -7180,10 +7183,10 @@ packages:
dependencies:
'@vue/compiler-sfc': 3.3.13
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
/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==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@ -7208,7 +7211,7 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 18.19.3
'@types/node': 20.5.1
esbuild: 0.15.18
postcss: 8.4.32
resolve: 1.22.8

View File

@ -5,8 +5,8 @@
</template>
<script lang="ts" setup>
import {computed} from "vue";
import {useSettingStore} from "@/store/modules/setting"
import { computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
// element
import zhCn from 'element-plus/es/locale/lang/zh-cn'

View File

@ -1,5 +1,5 @@
export const errorCodeType = function (code: string): string {
let errMessage:string = "未知错误"
let errMessage = '未知错误'
switch (code) {
case 400:
errMessage = '请求失败!请您稍后重试'

View File

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

View File

@ -4,6 +4,6 @@ export function login(data) {
return request({
url: '/vue-element-perfect/user/login',
method: 'post',
data
data,
})
}

View File

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

File diff suppressed because one or more lines are too long

View File

@ -7,8 +7,8 @@
:info="defaultOptions.info"
:full="defaultOptions.full"
:fixed="defaultOptions.fixed"
:autoCropWidth="defaultOptions.autoCropWidth"
:autoCropHeight="defaultOptions.autoCropHeight"
:auto-crop-width="defaultOptions.autoCropWidth"
:auto-crop-height="defaultOptions.autoCropHeight"
:fixed-box="defaultOptions.fixedBox"
:auto-crop="defaultOptions.autoCrop"
:center-box="defaultOptions.centerBox"
@ -24,12 +24,12 @@ import 'vue-cropper/dist/index.css'
const emit = defineEmits(['change'])
let props = defineProps({
avatarUrl: {
type:String
type: String,
},
options: {
type: Object,
default:()=>{}
}
default: () => {},
},
})
let cropper = ref()
@ -49,11 +49,11 @@ const defaultOptions = reactive({
high: true, // dpr
fixedBox: false, //
full: false, //
...props.options
...props.options,
})
const getBase64 = () => {
return new Promise(resolve => {
return new Promise((resolve) => {
cropper.value.getCropData((data) => {
resolve(data)
})
@ -76,11 +76,10 @@ const realTime = (data)=>{
emit('change', data)
}
defineExpose({
getBase64,
rotateLeft,
rotateRight,
zoom
zoom,
})
</script>

View File

@ -3,42 +3,42 @@
</template>
<script>
import { defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch } from "vue";
import { defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
// codemirror
import _CodeMirror from "codemirror";
import "codemirror/lib/codemirror.css";
import _CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// language
import 'codemirror/mode/javascript/javascript.js'
// theme css
import 'codemirror/theme/monokai.css'
// :
import "codemirror/addon/fold/foldgutter.css";
import "codemirror/addon/fold/foldcode.js";
import "codemirror/addon/fold/brace-fold.js";
import "codemirror/addon/fold/comment-fold.js";
import "codemirror/addon/fold/indent-fold.js";
import "codemirror/addon/fold/foldgutter.js";
import 'codemirror/addon/fold/foldgutter.css'
import 'codemirror/addon/fold/foldcode.js'
import 'codemirror/addon/fold/brace-fold.js'
import 'codemirror/addon/fold/comment-fold.js'
import 'codemirror/addon/fold/indent-fold.js'
import 'codemirror/addon/fold/foldgutter.js'
// :
// :
import "codemirror/addon/scroll/annotatescrollbar.js";
import "codemirror/addon/search/matchesonscrollbar.js";
import "codemirror/addon/search/match-highlighter.js";
import "codemirror/addon/search/jump-to-line.js";
import 'codemirror/addon/scroll/annotatescrollbar.js'
import 'codemirror/addon/search/matchesonscrollbar.js'
import 'codemirror/addon/search/match-highlighter.js'
import 'codemirror/addon/search/jump-to-line.js'
import "codemirror/addon/dialog/dialog.js";
import "codemirror/addon/dialog/dialog.css";
import "codemirror/addon/search/searchcursor.js";
import "codemirror/addon/search/search.js";
import 'codemirror/addon/dialog/dialog.js'
import 'codemirror/addon/dialog/dialog.css'
import 'codemirror/addon/search/searchcursor.js'
import 'codemirror/addon/search/search.js'
// :
// 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({
props: {
@ -46,29 +46,29 @@ export default defineComponent({
defaultValue: String,
readOnly: {
type: Boolean,
default: false
}
default: false,
},
},
setup(props, context) {
const { modelValue, defaultValue, readOnly } = toRefs(props);
const codeEditor = ref();
let editor;
const { modelValue, defaultValue, readOnly } = toRefs(props)
const codeEditor = ref()
let editor
watch(modelValue, () => {
if (null != editor && modelValue.value && modelValue.value !== editor.getValue()) {
// v-model
editor.setValue(modelValue.value);
editor.setValue(modelValue.value)
}
});
})
watch(readOnly, () => {
if (null != editor) {
editor.setOption("readOnly", readOnly.value);
editor.setOption('readOnly', readOnly.value)
}
});
})
onMounted(() => {
editor = CodeMirror.fromTextArea(codeEditor.value, {
value: modelValue.value,
// mime: "text/javascript",
mode: "application/json",
mode: 'application/json',
indentWithTabs: false, // n*tabntabfalse
smartIndent: true, // true
lineNumbers: true, //
@ -77,28 +77,28 @@ export default defineComponent({
// :
foldGutter: true,
lineWrapping: true, //
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
// :
styleActiveLine: false //
});
styleActiveLine: false, //
})
// change
editor.on("change", () => {
editor.on('change', () => {
// v-model
context.emit("update:modelValue", editor.getValue());
});
context.emit('update:modelValue', editor.getValue())
})
if (defaultValue.value) {
editor.setValue(defaultValue.value);
editor.setValue(defaultValue.value)
}
});
})
onBeforeUnmount(() => {
if (null !== editor) {
editor.toTextArea();
editor = null;
editor.toTextArea()
editor = null
}
});
return { codeEditor };
}
});
})
return { codeEditor }
},
})
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
@ -117,5 +117,4 @@ export default defineComponent({
background-color: transparent;
border-right: 1px solid #ddd;
}
</style>

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import { EChartsType } from 'echarts/core'
import { onMounted, ref } from 'vue'
const chartsRef = ref<HTMLElement | null>()
let colorList = ['#46ea91', '#2ba0ff', '#ed593b', '#7357ff', '#f2d750'];
let colorList = ['#46ea91', '#2ba0ff', '#ed593b', '#7357ff', '#f2d750']
const options = {
legend: {
icon: 'circle',
@ -245,7 +245,7 @@ const options = {
},
},
],
};
}
let chart: EChartsType
const initChart = () => {

View File

@ -1,5 +1,5 @@
<template>
<div class="echarts" ref="chartsRef" />
<div ref="chartsRef" class="echarts" />
</template>
<script setup lang="ts">
import BarCharts from './components/bar.vue'
@ -21,7 +21,6 @@
dataOptions.push(obj)
})
const options = {
color,
grid: {
@ -95,7 +94,6 @@
},
},
],
}
let chart: EChartsType

View File

@ -1,5 +1,5 @@
<template>
<div class="echarts" ref="chartsRef" />
<div ref="chartsRef" class="echarts" />
</template>
<script setup lang="ts">
import BarCharts from './components/bar.vue'
@ -8,7 +8,6 @@ import { EChartsType } from 'echarts/core'
import { onMounted, ref, reactive } from 'vue'
const chartsRef = ref<HTMLElement | null>()
const options = {
grid: {
top: '10%',
@ -89,7 +88,6 @@ const options = {
},
},
],
}
let chart: EChartsType

View File

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

View File

@ -25,10 +25,10 @@ var trafficWay = [
},
{ name: 'Ⅴ类', value: 20 },
{ name: '劣Ⅴ类', value: 20 },
];
]
var data = [];
var color = ['#fd566a', '#9787ff', '#fdb36a', '#fdd56a', '#6da7ff', '#63e1f2', '#ff3000'];
var data = []
var color = ['#fd566a', '#9787ff', '#fdb36a', '#fdd56a', '#6da7ff', '#63e1f2', '#ff3000']
for (var i = 0; i < trafficWay.length; i++) {
data.push(
{
@ -59,8 +59,8 @@ for (var i = 0; i < trafficWay.length; i++) {
borderWidth: 0,
},
},
}
);
},
)
}
var seriesOption = [
{
@ -76,16 +76,16 @@ var seriesOption = [
position: 'outside',
formatter: function (params) {
var percent = 0;
var total = 0;
var percent = 0
var total = 0
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 !== '') {
return params.name + '\t' + percent + '%';
return params.name + '\t' + percent + '%'
} else {
return '';
return ''
}
},
},
@ -99,10 +99,11 @@ var seriesOption = [
},
data: data,
},
];
]
let options = {
color: color,
title: [{
title: [
{
text: '水质监测',
top: '44%',
textAlign: 'center',
@ -114,7 +115,8 @@ let options = {
fontSize: 20,
fontWeight: '400',
},
}, {
},
{
text: '水环境监测站',
top: '49%',
textAlign: 'center',
@ -124,7 +126,8 @@ let options = {
fontSize: 20,
fontWeight: '400',
},
}, {
},
{
text: '9',
top: '53%',
textAlign: 'center',
@ -133,9 +136,10 @@ let options = {
color: '#f6ea2f',
fontSize: 25,
fontWeight: '800',
fontStyle: 'italic'
fontStyle: 'italic',
},
}, {
},
{
text: '个',
top: '53.5%',
textAlign: 'center',
@ -145,7 +149,8 @@ let options = {
fontSize: 16,
fontWeight: '400',
},
}],
},
],
tooltip: {
show: false,
},
@ -154,7 +159,7 @@ let options = {
show: false,
},
series: seriesOption,
};
}
let chart: EChartsType
const initChart = () => {
@ -174,6 +179,5 @@ onMounted(() => {
.echarts {
width: 100%;
height: 100%;
}
</style>

View File

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

View File

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

View File

@ -1,24 +1,19 @@
<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-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-option
v-for="item in config.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-option v-for="item in config.options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</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-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-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-form-item>
</template>
@ -36,7 +31,6 @@
(e: 'update:modelValue', value: any): void
}>()
const value = computed({
get() {
return props?.modelValue

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<template>
<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-icon style="margin-right: 4px"><plus /></el-icon> </el-button
>
@ -15,54 +15,32 @@
:fixed="item.fixed"
:label="item.label"
/>
<el-table-column
v-else
:prop="item.name"
:width="item.width"
:align="item.align"
:fixed="item.fixed"
:label="item.label"
>
<el-table-column v-else :prop="item.name" :width="item.width" :align="item.align" :fixed="item.fixed" :label="item.label">
<template #default="scope">
<template v-if="!item.slot">
<template v-if="item.readonly">
{{ scope.row[item.name] }}
</template>
<template v-else-if="item.valueType === 'select'">
<el-select
clearable
: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 v-if="scope.row.edit" v-model="scope.row[item.name]" clearable :placeholder="`请选择`">
<el-option v-for="ite in item.options" :key="ite.value" :label="ite.label" :value="ite.value" />
</el-select>
<span v-else>{{ filterOption(item, scope) }}</span>
</template>
<template v-else-if="item.valueType === 'date'">
<el-date-picker
v-if="scope.row.edit"
v-model="scope.row[item.name]"
type="date"
value-format="YYYY-MM-DD"
clearable
placeholder="请选择"
v-if="scope.row.edit"
/>
<span v-else>{{ scope.row[item.name] || '--' }}</span>
</template>
<template v-else>
<el-input
clearable
placeholder="请输入"
v-model="scope.row[item.name]"
v-if="scope.row.edit"
></el-input>
<el-input v-if="scope.row.edit" v-model="scope.row[item.name]" clearable placeholder="请输入"></el-input>
<span v-else>{{ scope.row[item.name] || '--' }}</span>
</template>
</template>
@ -72,59 +50,28 @@
</template>
<el-table-column prop="operator" label="操作" width="250px" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.edit"
type="primary"
size="small"
icon="CircleCheckFilled"
@click="confirmEdit(scope.row)"
>
<el-button v-if="scope.row.edit" type="primary" size="small" icon="CircleCheckFilled" @click="confirmEdit(scope.row)">
保存
</el-button>
<el-button
v-else
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"
>
<el-button v-else type="primary" size="small" icon="Edit" @click="scope.row.edit = !scope.row.edit"> 编辑 </el-button>
<el-popover v-model:visible="scope.row.visible" trigger="click" placement="top" :width="160">
<p style="display: flex; align-items: center; margin-bottom: 10px">
<el-icon color="#faad14" style="margin-right: 10px"><warning-filled /></el-icon>
删除此行</p
>
<div style="text-align: right; margin: 0">
<el-button size="small" @click="scope.row.visible = false">取消</el-button>
<el-button size="small" type="primary" @click="deleteAction(scope.row)"
>确定</el-button
>
<el-button size="small" type="primary" @click="deleteAction(scope.row)">确定</el-button>
</div>
<template #reference>
<el-button icon="Delete" @click="deleteCurrent(scope.row)" type="danger" size="small"
>删除</el-button
>
<el-button icon="Delete" type="danger" size="small" @click="deleteCurrent(scope.row)">删除</el-button>
</template>
</el-popover>
<el-button
v-if="scope.row.edit"
type="primary"
size="small"
icon="Edit"
@click="cancelEdit(scope.row)"
>
取消
</el-button>
<el-button v-if="scope.row.edit" type="primary" size="small" icon="Edit" @click="cancelEdit(scope.row)"> 取消 </el-button>
</template>
</el-table-column>
</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-icon style="margin-right: 4px"><plus /></el-icon> </el-button
>

View File

@ -1,7 +1,7 @@
<template>
<div class="zb-pro-table">
<div class="header">
<SearchForm @submit="onSubmit" :columns="baseFormColumns" />
<SearchForm :columns="baseFormColumns" @submit="onSubmit" />
</div>
<!----------底部---------------------->
@ -14,14 +14,14 @@
<!-- ------------表格--------------->
<div class="table">
<el-table
class="zb-table"
v-loading="loading"
@selection-change="(val) => emit('selection-change', val)"
class="zb-table"
:data="list"
:border="true"
@selection-change="(val) => emit('selection-change', val)"
>
<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">
<slot :name="item.name" :item="item" :row="scope.row"></slot>
</template>

View File

@ -12,16 +12,10 @@
</div>
</div>
</div>
<el-drawer
v-model="drawer" title="主题配置" size="300px">
<el-drawer v-model="drawer" title="主题配置" size="300px">
<div class="theme-item">
<label>导航栏布局</label>
<el-select
v-model="layout"
placeholder="请选择"
style="width: 150px"
@change="(val) => changeSwitch('mode',val)"
>
<el-select v-model="layout" placeholder="请选择" style="width: 150px" @change="(val) => changeSwitch('mode', val)">
<el-option label="纵向" value="vertical"></el-option>
<el-option label="横向" value="horizontal"></el-option>
<el-option label="分栏" value="columns"></el-option>
@ -65,11 +59,11 @@
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import {ElMessage} from "element-plus";
import {openLoading,closeLoading} from "@/utils/element"
import { ElMessage } from 'element-plus'
import { openLoading, closeLoading } from '@/utils/element'
import SwitchDark from '@/components/SwitchDark/index.vue'
import {PRIMARY_COLOR} from "@/config/index";
import {useSettingStore} from "@/store/modules/setting"
import { PRIMARY_COLOR } from '@/config/index'
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
const layout = ref(SettingStore.themeConfig.mode)
@ -83,17 +77,15 @@
const drawer = computed({
get() {
return SettingStore.themeConfig.showSetting;
return SettingStore.themeConfig.showSetting
},
set() {
changeSwitch('showSetting', !SettingStore.themeConfig.showSetting)
}
},
})
//
const predefineColor = [
'#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d'
];
const predefineColor = ['#409EFF', '#1890ff', '#304156', '#212121', '#11a983', '#13c2c2', '#6959CD', '#f5222d']
const operator = (type) => {
switch (type) {
@ -121,31 +113,30 @@
watch(
() => layout.value,
() => {
const body = document.body as HTMLElement;
body.setAttribute("class", `layout-${layout.value}`);
const body = document.body as HTMLElement
body.setAttribute('class', `layout-${layout.value}`)
},
{ immediate: true }
);
{ immediate: true },
)
//
const changePrimary = (val) => {
if (!val) {
primary.value = val = PRIMARY_COLOR;
ElMessage({ type: "success", message: `主题颜色已重置为 ${PRIMARY_COLOR}` });
primary.value = val = 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)
}
//
const changeGrayWeak = (type, val) => {
const body = document.documentElement as HTMLElement;
if (!val) return body.setAttribute("style", "");
if (type === "gray") body.setAttribute("style", "filter: grayscale(1)");
if (type === "weak") body.setAttribute("style", "filter: invert(80%)");
const body = document.documentElement as HTMLElement
if (!val) return body.setAttribute('style', '')
if (type === 'gray') body.setAttribute('style', 'filter: grayscale(1)')
if (type === 'weak') body.setAttribute('style', 'filter: invert(80%)')
changeSwitch(type, val)
}
</script>
<style lang="scss" scoped>
@ -193,15 +184,13 @@
border-radius: 5.5px;
font-size: 12px;
background: #ebf5ff;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease,
box-shadow 0.15s ease;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.item-child2 {
margin-top: 10px;
color: #b37feb;
background: #f7f2fd;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease,
box-shadow 0.15s ease;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
}
@ -217,6 +206,5 @@
font-size: 14px;
color: black;
justify-content: space-between;
}
</style>

View File

@ -1,24 +1,19 @@
<template>
<div class="m-wangEditor">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
<Editor
class="editor-content'"
style="height: 300px; overflow-y: hidden;"
v-model="valueHtml"
:defaultConfig="editorConfig"
class="editor-content'"
style="height: 300px; overflow-y: hidden"
:default-config="editorConfig"
:mode="mode"
@onCreated="handleCreated"
@on-created="handleCreated"
/>
</div>
</template>
<script lang="ts" setup>
// 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 { onBeforeUnmount, onMounted, watch, shallowRef, ref, computed } from 'vue'
let editors = null
@ -50,9 +45,9 @@
},
set(val) {
//
if (editorRef.value.isEmpty()) val = "";
if (editorRef.value.isEmpty()) val = ''
emit('update:modelValue', val)
}
},
})
//

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import {useSettingStore} from "@/store/modules/setting"
import {computed, onMounted, onUnmounted, watch} from "vue";
import {useRoute} from "vue-router";
import { useSettingStore } from '@/store/modules/setting'
import { computed, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
const { body } = document
@ -25,16 +25,15 @@ export const useResizeHandler = ()=>{
} else {
return false
}
}
function $_resizeHandler() {
if (!document.hidden) { // bool型表示页面是否处于隐藏状态。页面隐藏包括页面在后台标签页或者浏览器最小化
if (!document.hidden) {
// bool型表示页面是否处于隐藏状态。页面隐藏包括页面在后台标签页或者浏览器最小化
const isMobile = $_isMobile()
const isCollapse = collapse()
SettingStore.toggleDevice(isMobile ? 'mobile' : 'desktop')
if (isMobile) {
SettingStore.closeSideBar({ withoutAnimation: true })
}
@ -42,8 +41,6 @@ export const useResizeHandler = ()=>{
if (!isMobile) {
SettingStore.setCollapse(isCollapse)
}
}
}
onMounted(() => {
@ -65,6 +62,5 @@ export const useResizeHandler = ()=>{
window.removeEventListener('resize', $_resizeHandler)
})
return { device }
}

View File

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

View File

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

View File

@ -7,12 +7,12 @@
<el-scrollbar>
<div class="menu-wrap">
<div
class="item-menu-wrap"
:class="{
'active-menu':activeCurrentMenu===item.path
}"
v-for="item in menusRoutes"
:key="item.path"
class="item-menu-wrap"
:class="{
'active-menu': activeCurrentMenu === item.path,
}"
@click="handleChangeMenu(item)"
>
<el-icon :size="20">
@ -26,7 +26,7 @@
<div class="layout-columns-sub" :style="{ width: isCollapse ? '60px' : '210px' }">
<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>
<el-scrollbar>
<el-menu
@ -37,7 +37,7 @@
:collapse-transition="false"
class="menu-columns"
>
<SubMenu :menuList="subMenus" />
<SubMenu :menu-list="subMenus" />
</el-menu>
</el-scrollbar>
</div>
@ -57,17 +57,17 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import {usePermissionStore} from "@/store/modules/permission"
import { useSettingStore } from "@/store/modules/setting";
import { ref, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
import { useSettingStore } from '@/store/modules/setting'
import Footer from '../components/Footer/index.vue'
import SubMenu from '../components/SubMenu/SubMenu.vue'
import TagsView from '../components/TagsView/index.vue'
const PermissionStore = usePermissionStore()
const SettingStore = useSettingStore()
const route = useRoute()
const router = useRouter();
const router = useRouter()
import HeaderToolRight from '../components/Header/ToolRight.vue'
import HeaderToolLeft from '../components/Header/ToolLeft.vue'
import Main from '../components/Main/index.vue'
@ -76,7 +76,7 @@ const permission_routes = computed(() => PermissionStore.permission_routes)
//
const menusRoutes = computed(() => {
return PermissionStore.permission_routes.filter(item=>!item.hidden)
return PermissionStore.permission_routes.filter((item) => !item.hidden)
})
const activeCurrentMenu = ref('')
@ -90,28 +90,31 @@ const activeMenu = computed(() => {
const basePath = ref<string>('/')
const subMenus = ref([])
watch(()=>[route],()=>{
if (!menusRoutes.value.length) return;
watch(
() => [route],
() => {
if (!menusRoutes.value.length) return
const [firstMenu] = route.matched
activeCurrentMenu.value = firstMenu.path;
let menuItem = menusRoutes.value.find(item=>firstMenu.path === item.path)
activeCurrentMenu.value = firstMenu.path
let menuItem = menusRoutes.value.find((item) => firstMenu.path === item.path)
if (menuItem && menuItem.children?.length) {
subMenus.value = menuItem.children
} else {
subMenus.value = []
}
basePath.value = firstMenu.path
},{
},
{
deep: true,
immediate:true
})
immediate: true,
},
)
const handleChangeMenu = (item) => {
router.push(item.path);
router.push(item.path)
}
</script>
<style lang="scss" scoped>
.main-columns {
display: flex;
@ -148,7 +151,7 @@ const handleChangeMenu = (item)=>{
height: 70px;
width: 70px;
cursor: pointer;
transition: all .3s ease;
transition: all 0.3s ease;
}
.active-menu {
background: $primaryColor;

View File

@ -32,6 +32,3 @@
height: 100%;
}
}

View File

@ -5,7 +5,8 @@
class="m-layout-header"
:class="{
'fixed-header': themeConfig.fixedHeader,
}">
}"
>
<div class="header-inner">
<el-menu
mode="horizontal"
@ -16,18 +17,12 @@
:collapse-transition="false"
class="menu-horizontal"
>
<SubItem
v-for="route in permission_routes"
:key="route.path"
:item="route"
/>
<SubItem v-for="route in permission_routes" :key="route.path" :item="route" />
</el-menu>
<HeaderToolRight />
</div>
<TagsView v-if="themeConfig.showTag" />
</div>
</template>
<script lang="ts" setup>
@ -37,15 +32,15 @@ import HeaderToolRight from '../../components/Header/ToolRight.vue'
import TagsView from '../../components/TagsView/index.vue'
import SubItem from '../../components/SubMenu/SubItem.vue'
import { useRoute } from 'vue-router'
import {usePermissionStore} from "@/store/modules/permission"
import { usePermissionStore } from '@/store/modules/permission'
const PermissionStore = usePermissionStore()
const route = useRoute()
//
const permission_routes = computed(() => PermissionStore.permission_routes)
import {computed} from "vue";
import {useSettingStore} from "@/store/modules/setting"
import { computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
const activeMenu = computed(() => {
@ -56,14 +51,11 @@ const activeMenu = computed(() => {
return path
})
//
const themeConfig = computed(() => SettingStore.themeConfig)
const isCollapse = computed(() => !SettingStore.isCollapse)
</script>
<style lang="scss" scoped>
@import "./index.scss";
@import './index.scss';
</style>

View File

@ -8,9 +8,8 @@
height: 90px;
}
.zb-no-fixed-header {
width: 100%!important;;
width: 100% !important;
}
.m-layout-header {
@ -45,8 +44,6 @@
width: calc(100% - 210px);
}
.el-dropdown {
display: flex;
height: 100%;

View File

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

View File

@ -30,13 +30,13 @@
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import {ElMessage, ElMessageBox} from "element-plus";
import {computed, ref} from "vue";
import { ElMessage, ElMessageBox } from 'element-plus'
import { computed, ref } from 'vue'
import AvatarLogo from '@/assets/image/avatar.png'
import {useUserStore} from "@/store/modules/user"
import {useTagsViewStore} from "@/store/modules/tagsView"
import {usePermissionStore} from "@/store/modules/permission"
import { useUserStore } from '@/store/modules/user'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { usePermissionStore } from '@/store/modules/permission'
import PersonalDialog from './PersonalDialog.vue'
const router = useRouter()
@ -52,7 +52,7 @@ const currentRoles = computed({
;(async () => {
await UserStore.getInfo([val])
router.push({
path:'/'
path: '/',
})
location.reload()
})()
@ -80,9 +80,9 @@ const logOut = async () => {
TagsViewStore.clearVisitedView()
PermissionStore.clearRoutes()
ElMessage({
type: "success",
message: "退出登录成功!"
});
type: 'success',
message: '退出登录成功!',
})
})
.catch(() => {})
}
@ -93,7 +93,7 @@ const modifyPassword = ()=>{
<style lang="scss" scoped>
.avatar {
margin-right: 6px
margin-right: 6px;
}
.el-dropdown-link {
cursor: pointer;

View File

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

View File

@ -1,7 +1,7 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<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">
<span class="breadcrumb-title">首页</span>
</div>
@ -23,12 +23,9 @@ const router = useRouter()
const handleLink = (item) => {
router.push({
path:item.path
path: item.path,
})
}
const matched = computed(() => route.matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false));
const matched = computed(() => route.matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false))
</script>

View File

@ -1,14 +1,13 @@
<template>
<div class="m-headerSearch">
<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-dialog v-model="isShowSearch" width="600px" destroy-on-close :show-close="false">
<el-select
style="width: 100%"
ref="headerSearchSelect"
v-model="search"
style="width: 100%"
:remote-method="querySearch"
filterable
default-first-option
@ -17,7 +16,12 @@
class="header-search-select"
@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-select>
</el-dialog>
@ -29,14 +33,14 @@ import {computed, onMounted, ref, watch} from 'vue'
import path from 'path-browserify'
import Fuse from 'fuse.js'
import { useVueFuse } from 'vue-fuse'
import {useRouter} from "vue-router";
import { useRouter } from 'vue-router'
const router = useRouter()
const isShowSearch = ref(false);
const options = ref([]);
const searchPool = ref([]);
const search = ref('');
const fuse = ref(null);
import {usePermissionStore} from "@/store/modules/permission"
const isShowSearch = ref(false)
const options = ref([])
const searchPool = ref([])
const search = ref('')
const fuse = ref(null)
import { usePermissionStore } from '@/store/modules/permission'
const PermissionStore = usePermissionStore()
const routes = computed(() => PermissionStore.routes)
@ -52,13 +56,16 @@ const initFuse = (list)=> {
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
keys: [
{
name: 'title',
weight: 0.7
}, {
weight: 0.7,
},
{
name: 'path',
weight: 0.3
}]
weight: 0.3,
},
],
})
}
@ -72,11 +79,13 @@ const generateRoutes = (routes, basePath = '/', prefixTitle = [])=>{
for (const router of routes) {
//
if (router.hidden) { continue }
if (router.hidden) {
continue
}
const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle]
title: [...prefixTitle],
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
@ -147,7 +156,6 @@ const querySearch=(query)=> {
}
}
.header-search-select {
}
/* 菜单搜索样式 */
.m-headerSearch {
@ -166,6 +174,4 @@ const querySearch=(query)=> {
}
}
}
</style>

View File

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

View File

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

View File

@ -3,22 +3,28 @@
<el-popover width="200px" placement="bottom">
<template #reference>
<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>
</template>
<div>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="通知" name="first">
<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" />
<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 />
<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-tabs>

View File

@ -1,16 +1,14 @@
<template>
<div class="m-screenful">
<el-tooltip effect="dark" content="全屏" placement="bottom">
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="toggle"
className="header-icon"
/>
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" class-name="header-icon" @click="toggle" />
</el-tooltip>
</div>
</template>
<script lang="ts" setup>
import { useFullscreen } from "@vueuse/core";
const { toggle, isFullscreen } = useFullscreen();
import { useFullscreen } from '@vueuse/core'
const { toggle, isFullscreen } = useFullscreen()
</script>
<style lang="scss" scoped>

View File

@ -1,14 +1,13 @@
<template>
<div class="m-setting">
<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>
</div>
</template>
<script lang="ts" setup>
import {useSettingStore} from "@/store/modules/setting"
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
const changeSwitch = (key, val) => {
SettingStore.setThemeConfig({ key, val })
@ -40,6 +39,4 @@
color: white;
}
}
</style>

View File

@ -12,24 +12,24 @@
</template>
<script setup lang="ts">
import { reactive, computed } from "vue";
import {useSettingStore} from "@/store/modules/setting"
import { reactive, computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
const globalComSize = computed((): string => SettingStore.themeConfig.globalComSize)
const assemblySizeListCh = reactive<{ [key: string]: any }>({
default: "默认",
large: "大型",
small: "小型"
});
default: '默认',
large: '大型',
small: '小型',
})
const assemblySizeList = reactive<string[]>(["default", "large", "small"]);
const assemblySizeList = reactive<string[]>(['default', 'large', 'small'])
const setAssemblySize = (item: string) => {
if (globalComSize.value === item) return;
if (globalComSize.value === item) return
SettingStore.setThemeConfig({ key: 'globalComSize', val: item })
};
}
</script>
<style scoped lang="scss">

View File

@ -2,7 +2,7 @@
<div class="app-main">
<router-view v-slot="{ Component, route }">
<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" />
</keep-alive>
</transition>

View File

@ -3,9 +3,9 @@
</template>
<script lang="ts" setup>
import {computed} from "vue";
import { computed } from 'vue'
import { useResizeHandler } from '@/hooks/useResizeHandler'
import {useSettingStore} from "@/store/modules/setting"
import { useSettingStore } from '@/store/modules/setting'
let { device } = useResizeHandler()
const SettingStore = useSettingStore()
@ -19,6 +19,4 @@ const handleClickOutside = () => {
}
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View File

@ -2,10 +2,10 @@
<div class="sidebar-logo-container">
<transition name="sidebarLogoFadeCl">
<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 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>
</router-link>
</transition>
@ -13,7 +13,7 @@
</template>
<script lang="ts" setup>
defineProps<{ isCollapse: boolean }>();
defineProps<{ isCollapse: boolean }>()
</script>
<style lang="scss" scoped>
@ -41,7 +41,6 @@ defineProps<{ isCollapse: boolean }>();
width: 32px;
height: 32px;
vertical-align: middle;
}
& .sidebar-title {
display: inline-block;

View File

@ -1,6 +1,6 @@
<template>
<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-menu
:default-active="activeMenu"

View File

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

View File

@ -10,17 +10,12 @@
</el-menu-item>
</app-link>
</template>
<el-sub-menu :index="item.path" v-else teleported >
<el-sub-menu v-else :index="item.path" teleported>
<template #title>
<el-icon :size="20"> <component :is="item.meta?.icon"></component></el-icon>
<span>{{ item.meta && item.meta.title }}</span>
</template>
<sub-item
v-for="child in item.children"
:key="child.path"
:item="child"
/>
<sub-item v-for="child in item.children" :key="child.path" :item="child" />
</el-sub-menu>
</template>
</template>

View File

@ -2,9 +2,7 @@
<template v-for="subItem in menuList" :key="subItem.path">
<template v-if="!subItem.hidden">
<template v-if="!subItem.alwaysShow && hasOneChild(subItem.children, subItem)">
<MenuItem
:subItem="hasOneChild(subItem.children, subItem)"
/>
<MenuItem :sub-item="hasOneChild(subItem.children, subItem)" />
</template>
<el-sub-menu v-else :index="subItem.path">
<template #title>
@ -13,20 +11,20 @@
</el-icon>
<span>{{ subItem?.meta?.title }}</span>
</template>
<SubMenu :menuList="subItem.children" />
<SubMenu :menu-list="subItem.children" />
</el-sub-menu>
</template>
</template>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ref } from 'vue'
import MenuItem from './MenuItem.vue'
let props = defineProps({
menuList: {
type: Array,
default:()=>[]
default: () => [],
},
})
@ -50,5 +48,4 @@ const hasOneChild = (children = [], parent) => {
}
return false
}
</script>

View File

@ -6,18 +6,26 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="refresh"><el-icon :size="14"><Refresh /></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-item @click="refresh"
><el-icon :size="14"><Refresh /></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>
</template>
</el-dropdown>
</template>
<script lang="ts" setup>
import {computed} from "vue";
import {useSettingStore} from "@/store/modules/setting"
import {useTagsViewStore} from "@/store/modules/tagsView"
import { computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
@ -31,7 +39,6 @@ const refresh = () => {
const closeCurrentTab = (event) => {
TagsViewStore.toLastView(route.path)
TagsViewStore.delView(route.path)
}
//
const closeOtherTab = async () => {

View File

@ -1,12 +1,7 @@
<template>
<div class="m-tags-view">
<div class="tags-view">
<el-tabs
v-model="activeTabsValue"
type="card"
@tab-click="tabClick"
@tab-remove="removeTab"
>
<el-tabs v-model="activeTabsValue" type="card" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane
v-for="item in visitedViews"
:key="item.path"
@ -16,7 +11,7 @@
:closable="!(item.meta && item.meta.affix)"
>
<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>
</el-icon>
{{ item.title }}
@ -30,13 +25,13 @@
</div>
</template>
<script lang="ts" setup>
import {computed, watch, ref, onMounted} from "vue";
import { useRoute, useRouter } from "vue-router";
import { TabsPaneContext } from "element-plus";
import { computed, watch, ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { TabsPaneContext } from 'element-plus'
import MoreButton from './components/MoreButton'
import path from 'path-browserify'
import {useTagsViewStore} from "@/store/modules/tagsView"
import {usePermissionStore} from "@/store/modules/permission"
import { useTagsViewStore } from '@/store/modules/tagsView'
import { usePermissionStore } from '@/store/modules/permission'
const route = useRoute()
const router = useRouter()
@ -96,23 +91,23 @@ watch(route, () => {
let tabIndex = 2
const activeTabsValue = computed({
get: () => {
return TagsViewStore.activeTabsValue;
return TagsViewStore.activeTabsValue
},
set: val => {
TagsViewStore.setTabsMenuValue(val);
}
});
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);
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);
};
let path = tabItem.props.name as string
router.push(path)
}
const isActive = (path) => {
return path === route.path

View File

@ -1,9 +1,9 @@
import { createApp } from 'vue'
import App from './App.vue'
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'
// 权限路由
@ -17,15 +17,15 @@ import 'element-plus/dist/index.css'
// 引入暗黑模式 element-plus 2.2 内置暗黑模式
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.js";
import '@/assets/iconfont/iconfont.css'
import '@/assets/iconfont/iconfont.js'
const app = createApp(App)
registerElIcons(app)
app.component('svg-icon',SvgIcon)
app.component('SvgIcon', SvgIcon)
app.component('PageWrapLayout', PageWrapLayout)
app.use(pinia)

View File

@ -49,135 +49,148 @@ export const userData =[
describe: '普通测试用户',
createTime: '2022-09-02 15:30:20',
},
]
export const deptData = [
{
"id": "0",
"deptName": "华东分部",
"orderNo": 1,
"createTime": "2011-02-25 18:37:39",
"remark": "世上无难事,只要肯登攀",
"status": false,
"children": [{
"id": "0-0",
"deptName": "研发部",
"orderNo": 1,
"createTime": "1995-10-07 03:22:40",
"remark": "不曾扬帆,何以至远方",
"status": true,
"parentDept": "0"
}, {
"id": "0-1",
"deptName": "市场部",
"orderNo": 2,
"createTime": "1972-06-20 09:33:40",
"remark": "努力到无能为力,拼搏到感动自己",
"status": false,
"parentDept": "0"
}, {
"id": "0-2",
"deptName": "商务部",
"orderNo": 3,
"createTime": "1992-10-31 02:54:45",
"remark": "没有过不了的坎,就怕自己不奋斗",
"status": false,
"parentDept": "0"
}, {
"id": "0-3",
"deptName": "财务部",
"orderNo": 4,
"createTime": "1971-07-06 13:01:49",
"remark": "没有口水与汗水,就没有成功的泪水。",
"status": true,
"parentDept": "0"
}]
}, {
"id": "1",
"deptName": "华南分部",
"orderNo": 2,
"createTime": "1995-12-24 06:36:26",
"remark": "这个世界从来不缺乏机遇,而是缺少抓住机遇的手。",
"status": false,
"children": [{
"id": "1-0",
"deptName": "研发部",
"orderNo": 1,
"createTime": "2022-05-10 12:44:05",
"remark": "奋斗令我们的生活充满生机,责任让我们的生命充满意义!",
"status": true,
"parentDept": "1"
}, {
"id": "1-1",
"deptName": "市场部",
"orderNo": 2,
"createTime": "2022-07-15 02:53:29",
"remark": "学习之心不可无,懒惰之心不可有。",
"status": true,
"parentDept": "1"
id: '0',
deptName: '华东分部',
orderNo: 1,
createTime: '2011-02-25 18:37:39',
remark: '世上无难事,只要肯登攀',
status: false,
children: [
{
id: '0-0',
deptName: '研发部',
orderNo: 1,
createTime: '1995-10-07 03:22:40',
remark: '不曾扬帆,何以至远方',
status: true,
parentDept: '0',
},
{
"id": "1-2",
"deptName": "商务部",
"orderNo": 3,
"createTime": "2022-08-11 22:44:55",
"remark": "学会等待,学会坚持,成功是一个循序渐进的过程。",
"status": true,
"parentDept": "1"
}, {
"id": "1-3",
"deptName": "财务部",
"orderNo": 4,
"createTime": "2022-10-26 19:38:29",
"remark": "能克服困难的人,可使困难化为良机",
"status": false,
"parentDept": "1"
}
id: '0-1',
deptName: '市场部',
orderNo: 2,
createTime: '1972-06-20 09:33:40',
remark: '努力到无能为力,拼搏到感动自己',
status: false,
parentDept: '0',
},
{
id: '0-2',
deptName: '商务部',
orderNo: 3,
createTime: '1992-10-31 02:54:45',
remark: '没有过不了的坎,就怕自己不奋斗',
status: false,
parentDept: '0',
},
{
id: '0-3',
deptName: '财务部',
orderNo: 4,
createTime: '1971-07-06 13:01:49',
remark: '没有口水与汗水,就没有成功的泪水。',
status: true,
parentDept: '0',
},
],
},
{
id: '1',
deptName: '华南分部',
orderNo: 2,
createTime: '1995-12-24 06:36:26',
remark: '这个世界从来不缺乏机遇,而是缺少抓住机遇的手。',
status: false,
children: [
{
id: '1-0',
deptName: '研发部',
orderNo: 1,
createTime: '2022-05-10 12:44:05',
remark: '奋斗令我们的生活充满生机,责任让我们的生命充满意义!',
status: true,
parentDept: '1',
},
{
id: '1-1',
deptName: '市场部',
orderNo: 2,
createTime: '2022-07-15 02:53:29',
remark: '学习之心不可无,懒惰之心不可有。',
status: true,
parentDept: '1',
},
{
id: '1-2',
deptName: '商务部',
orderNo: 3,
createTime: '2022-08-11 22:44:55',
remark: '学会等待,学会坚持,成功是一个循序渐进的过程。',
status: true,
parentDept: '1',
},
{
id: '1-3',
deptName: '财务部',
orderNo: 4,
createTime: '2022-10-26 19:38:29',
remark: '能克服困难的人,可使困难化为良机',
status: false,
parentDept: '1',
},
],
},
{
id: '2',
deptName: '西北分部',
orderNo: 3,
createTime: '2022-08-27 16:49:21',
remark: '行为决定性格,性格决定命运',
status: false,
children: [
{
id: '2-0',
deptName: '研发部',
orderNo: 1,
createTime: '2022-12-11 03:49:33',
remark: '带着自己的梦,以一种骄傲的姿态走下去',
status: false,
parentDept: '2',
},
{
id: '2-1',
deptName: '市场部',
orderNo: 2,
createTime: '2022-06-18 20:15:34',
remark: '当世界都在说放弃的时候,轻轻的告诉自己:再试一次',
status: true,
parentDept: '2',
},
{
id: '2-2',
deptName: '商务部',
orderNo: 3,
createTime: '2022-07-17 09:37:41',
remark: '不怕万人阻挡在前方,只怕自己先行投降',
status: true,
parentDept: '2',
},
{
id: '2-3',
deptName: '财务部',
orderNo: 4,
createTime: '2022-11-23 04:34:33',
remark: '胸怀临云志,莫负少年时',
status: false,
parentDept: '2',
},
],
},
]
}, {
"id": "2",
"deptName": "西北分部",
"orderNo": 3,
"createTime": "2022-08-27 16:49:21",
"remark": "行为决定性格,性格决定命运",
"status": false,
"children": [{
"id": "2-0",
"deptName": "研发部",
"orderNo": 1,
"createTime": "2022-12-11 03:49:33",
"remark": "带着自己的梦,以一种骄傲的姿态走下去",
"status": false,
"parentDept": "2"
}, {
"id": "2-1",
"deptName": "市场部",
"orderNo": 2,
"createTime": "2022-06-18 20:15:34",
"remark": "当世界都在说放弃的时候,轻轻的告诉自己:再试一次",
"status": true,
"parentDept": "2"
}, {
"id": "2-2",
"deptName": "商务部",
"orderNo": 3,
"createTime": "2022-07-17 09:37:41",
"remark": "不怕万人阻挡在前方,只怕自己先行投降",
"status": true,
"parentDept": "2"
}, {
"id": "2-3",
"deptName": "财务部",
"orderNo": 4,
"createTime": "2022-11-23 04:34:33",
"remark": "胸怀临云志,莫负少年时",
"status": false,
"parentDept": "2"
}]
}]
export const roleData = [
{
@ -201,10 +214,8 @@ export const roleData =[
roleIdentification: 'other',
createTime: '2022-09-02 15:30:20',
},
]
export const menuData = [
{
menuName: '首页',
@ -265,10 +276,10 @@ export const menuData =[
id: 22,
level: 3,
createTime: '2022-09-02',
}
]
}
]
},
],
},
],
},
{
@ -320,10 +331,10 @@ export const menuData =[
id: 22,
level: 3,
createTime: '2022-09-02',
}
]
}
]
},
],
},
],
},
{
menuName: '基础组件',
@ -374,103 +385,103 @@ export const menuData =[
id: 22,
level: 3,
createTime: '2022-09-02',
}
]
}
]
},
],
},
],
},
]
export const dictionaryData = [
{
"id": 1,
"name": "性别",
"keyCode":'sex',
"createTime": "2011-02-25 18:37:39",
"remark": "性别",
id: 1,
name: '性别',
keyCode: 'sex',
createTime: '2011-02-25 18:37:39',
remark: '性别',
},
{
"id": 2,
"name": "证件类型",
"keyCode":'idType',
"createTime": "2011-02-25 18:37:39",
"remark": "证件类型",
}
id: 2,
name: '证件类型',
keyCode: 'idType',
createTime: '2011-02-25 18:37:39',
remark: '证件类型',
},
]
export const dictionaryDetailData = [
{
"id": 1,
"keyCode":'sex',
"name": "性别",
"createTime": "2011-02-25 18:37:39",
"remark": "性别",
"children":[
id: 1,
keyCode: 'sex',
name: '性别',
createTime: '2011-02-25 18:37:39',
remark: '性别',
children: [
{
"id": 11,
"name": "男",
"key":1,
"pid":1,
"createTime": "2011-02-25 18:37:39",
"remark": "男",
id: 11,
name: '男',
key: 1,
pid: 1,
createTime: '2011-02-25 18:37:39',
remark: '男',
},
{
"id": 12,
"name": "女",
"key":0,
"pid":1,
"createTime": "2011-02-25 18:37:39",
"remark": "女",
}
]
},
{
"id": 2,
"keyCode":'idType',
"name": "证件类型",
"createTime": "2011-02-25 18:37:39",
"remark": "证件类型",
"children":[
{
"id": 21,
"name": "身份证",
"key":1,
"pid":2,
"createTime": "2011-02-25 18:37:39",
"remark": "身份证",
},
{
"id": 22,
"name": "社保卡",
"key":2,
"pid":2,
"createTime": "2011-02-25 18:37:39",
"remark": "社保卡",
},
{
"id": 23,
"name": "驾驶证",
"key":3,
"pid":2,
"createTime": "2011-02-25 18:37:39",
"remark": "驾驶证",
},
{
"id": 24,
"name": "护照",
"key":4,
"pid":2,
"createTime": "2011-02-25 18:37:39",
"remark": "护照",
},
{
"id": 25,
"name": "工作证",
"key":5,
"pid":2,
"createTime": "2011-02-25 18:37:39",
"remark": "工作证",
}
]
}
id: 12,
name: '女',
key: 0,
pid: 1,
createTime: '2011-02-25 18:37:39',
remark: '女',
},
],
},
{
id: 2,
keyCode: 'idType',
name: '证件类型',
createTime: '2011-02-25 18:37:39',
remark: '证件类型',
children: [
{
id: 21,
name: '身份证',
key: 1,
pid: 2,
createTime: '2011-02-25 18:37:39',
remark: '身份证',
},
{
id: 22,
name: '社保卡',
key: 2,
pid: 2,
createTime: '2011-02-25 18:37:39',
remark: '社保卡',
},
{
id: 23,
name: '驾驶证',
key: 3,
pid: 2,
createTime: '2011-02-25 18:37:39',
remark: '驾驶证',
},
{
id: 24,
name: '护照',
key: 4,
pid: 2,
createTime: '2011-02-25 18:37:39',
remark: '护照',
},
{
id: 25,
name: '工作证',
key: 5,
pid: 2,
createTime: '2011-02-25 18:37:39',
remark: '工作证',
},
],
},
]

View File

@ -128,5 +128,5 @@ export const tableList = [
city: '杭州',
address: '杭州市滨江区建业路228号',
zip: 200433,
}
},
]

View File

@ -1,8 +1,8 @@
import router from '@/routers/index'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import {useUserStore} from "@/store/modules/user"
import {usePermissionStore} from "@/store/modules/permission"
import { useUserStore } from '@/store/modules/user'
import { usePermissionStore } from '@/store/modules/permission'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
@ -14,15 +14,14 @@ router.beforeEach(async(to, from, next) => {
// 开启进度条
NProgress.start()
// 设置标题
if(typeof(to.meta.title) === 'string'){
if (typeof to.meta.title === 'string') {
document.title = to.meta.title || 'vue-admin-perfect'
}
const UserStore = useUserStore();
const UserStore = useUserStore()
// 确定用户是否已登录过存在Token
const hasToken = UserStore.token
if (hasToken) {
if (to.path === '/login') {
// 如果已登录,请重定向到主页
@ -35,7 +34,7 @@ router.beforeEach(async(to, from, next) => {
// 获取权限列表进行接口访问 因为这里页面要切换权限
const accessRoutes = await PermissionStore.generateRoutes(UserStore.roles)
hasRoles = false
accessRoutes.forEach(item => router.addRoute(item)) // 动态添加访问路由表
accessRoutes.forEach((item) => router.addRoute(item)) // 动态添加访问路由表
next({ ...to, replace: true }) // // 这里相当于push到一个页面 不在进入路由拦截
} else {
next() // // 如果不传参数就会重新执行路由拦截,重新进到这里
@ -54,7 +53,5 @@ router.beforeEach(async(to, from, next) => {
})
router.afterEach(() => {
NProgress.done();
});
NProgress.done()
})

View File

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

View File

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

View File

@ -1,24 +1,26 @@
/** 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',
component: Layout,
redirect: '/chat/index',
name: 'chat',
meta: {
title: '聊天框',
icon: 'chat-square'
icon: 'chat-square',
},
children: [
{
path: '/chat/index',
component: () => import('@/views/chat/index.vue'),
name: 'chatBox',
meta: { title: '聊天框', icon: 'chat-square' }
}
meta: { title: '聊天框', icon: 'chat-square' },
},
],
},
]
}]
export default chartsRouter

View File

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

View File

@ -1,8 +1,9 @@
/** 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',
component: Layout,
redirect: '/echarts/migration',
@ -10,43 +11,43 @@ const echartsRouter = [{
meta: {
title: 'Echarts',
icon: 'trend-charts',
roles:['other']
roles: ['other'],
},
children: [
{
path: '/echarts/migration',
component: () => import('@/views/echarts/migrationMap/index.vue'),
name: 'migration',
meta: { title: '迁徙图', roles:['other'] , icon: 'MenuIcon' }
meta: { title: '迁徙图', roles: ['other'], icon: 'MenuIcon' },
},
{
path: '/echarts/bar',
component: () => import('@/views/echarts/barEcharts/index.vue'),
name: 'bar',
meta: { title: '柱状图', roles:['other'] , icon: 'MenuIcon'}
meta: { title: '柱状图', roles: ['other'], icon: 'MenuIcon' },
},
{
path: '/echarts/graph',
component: () => import('@/views/echarts/graphEcharts/index.vue'),
name: 'graph',
meta: { title: '雷达图', roles:['other'] , icon: 'MenuIcon'}
meta: { title: '雷达图', roles: ['other'], icon: 'MenuIcon' },
},
{
path: '/echarts/pie',
component: () => import('@/views/echarts/pieEcharts/index.vue'),
name: 'pie',
meta: { title: '饼图', roles:['other'] , icon: 'MenuIcon'}
meta: { title: '饼图', roles: ['other'], icon: 'MenuIcon' },
},
{
path: '/echarts/simple',
component: () => import('@/views/echarts/simple/index.vue'),
name: 'echarts-simple',
meta: { title: '简单图表', roles:['other'] , icon: 'MenuIcon'}
meta: { title: '简单图表', roles: ['other'], icon: 'MenuIcon' },
},
],
},
]
}]
export default echartsRouter

View File

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

View File

@ -1,15 +1,14 @@
import Layout from '@/layout/index.vue'
import Layout from "@/layout/index.vue";
const externalLink = [{
const externalLink = [
{
path: '/external-link',
component: Layout,
redirect: '/external-link/wechat',
name: 'external-link',
meta: {
title: '外部链接',
icon: 'link'
icon: 'link',
},
children: [
// {
@ -21,12 +20,12 @@ const externalLink = [{
{
path: 'https://github.com/zouzhibin/vue-admin-perfect',
name: 'github',
meta: { title: 'Github 地址', icon: 'MenuIcon' }
meta: { title: 'Github 地址', icon: 'MenuIcon' },
},
{
path: 'https://gitee.com/yuanzbz/vue-admin-perfect?_from=gitee_search',
name: 'github',
meta: { title: 'Gitee 地址', icon: 'MenuIcon' }
meta: { title: 'Gitee 地址', icon: 'MenuIcon' },
},
{
path: 'https://yuanzbz.gitee.io/vue-admin-simple',
@ -38,9 +37,10 @@ const externalLink = [{
path: '/external-link/iframe',
component: () => import('@/views/externalLinks/iframe/index.vue'),
name: 'iframe',
meta: { title: '内嵌 iframe', icon: 'MenuIcon' }
meta: { title: '内嵌 iframe', icon: 'MenuIcon' },
},
],
},
]
}]
export default externalLink

View File

@ -1,49 +1,50 @@
/** 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',
component: Layout,
redirect: '/function-page/404',
name: 'function-page',
meta: {
title: '功能页面',
icon: 'ElementPlus'
icon: 'ElementPlus',
},
children: [
{
path: '/function-page/tools',
component: () => import('@/views/functionPage/tools/index.vue'),
name: 'tools',
meta: { title: '工具链集合', keepAlive: true , icon: 'MenuIcon'}
meta: { title: '工具链集合', keepAlive: true, icon: 'MenuIcon' },
},
{
path: '/function-page/404',
component: () => import('@/views/errorPages/404.vue'),
name: 'function-404',
meta: { title: '404 页面', keepAlive: true , icon: 'MenuIcon'}
meta: { title: '404 页面', keepAlive: true, icon: 'MenuIcon' },
},
{
path: '/function-page/403',
component: () => import('@/views/errorPages/403.vue'),
name: 'function-403',
meta: { title: '403 页面', keepAlive: true , icon: 'MenuIcon'}
meta: { title: '403 页面', keepAlive: true, icon: 'MenuIcon' },
},
{
path: '/function-page/fullscreen',
component: () => import('@/views/functionPage/fullscreen/index.vue'),
name: 'fullscreen',
meta: { title: '元素 全屏', keepAlive: true , icon: 'MenuIcon'}
meta: { title: '元素 全屏', keepAlive: true, icon: 'MenuIcon' },
},
{
path: '/function-page/information-list',
component: () => import('@/views/functionPage/informationList/index.vue'),
name: 'informationList',
meta: { title: '信息列表', keepAlive: true , icon: 'MenuIcon'}
meta: { title: '信息列表', keepAlive: true, icon: 'MenuIcon' },
},
],
},
]
}]
export default functionPageRouter

View File

@ -1,17 +1,16 @@
/** 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',
component: Layout,
redirect: '/nested/menu1',
name: 'nested',
meta: {
title: '路由嵌套',
icon: 'HelpFilled'
icon: 'HelpFilled',
},
children: [
{
@ -26,7 +25,7 @@ const nestedRouter = [{
path: '/nested/menu1/menu1-1',
component: () => import('@/views/nested/menu1/menu1-1/index.vue'),
name: 'menu1-1',
meta: { title: '菜单 1-1' , icon: 'MenuIcon'}
meta: { title: '菜单 1-1', icon: 'MenuIcon' },
},
{
path: '/nested/menu1/menu1-2',
@ -39,32 +38,32 @@ const nestedRouter = [{
path: '/nested/menu1/menu1-2/menu1-2-1',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1/index.vue'),
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',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2/index.vue'),
name: 'menu1-2-2',
meta: { title: '菜单 1-2-2' , icon: 'MenuIcon'}
}
]
meta: { title: '菜单 1-2-2', icon: 'MenuIcon' },
},
],
},
{
path: '/nested/menu1/menu1-3',
component: () => import('@/views/nested/menu1/menu1-3/index.vue'),
name: 'menu1-3',
meta: { title: '菜单 1-3' , icon: 'MenuIcon'}
}
]
meta: { title: '菜单 1-3', icon: 'MenuIcon' },
},
],
},
{
path: '/nested/menu2',
component: () => import('@/views/nested/menu2/index.vue'),
name: 'nested-menu2',
meta: { title: '菜单2', icon: 'MenuIcon'}
meta: { title: '菜单2', icon: 'MenuIcon' },
},
],
},
]
}]
export default nestedRouter

View File

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

View File

@ -6,21 +6,18 @@ export const Store = defineStore({
// id: 必须的,在所有 Store 中唯一
id: 'globalState',
// state: 返回对象的函数
state: ()=>({
}),
state: () => ({}),
getters: {},
actions: {},
persist: {
// 本地存储的名称
key: "globalState",
key: 'globalState',
//保存的位置
storage: window.sessionStorage, //localstorage
},
})
const pinia = createPinia();
const pinia = createPinia()
//pinia使用
pinia.use(piniaPluginPersistedstate);
pinia.use(piniaPluginPersistedstate)
export default pinia

View File

@ -1,7 +1,7 @@
import { defineStore } from 'pinia'
import { asyncRoutes, constantRoutes, routerArray, notFoundRouter } from '@/routers/index'
import {hasPermission,filterAsyncRoutes} from "@/utils/routers"
import {filterKeepAlive,filterRoutes} from "@/utils/routers";
import { hasPermission, filterAsyncRoutes } from '@/utils/routers'
import { filterKeepAlive, filterRoutes } from '@/utils/routers'
export const usePermissionStore = defineStore({
// id: 必须的,在所有 Store 中唯一
id: 'permissionState',
@ -15,18 +15,18 @@ export const usePermissionStore = defineStore({
cacheRoutes: {},
}),
getters: {
permission_routes:state=> {
permission_routes: (state) => {
return state.routes
},
keepAliveRoutes: state=>{
keepAliveRoutes: (state) => {
return filterKeepAlive(asyncRoutes)
}
},
},
// 可以同步 也可以异步
actions: {
// 生成路由
generateRoutes(roles) {
return new Promise(resolve => {
return new Promise((resolve) => {
// 在这判断是否有权限,哪些角色拥有哪些权限
let accessedRoutes
if (roles && roles.length && !roles.includes('admin')) {
@ -49,10 +49,6 @@ export const usePermissionStore = defineStore({
getCacheRoutes() {
this.cacheRoutes = filterKeepAlive(asyncRoutes)
return this.cacheRoutes
}
},
},
})

View File

@ -1,6 +1,5 @@
import { defineStore } from 'pinia'
import {PRIMARY_COLOR} from "../../config";
import { PRIMARY_COLOR } from '../../config'
export const useSettingStore = defineStore({
// id: 必须的,在所有 Store 中唯一
@ -39,7 +38,7 @@ export const useSettingStore = defineStore({
// 灰色模式
gray: false,
// 色弱模式
weak:false
weak: false,
},
}),
getters: {},
@ -68,7 +67,7 @@ export const useSettingStore = defineStore({
setTimeout(() => {
this.isReload = true
}, 50)
}
},
},
// 这部分数据不需要存储
// persist: {
@ -77,5 +76,4 @@ export const useSettingStore = defineStore({
// //保存的位置
// storage: window.localStorage,//localstorage
// },
})

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import router from "@/routers/index";
import router from '@/routers/index'
export const useTagsViewStore = defineStore({
// id: 必须的,在所有 Store 中唯一
@ -9,7 +9,6 @@ export const useTagsViewStore = defineStore({
activeTabsValue: '/home',
visitedViews: [],
cachedViews: [],
}),
getters: {},
// 可以同步 也可以异步
@ -22,83 +21,79 @@ export const useTagsViewStore = defineStore({
},
removeView(routes) {
return new Promise((resolve, reject) => {
this.visitedViews = this.visitedViews.filter(item=>!routes.includes(item.path))
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.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'
})
title: view.meta.title || 'no-name',
}),
)
if (view.meta.keepAlive) {
this.cachedViews.push(view.name)
}
},
delView(activeTabPath) {
return new Promise(resolve => {
return new Promise((resolve) => {
this.delVisitedView(activeTabPath)
this.delCachedView(activeTabPath)
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews]
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);
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)
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)
this.cachedViews = this.cachedViews.filter((v) => {
return v.path !== path || v.meta.affix
})
resolve([...this.visitedViews])
})
},
delCachedView(view) {
return new Promise(resolve => {
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)
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;
});
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'});
this.activeTabsValue = '/home'
router.push({ path: '/home' })
},
updateVisitedView(view) {
for (let v of this.visitedViews) {
@ -107,7 +102,6 @@ export const useTagsViewStore = defineStore({
break
}
}
}
},
},
})

View File

@ -1,6 +1,5 @@
import { defineStore } from 'pinia'
export const useUserStore = defineStore({
// id: 必须的,在所有 Store 中唯一
id: 'userState',
@ -11,8 +10,7 @@ export const useUserStore = defineStore({
// 登录用户信息
userInfo: {},
// 角色
roles:localStorage.roles?JSON.parse(localStorage.roles):[]
roles: localStorage.roles ? JSON.parse(localStorage.roles) : [],
}),
getters: {},
// 可以同步 也可以异步
@ -52,14 +50,12 @@ export const useUserStore = defineStore({
resolve(null)
})
},
},
// 进行持久化存储
persist: {
// 本地存储的名称
key: "userState",
key: 'userState',
//保存的位置
storage: window.localStorage, //localstorage
},
})

View File

@ -12,7 +12,6 @@ body{
background: #f0f2f5;
}
/* 常用 flex */
.flex-center {
display: flex;
@ -36,14 +35,12 @@ body{
}
}
/** 设置滚动条 **/
::-webkit-scrollbar {
width: 7px;
height: 8px;
}
::-webkit-scrollbar-track {
background-color: rgb(0 0 0 / 5%);
}
@ -56,7 +53,6 @@ body{
box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);
}
/* nprogress样式 */
#nprogress .bar {
background: $primaryColor !important;

View File

@ -7,14 +7,14 @@ html.dark {
--zb-border-light: 1px solid #4c4d4f;
body {
background: none;
}
// 编辑器
.w-e-toolbar, .w-e-text-container, .w-e-menu-panel{
.w-e-toolbar,
.w-e-text-container,
.w-e-menu-panel {
background: none !important;
}
// 富文本
@ -44,8 +44,6 @@ html.dark {
}
}
.sidebar-logo-container {
background: none;
box-sizing: border-box;
@ -60,7 +58,6 @@ html.dark {
color: var(--el-text-color-primary) !important;
}
.el-table__header th {
font-weight: bold;
color: white;
@ -70,7 +67,6 @@ html.dark {
color: white;
}
.zb-pro-table {
.header {
background: none !important;
@ -106,8 +102,6 @@ html.dark {
border-bottom: none !important;
}
}
}
// 内容区
@ -150,8 +144,6 @@ html.dark {
border-top: var(--zb-border-light);
}
// 登录
.login-container {
background-color: #191919 !important;

View File

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

View File

@ -57,7 +57,6 @@
overflow: hidden;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
@ -225,4 +224,3 @@
}
}
}

View File

@ -38,8 +38,6 @@
opacity: 0;
}
// 面包屑动画 方案1
.breadcrumb-enter-active {
transition: all 0.25s;

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
import { ElLoading } from 'element-plus'
import {
Loading
} from '@element-plus/icons-vue'
import { Loading } from '@element-plus/icons-vue'
let loading = null
export const openLoading = (options = {}) => {

View File

@ -1,34 +1,36 @@
import ExcelJS from 'exceljs'
const autoWidthAction = (val, width = 10) => {
if (val == null) {
width = 10;
width = 10
} else if (val.toString().charCodeAt(0) > 255) {
/*if chinese*/
width = val.toString().length * 2;
width = val.toString().length * 2
} 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();
const workbook = new ExcelJS.Workbook()
// 设置信息
workbook.creator = "Me";
workbook.title = filename;
workbook.created = new Date();
workbook.modified = new Date();
workbook.creator = 'Me'
workbook.title = filename
workbook.created = new Date()
workbook.modified = new Date()
// 创建工作表
const worksheet = workbook.addWorksheet(filename);
const worksheet = workbook.addWorksheet(filename)
// 设置列名
const columnsName = [];
const columnsName = []
column.forEach((item, index) => {
const obj = {
header: item.label, key:item.name, width: null
header: item.label,
key: item.name,
width: null,
}
if (autoWidth) {
const maxArr = [autoWidthAction(item.label)]
data.forEach(ite=>{
data.forEach((ite) => {
const str = ite[item.name] || ''
if (str) {
maxArr.push(autoWidthAction(str))
@ -37,39 +39,36 @@ export const exportExcel = async ({column,data,filename,autoWidth,format})=>{
obj.width = Math.max(...maxArr) + 5
}
// 设置列名、键和宽度
columnsName.push(obj);
columnsName.push(obj)
})
worksheet.columns = columnsName;
worksheet.columns = columnsName
// 添加行
worksheet.addRows(data);
worksheet.addRows(data)
// 写入文件
const uint8Array =
format === "xlsx"
? await workbook.xlsx.writeBuffer()
: await workbook.csv.writeBuffer();
const uint8Array = format === 'xlsx' ? await workbook.xlsx.writeBuffer() : await workbook.csv.writeBuffer()
const blob = new Blob([uint8Array], { type: "application/octet-binary" });
const blob = new Blob([uint8Array], { type: 'application/octet-binary' })
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + `.${format}`);
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); // 释放内存
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) {
const {color, fontSize, horizontal, bold} = attr || {};
const { color, fontSize, horizontal, bold } = attr || {}
// eslint-disable-next-line no-param-reassign
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: color },
};
}
// eslint-disable-next-line no-param-reassign
cell.font = {
bold: bold ?? true,
@ -77,30 +76,32 @@ export function addCellStyle(cell, attr) {
// italic: true,
// name: '微软雅黑',
color: { argb: 'ff0000' },
};
}
// 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 }) => {
// 创建excel
const workbook = new ExcelJS.Workbook();
const workbook = new ExcelJS.Workbook()
// 设置信息
workbook.creator = "Me";
workbook.title = filename;
workbook.created = new Date();
workbook.modified = new Date();
workbook.creator = 'Me'
workbook.title = filename
workbook.created = new Date()
workbook.modified = new Date()
// 创建工作表
const worksheet = workbook.addWorksheet(filename);
const worksheet = workbook.addWorksheet(filename)
// 设置列名
const columnsName = [];
const columnsName = []
column.forEach((item, index) => {
const obj = {
header: item.label, key:item.name, width: null
header: item.label,
key: item.name,
width: null,
}
if (autoWidth) {
const maxArr = [autoWidthAction(item.label)]
data.forEach(ite=>{
data.forEach((ite) => {
const str = ite[item.name] || ''
if (str) {
maxArr.push(autoWidthAction(str))
@ -109,79 +110,64 @@ export const exportStyleExcel =async ({column,data,filename,autoWidth,format})=>
obj.width = Math.max(...maxArr) + 5
}
// 设置列名、键和宽度
columnsName.push(obj);
columnsName.push(obj)
})
worksheet.columns = columnsName;
worksheet.columns = columnsName
// 添加行
worksheet.addRows(data);
worksheet.addRows(data)
// 写入文件
// 设置表头颜色
// 给表头添加背景色。因为表头是第一行,可以通过 getRow(1) 来获取表头这一行
const headerRow = worksheet.getRow(1);
const headerRow = worksheet.getRow(1)
// 通过 cell 设置样式,更精准
headerRow.eachCell((cell) => addCellStyle(cell, {color: 'dff8ff', fontSize: 12, horizontal: 'left'}));
headerRow.eachCell((cell) => addCellStyle(cell, { color: 'dff8ff', fontSize: 12, horizontal: 'left' }))
const uint8Array =
format === "xlsx"
? await workbook.xlsx.writeBuffer()
: await workbook.csv.writeBuffer();
const uint8Array = format === 'xlsx' ? await workbook.xlsx.writeBuffer() : await workbook.csv.writeBuffer()
const blob = new Blob([uint8Array], { type: "application/octet-binary" });
const blob = new Blob([uint8Array], { type: 'application/octet-binary' })
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + `.${format}`);
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); // 释放内存
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) {
// 需要的列数,四舍五入
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 workbook = new ExcelJS.Workbook()
// 创建工作表
const sheet = workbook.addWorksheet("sheet1");
const sheet = workbook.addWorksheet('sheet1')
// 添加表头
sheet.getRow(1).values = ["序号", "日期","地址" ,"配送消息" ,,, ];
sheet.getRow(2).values = [
"序号",
"日期",
"地址",
"省份",
"城市",
"邮编"
];
const headers = [];
sheet.getRow(1).values = ['序号', '日期', '地址', '配送消息', , ,]
sheet.getRow(2).values = ['序号', '日期', '地址', '省份', '城市', '邮编']
const headers = []
column.forEach((item, index) => {
if (item.children) {
item.children.forEach(itemChild=>{
item.children.forEach((itemChild) => {
const obj = {
key:itemChild.name, width: null
key: itemChild.name,
width: null,
}
const maxArr = [autoWidthAction(itemChild.label)]
data.forEach(ite=>{
data.forEach((ite) => {
const str = ite[itemChild.name] || ''
if (str) {
maxArr.push(autoWidthAction(str))
@ -189,15 +175,15 @@ export const exportMultiHeaderExcel = ({column,data,filename,autoWidth})=>{
})
obj.width = Math.max(...maxArr) + 5
// 设置列名、键和宽度
headers.push(obj);
headers.push(obj)
})
} else {
const obj = {
key:item.name, width: null
key: item.name,
width: null,
}
const maxArr = [autoWidthAction(item.label)]
data.forEach(ite=>{
data.forEach((ite) => {
const str = ite[item.name] || ''
if (str) {
maxArr.push(autoWidthAction(str))
@ -205,75 +191,62 @@ export const exportMultiHeaderExcel = ({column,data,filename,autoWidth})=>{
})
obj.width = Math.max(...maxArr) + 5
// 设置列名、键和宽度
headers.push(obj);
headers.push(obj)
}
})
sheet.columns = headers;
sheet.addRows(data);
sheet.columns = headers
sheet.addRows(data)
// 合并单元格
sheet.mergeCells(`D1:F1`);
sheet.mergeCells("A1:A2");
sheet.mergeCells("B1:B2");
sheet.mergeCells("C1:C2");
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" }]);
const blob = new Blob([data, { type: 'application/vnd.ms-excel' }])
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + ".xlsx");
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); // 释放内存
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;
let pointer = -1
nameRow1.forEach((name, index) => {
// 当 index 小于指针时,说明这一列已经被合并过了,不能再合并
if (index <= pointer) return;
if (index <= pointer) return
// 是否应该列合并
const shouldVerticalMerge = name === nameRow2[index];
const shouldVerticalMerge = name === nameRow2[index]
// 是否应该行合并
const shouldHorizontalMerge = index !== nameRow1.lastIndexOf(name);
const shouldHorizontalMerge = index !== nameRow1.lastIndexOf(name)
console.log('==', name, nameRow2[index], index, nameRow1.lastIndexOf(name), shouldVerticalMerge, shouldHorizontalMerge)
pointer = nameRow1.lastIndexOf(name);
pointer = nameRow1.lastIndexOf(name)
if (shouldVerticalMerge && shouldHorizontalMerge) {
// 两个方向都合并
worksheet.mergeCells(
Number(rowHeader1.number),
index + 1,
Number(rowHeader2.number),
nameRow1.lastIndexOf(name) + 1,
);
worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader2.number), nameRow1.lastIndexOf(name) + 1)
console.log('==')
} else if (shouldVerticalMerge && !shouldHorizontalMerge) {
// 只在垂直方向上同一列的两行合并
worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader2.number), index + 1);
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,
);
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 };
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
* @param {(Object|string|number)} time
@ -79,9 +78,7 @@ export function formatTime(time, option) {
if (option) {
return parseTime(time, option)
} else {
return (
d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
)
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
}
}
@ -111,7 +108,7 @@ export function getQueryObject(url) {
export function byteLength(str) {
// returns the byte length of an utf8 string
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)
if (code > 0x7f && code <= 0x7ff) s++
else if (code > 0x7ff && code <= 0xffff) s += 2
@ -217,8 +214,7 @@ export function toggleClass(element, className) {
if (nameIndex === -1) {
classString += '' + className
} else {
classString =
classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length)
classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length)
}
element.className = classString
}
@ -347,10 +343,10 @@ export function removeClass(ele, cls) {
}
export function getColor() {
var str = '#'
var arr = ['1', '2', '3', '4', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
for (var i = 0; i < 6; i++) {
var num = parseInt(Math.random() * 16)
let str = '#'
const arr = ['1', '2', '3', '4', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
for (let i = 0; i < 6; i++) {
const num = parseInt(Math.random() * 16)
str += arr[num]
}
return str
@ -360,14 +356,14 @@ export const isArray = function (value) {
return objToString.call(value) === '[object Array]'
}
let funProto = Function.prototype
let objProto = Object.prototype
const funProto = Function.prototype
const objProto = Object.prototype
let getPrototypeOf = Object.getPrototypeOf
const getPrototypeOf = Object.getPrototypeOf
let objToString = objProto.toString
let hasOwnProperty = objProto.hasOwnProperty
let funToString = funProto.toString
const objToString = objProto.toString
const hasOwnProperty = objProto.hasOwnProperty
const funToString = funProto.toString
// 检查给定的值是否是字符串
export const isString = function (value) {
return objToString.call(value) === '[object String]'
@ -378,22 +374,20 @@ export const isPlainObject = function (value) {
return false
}
let prototype = getPrototypeOf(value)
const prototype = getPrototypeOf(value)
if (prototype === null) {
return true
}
let constructor = hasOwnProperty.call(prototype, 'constructor') && prototype.constructor
const constructor = hasOwnProperty.call(prototype, 'constructor') && prototype.constructor
return (
typeof constructor === 'function' && funToString.call(constructor) === funToString.call(Object)
)
return typeof constructor === 'function' && funToString.call(constructor) === funToString.call(Object)
}
// // 深度克隆 array 数组或 json 对象,返回克隆后的副本
export const deepObjClone = function (obj) {
let weakMap = new WeakMap()
const weakMap = new WeakMap()
function clone(obj) {
if (obj == null) {
return obj
@ -409,11 +403,11 @@ export const deepObjClone = function (obj) {
if (weakMap.get(obj)) {
return weakMap.get(obj)
}
let copy = new obj.constructor()
const copy = new obj.constructor()
weakMap.set(obj, copy)
for (let key in obj) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
let value = obj[key]
const value = obj[key]
copy[key] = clone(value)
}
}
@ -422,13 +416,12 @@ export const deepObjClone = function (obj) {
return clone(obj)
}
export function getTimeStateStr() {
let timeNow = new Date();
let hours = timeNow.getHours();
if (hours >= 6 && hours <= 10) return `早上好`;
if (hours >= 10 && hours <= 14) return `中午好`;
if (hours >= 14 && hours <= 18) return `下午好`;
if (hours >= 18 && hours <= 24) return `晚上好`;
if (hours >= 0 && hours <= 6) return `凌晨好`;
const timeNow = new Date()
const hours = timeNow.getHours()
if (hours >= 6 && hours <= 10) return `早上好`
if (hours >= 10 && hours <= 14) return `中午好`
if (hours >= 14 && hours <= 18) return `下午好`
if (hours >= 18 && hours <= 24) return `晚上好`
if (hours >= 0 && hours <= 6) return `凌晨好`
}

View File

@ -1,4 +1,4 @@
import raf from "raf";
import raf from 'raf'
/**
* requestAnimationFrame 节流
@ -6,20 +6,20 @@ import raf from "raf";
* 这里使用 requestAnimationFrame 来优化滚动处理在一帧中只进行一次有效重绘
*/
export default function requestAnimationFrameThrottle(callback) {
let id;
let id
const factory = args => () => {
id = null;
callback(...args);
};
const factory = (args) => () => {
id = null
callback(...args)
}
const throttled = (...args) => {
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)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm &&
this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
destroyListener() {
window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null
this.$_sidebarElm &&
this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
resize() {
const { chart } = this

View File

@ -6,7 +6,7 @@ import path from 'path-browserify'
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
routes.forEach((route) => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
@ -25,7 +25,7 @@ export function filterAsyncRoutes(routes, roles) {
*/
export function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
return roles.some((role) => route.meta.roles.includes(role))
} else {
return false
}
@ -39,9 +39,9 @@ export function hasPermission(roles, route) {
* */
export function filterKeepAlive(routers) {
let cacheRouter: any[] = [];
let deep = (routers)=>{
routers.forEach(item=>{
const cacheRouter: any[] = []
const deep = (routers) => {
routers.forEach((item) => {
if (item.meta?.keepAlive && item.name) {
cacheRouter.push(item.name)
}
@ -54,13 +54,8 @@ export function filterKeepAlive(routers){
return cacheRouter
}
export function handleRoutes(routers, pathUrl = '') {
routers.forEach(item=>{
routers.forEach((item) => {
item.path = path.resolve(pathUrl, item.path)
if(item.children&&item.children.length){
}
})
}

View File

@ -1,4 +1,3 @@
/**
* @param {string} path
* @returns {Boolean}
@ -85,7 +84,6 @@ export function isArray(arg) {
return Array.isArray(arg)
}
/**
*
* @param val
@ -93,12 +91,11 @@ export function isArray(arg) {
*/
export function verifyPhone(val: string) {
// false: 手机号码不正确
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0|1,5-9]))\d{8}$/.test(val)) return false;
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0|1,5-9]))\d{8}$/.test(val)) return false
// true: 手机号码正确
else return true;
else return true
}
/**
*
* @param val
@ -108,9 +105,9 @@ export function verifyPhone(val: string) {
*/
export function verifyTextColor(val: string, text = '', color = 'red') {
// 返回内容,添加颜色
let v = text.replace(new RegExp(val, 'gi'), `<span style='color: ${color}'>${val}</span>`);
const v = text.replace(new RegExp(val, 'gi'), `<span style='color: ${color}'>${val}</span>`)
// 返回结果
return v;
return v
}
/**
@ -119,7 +116,7 @@ export function verifyTextColor(val: string, text = '', color = 'red') {
* @returns true:
*/
export function verifyIdCard(val: string) {
let regx = /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/
const regx = /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/
return regx.test(val)
}
@ -129,18 +126,17 @@ export function verifyIdCard(val:string) {
* @returns true:
*/
export function verifyWebsite(val: string) {
let regx = /^((https?|ftp):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\/\w\.-]*)*\/?/
const regx = /^((https?|ftp):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\/\w\.-]*)*\/?/
return regx.test(val)
}
/**
* html标签
* @param val
* @returns true: html标签
*/
export function verifyHtml(val: string) {
let regx = /<(.*)>.*<\/\1>|<(.*) \/>/
const regx = /<(.*)>.*<\/\1>|<(.*) \/>/
return regx.test(val)
}
@ -150,7 +146,8 @@ export function verifyHtml(val:string) {
* @returns true:
*/
export function verifyDate(val: string) {
let regx = /^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;
const regx =
/^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/
return regx.test(val)
}
@ -160,12 +157,11 @@ export function verifyDate(val:string) {
* @returns true:
*/
export function verifyEmail(val: string) {
let regx = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
const regx =
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
return regx.test(val)
}
/**
*
* @param verifyPhone
@ -180,5 +176,3 @@ export function validatorMethod(verifyPhone:(string)=>boolean,message:string) {
}
}
}

View File

@ -11,7 +11,7 @@ function watermark (options) {
globalAlpha = 0.3, // 设置图形和图像透明度的值
rotate = 16, // 文字旋转角度
zIndex = 1000, // 元素堆叠顺序
isCancel =true
isCancel = true,
} = options
const canvas = document.createElement('canvas')
@ -48,7 +48,6 @@ function watermark (options) {
container.style.position = 'relative'
container.appendChild(watermarkDiv) // 添加元素
// 监听删除 防止用户去手动删除,如果手动删除 ,在重新添加
// const MutationObserver = window.MutationObserver || window.WebKitMutationObserver
// // 检查浏览器是否支持这个API

View File

@ -1,4 +1,3 @@
import wePng from '@/assets/image/we.png'
export const chatData = [
{
@ -15,5 +14,4 @@ export const chatData = [
id: 2,
type: 1, // 文字
},
]

View File

@ -1,40 +1,30 @@
<template>
<div class="g-container-chat">
<div class="g-layout-content-center" style="width: 100%" ref="chatContentBoxs">
<div ref="chatContentBoxs" class="g-layout-content-center" style="width: 100%">
<div v-for="(item, index) in chatDatas" :key="item.id" style="padding-top: 15px">
<div class="from_user_info" v-if="item.is_self === 0">
<div v-if="item.is_self === 0" class="from_user_info">
<div class="contact-nickname">{{ item.created_at }} 林峰</div>
<div class="receive-message-wrap">
<div class="avatar-show">
<img src="@/assets/image/avatar.png" />
</div>
<div style="position: relative; display: flex">
<div class="receive-message-info" v-html="item.content" v-if="item.type===1"></div>
<div class="public-show-pic" v-else-if="item.type === 2">
<el-image
:src="item.content"
:preview-src-list="[item.content]"
style="max-width: 200px"
:data-resid="Date.now()"
/>
<div v-if="item.type === 1" class="receive-message-info" v-html="item.content"></div>
<div v-else-if="item.type === 2" class="public-show-pic">
<el-image :src="item.content" :preview-src-list="[item.content]" style="max-width: 200px" :data-resid="Date.now()" />
</div>
</div>
</div>
</div>
<div class="ower-user_info" v-else>
<div v-else class="ower-user_info">
<div class="contact-nickname">{{ item.created_at }} 林峰</div>
<div class="receive-message-wrap public-show-pic">
<div style="position: relative; display: flex">
<div class="receive-message-info" v-if="item.type == 1">
<div v-if="item.type == 1" class="receive-message-info">
<div class="text-content" v-html="item.content"></div>
</div>
<div class="public-show-pic" v-else-if="item.type === 2">
<el-image
:src="item.content"
:preview-src-list="[item.content]"
style="max-width: 200px"
:data-resid="Date.now()"
/>
<div v-else-if="item.type === 2" class="public-show-pic">
<el-image :src="item.content" :preview-src-list="[item.content]" style="max-width: 200px" :data-resid="Date.now()" />
</div>
</div>
<div class="avatar-show">
@ -45,12 +35,7 @@
</div>
</div>
<u-toolbar @insert="insertInput" @upload="upload" />
<div
@blur="contentBlur"
ref="contenteditableInputs"
class="g-chat-container-footer-input"
contenteditable="true"
></div>
<div ref="contenteditableInputs" class="g-chat-container-footer-input" contenteditable="true" @blur="contentBlur"></div>
<div class="g-chat-container-footer-btn">
<div class="" style="margin-right: 10px"> Ctrl+V粘贴, Ctrl+Enter换行 </div>
<el-button @click="sendInfoAction">发送</el-button>

View File

@ -9,13 +9,8 @@
<el-icon style="font-size: 20px"><FolderOpened /></el-icon>
</el-upload>
</div>
<div class="emoje-border-wrap" v-show="isShowEmoji">
<div
class="title-emoje"
v-for="(item, index) in emojis"
@click.stop="selectSigleEmojeAction($event, item)"
:key="index"
>
<div v-show="isShowEmoji" class="emoje-border-wrap">
<div v-for="(item, index) in emojis" :key="index" class="title-emoje" @click.stop="selectSigleEmojeAction($event, item)">
<img :src="`static/face/${item}.png`" :title="item" />
</div>
</div>
@ -39,7 +34,7 @@
node.title = item
emit('insert', node, 'IMG')
} else {
emit('insert', node)
emit('insert')
}
}
@ -48,7 +43,7 @@
emit('insert', null, null)
}
const beforeUploadAction = (file, fileList) => {
const beforeUploadAction = (file) => {
return new Promise((resolve, reject) => {
var reader = new FileReader()
let reg = /\.jpg$|\.jpeg$|\.gif$|\.png$/i
@ -67,7 +62,7 @@
}
onMounted(() => {
window.onclick = function (event) {
window.onclick = function () {
isShowEmoji.value = false
}
})

View File

@ -1,9 +1,7 @@
<template>
<div class="app-container">
<div class="app-container-inner m-chat">
<div style="margin-bottom: 15px;flex-shrink: 0"
>聊天内容框功能有发送emoji表情上传图片发送图片内容滚动发送文字</div
>
<div style="margin-bottom: 15px; flex-shrink: 0">聊天内容框功能有发送emoji表情上传图片发送图片内容滚动发送文字</div>
<u-chat-box class="m-chat-box" />
</div>
</div>

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