基础模块升级
commit
ff11cae03f
|
|
@ -0,0 +1,13 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
VITE_PROXY=/api
|
||||
VITE_FILE_URL=https://file.zcloudchina.com/KjkFile
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
VITE_BASE=/
|
||||
VITE_BASE_URL=http://192.168.0.14:8059/new_template/
|
||||
|
|
@ -0,0 +1 @@
|
|||
VITE_BASE=/
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
public
|
||||
dist
|
||||
package.json
|
||||
!.prettierrc.cjs
|
||||
env.d.ts
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-recommended",
|
||||
"standard",
|
||||
"@vue/prettier",
|
||||
"eslint:recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["vue"],
|
||||
rules: {
|
||||
"no-console": "warn",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-v-html": "off",
|
||||
"vue/require-default-prop": "off",
|
||||
camelcase: "off",
|
||||
eqeqeq: "error",
|
||||
"vue/no-template-shadow": "error",
|
||||
"vue/attribute-hyphenation": "error",
|
||||
"vue/html-end-tags": "error",
|
||||
"vue/eqeqeq": "error",
|
||||
"vue/component-name-in-template-casing": ["error", "kebab-case"],
|
||||
"vue/enforce-style-attribute": ["error", { allow: ["scoped", "module"] }],
|
||||
"vue/v-on-event-hyphenation": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
autofix: true,
|
||||
},
|
||||
],
|
||||
"vue/require-explicit-emits": "error",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{ vars: "all", args: "after-used", ignoreRestSiblings: false },
|
||||
],
|
||||
"linebreak-style": ["off", "windows"],
|
||||
"no-restricted-properties": [
|
||||
"error",
|
||||
{ object: "Object", property: "assign" },
|
||||
],
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "VariableDeclarator[id.name='pd']",
|
||||
message: "不允许使用 pd,请改用有语义化的变量名",
|
||||
},
|
||||
{
|
||||
selector: "ObjectExpression > Property[key.name='pd']",
|
||||
message: "不允许使用 pd,请改用有语义化的变量名",
|
||||
},
|
||||
],
|
||||
},
|
||||
globals: {},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
/src/test/
|
||||
/target/
|
||||
/.idea
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
extends: ["@vue/prettier", "plugin:prettier/recommended"],
|
||||
endOfLine: "crlf",
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vite/client" />
|
||||
interface ImportMetaEnv {
|
||||
// Auto generate by env-parse
|
||||
readonly VITE_PROXY: string
|
||||
readonly VITE_FILE_URL: string
|
||||
readonly VITE_BASE: string
|
||||
readonly VITE_BASE_URL: string
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
|
||||
<title>template</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<noscript>
|
||||
<strong>很抱歉,如果没有启用JavaScript,网站无法正常工作,请启用JavaScript使其正常工作。</strong>
|
||||
</noscript>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "template",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --mode development",
|
||||
"build": "vite build --mode production",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue --fix src .prettierrc.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@icon-park/vue-next": "^1.4.2",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"@vueuse/integrations": "^12.0.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.7.9",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.9.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pinia": "^2.3.0",
|
||||
"pinia-plugin-persistedstate": "^3.2.3",
|
||||
"plyr": "^3.7.8",
|
||||
"qrcode": "^1.5.4",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"v-viewer": "^3.0.21",
|
||||
"viewerjs": "^1.11.7",
|
||||
"vue": "^3.5.13",
|
||||
"vue-countup-v3": "^1.4.2",
|
||||
"vue-draggable-plus": "^0.3.5",
|
||||
"vue-esign": "^1.1.4",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue3-pdfjs": "^0.1.6",
|
||||
"vue3-print-nb": "^0.1.4",
|
||||
"vue3-puzzle-vcode": "^1.1.7",
|
||||
"vue3-seamless-scroll": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.19.68",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/eslint-config-prettier": "^7.1.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-n": "^15.7.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^6.6.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"prettier": "^2.8.8",
|
||||
"sass": "^1.83.0",
|
||||
"unplugin-auto-import": "^0.12.2",
|
||||
"unplugin-element-plus": "^0.8.0",
|
||||
"unplugin-vue-components": "^0.22.12",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-env-parse": "^1.0.15",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-remove-console": "^2.2.0",
|
||||
"vue-eslint-parser": "^9.4.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {
|
||||
overrideBrowserslist: [
|
||||
"Android 4.1",
|
||||
"iOS 7.1",
|
||||
"Chrome > 31",
|
||||
"ff > 31",
|
||||
"ie >= 8",
|
||||
"> 1%",
|
||||
],
|
||||
grid: true,
|
||||
},
|
||||
// '@our-patches/postcss-px-to-viewport': {
|
||||
// unitToConvert: 'px',
|
||||
// viewportWidth: 1920,
|
||||
// unitPrecision: 3,
|
||||
// viewportUnit: 'vw',
|
||||
// selectorBlackList: ['.ignore', '.hairlines'],
|
||||
// minPixelValue: 1,
|
||||
// mediaQuery: false,
|
||||
// exclude: [/^node_modules$/],
|
||||
// include: [/BI/],
|
||||
// landscapeUnit: 'vw',
|
||||
// landscapeWidth: 750,
|
||||
// }
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<suspense>
|
||||
<template #default>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div>加载中...</div>
|
||||
</template>
|
||||
</suspense>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import router from "./router";
|
||||
import { useRouterStore } from "./pinia/router";
|
||||
import { useMenuStore } from "./pinia/menu";
|
||||
import { useUserStore } from "@/pinia/user";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import pinia from "./pinia";
|
||||
import children from "@/components/children/index";
|
||||
import { MODEL } from "@/assets/js/constant";
|
||||
import asyncRouter from "@/assets/js/asyncRouter";
|
||||
// import { getAsyncRouter } from "@/request/api";
|
||||
// import conversionRouterMeta from "@/assets/js/conversion_router_meta.js";
|
||||
|
||||
const modules = import.meta.glob([
|
||||
"./views/**/*.vue",
|
||||
"!./views/**/components/*.vue",
|
||||
]); // 获取到views下所有的vue文件
|
||||
let storageRouter = null; // 用来获取后台拿到的路由
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const routerStore = useRouterStore(pinia);
|
||||
const menuStore = useMenuStore(pinia);
|
||||
const userStore = useUserStore(pinia);
|
||||
// 需要登陆
|
||||
if (to.meta.isLogin !== false) {
|
||||
if (!userStore.getUserInfo.userId) {
|
||||
next("/login");
|
||||
return;
|
||||
}
|
||||
if (!storageRouter) {
|
||||
// 变量里没有储存路由
|
||||
// pinia里没有储存路由,去后台获取路由
|
||||
if (routerStore.getRouters.length === 0) {
|
||||
// const { menuList, permissions } = await getAsyncRouter();
|
||||
// userStore.setPermissions(permissions);
|
||||
storageRouter = asyncRouter; // 死路由
|
||||
// storageRouter = conversionRouterMeta(menuList); // 后台请求得到的路由数据
|
||||
routerStore.setRouters(storageRouter); // 存储路由
|
||||
routerGo(to, next); // 执行路由跳转方法
|
||||
} else {
|
||||
// pinia里储存了路由
|
||||
storageRouter = routerStore.getRouters; // 拿到路由
|
||||
routerGo(to, next); // 执行路由跳转方法
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
// 不需要登陆,清空储存路由
|
||||
storageRouter = null;
|
||||
routerStore.$reset();
|
||||
menuStore.$reset();
|
||||
userStore.$reset();
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
function routerGo(to, next) {
|
||||
const menuStore = useMenuStore(pinia);
|
||||
storageRouter = filterAsyncRouter(cloneDeep(storageRouter)); // 过滤路由
|
||||
for (let i = 0; i < storageRouter.length; i++) {
|
||||
router.addRoute("app", storageRouter[i]); // 动态添加路由
|
||||
}
|
||||
router.addRoute({ path: "/:pathMatch(.*)*", redirect: "/404" }); // 将404路由添加到最后
|
||||
for (let i = 0; i < router.options.routes.length; i++) {
|
||||
if (router.options.routes[i].path === "/") {
|
||||
menuStore.setMenus(
|
||||
router.options.routes[i].children.concat(storageRouter)
|
||||
); // 将路由数据存到一个新的pinia里,做菜单渲染
|
||||
if (!menuStore.getModel) {
|
||||
menuStore.setModel(MODEL["1"]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
next({ ...to, replace: true }); // 等待addRoute执行完毕跳转路由
|
||||
}
|
||||
|
||||
function filterAsyncRouter(asyncRouterMap) {
|
||||
// 遍历后台传来的路由字符串,转换为组件对象
|
||||
return asyncRouterMap.filter((route) => {
|
||||
route.name = ""; // 后台将mete.title存成了name,这里清空,name重复会找不到路由
|
||||
if (route.component) {
|
||||
if (route.component === "children") {
|
||||
route.component = children;
|
||||
} else {
|
||||
if (route.component.charAt(0) === "/") {
|
||||
route.component = modules[`./views${route.component}.vue`];
|
||||
} else {
|
||||
route.component = modules[`./views/${route.component}.vue`];
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果存在children递归
|
||||
if (route.list && route.list.length) {
|
||||
route.children = filterAsyncRouter(route.list);
|
||||
delete route.list;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
// 文字超出几行隐藏,最多5行
|
||||
// 使用超出1行隐藏,如果使用了flex,则需要给父元素设置min-width: 0;
|
||||
@for $i from 1 through 5 {
|
||||
.line-#{$i} {
|
||||
@if $i == 1 {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
} @else {
|
||||
display: -webkit-box !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
-webkit-line-clamp: $i;
|
||||
-webkit-box-orient: vertical !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成1-50的margin和padding(正负)
|
||||
@for $i from 1 through 50 {
|
||||
.m-#{$i} {
|
||||
margin: #{$i}px;
|
||||
}
|
||||
.mt-#{$i} {
|
||||
margin-top: #{$i}px;
|
||||
}
|
||||
.mr-#{$i} {
|
||||
margin-right: #{$i}px;
|
||||
}
|
||||
.mb-#{$i} {
|
||||
margin-bottom: #{$i}px;
|
||||
}
|
||||
.ml-#{$i} {
|
||||
margin-left: #{$i}px;
|
||||
}
|
||||
.mtb-#{$i} {
|
||||
margin-top: #{$i}px;
|
||||
margin-bottom: #{$i}px;
|
||||
}
|
||||
.mlr-#{$i} {
|
||||
margin-left: #{$i}px;
|
||||
margin-right: #{$i}px;
|
||||
}
|
||||
.p-#{$i} {
|
||||
padding: #{$i}px;
|
||||
}
|
||||
.pt-#{$i} {
|
||||
padding-top: #{$i}px;
|
||||
}
|
||||
.pr-#{$i} {
|
||||
padding-right: #{$i}px;
|
||||
}
|
||||
.pb-#{$i} {
|
||||
padding-bottom: #{$i}px;
|
||||
}
|
||||
.pl-#{$i} {
|
||||
padding-left: #{$i}px;
|
||||
}
|
||||
.ptb-#{$i} {
|
||||
padding-top: #{$i}px;
|
||||
padding-bottom: #{$i}px;
|
||||
}
|
||||
.plr-#{$i} {
|
||||
padding-left: #{$i}px;
|
||||
padding-right: #{$i}px;
|
||||
}
|
||||
.m--#{$i} {
|
||||
margin: -#{$i}px;
|
||||
}
|
||||
.mt--#{$i} {
|
||||
margin-top: -#{$i}px;
|
||||
}
|
||||
.mr--#{$i} {
|
||||
margin-right: -#{$i}px;
|
||||
}
|
||||
.mb--#{$i} {
|
||||
margin-bottom: -#{$i}px;
|
||||
}
|
||||
.ml--#{$i} {
|
||||
margin-left: -#{$i}px;
|
||||
}
|
||||
.mtb--#{$i} {
|
||||
margin-top: -#{$i}px;
|
||||
margin-bottom: -#{$i}px;
|
||||
}
|
||||
.mlr--#{$i} {
|
||||
margin-left: -#{$i}px;
|
||||
margin-right: -#{$i}px;
|
||||
}
|
||||
.p--#{$i} {
|
||||
padding: -#{$i}px;
|
||||
}
|
||||
.pt--#{$i} {
|
||||
padding-top: -#{$i}px;
|
||||
}
|
||||
.pr--#{$i} {
|
||||
padding-right: -#{$i}px;
|
||||
}
|
||||
.pb--#{$i} {
|
||||
padding-bottom: -#{$i}px;
|
||||
}
|
||||
.pl--#{$i} {
|
||||
padding-left: -#{$i}px;
|
||||
}
|
||||
.ptb--#{$i} {
|
||||
padding-top: -#{$i}px;
|
||||
padding-bottom: -#{$i}px;
|
||||
}
|
||||
.plr--#{$i} {
|
||||
padding-left: -#{$i}px;
|
||||
padding-right: -#{$i}px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
|
||||
&:not(dd,dl,dt) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-size: revert;
|
||||
}
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active {
|
||||
-webkit-transition-delay: 99999s;
|
||||
-webkit-transition: color 99999s ease-out, background-color 99999s ease-out;
|
||||
}
|
||||
|
||||
#app {
|
||||
background-color: #f6f8f9;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.end {
|
||||
.el-form-item__content {
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
|
||||
.p0.el-descriptions__content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.p0 .el-descriptions tr {
|
||||
.el-descriptions__label {
|
||||
border-left: none !important;
|
||||
}
|
||||
|
||||
.el-descriptions__content {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
&:first-child .el-descriptions__cell {
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
&:last-child .el-descriptions__cell {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.text-blue {
|
||||
color: #0000ff;
|
||||
}
|
||||
|
||||
.text-yellow {
|
||||
color: #bebe05;
|
||||
}
|
||||
|
||||
.text-orange {
|
||||
color: #de9004;
|
||||
}
|
||||
|
||||
.text-red {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #0bb20c;
|
||||
}
|
||||
|
||||
.tc {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tr {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tl {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.print_use {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.viewer-zoom-in, .viewer-zoom-out, .viewer-one-to-one, .viewer-reset, .viewer-prev, .viewer-play, .viewer-next, .viewer-rotate-left, .viewer-rotate-right, .viewer-flip-horizontal, .viewer-flip-vertical, .viewer-fullscreen, .viewer-fullscreen-exit, .viewer-close {
|
||||
&::before {
|
||||
font-size: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vue-pdf__wrapper-annotation-layer {
|
||||
height: 0 !important;
|
||||
}
|
||||
.w-e-bar{
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-bottom: none;
|
||||
}
|
||||
.w-e-text-container{
|
||||
border: 1px solid var(--el-border-color);
|
||||
}
|
||||
.w-e-bar-divider {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// 打印时去掉页眉页脚
|
||||
@page {
|
||||
size: auto;
|
||||
margin: 3mm;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
.print_use {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
display: table;
|
||||
|
||||
td, th {
|
||||
border: 1px solid #eaeaea;
|
||||
padding: 8px;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.print_no_use {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
.el-select, .el-cascader, .el-date-editor.el-input, .el-date-editor.el-input__wrapper, .el-input__wrapper, .el-input-number, .el-select-v2 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.el-pagination .el-select {
|
||||
width: 128px !important;
|
||||
}
|
||||
|
||||
.el-pagination--small .el-select {
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
.el-table .el-table__cell {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-descriptions__label {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.el-descriptions__content {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.el-divider__text {
|
||||
font-size: 16px !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
--el-dialog-margin-top: 50px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
padding-left: 20px;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid #f1f1f1;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table {
|
||||
* {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
th.el-table__cell {
|
||||
--el-table-header-bg-color: rgb(245, 247, 250);
|
||||
font-weight: bold;
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.el-table__cell {
|
||||
padding: 8px 0 !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-page-header {
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
padding: 0 20px 20px 20px;
|
||||
margin: 0 -20px 20px -20px;
|
||||
|
||||
.el-page-header__content {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.el-button > span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
//router-view动画
|
||||
.view-leave-active {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
transition: all .5s;
|
||||
transform-origin: center top;
|
||||
}
|
||||
|
||||
.view-enter-active .view-leave-active {
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.view-enter-from, .view-leave-active {
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
|
||||
//面包屑动画
|
||||
.breadcrumb-enter-active,
|
||||
.breadcrumb-leave-active {
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.breadcrumb-move {
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 345 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -0,0 +1,25 @@
|
|||
import CryptoJS from "crypto-js";
|
||||
|
||||
const key = CryptoJS.enc.Utf8.parse("daac3ae52eff4cec"); // 16位
|
||||
|
||||
const encrypt = (word) => {
|
||||
let encrypted = "";
|
||||
if (typeof word === "string") {
|
||||
const src = CryptoJS.enc.Utf8.parse(word);
|
||||
encrypted = CryptoJS.AES.encrypt(src, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
} else if (typeof word === "object") {
|
||||
// 对象格式的转成json字符串
|
||||
const data = JSON.stringify(word);
|
||||
const src = CryptoJS.enc.Utf8.parse(data);
|
||||
encrypted = CryptoJS.AES.encrypt(src, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
}
|
||||
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
|
||||
};
|
||||
|
||||
export { encrypt };
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
import { MODEL } from "@/assets/js/constant";
|
||||
|
||||
export default [
|
||||
{
|
||||
path: "/system_management",
|
||||
redirect: "/system_management/menu",
|
||||
component: "children",
|
||||
meta: { title: "系统管理", model: MODEL["2"], icon: "system" },
|
||||
list: [
|
||||
{
|
||||
path: "/system_management/role",
|
||||
component: "system_management/role/index",
|
||||
meta: {
|
||||
title: "角色管理",
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
icon: "category-management",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/system_management/menu",
|
||||
component: "system_management/menu/index",
|
||||
meta: {
|
||||
title: "菜单管理",
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
icon: "application-menu",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/system_management/data_dictionary",
|
||||
component: "system_management/data_dictionary/index",
|
||||
meta: {
|
||||
title: "数据字典",
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
icon: "ad-product",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/schedule/job",
|
||||
component: "schedule/job/index",
|
||||
meta: {
|
||||
title: "定时任务",
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
icon: "alarm-clock",
|
||||
},
|
||||
list: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/user_management",
|
||||
redirect: "/user_management/user",
|
||||
component: "children",
|
||||
meta: {
|
||||
title: "用户管理",
|
||||
model: MODEL["2"],
|
||||
icon: "user",
|
||||
},
|
||||
list: [
|
||||
{
|
||||
path: "/user_management/department",
|
||||
component: "user_management/department/index",
|
||||
meta: {
|
||||
title: "部门管理",
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
icon: "newspaper-folding",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/user_management/user",
|
||||
component: "user_management/user/index",
|
||||
meta: {
|
||||
title: "账号管理",
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
icon: "people",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { ElMessage } from "element-plus";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import pinia from "@/pinia/index.js";
|
||||
|
||||
export default {
|
||||
install: (app) => {
|
||||
app.directive("button", {
|
||||
mounted(el, { value }) {
|
||||
const userStore = useUserStore(pinia);
|
||||
if (value) {
|
||||
if (!userStore.getPermissions.includes(value)) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
} else ElMessage.error("参数无效,请联系管理员");
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 将常用的值储存成常量,防止重复使用写错
|
||||
|
||||
// 头部导航条切换的model
|
||||
export const MODEL = {
|
||||
1: "other",
|
||||
2: "system",
|
||||
};
|
||||
// 头部导航条
|
||||
export const MENU = [
|
||||
{ title: "基础数据", model: MODEL["1"], icon: "database-position" },
|
||||
{ title: "系统管理", model: MODEL["2"], icon: "setting" },
|
||||
];
|
||||
|
||||
export const styleText =
|
||||
'<style type="text/css" media="print">\n' +
|
||||
" @page { size: landscape; }\n" +
|
||||
"</style>";
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
export default function conversionRouterMeta(menuList) {
|
||||
for (let i = 0; i < menuList.length; i++) {
|
||||
menuList[i].meta = JSON.parse(menuList[i].meta);
|
||||
if (menuList[i].list.length > 0) {
|
||||
conversionRouterMeta(menuList[i].list);
|
||||
}
|
||||
}
|
||||
return menuList;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import {
|
||||
getDataDictionaries,
|
||||
getDepartmentTree,
|
||||
} from "@/request/data_dictionary";
|
||||
import { ref } from "vue";
|
||||
|
||||
// 部门
|
||||
export const appFnGetDepartmentTree = async (parentId) => {
|
||||
const resData = await getDepartmentTree(parentId);
|
||||
return ref(resData.deptTree);
|
||||
};
|
||||
// 学历
|
||||
export const appFnGetDegree = async () => {
|
||||
const resData = await getDataDictionaries({
|
||||
parentId: "ddce2eac1cf27e4b114231051ec9123b",
|
||||
});
|
||||
return ref(resData.dictionariesList);
|
||||
};
|
||||
// 无法确定parentId的数据字典
|
||||
export const appFnGetDataDictionary = async (parentId) => {
|
||||
const resData = await getDataDictionaries({ parentId });
|
||||
return ref(resData.dictionariesList);
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import mitt from "mitt";
|
||||
export default mitt();
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import dayjs from "dayjs";
|
||||
import { setRefreshToken } from "@/request/api.js";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import pinia from "@/pinia/index.js";
|
||||
|
||||
export default async function () {
|
||||
const userStore = useUserStore(pinia);
|
||||
if (userStore.getTokenTime) {
|
||||
if (dayjs().diff(dayjs(userStore.getTokenTime), "minute") >= 5) {
|
||||
await userStore.setTokenTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
|
||||
await setRefreshToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export const PHONE =
|
||||
/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/;
|
||||
export const UNIFIED_SOCIAL_CREDIT_CODE =
|
||||
/^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/;
|
||||
export const ID_NUMBER =
|
||||
/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { ElMessage } from "element-plus";
|
||||
import dayjs from "dayjs";
|
||||
import axios from "axios";
|
||||
|
||||
export default function useDownloadBlob(
|
||||
url,
|
||||
option = { name: "", type: "", params: {} }
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(url, { responseType: "blob", params: { ...option.params } })
|
||||
.then((resData) => {
|
||||
if (resData.data.type === "application/json") {
|
||||
throw new Error("导出失败");
|
||||
}
|
||||
const blob = new Blob([resData.data], {
|
||||
type: option.type || "application/vnd.ms-excel",
|
||||
});
|
||||
const downloadElement = document.createElement("a");
|
||||
const href = window.URL.createObjectURL(blob);
|
||||
downloadElement.style.display = "none";
|
||||
downloadElement.href = href;
|
||||
downloadElement.download =
|
||||
option.name || dayjs().format("YYYY-MM-DD HH:mm:ss");
|
||||
document.body.appendChild(downloadElement);
|
||||
downloadElement.click();
|
||||
document.body.removeChild(downloadElement);
|
||||
window.URL.revokeObjectURL(href);
|
||||
resolve(resData);
|
||||
})
|
||||
.catch((err) => {
|
||||
ElMessage.error("导出失败");
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { ElMessageBox } from "element-plus";
|
||||
import { getFileName, getFileSuffix } from "@/assets/js/utils.js";
|
||||
|
||||
export default async function useDownloadFile(url, name) {
|
||||
if (!url) throw new Error("没有下载地址");
|
||||
await ElMessageBox.confirm("确定要下载此文件吗?", { type: "warning" });
|
||||
const FILE_URL = import.meta.env.VITE_FILE_URL;
|
||||
if (name) {
|
||||
if (!getFileSuffix(url)) name = name + getFileSuffix(url);
|
||||
} else name = getFileName(url);
|
||||
fetch(url.indexOf(FILE_URL) === -1 ? FILE_URL + url : url)
|
||||
.then((res) => res.blob())
|
||||
.then((blob) => {
|
||||
const a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
a.style.display = "none";
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
a.href = url;
|
||||
a.download = `${name}`;
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { ElMessage } from "element-plus";
|
||||
import { useTemplateRef } from "vue";
|
||||
|
||||
export default function useFormValidate() {
|
||||
const formRef = useTemplateRef("formRef");
|
||||
const validate = (message = "请补全必填项!") => {
|
||||
return new Promise((resolve, reject) => {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
resolve(valid);
|
||||
} else {
|
||||
reject(valid);
|
||||
ElMessage.warning(message);
|
||||
setTimeout(() => {
|
||||
const element = document.querySelectorAll(
|
||||
".el-form-item__error"
|
||||
)[0];
|
||||
element.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return { validate, formRef };
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import { nextTick, ref, useTemplateRef } from "vue";
|
||||
import { getDataType } from "@/assets/js/utils.js";
|
||||
import {
|
||||
getQueryCriteria,
|
||||
setQueryCriteria,
|
||||
} from "@/assets/js/useQueryCriteria.js";
|
||||
|
||||
/**
|
||||
* @param api {Function} 接口函数
|
||||
* @param options {Object?: {callback, params, defaultSearchForm, immediate, usePagination, key}} 配置项
|
||||
* @param options.callback {Function?} 回调函数(返回值【第一个参数表格数据,第二个参数后台返回的所有数据】)
|
||||
* @param options.params {Object?} 其它接口参数
|
||||
* @param options.defaultSearchForm {Object?} searchForm默认值
|
||||
* @param options.immediate {Boolean?} 是否立即执行接口函数(默认是)
|
||||
* @param options.usePagination {Boolean?} 是否使用分页(默认是)
|
||||
* @param options.clearSelection {Boolean?} 调用resetPagination是是否清空表格选择数据(默认是)
|
||||
* @param options.key {String?} 返回的存放数组的key(默认list)
|
||||
* @param options.isStorageQueryCriteria {Boolean?} 是否保存查询条件(默认是)
|
||||
* @param options.tabsActiveName {String?} 存在tabs组件时,当前tabs的activeName,用于缓存查询条件
|
||||
* @return {Object} 返回对象包含以下属性:list 表格数据,pagination 分页数据,searchForm 搜索表单数据,tableRef 表格实例,getData 获取数据函数,resetPagination 重置分页函数
|
||||
*/
|
||||
|
||||
export default function useListData(api, options = {}) {
|
||||
if (getDataType(api) !== "Function") throw new Error("api必须是一个函数");
|
||||
if (getDataType(options) !== "Object")
|
||||
throw new Error("options必须是一个对象");
|
||||
if (options.immediate && getDataType(options.immediate) !== "Boolean")
|
||||
throw new Error("options.immediate必须是一个布尔值");
|
||||
if (options.usePagination && getDataType(options.usePagination) !== "Boolean")
|
||||
throw new Error("options.usePagination必须是一个布尔值");
|
||||
if (options.key && getDataType(options.key) !== "String")
|
||||
throw new Error("options.key必须是一个字符串");
|
||||
if (
|
||||
options.callback &&
|
||||
getDataType(options.callback) !== "Function" &&
|
||||
getDataType(options.callback) !== "AsyncFunction"
|
||||
)
|
||||
throw new Error("options.callback必须是一个函数");
|
||||
if (
|
||||
options.defaultSearchForm &&
|
||||
getDataType(options.defaultSearchForm) !== "Object"
|
||||
)
|
||||
throw new Error("options.defaultSearchForm必须是一个对象");
|
||||
if (
|
||||
options.clearSelection &&
|
||||
getDataType(options.clearSelection) !== "Boolean"
|
||||
)
|
||||
throw new Error("options.clearSelection必须是一个布尔值");
|
||||
if (options.params && getDataType(options.params) !== "Object")
|
||||
throw new Error("options.otherParams必须是一个对象");
|
||||
if (
|
||||
options.isStorageQueryCriteria &&
|
||||
getDataType(options.isStorageQueryCriteria) !== "Boolean"
|
||||
)
|
||||
throw new Error("options.isStorageQueryCriteria必须是一个布尔值");
|
||||
if (
|
||||
options.tabsActiveName &&
|
||||
getDataType(options.tabsActiveName) !== "String"
|
||||
)
|
||||
throw new Error("options.tabsActiveName必须是一个字符串");
|
||||
const immediate = options.immediate ?? true;
|
||||
const usePagination = options.usePagination ?? true;
|
||||
const key = options.key ?? "list";
|
||||
const defaultSearchForm = options.defaultSearchForm ?? {};
|
||||
const clearSelection = options.clearSelection ?? true;
|
||||
const isStorageQueryCriteria = options.isStorageQueryCriteria ?? true;
|
||||
const list = ref([]);
|
||||
const queryCriteria = getQueryCriteria();
|
||||
const pagination = ref(
|
||||
queryCriteria.pagination || {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
}
|
||||
);
|
||||
const searchForm = ref(queryCriteria.searchForm || defaultSearchForm);
|
||||
const tableRef = useTemplateRef("tableRef");
|
||||
const getData = async (params = {}) => {
|
||||
const resData = await api({
|
||||
...(usePagination
|
||||
? {
|
||||
curPage: pagination.value.currentPage,
|
||||
limit: pagination.value.pageSize,
|
||||
}
|
||||
: {}),
|
||||
...searchForm.value,
|
||||
...(options.params || {}),
|
||||
...(getDataType(params) === "Object" ? params : {}),
|
||||
});
|
||||
list.value = usePagination ? resData.page[key] : resData[key];
|
||||
if (usePagination) pagination.value.total = resData.page.totalCount;
|
||||
options.callback && options.callback(list.value, resData);
|
||||
!usePagination &&
|
||||
clearSelection &&
|
||||
tableRef.value &&
|
||||
tableRef.value.clearSelection();
|
||||
if (isStorageQueryCriteria) {
|
||||
setQueryCriteria({
|
||||
searchForm: searchForm.value,
|
||||
pagination: pagination.value,
|
||||
tabsActiveName: options.tabsActiveName,
|
||||
});
|
||||
}
|
||||
};
|
||||
immediate && getData().then();
|
||||
const resetPagination = async (params) => {
|
||||
list.value = [];
|
||||
pagination.value = {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
};
|
||||
await nextTick();
|
||||
await getData(params);
|
||||
clearSelection && tableRef.value && tableRef.value.clearSelection();
|
||||
};
|
||||
return {
|
||||
list,
|
||||
pagination,
|
||||
searchForm,
|
||||
tableRef,
|
||||
getData: async (params) => await getData(params),
|
||||
resetPagination: async (params) => await resetPagination(params),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
|
||||
|
||||
export const getQueryCriteria = () => {
|
||||
const miscellaneousStore = useMiscellaneousStore();
|
||||
const key = window.location.href;
|
||||
let queryCriteria = miscellaneousStore.getQueryCriteria[key] || {};
|
||||
if (queryCriteria.tabsActiveName) {
|
||||
queryCriteria =
|
||||
miscellaneousStore.getQueryCriteria[
|
||||
key + "/" + queryCriteria.tabsActiveName
|
||||
] || {};
|
||||
}
|
||||
const pagination = queryCriteria.pagination;
|
||||
const searchForm = queryCriteria.searchForm;
|
||||
const tabsActiveName = queryCriteria.tabsActiveName;
|
||||
return {
|
||||
pagination,
|
||||
searchForm,
|
||||
tabsActiveName,
|
||||
};
|
||||
};
|
||||
export const setQueryCriteria = (data) => {
|
||||
const miscellaneousStore = useMiscellaneousStore();
|
||||
let key = window.location.href;
|
||||
if (data.tabsActiveName) {
|
||||
miscellaneousStore.setQueryCriteria({
|
||||
...miscellaneousStore.getQueryCriteria,
|
||||
[key]: {
|
||||
...miscellaneousStore.getQueryCriteria[key],
|
||||
tabsActiveName: data.tabsActiveName,
|
||||
},
|
||||
});
|
||||
key = key + "/" + data.tabsActiveName;
|
||||
}
|
||||
miscellaneousStore.setQueryCriteria({
|
||||
...miscellaneousStore.getQueryCriteria,
|
||||
[key]: {
|
||||
...miscellaneousStore.getQueryCriteria[key],
|
||||
...data,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const resetQueryCriteria = () => {
|
||||
const miscellaneousStore = useMiscellaneousStore();
|
||||
miscellaneousStore.resetQueryCriteria();
|
||||
};
|
||||
|
||||
export const getTabsActiveName = () => {
|
||||
const key = window.location.href;
|
||||
const miscellaneousStore = useMiscellaneousStore();
|
||||
const queryCriteria = miscellaneousStore.getQueryCriteria[key] || {};
|
||||
return queryCriteria.tabsActiveName;
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { ref } from "vue";
|
||||
|
||||
const useSearchCollapse = () => {
|
||||
const collapse = ref(true);
|
||||
const changeSearchCollapse = () => {
|
||||
collapse.value = !collapse.value;
|
||||
};
|
||||
return {
|
||||
collapse,
|
||||
changeSearchCollapse,
|
||||
};
|
||||
};
|
||||
export default useSearchCollapse;
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
import { ElMessage } from "element-plus";
|
||||
|
||||
/**
|
||||
* @description 计算序号
|
||||
* @param {Object} pagination 分页数据对象
|
||||
* @param {number | string} pagination.currentPage 当前页
|
||||
* @param {number | string} pagination.pageSize 每页条数
|
||||
* @param {number} index 当页数据的索引值
|
||||
* @return {number} 序号
|
||||
**/
|
||||
export function serialNumber(pagination, index) {
|
||||
return (pagination.currentPage - 1) * pagination.pageSize + (index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 字符串数组转数组
|
||||
* @param {string} value 转换的字符串数组
|
||||
* @return {Array} 转换后的数组
|
||||
**/
|
||||
export function toArrayString(value) {
|
||||
// eslint-disable-next-line no-eval
|
||||
return value ? eval(value).map(String) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断文件后缀名是否符合
|
||||
* @param {string} name 文件名字
|
||||
* @param {string} suffix 文件后缀
|
||||
* @return {boolean} 是否符合
|
||||
**/
|
||||
export function interceptTheSuffix(name, suffix) {
|
||||
return (
|
||||
name.substring(name.lastIndexOf("."), name.length).toLowerCase() ===
|
||||
suffix.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 图片转base64
|
||||
* @param {string} imgUrl 图片地址
|
||||
* @return {Promise} Promise实例,then包含base64编码
|
||||
**/
|
||||
export function image2Base64(imgUrl) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = imgUrl;
|
||||
img.crossOrigin = "Anonymous";
|
||||
img.onload = function () {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height);
|
||||
const ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
|
||||
resolve(canvas.toDataURL("image/" + ext));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断图片是否可访问成功
|
||||
* @param {string} imgUrl 图片地址
|
||||
* @return {Promise} Promise实例
|
||||
**/
|
||||
export function checkImgExists(imgUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ImgObj = new Image();
|
||||
ImgObj.src = imgUrl;
|
||||
ImgObj.onload = function (res) {
|
||||
resolve(res);
|
||||
};
|
||||
ImgObj.onerror = function (err) {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取数据类型
|
||||
* @param {any} data 数据
|
||||
* @return {string} 数据类型
|
||||
**/
|
||||
export function getDataType(data) {
|
||||
return Object.prototype.toString.call(data).slice(8, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 数组去重
|
||||
* @param {Array<number,string>} arr 去重的数组
|
||||
* @return {Array} 去重后的数组
|
||||
**/
|
||||
export function ArrayDeduplication(arr) {
|
||||
return [...new Set(arr)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 数组对象去重
|
||||
* @param {Array} arr 去重的数组
|
||||
* @param {string} name 去重的key
|
||||
* @return {Array} 去重后的数组
|
||||
**/
|
||||
export function arrayObjectDeduplication(arr, name) {
|
||||
const obj = {};
|
||||
arr = arr.reduce(function (previousValue, currentValue) {
|
||||
if (!obj[currentValue[name]]) {
|
||||
obj[currentValue[name]] = true;
|
||||
previousValue.push(currentValue);
|
||||
}
|
||||
return previousValue;
|
||||
}, []);
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查找字符串中指定的值第几次出现的位置
|
||||
* @param {Array} str 查找的字符串数组
|
||||
* @param {string} char 查找的值
|
||||
* @param {number} num 第几次出现
|
||||
* @return {number} 出现的位置
|
||||
**/
|
||||
export function findCharIndex(str, char, num) {
|
||||
let index = str.indexOf(char);
|
||||
if (index === -1) return -1;
|
||||
for (let i = 0; i < num - 1; i++) {
|
||||
index = str.indexOf(char, index + 1);
|
||||
if (index === -1) return -1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 生成指定两个值之间的随机数
|
||||
* @param {number} min 最小值
|
||||
* @param {number} max 最大值
|
||||
* @return {number} 随机数
|
||||
**/
|
||||
export function randoms(min, max) {
|
||||
return Math.random() * (max - min + 1) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 千位分隔符
|
||||
* @param {number | string} num 转换的值
|
||||
* @return {string} 转换后的值
|
||||
**/
|
||||
export function numFormat(num) {
|
||||
if (num) {
|
||||
const numArr = num.toString().split(".");
|
||||
const arr = numArr[0].split("").reverse();
|
||||
let res = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (i % 3 === 0 && i !== 0) {
|
||||
res.push(",");
|
||||
}
|
||||
res.push(arr[i]);
|
||||
}
|
||||
res.reverse();
|
||||
if (numArr[1]) {
|
||||
res = res.join("").concat("." + numArr[1]);
|
||||
} else {
|
||||
res = res.join("");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证是否为空
|
||||
* @param {any} value 验证的值
|
||||
* @return {boolean} 是否为空
|
||||
**/
|
||||
export function isEmpty(value) {
|
||||
return (
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
(typeof value === "object" && Object.keys(value).length === 0) ||
|
||||
(typeof value === "string" && value.trim().length === 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取url参数
|
||||
* @param {string} name 获取的key
|
||||
* @return {string} 获取的值
|
||||
**/
|
||||
export function getUrlParam(name) {
|
||||
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
||||
const r = window.location.search.substr(1).match(reg);
|
||||
if (r != null) return decodeURI(r[2]);
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 数据分页
|
||||
* @param {Array} list 分页的数组
|
||||
* @param {number | string} currentPage 当前页
|
||||
* @param {number | string} pageSize 每页条数
|
||||
* @return {Array} 分页后的数组
|
||||
**/
|
||||
export function paging(list, currentPage, pageSize) {
|
||||
return list.filter((item, index) => {
|
||||
return (
|
||||
index < +currentPage * +pageSize &&
|
||||
index >= (+currentPage - 1) * +pageSize
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取文件后缀
|
||||
* @param {string} name 文件名
|
||||
* @return {string} 文件后缀
|
||||
**/
|
||||
export function getFileSuffix(name) {
|
||||
return name.substring(name.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取文件名称
|
||||
* @param {string} name 文件地址
|
||||
* @return {string} 文件名称
|
||||
**/
|
||||
export function getFileName(name) {
|
||||
if (!name) return "";
|
||||
return name.substring(name.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 读取txt文档
|
||||
* @param {string} filePah 文档路径
|
||||
* @return {resolve,string} 读取后的内容
|
||||
**/
|
||||
export function readTxtDocument(filePah) {
|
||||
return new Promise((resolve) => {
|
||||
const FILE_URL = import.meta.env.VITE_FILE_URL;
|
||||
const file_url = FILE_URL + filePah;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("get", file_url, true);
|
||||
xhr.responseType = "blob";
|
||||
xhr.onload = function (event) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(event.target.response, "GB2312");
|
||||
reader.onload = function () {
|
||||
resolve(reader.result);
|
||||
};
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将秒转换成时分秒
|
||||
* @param {string,number} second 需要转换的秒数
|
||||
* @return {string} 转换后的时间
|
||||
**/
|
||||
export function secondConversion(second) {
|
||||
if (!second) return 0;
|
||||
const h = parseInt(second / 60 / 60, 10);
|
||||
const m = parseInt((second / 60) % 60, 10);
|
||||
const s = parseInt(second % 60, 10);
|
||||
if (h) {
|
||||
return h + "小时" + m + "分钟" + s + "秒";
|
||||
} else {
|
||||
if (m) {
|
||||
return m + "分钟" + s + "秒";
|
||||
} else {
|
||||
return s + "秒";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 附件添加前缀
|
||||
* @param {Array} list 附件数组
|
||||
* @return {Array} 添加完的数组
|
||||
**/
|
||||
export function addingPrefixToFile(list) {
|
||||
const FILE_URL = import.meta.env.VITE_FILE_URL;
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
list[i].url = FILE_URL + list[i].filepath;
|
||||
list[i].name = getFileName(list[i].filepath);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证重复选择
|
||||
* @param {Array} list 验证的数组
|
||||
* @param {number} index 选择的索引
|
||||
* @param {string} key 验证的字段
|
||||
* @param {string} id 验证的值
|
||||
**/
|
||||
export async function verifyDuplicateSelection(list, index, key, id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.some((item) => item[key] === id)) {
|
||||
ElMessage.warning("不能重复选择");
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
reject();
|
||||
} else {
|
||||
list[index][key] = id;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 翻译状态
|
||||
* @param {number | string} status 状态
|
||||
* @param {Array} list 翻译的数组
|
||||
* @return {string} 翻译后的状态
|
||||
**/
|
||||
export function translationStatus(status, list) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (status === list[i].ID) {
|
||||
return list[i].NAME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 计算文件大小
|
||||
* @param {number | string} size 文件kb
|
||||
* @return {string} 计算后的文件大小
|
||||
**/
|
||||
export function calculateFileSize(size) {
|
||||
return size > 1024
|
||||
? (size / 1024 + "").substring(0, (size / 1024 + "").lastIndexOf(".") + 3) +
|
||||
"MB"
|
||||
: size + "KB";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据身份证号获取出生日期和性别
|
||||
* @param {String} idCard 身份证号
|
||||
* @return {Object} 出生日期和性别 date sex
|
||||
**/
|
||||
export function idCardGetDateAndGender(idCard) {
|
||||
const reg =
|
||||
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
|
||||
let sex = "";
|
||||
let date = "";
|
||||
if (reg.test(idCard)) {
|
||||
const org_birthday = idCard.substring(6, 14);
|
||||
const org_gender = idCard.substring(16, 17);
|
||||
const birthday =
|
||||
org_birthday.substring(0, 4) +
|
||||
"-" +
|
||||
org_birthday.substring(4, 6) +
|
||||
"-" +
|
||||
org_birthday.substring(6, 8);
|
||||
const birthdays = new Date(birthday.replace(/-/g, "/"));
|
||||
const Month = birthdays.getMonth() + 1;
|
||||
let MonthDate;
|
||||
const DayDate = birthdays.getDate();
|
||||
let Day;
|
||||
if (Month < 10) MonthDate = "0" + Month;
|
||||
else MonthDate = Month;
|
||||
if (DayDate < 10) Day = "0" + DayDate;
|
||||
else Day = DayDate;
|
||||
sex = org_gender % 2 === 1 ? "1" : "0";
|
||||
date = birthdays.getFullYear() + "-" + MonthDate + "-" + Day;
|
||||
}
|
||||
return { sex, date };
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取select的label
|
||||
* @param {Array} list 获取的数组
|
||||
* @param {number | string} value 获取的值
|
||||
* @param {string?} idKey 获取的id
|
||||
* @param {string?} labelKey 获取的label
|
||||
* @return {string} 获取的label
|
||||
**/
|
||||
export function getSelectLabel(
|
||||
list,
|
||||
value,
|
||||
idKey = "bianma",
|
||||
labelKey = "name"
|
||||
) {
|
||||
const result = list.find((item) => item[idKey] === value);
|
||||
return result ? result[labelKey] : "";
|
||||
}
|
||||
|
||||
export function getFileUrl() {
|
||||
return import.meta.env.VITE_FILE_URL;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineOptions({
|
||||
name: "Children",
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<el-tree-select
|
||||
v-model="modelValue"
|
||||
:data="tree"
|
||||
node-key="departmentId"
|
||||
:props="{
|
||||
children: 'list',
|
||||
label: 'name',
|
||||
}"
|
||||
:render-after-expand="false"
|
||||
accordion
|
||||
:check-strictly="checkStrictly"
|
||||
:clearable="clearable"
|
||||
:show-checkbox="showCheckbox"
|
||||
:multiple="multiple"
|
||||
:collapse-tags="collapseTags"
|
||||
@change="change"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { appFnGetDepartmentTree } from "@/assets/js/data_dictionary";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "AppDepartment",
|
||||
});
|
||||
defineProps({
|
||||
checkStrictly: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showCheckbox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collapseTags: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["change"]);
|
||||
const modelValue = defineModel({ type: [String, Array], required: true });
|
||||
const change = async () => {
|
||||
await nextTick();
|
||||
emits("change", modelValue.value);
|
||||
};
|
||||
const tree = await appFnGetDepartmentTree(0);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div style="flex: 1">
|
||||
<toolbar :editor="editorRef" :default-config="toolbarConfig" />
|
||||
<editor
|
||||
v-model="modelValue"
|
||||
:style="{ height, 'overflow-y': 'hidden' }"
|
||||
@on-created="fnEditorCreated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
|
||||
import "@wangeditor/editor/dist/css/style.css";
|
||||
import { shallowRef, onBeforeUnmount } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "AppEditor",
|
||||
});
|
||||
defineProps({
|
||||
height: {
|
||||
type: String,
|
||||
default: "300px",
|
||||
},
|
||||
});
|
||||
const modelValue = defineModel({ type: String, required: true });
|
||||
const editorRef = shallowRef();
|
||||
const toolbarConfig = {
|
||||
excludeKeys: [
|
||||
"color",
|
||||
"bgColor",
|
||||
"group-image",
|
||||
"group-video",
|
||||
"insertLink",
|
||||
"codeBlock",
|
||||
"emotion",
|
||||
"todo",
|
||||
],
|
||||
};
|
||||
const fnEditorCreated = (editor) => {
|
||||
editorRef.value = editor;
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
editorRef.value && editorRef.value.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<el-dialog v-model="visible" :title="title" :before-close="fnClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="附件" prop="file">
|
||||
<app-upload v-model:file-list="form.file" accept=".xls,.xlsx" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnExportTemplates">导出模板</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">确定</el-button>
|
||||
<el-button @click="fnClose">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppUpload from "@/components/upload/index.vue";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { ref, watchEffect } from "vue";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
|
||||
defineOptions({
|
||||
name: "AppImportFile",
|
||||
});
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "导入",
|
||||
},
|
||||
templateUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["submit"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const rules = {
|
||||
file: [{ required: true, message: "附件不能为空", trigger: "change" }],
|
||||
};
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const form = ref({
|
||||
file: [],
|
||||
});
|
||||
const fnExportTemplates = async () => {
|
||||
await ElMessageBox.confirm("确定要下载excel模板吗?", { type: "warning" });
|
||||
window.open("https://file.zcloudchina.com/KjkFile" + props.templateUrl);
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (!props.visible) {
|
||||
formRef.value && formRef.value.resetFields();
|
||||
}
|
||||
});
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate("请上传附件");
|
||||
const formData = new FormData();
|
||||
formData.append("paperFiles", form.value.file[0].raw);
|
||||
formData.append("FFILEName", form.value.file[0].name);
|
||||
emits("submit", formData);
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<el-pagination
|
||||
size="small"
|
||||
:current-page="pagination.currentPage || 1"
|
||||
:page-size="pagination.pageSize || 10"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total || 0"
|
||||
@update:current-page="handleCurrentChange"
|
||||
@update:page-size="handleSizeChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineOptions({
|
||||
name: "AppPagination",
|
||||
});
|
||||
const emits = defineEmits(["get-data"]);
|
||||
const pagination = defineModel("pagination", { type: Object, required: true });
|
||||
const handleCurrentChange = (val) => {
|
||||
pagination.value = {
|
||||
currentPage: val,
|
||||
pageSize: pagination.value.pageSize,
|
||||
total: pagination.value.total,
|
||||
};
|
||||
emits("get-data");
|
||||
};
|
||||
const handleSizeChange = (val) => {
|
||||
pagination.value = {
|
||||
currentPage: 1,
|
||||
pageSize: val,
|
||||
total: pagination.value.total,
|
||||
};
|
||||
emits("get-data");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
title="文档"
|
||||
:model-value="visible && model === 'dialog'"
|
||||
:append-to-body="appendToBody"
|
||||
@update:model-value="visible = false"
|
||||
>
|
||||
<div v-if="visible" style="height: 690px; overflow-y: auto">
|
||||
<vue-pdf
|
||||
v-for="page in numOfPages"
|
||||
:key="page"
|
||||
:src="fnSrc(props.src)"
|
||||
:page="page"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnDownload"> 下载 </el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<div
|
||||
v-if="model === 'normal'"
|
||||
:key="src"
|
||||
style="height: 690px; overflow-y: auto"
|
||||
>
|
||||
<vue-pdf
|
||||
v-for="page in numOfPages"
|
||||
:key="page"
|
||||
:src="fnSrc(props.src)"
|
||||
:page="page"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watchEffect, ref } from "vue";
|
||||
import { VuePdf, createLoadingTask } from "vue3-pdfjs/esm";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
|
||||
const VITE_FILE_URL = import.meta.env.VITE_FILE_URL;
|
||||
defineOptions({
|
||||
name: "AppPdf",
|
||||
});
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
model: {
|
||||
type: String,
|
||||
validator: (value) => {
|
||||
const typeList = ["dialog", "normal"];
|
||||
if (typeList.includes(value)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(`model必须是${typeList.join("、")}之一`);
|
||||
}
|
||||
},
|
||||
default: "dialog",
|
||||
},
|
||||
appendToBody: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const fnDownload = async () => {
|
||||
await ElMessageBox.confirm("确定要下载吗?", { type: "warning" });
|
||||
window.open(VITE_FILE_URL + props.src);
|
||||
};
|
||||
const visible = defineModel("visible", { type: Boolean, default: false });
|
||||
const numOfPages = ref(0);
|
||||
const fnSrc = (src) => {
|
||||
if (!src) return;
|
||||
if (src.indexOf("http") !== -1 || src.indexOf("https") !== -1) return src;
|
||||
else return VITE_FILE_URL + src;
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (visible.value || props.src) {
|
||||
const loadingTask = createLoadingTask(fnSrc(props.src));
|
||||
loadingTask.promise
|
||||
.then((pdf) => {
|
||||
numOfPages.value = pdf.numPages;
|
||||
})
|
||||
.catch(() => {
|
||||
visible.value = false;
|
||||
ElMessage.error("文件加载失败");
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="tc mt-20 mb-20">
|
||||
<img :src="src" alt="" width="200" height="200" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useQRCode } from "@vueuse/integrations/useQRCode";
|
||||
|
||||
defineOptions({
|
||||
name: "AppQrCode",
|
||||
});
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const src = useQRCode(() => props.src, {
|
||||
width: 200,
|
||||
height: 200,
|
||||
margin: 1,
|
||||
correctLevel: "H",
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<el-form
|
||||
:model="modelValue"
|
||||
:label-width="labelWidth"
|
||||
@submit.prevent="emits('submit')"
|
||||
>
|
||||
<el-row>
|
||||
<slot :collapse="collapse"></slot>
|
||||
<el-col :span="6">
|
||||
<el-form-item label-width="10px">
|
||||
<el-button type="primary" native-type="submit">搜索</el-button>
|
||||
<el-button native-type="reset" @click="emits('submit')">
|
||||
重置
|
||||
</el-button>
|
||||
<app-search-collapse-button
|
||||
v-if="showCollapseButton"
|
||||
:change-search-collapse="changeSearchCollapse"
|
||||
:collapse="collapse"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppSearchCollapseButton from "@/components/search_collapse_button/index.vue";
|
||||
import useSearchCollapse from "@/assets/js/useSearchCollapse.js";
|
||||
|
||||
const { collapse, changeSearchCollapse } = useSearchCollapse();
|
||||
defineOptions({
|
||||
name: "AppSearch",
|
||||
});
|
||||
defineProps({
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: "100px",
|
||||
},
|
||||
showCollapseButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const modelValue = defineModel({ type: Object, required: true });
|
||||
const emits = defineEmits(["submit"]);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<el-button
|
||||
v-if="collapse"
|
||||
:icon="ArrowDown"
|
||||
link
|
||||
text
|
||||
type="primary"
|
||||
@click="changeSearchCollapse"
|
||||
>
|
||||
展开
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!collapse"
|
||||
:icon="ArrowUp"
|
||||
link
|
||||
text
|
||||
type="primary"
|
||||
@click="changeSearchCollapse"
|
||||
>
|
||||
收起
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ArrowDown, ArrowUp } from "@element-plus/icons-vue";
|
||||
|
||||
defineOptions({
|
||||
name: "AppSearchCollapseButton",
|
||||
});
|
||||
defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
changeSearchCollapse: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
size="small"
|
||||
:data="data"
|
||||
:border="border"
|
||||
:stripe="stripe"
|
||||
:height="height"
|
||||
:max-height="maxHeight"
|
||||
:highlight-current-row="highlightCurrentRow"
|
||||
:row-key="getRowKey"
|
||||
:row-class-name="rowClassName"
|
||||
:row-style="rowStyle"
|
||||
:show-header="showHeader"
|
||||
:show-summary="showSummary"
|
||||
:summary-method="summaryMethod"
|
||||
:span-method="spanMethod"
|
||||
:default-expand-all="defaultExpandAll"
|
||||
:tree-props="treeProps"
|
||||
:header-cell-style="headerCellStyle"
|
||||
:cell-style="cellStyle"
|
||||
@row-click="rowClick"
|
||||
@row-dblclick="rowDblclick"
|
||||
>
|
||||
<el-table-column
|
||||
v-if="showSelection"
|
||||
type="selection"
|
||||
reserve-selection
|
||||
width="60"
|
||||
/>
|
||||
<el-table-column v-if="showIndex" label="序号" width="60">
|
||||
<template #default="{ $index }">
|
||||
{{ serialNumber(pagination, $index) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<slot></slot>
|
||||
</el-table>
|
||||
<div v-if="showPagination || slots.button" class="table_footer">
|
||||
<div>
|
||||
<slot name="button"></slot>
|
||||
</div>
|
||||
<app-pagination
|
||||
v-if="showPagination"
|
||||
v-model:pagination="pagination"
|
||||
@get-data="emits('get-data')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useSlots, useTemplateRef } from "vue";
|
||||
import AppPagination from "@/components/pagination/index.vue";
|
||||
import { serialNumber } from "@/assets/js/utils.js";
|
||||
|
||||
const slots = useSlots();
|
||||
defineOptions({
|
||||
name: "AppTable",
|
||||
});
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showPagination: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showSelection: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
stripe: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
highlightCurrentRow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showSummary: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
defaultExpandAll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
rowKey: {
|
||||
type: [String, Function],
|
||||
},
|
||||
maxHeight: {
|
||||
type: [String, Number],
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
},
|
||||
rowClassName: {
|
||||
type: Function,
|
||||
},
|
||||
rowStyle: {
|
||||
type: Function,
|
||||
},
|
||||
summaryMethod: {
|
||||
type: Function,
|
||||
},
|
||||
spanMethod: {
|
||||
type: Function,
|
||||
},
|
||||
treeProps: {
|
||||
type: Object,
|
||||
default: () => ({ hasChildren: "hasChildren", children: "children" }),
|
||||
},
|
||||
headerCellStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
cellStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const pagination = defineModel("pagination", {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
}),
|
||||
});
|
||||
const emits = defineEmits(["get-data", "row-click", "row-dblclick"]);
|
||||
const tableRef = useTemplateRef("tableRef");
|
||||
const getRowKey = (row) => {
|
||||
if (!props.rowKey) return;
|
||||
if (typeof props.rowKey === "string") return row[props.rowKey];
|
||||
else return props.rowKey(row);
|
||||
};
|
||||
const rowClick = (row, column, event) => {
|
||||
emits("row-click", row, column, event);
|
||||
};
|
||||
const rowDblclick = (row, column, event) => {
|
||||
emits("row-dblclick", row, column, event);
|
||||
};
|
||||
const getSelectionRows = () => {
|
||||
return tableRef.value.getSelectionRows();
|
||||
};
|
||||
const clearSelection = () => {
|
||||
return tableRef.value.clearSelection();
|
||||
};
|
||||
const toggleRowSelection = (value, selected = true) => {
|
||||
tableRef.value.toggleRowSelection(value, selected);
|
||||
};
|
||||
defineExpose({
|
||||
getSelectionRows,
|
||||
clearSelection,
|
||||
toggleRowSelection,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table_footer {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
style="width: 100%"
|
||||
:file-list="fileList"
|
||||
:action="action"
|
||||
multiple
|
||||
:limit="limit"
|
||||
:list-type="listType"
|
||||
:auto-upload="autoUpload"
|
||||
:disabled="disabled"
|
||||
:accept="accept"
|
||||
:on-remove="onRemove"
|
||||
:before-remove="beforeRemove"
|
||||
:on-change="onChange"
|
||||
:on-exceed="onExceed"
|
||||
:on-preview="onPreview"
|
||||
:http-request="httpRequest"
|
||||
:show-file-list="showFileList"
|
||||
:class="{ hide: fileList.length === limit }"
|
||||
>
|
||||
<el-icon v-if="listType === 'picture-card'" size="32"><plus /></el-icon>
|
||||
<el-button v-else type="primary">点击选择文件上传</el-button>
|
||||
<template #tip>
|
||||
<div class="mt-10 text-red"><slot name="tip"></slot></div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-dialog v-model="visible" title="查看图片">
|
||||
<img
|
||||
:src="imageUrl"
|
||||
alt="Preview Image"
|
||||
style="width: 100%; object-fit: scale-down"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, useTemplateRef } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { setDeleteImg } from "@/request/api";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
|
||||
defineOptions({
|
||||
name: "AppUpload",
|
||||
});
|
||||
const props = defineProps({
|
||||
autoUpload: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
listType: {
|
||||
type: String,
|
||||
default: "text",
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
ratio: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
deleteToServer: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showFileList: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
httpRequest: {
|
||||
type: Function,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
const visible = ref(false);
|
||||
const imageUrl = ref("");
|
||||
const fileList = defineModel("fileList", { type: Array, required: true });
|
||||
const emits = defineEmits(["preview"]);
|
||||
const uploadRef = useTemplateRef("uploadRef");
|
||||
const onExceed = () => {
|
||||
ElMessage.warning(`最多上传${props.limit}个文件`);
|
||||
};
|
||||
const beforeRemove = async (uploadFile) => {
|
||||
if (props.deleteToServer && uploadFile.imgfilesId) {
|
||||
await ElMessageBox.confirm("确定要删除吗?", {
|
||||
type: "warning",
|
||||
});
|
||||
await setDeleteImg({
|
||||
imgfilesId: uploadFile.imgfilesId,
|
||||
filepath: uploadFile.filepath,
|
||||
});
|
||||
}
|
||||
};
|
||||
const onRemove = (uploadFile, uploadFiles) => {
|
||||
fileList.value = uploadFiles;
|
||||
};
|
||||
const onChange = (uploadFile, uploadFiles) => {
|
||||
const accept = props.accept && props.accept.split(",");
|
||||
const ratio = props.ratio && props.ratio.split("*");
|
||||
const suffix = uploadFile.raw.name.substring(
|
||||
uploadFile.raw.name.lastIndexOf("."),
|
||||
uploadFile.raw.name.length
|
||||
);
|
||||
const size = props.size * 1024 * 1024;
|
||||
if (ratio) {
|
||||
const img = new Image();
|
||||
img.src = uploadFile.url;
|
||||
img.onload = () => {
|
||||
if (img.width !== +ratio[0] && img.height !== +ratio[1]) {
|
||||
ElMessage.warning(`只能上传${props.ratio}分辨率的图片`);
|
||||
uploadRef.value.handleRemove(uploadFile.raw);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (size) {
|
||||
if (uploadFile.size > size) {
|
||||
ElMessage.warning(`文件大小不能超过${props.size}M`);
|
||||
uploadRef.value.handleRemove(uploadFile.raw);
|
||||
}
|
||||
}
|
||||
if (accept) {
|
||||
if (accept.includes(suffix)) {
|
||||
fileList.value = uploadFiles;
|
||||
} else {
|
||||
ElMessage.warning(`只能上传${props.accept}格式的文件`);
|
||||
uploadRef.value.handleRemove(uploadFile.raw);
|
||||
}
|
||||
} else {
|
||||
fileList.value = uploadFiles;
|
||||
}
|
||||
};
|
||||
const onPreview = (uploadFile) => {
|
||||
if (props.listType === "picture-card") {
|
||||
visible.value = true;
|
||||
imageUrl.value = uploadFile.url;
|
||||
} else {
|
||||
emits("preview", uploadFile);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.hide :deep(.el-upload--picture-card) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
<template>
|
||||
<div class="mi-captcha">
|
||||
<div class="mi-captcha-content">
|
||||
<!-- 没有进行验证-->
|
||||
<div
|
||||
v-if="!verificationPass"
|
||||
class="mi-captcha-radar"
|
||||
@click="verificationShow = true"
|
||||
>
|
||||
<div class="mi-captcha-radar-ready">
|
||||
<div class="mi-captcha-radar-ring" />
|
||||
<div class="mi-captcha-radar-dot" />
|
||||
</div>
|
||||
<div class="mi-captcha-radar-tip">点击按钮进行验证</div>
|
||||
</div>
|
||||
<!-- 验证通过-->
|
||||
<div
|
||||
v-if="verificationPass"
|
||||
class="mi-captcha-radar mi-captcha-radar-pass"
|
||||
>
|
||||
<div class="mi-captcha-radar-success mi-captcha-radar-success-icon">
|
||||
<span role="img" aria-label="verified">
|
||||
<svg
|
||||
focusable="false"
|
||||
class=""
|
||||
data-icon="verified"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
viewBox="64 64 896 896"
|
||||
>
|
||||
<path
|
||||
d="M447.8 588.8l-7.3-32.5c-.2-1-.6-1.9-1.1-2.7a7.94 7.94 0 00-11.1-2.2L405 567V411c0-4.4-3.6-8-8-8h-81c-4.4 0-8 3.6-8 8v36c0 4.4 3.6 8 8 8h37v192.4a8 8 0 0012.7 6.5l79-56.8c2.6-1.9 3.8-5.1 3.1-8.3zm-56.7-216.6l.2.2c3.2 3 8.3 2.8 11.3-.5l24.1-26.2a8.1 8.1 0 00-.3-11.2l-53.7-52.1a8 8 0 00-11.2.1l-24.7 24.7c-3.1 3.1-3.1 8.2.1 11.3l54.2 53.7z"
|
||||
/>
|
||||
<path
|
||||
d="M866.9 169.9L527.1 54.1C523 52.7 517.5 52 512 52s-11 .7-15.1 2.1L157.1 169.9c-8.3 2.8-15.1 12.4-15.1 21.2v482.4c0 8.8 5.7 20.4 12.6 25.9L499.3 968c3.5 2.7 8 4.1 12.6 4.1s9.2-1.4 12.6-4.1l344.7-268.6c6.9-5.4 12.6-17 12.6-25.9V191.1c.2-8.8-6.6-18.3-14.9-21.2zM810 654.3L512 886.5 214 654.3V226.7l298-101.6 298 101.6v427.6z"
|
||||
/>
|
||||
<path
|
||||
d="M452 297v36c0 4.4 3.6 8 8 8h108v274h-38V405c0-4.4-3.6-8-8-8h-35c-4.4 0-8 3.6-8 8v210h-31c-4.4 0-8 3.6-8 8v37c0 4.4 3.6 8 8 8h244c4.4 0 8-3.6 8-8v-37c0-4.4-3.6-8-8-8h-72V493h58c4.4 0 8-3.6 8-8v-35c0-4.4-3.6-8-8-8h-58V341h63c4.4 0 8-3.6 8-8v-36c0-4.4-3.6-8-8-8H460c-4.4 0-8 3.6-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mi-captcha-radar-tip">通过验证</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<verification
|
||||
:show="verificationShow"
|
||||
@success="verificationSuccess"
|
||||
@close="verificationClose"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Verification from "vue3-puzzle-vcode";
|
||||
import { ref } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "AppVerification",
|
||||
});
|
||||
const verificationPass = defineModel("verificationPass", {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
const verificationShow = ref(false);
|
||||
const verificationClose = () => {
|
||||
verificationShow.value = false;
|
||||
};
|
||||
const verificationSuccess = () => {
|
||||
verificationPass.value = true;
|
||||
verificationClose();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mi-captcha {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
height: 2.625rem;
|
||||
font-family: "Pingfang SC", "Microsoft YaHei", "Monospaced Number",
|
||||
"Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"PingFang SC", "Hiragino Sans GB", "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.mi-captcha-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mi-captcha-radar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #1d1e23;
|
||||
background-image: linear-gradient(315deg, #a8b4d3 0%, #adc0ed 74%);
|
||||
border: 1px solid #96a4c8;
|
||||
cursor: pointer;
|
||||
min-width: 10rem;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mi-captcha-radar-ready,
|
||||
.mi-captcha-radar-success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
transition: all 0.4s ease;
|
||||
width: 1.875rem;
|
||||
height: 1.875rem;
|
||||
margin: 0.375rem;
|
||||
}
|
||||
|
||||
.mi-captcha-radar-ring,
|
||||
.mi-captcha-radar-dot {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
transform: scale(0.4);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: inset 0 0 0 1px #2c67ec;
|
||||
background-image: linear-gradient(0, rgba(0, 0, 0, 0) 50%, #fff 50%),
|
||||
linear-gradient(0, #fff 50%, rgba(0, 0, 0, 0) 50%);
|
||||
}
|
||||
|
||||
.mi-captcha-radar-dot {
|
||||
background: #2c67ec;
|
||||
}
|
||||
|
||||
.mi-captcha-radar-ring {
|
||||
animation: mi-anim-wait 1s infinite;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.mi-captcha-radar-success {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mi-captcha-radar-success-icon {
|
||||
color: #f6ca9d;
|
||||
animation-name: mi-captcha-success;
|
||||
animation-timing-function: ease;
|
||||
animation-iteration-count: 1;
|
||||
animation-delay: 0.5s;
|
||||
animation-duration: 0.4s;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.mi-captcha-radar-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-direction: row;
|
||||
height: 2.625rem;
|
||||
padding-left: 0.125rem;
|
||||
font-size: 0.875rem;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mi-captcha-radar-pass .mi-captcha-radar-tip {
|
||||
color: #f6ca9d;
|
||||
}
|
||||
|
||||
@keyframes mi-captcha-success {
|
||||
25% {
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mi-anim-wait {
|
||||
60% {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
}
|
||||
|
||||
:deep {
|
||||
.vue-auth-box_ {
|
||||
background: #2e63d8 !important;
|
||||
border: 1px solid #2752b3 !important;
|
||||
}
|
||||
.mi-captcha-radar-pass .mi-captcha-radar-tip {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.mi-captcha-radar-success-icon {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<el-dialog v-model="visible" title="视频" width="840px" @close="fnClose">
|
||||
<video
|
||||
v-if="visible"
|
||||
:id="className"
|
||||
playsinline
|
||||
controls
|
||||
:data-poster="poster"
|
||||
style="width: 100%"
|
||||
>
|
||||
<source :src="fnGetSrc()" type="video/mp4" />
|
||||
</video>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Plyr from "plyr";
|
||||
import "plyr/dist/plyr.css";
|
||||
import { nextTick, ref, watchEffect } from "vue";
|
||||
import { uniqueId } from "lodash-es";
|
||||
import { addLocalFilePrefix } from "@/assets/js/utils.js";
|
||||
|
||||
let player = null;
|
||||
defineOptions({
|
||||
name: "AppVideo",
|
||||
});
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
poster: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const className = ref(uniqueId("_"));
|
||||
const fnCreateVideo = async () => {
|
||||
await nextTick();
|
||||
const video = document.querySelector(`#${className.value}`);
|
||||
player = new Plyr(video);
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.visible || props.src) fnCreateVideo();
|
||||
});
|
||||
const fnClose = () => {
|
||||
player && player.destroy();
|
||||
player = null;
|
||||
visible.value = false;
|
||||
};
|
||||
const fnGetSrc = () => {
|
||||
return addLocalFilePrefix(props.src);
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div
|
||||
style="display: flex; justify-content: space-between; align-items: center"
|
||||
>
|
||||
<div class="breadcrumb">
|
||||
<el-breadcrumb class="app-breadcrumb" separator=">">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item
|
||||
v-for="(item, index) in breadcrumbList"
|
||||
:key="item.path"
|
||||
>
|
||||
<router-link v-if="index === 0" :to="item.path">
|
||||
{{ item.meta.title }}
|
||||
</router-link>
|
||||
<span v-else-if="index !== breadcrumbList.length - 1">
|
||||
{{ item.meta.title }}
|
||||
</span>
|
||||
<span v-else class="no-redirect">{{ item.meta.title }}</span>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
defineOptions({
|
||||
name: "LayoutBreadcrumb",
|
||||
});
|
||||
const route = useRoute();
|
||||
const breadcrumbList = ref([]);
|
||||
const fnGetBreadcrumb = () => {
|
||||
const matched = route.matched.filter((item) => item.meta?.title);
|
||||
if (matched[0].path === "/") matched[0].path = "/index";
|
||||
breadcrumbList.value = matched.filter(
|
||||
(item) => item.meta?.title && item.meta?.breadcrumb !== false
|
||||
);
|
||||
};
|
||||
fnGetBreadcrumb();
|
||||
watch(
|
||||
() => route,
|
||||
() => fnGetBreadcrumb(),
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
font-size: 14px;
|
||||
line-height: var(--el-header-height);
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.el-breadcrumb__inner a {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<el-dialog v-model="visible" title="修改密码" width="600px" @close="fnClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="旧密码" prop="password">
|
||||
<el-input v-model="form.password" type="password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="form.newPassword" type="password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="newPasswordConfirm">
|
||||
<el-input v-model="form.newPasswordConfirm" type="password" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">确认修改</el-button>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { ref } from "vue";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import { setChangePassword } from "@/request/api.js";
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["update:visible", "submit"]);
|
||||
const visible = useVModel(props, "visible", emits);
|
||||
const form = ref({
|
||||
password: "",
|
||||
newPassword: "",
|
||||
newPasswordConfirm: "",
|
||||
});
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const validatePass = (_rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请再次输入新密码"));
|
||||
} else if (value !== form.value.newPassword) {
|
||||
callback(new Error("两次输入密码不一致!"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const rules = {
|
||||
password: [{ required: true, message: "请输入旧密码", trigger: "blur" }],
|
||||
newPassword: [
|
||||
{ required: true, message: "请输入新密码", trigger: "blur" },
|
||||
{ min: 6, max: 18, message: "密码长度为6-18位", trigger: "blur" },
|
||||
],
|
||||
newPasswordConfirm: [
|
||||
{ required: true, validator: validatePass, trigger: "blur" },
|
||||
],
|
||||
};
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
await setChangePassword({ ...form.value });
|
||||
fnClose();
|
||||
emits("submit");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<template>
|
||||
<div class="header">
|
||||
<div class="logo">
|
||||
<!-- <img-->
|
||||
<!-- src="/src/assets/images/public/logo1.png"-->
|
||||
<!-- alt=""-->
|
||||
<!-- width="134"-->
|
||||
<!-- height="28"-->
|
||||
<!-- />-->
|
||||
</div>
|
||||
<div class="breadcrumb">
|
||||
<layout-breadcrumb v-if="route.meta.isBreadcrumb !== false" />
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="menu">
|
||||
<ul>
|
||||
<li
|
||||
v-for="(item, index) in MENU"
|
||||
:key="index"
|
||||
:class="{ active: item.model === menuStore.getModel }"
|
||||
@click="switchMenu(item.model)"
|
||||
>
|
||||
<component
|
||||
:is="'icon-' + item.icon"
|
||||
theme="filled"
|
||||
fill="#a5b2c2"
|
||||
size="18"
|
||||
:stroke-width="3"
|
||||
style="height: 18px"
|
||||
/>
|
||||
<div class="title">{{ item.title }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="user">
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
placement="bottom-end"
|
||||
@command="dropdownCommand"
|
||||
>
|
||||
<div class="user_info">
|
||||
<el-avatar shape="circle" :size="30" fit="fill" :src="tx" />
|
||||
<span>{{ userStore.getUserInfo.username }}</span>
|
||||
<icon-down
|
||||
theme="filled"
|
||||
size="16"
|
||||
fill="#a2c2d3"
|
||||
:stroke-width="3"
|
||||
/>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="modifyPassword">
|
||||
修改密码
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="signOut">退出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<change-password
|
||||
v-model:visible="passwordDialogVisible"
|
||||
@submit="fnSignOut"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useMenuStore } from "@/pinia/menu.js";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import { MENU } from "@/assets/js/constant.js";
|
||||
import { logout } from "@/request/api.js";
|
||||
import LayoutBreadcrumb from "@/layout/breadcrumb/index.vue";
|
||||
import tx from "@/assets/images/public/tx.png";
|
||||
import ChangePassword from "./components/change_password.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "LayoutHeader",
|
||||
});
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const menuStore = useMenuStore();
|
||||
const userStore = useUserStore();
|
||||
const passwordDialogVisible = ref(false);
|
||||
const dropdownCommand = async (command) => {
|
||||
if (command === "signOut") {
|
||||
await fnSignOut();
|
||||
}
|
||||
if (command === "modifyPassword") {
|
||||
passwordDialogVisible.value = true;
|
||||
}
|
||||
};
|
||||
const switchMenu = (model) => {
|
||||
menuStore.setModel(model);
|
||||
};
|
||||
const fnSignOut = async () => {
|
||||
await logout();
|
||||
userStore.$reset();
|
||||
await router.replace("/login");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||
padding-right: 20px;
|
||||
background-color: #fff;
|
||||
|
||||
.logo {
|
||||
width: 210px;
|
||||
height: 50px;
|
||||
background-color: #2b2f3a;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
.menu {
|
||||
margin-right: 30px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
|
||||
li {
|
||||
height: 50px;
|
||||
padding: 0 10px;
|
||||
margin: 0 10px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.active {
|
||||
color: #79bbff;
|
||||
border-bottom: 2px solid #79bbff;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
margin-top: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
.el-avatar {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.user_info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<layout-header />
|
||||
</el-header>
|
||||
<el-container>
|
||||
<el-aside>
|
||||
<el-scrollbar style="height: calc(100vh - 50px)">
|
||||
<el-menu
|
||||
router
|
||||
unique-opened
|
||||
:default-active="$route.meta.activeMenu || $route.path"
|
||||
background-color="rgb(48, 65, 86)"
|
||||
text-color="#edf7ff"
|
||||
active-text-color="#409eff"
|
||||
>
|
||||
<layout-menu :menus="routes" />
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<el-scrollbar style="height: calc(100vh - 50px)">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="view" mode="out-in">
|
||||
<el-card :key="route.path">
|
||||
<el-page-header
|
||||
v-if="route.meta.isBack !== false"
|
||||
title="返回"
|
||||
@back="router.back()"
|
||||
>
|
||||
<template #content>
|
||||
{{
|
||||
route.matched.filter((item) => item.meta?.title).at(-1)
|
||||
.meta.title
|
||||
}}
|
||||
</template>
|
||||
</el-page-header>
|
||||
<component :is="Component"></component>
|
||||
</el-card>
|
||||
</transition>
|
||||
</router-view>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LayoutHeader from "@/layout/header/index.vue";
|
||||
import LayoutMenu from "@/layout/menu/index.vue";
|
||||
import { computed } from "vue";
|
||||
import { useMenuStore } from "@/pinia/menu.js";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
defineOptions({
|
||||
name: "Layout",
|
||||
});
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const menuStore = useMenuStore();
|
||||
const routes = computed(() => menuStore.getMenus);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.el-header {
|
||||
--el-header-padding: 0;
|
||||
--el-header-height: 50px;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
width: 210px;
|
||||
background-color: rgb(48, 65, 86);
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
width: 210px;
|
||||
min-height: calc(100vh - 50px);
|
||||
border-right: none;
|
||||
background-color: rgb(48, 65, 86);
|
||||
|
||||
:deep {
|
||||
span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep {
|
||||
.el-sub-menu__title *,
|
||||
.el-menu-item * {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
.el-card {
|
||||
margin: 15px;
|
||||
border: none !important;
|
||||
min-height: calc(100vh - 115px);
|
||||
}
|
||||
.is-always-shadow {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-main {
|
||||
--el-main-padding: 0;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
margin: 20px;
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<template v-for="menu in props.menus" :key="menu.path">
|
||||
<!-- 没有二级导航-->
|
||||
<el-menu-item
|
||||
v-if="fnIsShowMenuItem(menu)"
|
||||
:index="menu.path"
|
||||
@click="fnNavigate"
|
||||
>
|
||||
<component
|
||||
:is="'icon-' + menu.meta?.icon"
|
||||
v-if="menu.meta?.icon"
|
||||
theme="filled"
|
||||
fill="#a5b2c2"
|
||||
size="18"
|
||||
:stroke-width="3"
|
||||
style="margin-right: 15px"
|
||||
/>
|
||||
<span>{{ menu.meta?.title }}</span>
|
||||
</el-menu-item>
|
||||
<!-- 有二级导航-->
|
||||
<el-sub-menu v-else-if="fnIsShowSubmenu(menu)" :index="menu.path">
|
||||
<template #title>
|
||||
<component
|
||||
:is="'icon-' + menu.meta?.icon"
|
||||
v-if="menu.meta?.icon"
|
||||
theme="filled"
|
||||
fill="#bfcbd9"
|
||||
size="18"
|
||||
:stroke-width="3"
|
||||
style="margin-right: 10px"
|
||||
/>
|
||||
<span>{{ menu.meta?.title }}</span>
|
||||
</template>
|
||||
<!-- 递归调用当前组件生成导航-->
|
||||
<layout-menu :menus="menu.children" />
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
import { resetQueryCriteria } from "@/assets/js/useQueryCriteria.js";
|
||||
|
||||
const router = useRouter();
|
||||
defineOptions({
|
||||
name: "LayoutMenu",
|
||||
});
|
||||
const props = defineProps({
|
||||
menus: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const fnIsShowMenuItem = (menu) => {
|
||||
if (menu.meta?.isMenu === false) {
|
||||
return false;
|
||||
}
|
||||
if (menu.meta?.isSubMenu === false) {
|
||||
return true;
|
||||
} else {
|
||||
return !menu.children || menu.children.length === 0;
|
||||
}
|
||||
};
|
||||
const fnIsShowSubmenu = (menu) => {
|
||||
if (menu.meta?.isMenu === false) {
|
||||
return false;
|
||||
}
|
||||
if (menu.meta?.isSubMenu === false) {
|
||||
return false;
|
||||
} else {
|
||||
return menu.children && menu.children.length > 0;
|
||||
}
|
||||
};
|
||||
const fnNavigate = (event) => {
|
||||
resetQueryCriteria();
|
||||
router.push(event.index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { createApp } from "vue";
|
||||
import "@/assets/css/common.scss";
|
||||
import "@/assets/css/transition.scss";
|
||||
import "@/assets/css/element.scss";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import App from "./App";
|
||||
import pinia from "./pinia";
|
||||
import router from "./router";
|
||||
import "normalize.css";
|
||||
import "animate.css";
|
||||
import "viewerjs/dist/viewer.css";
|
||||
import VueViewer from "v-viewer";
|
||||
import print from "vue3-print-nb";
|
||||
import button from "@/assets/js/button";
|
||||
import "./addRouters";
|
||||
import "element-plus/es/components/loading/style/css";
|
||||
import "element-plus/es/components/message/style/css";
|
||||
import "element-plus/es/components/message-box/style/css";
|
||||
import { install } from "@icon-park/vue-next/es/all";
|
||||
import ElDialog from "element-plus/es/components/dialog/index";
|
||||
|
||||
ElDialog.props.closeOnClickModal.default = false;
|
||||
ElDialog.props.closeOnPressEscape.default = false;
|
||||
const app = createApp(App);
|
||||
install(app, "icon");
|
||||
app
|
||||
.use(pinia)
|
||||
.use(router)
|
||||
.use(VueViewer, {
|
||||
defaultOptions: {
|
||||
zIndex: 9999,
|
||||
},
|
||||
})
|
||||
.use(print)
|
||||
.use(button)
|
||||
.mount("#app");
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { createPinia } from "pinia";
|
||||
import piniaPersistedstate from "pinia-plugin-persistedstate";
|
||||
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPersistedstate);
|
||||
|
||||
export default pinia;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useMenuStore = defineStore("menuStore", {
|
||||
state: () => ({
|
||||
menusAll: [],
|
||||
menus: [],
|
||||
model: "",
|
||||
}),
|
||||
getters: {
|
||||
getMenus: (state) => state.menus,
|
||||
getModel: (state) => state.model,
|
||||
},
|
||||
actions: {
|
||||
setMenus(menus) {
|
||||
this.menusAll = menus;
|
||||
},
|
||||
setModel(model) {
|
||||
this.model = model;
|
||||
this.menus = [];
|
||||
for (let i = 0; i < this.menusAll.length; i++) {
|
||||
if (this.menusAll[i].meta.model === model) {
|
||||
this.menus.push(this.menusAll[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useMiscellaneousStore = defineStore("miscellaneousStore", {
|
||||
state: () => ({
|
||||
queryCriteria: {},
|
||||
}),
|
||||
getters: {
|
||||
getQueryCriteria() {
|
||||
return this.queryCriteria;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setQueryCriteria(data) {
|
||||
this.queryCriteria = data;
|
||||
},
|
||||
resetQueryCriteria() {
|
||||
this.queryCriteria[window.location.href] = {};
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
paths: [],
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useRouterStore = defineStore("routerStore", {
|
||||
state: () => ({
|
||||
routers: [],
|
||||
}),
|
||||
getters: {
|
||||
getRouters: (state) => state.routers,
|
||||
},
|
||||
actions: {
|
||||
setRouters(routers) {
|
||||
this.routers = routers;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore("userStore", {
|
||||
state: () => ({
|
||||
userInfo: {},
|
||||
token: "",
|
||||
tokenTime: "1",
|
||||
permissions: [],
|
||||
}),
|
||||
getters: {
|
||||
getUserInfo: (state) => state.userInfo,
|
||||
getToken: (state) => state.token,
|
||||
getTokenTime: (state) => state.tokenTime,
|
||||
getPermissions: (state) => state.permissions,
|
||||
},
|
||||
actions: {
|
||||
setUserInfo(userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
},
|
||||
async setToken(token) {
|
||||
this.token = token;
|
||||
},
|
||||
async setTokenTime(tokenTime) {
|
||||
this.tokenTime = tokenTime;
|
||||
},
|
||||
setPermissions(permissions) {
|
||||
this.permissions = permissions;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { post } from "./axios";
|
||||
|
||||
export const Login = (params) => post("/sys/login", params); // 登录
|
||||
export const logout = (params) => post("/sys/logout", params); // 退出登录
|
||||
export const getAsyncRouter = (params) => post("/sys/menu/nav", params); // 获取动态路由
|
||||
export const getUserInfo = (params) => post("/sys/user/info", params); // 获取用户信息
|
||||
export const setChangePassword = (params) => post("/sys/user/password", params); // 修改密码
|
||||
export const setRefreshToken = (params) =>
|
||||
post("/sys/refreshToken", { loading: false, ...params }); // 刷新token
|
||||
export const setDeleteImg = (params) => post("/busImgfiles/delete", params); // 删除附件
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import axios from "axios";
|
||||
import { ElLoading, ElMessage } from "element-plus";
|
||||
import router from "../router";
|
||||
import pinia from "../pinia";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import refreshToken from "@/assets/js/refreshToken.js";
|
||||
|
||||
let loading = null;
|
||||
function startLoading() {
|
||||
loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: "加载中...",
|
||||
background: "rgba(0, 0, 0, 0.5)",
|
||||
});
|
||||
}
|
||||
|
||||
function endLoading() {
|
||||
loading && loading.close();
|
||||
}
|
||||
|
||||
// axios.defaults.baseURL = import.meta.env[
|
||||
// import.meta.env.DEV ? "VITE_PROXY" : "VITE_BASE_URL"
|
||||
// ];
|
||||
axios.defaults.baseURL = import.meta.env.VITE_PROXY;
|
||||
axios.defaults.timeout = 1000 * 60 * 10;
|
||||
// axios.defaults.withCredentials = true;
|
||||
axios.interceptors.request.use(
|
||||
async (config) => {
|
||||
const userStore = useUserStore(pinia);
|
||||
config.headers.Token = userStore.getToken;
|
||||
if (config.method === "get" || config.method === "GET") {
|
||||
if (config.params.loading !== false) startLoading();
|
||||
}
|
||||
if (config.method === "post" || config.method === "POST") {
|
||||
if (config.data.loading !== false) startLoading();
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(config) => {
|
||||
endLoading();
|
||||
if (config.data.code === 401) {
|
||||
ElMessage.error("登录失效,请重新登录");
|
||||
router.push("/login").then();
|
||||
} else {
|
||||
refreshToken();
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
if (error && error.response) {
|
||||
error.message = `连接错误${error.response.status}`;
|
||||
import.meta.env.DEV &&
|
||||
ElMessage.error(`连接错误${error.response.status}`);
|
||||
} else {
|
||||
error.message = "连接到服务器失败";
|
||||
ElMessage.error("连接到服务器失败");
|
||||
}
|
||||
return Promise.reject(error.message);
|
||||
}
|
||||
);
|
||||
|
||||
export function post(url, params = {}) {
|
||||
const userStore = useUserStore(pinia);
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(url, {
|
||||
corpinfoId: userStore.getUserInfo.corpinfoId,
|
||||
userId: userStore.getUserInfo.userId,
|
||||
...params,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.result === "success") {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
ElMessage.error(res.data.msg || "系统开小差了");
|
||||
reject(res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function get(url, params = {}) {
|
||||
const userStore = useUserStore(pinia);
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(url, {
|
||||
params: {
|
||||
corpinfoId: userStore.getUserInfo.corpinfoId,
|
||||
userId: userStore.getUserInfo.userId,
|
||||
...params,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.result === "success") {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
ElMessage.error(res.data.msg || "系统开小差了");
|
||||
reject(res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function upload(url, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(url, params, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.result === "success") {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
ElMessage.error(res.data.msg || "系统开小差了");
|
||||
reject(res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { post } from "./axios";
|
||||
|
||||
// 获取部门
|
||||
export const getDepartmentTree = (parentId = "0") =>
|
||||
post("/oa/department/getTree", {
|
||||
loading: false,
|
||||
parentId,
|
||||
});
|
||||
// 获取数据字典
|
||||
export const getDataDictionaries = (params) =>
|
||||
post("/dictionaries/getLevels", {
|
||||
loading: false,
|
||||
...params,
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { post } from "./axios";
|
||||
|
||||
export const getScheduleJobList = (params) =>
|
||||
post("/sys/schedule/list", params); // 定时任务列表
|
||||
export const getScheduleJobInfo = (params) =>
|
||||
post("/sys/schedule/info", params); // 定时任务查看
|
||||
export const setScheduleJobAdd = (params) => post("/sys/schedule/save", params); // 定时任务新增
|
||||
export const setScheduleJobUpdate = (params) =>
|
||||
post("/sys/schedule/update", params); // 定时任务修改
|
||||
export const setScheduleJobDelete = (params) =>
|
||||
post("/sys/schedule/delete", params); // 定时任务删除
|
||||
export const setScheduleJobPause = (params) =>
|
||||
post("/sys/schedule/pause", params); // 定时任务暂停
|
||||
export const setScheduleJobResume = (params) =>
|
||||
post("/sys/schedule/resume", params); // 定时任务恢复
|
||||
export const setScheduleJobRun = (params) => post("/sys/schedule/run", params); // 定时任务执行
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { post } from "@/request/axios";
|
||||
|
||||
export const getRoleList = (params) => post("/sys/role/listPage", params); // 角色管理列表
|
||||
export const getRoleListAll = (params) => post("/sys/role/listAll", params); // 角色管理列表所有
|
||||
export const setRoleDelete = (params) => post("/sys/role/delete", params); // 角色管理删除
|
||||
export const setRoleAdd = (params) => post("/sys/role/save", params); // 角色管理添加
|
||||
export const setRoleEdit = (params) => post("/sys/role/update", params); // 角色管理修改
|
||||
export const getRoleView = (params) => post("/sys/role/info", params); // 角色管理查看
|
||||
export const getDataDictionaryList = (params) =>
|
||||
post("/sys/dictionaries/listPage", params); // 数据字典列表
|
||||
export const setDataDictionaryDelete = (params) =>
|
||||
post("/sys/dictionaries/delete", params); // 数据字典删除
|
||||
export const setDataDictionaryAdd = (params) =>
|
||||
post("/sys/dictionaries/save", params); // 数据字典添加
|
||||
export const setDataDictionaryEdit = (params) =>
|
||||
post("/sys/dictionaries/update", params); // 数据字典修改
|
||||
export const getDataDictionaryInfo = (params) =>
|
||||
post("/sys/dictionaries/info", params); // 数据字典查看
|
||||
export const getDataDictionaryRepeat = (params) =>
|
||||
post("/sys/dictionaries/findByBianma", params); // 数据字典验证编码是否重复
|
||||
export const getRouteList = (params) => post("/sys/menu/list", params); // 菜单管理列表
|
||||
export const getRouteView = (params) => post("/sys/menu/info", params); // 菜单管理查看
|
||||
export const setRouteAdd = (params) => post("/sys/menu/save", params); // 菜单管理添加
|
||||
export const setRouteEdit = (params) => post("/sys/menu/update", params); // 菜单管理修改
|
||||
export const setRouteDelete = (params) => post("/sys/menu/delete", params); // 菜单管理删除
|
||||
export const setRouteIcon = (params) => post("/sys/menu/icon", params); // 菜单管理修改图标
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { post, upload } from "@/request/axios";
|
||||
export const getDepartmentList = (params) =>
|
||||
post("/oa/department/listPage", params); // 部门管理列表
|
||||
export const setDepartmentDelete = (params) =>
|
||||
post("/oa/department/delete", params); // 部门管理删除
|
||||
export const setDepartmentAdd = (params) => post("/oa/department/save", params); // 部门管理添加
|
||||
export const setDepartmentEdit = (params) =>
|
||||
post("/oa/department/update", params); // 部门管理修改
|
||||
export const getDepartmentView = (params) =>
|
||||
post("/oa/department/info", params); // 部门管理查看
|
||||
export const getJobList = (params) => post("/oa/post/listPage", params); // 岗位管理列表
|
||||
export const getJobListAll = (params) => post("/oa/post/listAll", params); // 岗位管理列表所有
|
||||
export const setJobDelete = (params) => post("/oa/post/delete", params); // 岗位管理删除
|
||||
export const getJobView = (params) => post("/oa/post/info", params); // 岗位管理查看
|
||||
export const setJobAdd = (params) => post("/oa/post/save", params); // 岗位管理添加
|
||||
export const setJobEdit = (params) => post("/oa/post/update", params); // 岗位管理修改
|
||||
export const getUserList = (params) => post("/sys/user/list", params); // 用户管理列表
|
||||
export const getUserListAll = (params) => post("sys/user/listAll", params); // 用户管理列表所有
|
||||
export const setUserDelete = (params) => post("/sys/user/delete", params); // 用户管理删除
|
||||
export const setUserResetPassword = (params) =>
|
||||
post("/sys/user/resetPassword", params); // 用户管理重置密码
|
||||
export const getUserView = (params) => post("/sys/user/getInfo", params); // 用户管理查看
|
||||
export const getUserUserNameRepeat = (params) =>
|
||||
post("/sys/user/hasUser", params); // 用户管理用户名去重
|
||||
export const setUserAdd = (params) => post("/sys/user/save", params); // 用户管理添加
|
||||
export const setUserEdit = (params) => post("/sys/user/update", params); // 用户管理修改
|
||||
export const setUserExercisesImport = (params) =>
|
||||
upload("/sys/user/readExcel", params); // 人员导入
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import layout from "@/layout/index.vue";
|
||||
// import children from "../components/children/index.vue";
|
||||
|
||||
/**
|
||||
* path 从跟开始每一级都要写
|
||||
* name 和path一样
|
||||
* redirect 重定向到哪一个路由,子级路由第一个的path,(没有子级路由不需要填写)
|
||||
* component 路由对应的组件位置(必填,views下的文件,views和.vue不需要填写)
|
||||
* meta参数说明
|
||||
* title 显示在菜单和面包屑中名称
|
||||
* model 归类到头部导航哪一级中(最外层路由需要填写)
|
||||
* activeMenu 当前路由选中状态是哪个导航(isSubMenu:false时需要填写,设置isSubMenu:false路由的path)
|
||||
* isMenu false 不显示当前菜单
|
||||
* isLogin false 不需要登录可以访问
|
||||
* breadcrumb false 当前页不显示在面包屑中
|
||||
* isBreadcrumb false 当前页不显示面包屑
|
||||
* isSubMenu false 当前菜单不显示子菜单
|
||||
* isBack false 当前菜页不显示返回按钮
|
||||
**/
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/login",
|
||||
name: "/login",
|
||||
meta: { title: "登录", isLogin: false },
|
||||
component: () => import("@/views/login/index"),
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "app",
|
||||
redirect: "/index",
|
||||
meta: { title: "首页" },
|
||||
component: layout,
|
||||
children: [
|
||||
{
|
||||
path: "/index",
|
||||
name: "/index",
|
||||
meta: {
|
||||
title: "首页",
|
||||
breadcrumb: false,
|
||||
isMenu: false,
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
},
|
||||
component: () => import("@/views/index/index"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/404",
|
||||
name: "/404",
|
||||
meta: { title: "404", isBreadcrumb: false, isMenu: false },
|
||||
component: () => import("@/views/404"),
|
||||
},
|
||||
];
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
} else {
|
||||
return { left: 0, top: 0 };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -0,0 +1,565 @@
|
|||
<template>
|
||||
<div class="containerer">
|
||||
<div class="container container-star">
|
||||
<div v-for="item in 30" :key="item" class="star-1"></div>
|
||||
<div v-for="item in 30" :key="item" class="star-2"></div>
|
||||
</div>
|
||||
<div class="container container-bird">
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top"></div>
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top"></div>
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top"></div>
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top"></div>
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top"></div>
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top"></div>
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-title">
|
||||
<div class="title">
|
||||
<div class="number">4</div>
|
||||
<div class="moon">
|
||||
<div class="face">
|
||||
<div class="mouth"></div>
|
||||
<div class="eyes">
|
||||
<div class="eye-left"></div>
|
||||
<div class="eye-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="number">4</div>
|
||||
</div>
|
||||
<div class="subtitle">抱歉,您访问的页面不存在!</div>
|
||||
<button class="backbtn" @click="router.push({ name: '/index' })">
|
||||
返回首页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import url("https://fonts.googleapis.com/css?family=Lato|Russo+One");
|
||||
*,
|
||||
*:after,
|
||||
*:before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
//stars
|
||||
.container-star {
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
#04112b 0%,
|
||||
#474c88 70%,
|
||||
#a871d6 100%
|
||||
);
|
||||
&:after {
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0) 40%,
|
||||
rgba(15, 10, 38, 0.2) 100%
|
||||
);
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.star-1 {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
animation: twinkle 5s infinite ease-in-out;
|
||||
&:after {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transform: rotate(90deg);
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:before {
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(255, 255, 255, 0.5) 0%,
|
||||
rgba(255, 255, 255, 0) 60%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
top: -20%;
|
||||
left: -50%;
|
||||
}
|
||||
}
|
||||
@for $i from 1 through (30) {
|
||||
$top: random(100) + vh;
|
||||
$left: random(100) + vw;
|
||||
$size: random(6) + 3px;
|
||||
.star-1:nth-of-type(#{$i}) {
|
||||
top: $top;
|
||||
left: $left;
|
||||
width: $size;
|
||||
height: calc($size / 3);
|
||||
animation-delay: random(5) + s;
|
||||
&:before {
|
||||
width: $size * 2;
|
||||
height: $size * 2;
|
||||
top: -250%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.star-2 {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
animation: twinkle 5s infinite ease-in-out;
|
||||
}
|
||||
@for $i from 31 through (60) {
|
||||
$top: random(100) + vh;
|
||||
$left: random(100) + vw;
|
||||
$size: random(3) + 1px;
|
||||
.star-2:nth-of-type(#{$i}) {
|
||||
top: $top;
|
||||
left: $left;
|
||||
width: $size;
|
||||
height: $size;
|
||||
animation-delay: random(5) + s;
|
||||
&:before {
|
||||
width: $size * 2;
|
||||
height: $size * 2;
|
||||
top: -250%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//text and moon
|
||||
.container-title {
|
||||
width: 600px;
|
||||
height: 450px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
position: absolute;
|
||||
color: white;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.title > * {
|
||||
display: inline-block;
|
||||
font-size: 200px;
|
||||
}
|
||||
.number {
|
||||
text-shadow: 20px 20px 20px rgba(0, 0, 0, 0.2);
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 24px;
|
||||
margin-top: 3em;
|
||||
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
||||
font-weight: normal;
|
||||
}
|
||||
button {
|
||||
font-size: 14px;
|
||||
margin-top: 2em;
|
||||
padding: 0.5em 1em;
|
||||
letter-spacing: 1px;
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
z-index: 999;
|
||||
border: 1px solid white;
|
||||
border-radius: 5px;
|
||||
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: opacity 0.2s ease;
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
.moon {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
z-index: 2;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #fff,
|
||||
0 0 70px #fff, 0 0 80px #fff, 0 0 100px #ff1177;
|
||||
animation: rotate 5s ease-in-out infinite;
|
||||
.face {
|
||||
top: 60%;
|
||||
left: 47%;
|
||||
position: absolute;
|
||||
.mouth {
|
||||
border-top-left-radius: 50%;
|
||||
border-bottom-right-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
background-color: #5c3191;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
animation: snore 5s ease-in-out infinite;
|
||||
transform: rotate(45deg);
|
||||
box-shadow: inset -4px -4px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.eyes {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: -30px;
|
||||
.eye-left,
|
||||
.eye-right {
|
||||
border: 4px solid #5c3191;
|
||||
width: 30px;
|
||||
height: 15px;
|
||||
border-bottom-left-radius: 100px;
|
||||
border-bottom-right-radius: 100px;
|
||||
border-top: 0;
|
||||
position: absolute;
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: #5c3191;
|
||||
top: -2px;
|
||||
left: -4px;
|
||||
}
|
||||
&:after {
|
||||
left: auto;
|
||||
right: -4px;
|
||||
}
|
||||
}
|
||||
.eye-right {
|
||||
left: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//birds
|
||||
.container-bird {
|
||||
perspective: 2000px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.bird {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
height: 40px;
|
||||
width: 50px;
|
||||
transform: translate3d(-100vw, 0, 0) rotateY(90deg);
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.bird-container {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-style: preserve-3d;
|
||||
transform: translate3d(50px, 30px, -300px);
|
||||
}
|
||||
.wing {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 3px;
|
||||
transform-style: preserve-3d;
|
||||
transform-origin: center bottom;
|
||||
z-index: 300;
|
||||
}
|
||||
.wing-left {
|
||||
background: linear-gradient(to bottom, #a58dc4 0%, #7979a8 100%);
|
||||
transform: translate3d(0, 0, 0) rotateX(-30deg);
|
||||
animation: wingLeft 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.wing-right {
|
||||
background: linear-gradient(to bottom, #d9d3e2 0%, #b8a5d1 100%);
|
||||
transform: translate3d(0, 0, 0) rotateX(-30deg);
|
||||
animation: wingRight 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.wing-right-top,
|
||||
.wing-left-top {
|
||||
border-right: 25px solid transparent;
|
||||
border-left: 25px solid transparent;
|
||||
top: -20px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
transform-origin: 100% 100%;
|
||||
}
|
||||
.wing-right-top {
|
||||
border-bottom: 20px solid #b8a5d1;
|
||||
transform: translate3d(0, 0, 0) rotateX(60deg);
|
||||
animation: wingRightTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.wing-left-top {
|
||||
border-bottom: 20px solid #7979a8;
|
||||
transform: translate3d(0, 0, 0) rotateX(-60deg);
|
||||
animation: wingLeftTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.bird-anim:nth-child(1) {
|
||||
animation: bird1 30s linear infinite forwards;
|
||||
}
|
||||
.bird-anim:nth-child(2) {
|
||||
animation: bird2 30s linear infinite forwards;
|
||||
animation-delay: 3s;
|
||||
z-index: -1;
|
||||
}
|
||||
.bird-anim:nth-child(3) {
|
||||
animation: bird3 30s linear infinite forwards;
|
||||
animation-delay: 5s;
|
||||
}
|
||||
.bird-anim:nth-child(4) {
|
||||
animation: bird4 30s linear infinite forwards;
|
||||
animation-delay: 7s;
|
||||
}
|
||||
.bird-anim:nth-child(5) {
|
||||
animation: bird5 30s linear infinite forwards;
|
||||
animation-delay: 14s;
|
||||
}
|
||||
.bird-anim:nth-child(6) {
|
||||
animation: bird6 30s linear infinite forwards;
|
||||
animation-delay: 10s;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
//keyframes
|
||||
@keyframes rotate {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(-8deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes snore {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1) rotate(30deg);
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.5) rotate(30deg);
|
||||
border-bottom-left-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes twinkle {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingLeft {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(-50deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0, -20px, 0) rotateX(-130deg);
|
||||
background: linear-gradient(to bottom, #d9d3e2 0%, #b8a5d1 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingLeftTop {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(-10deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0px, 0px, 0) rotateX(-40deg);
|
||||
border-bottom: 20px solid #b8a5d1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingRight {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(50deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0, -20px, 0) rotateX(130deg);
|
||||
background: linear-gradient(to bottom, #a58dc4 0%, #7979a8 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingRightTop {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(10deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0px, 0px, 0px) rotateX(40deg);
|
||||
border-bottom: 20px solid #7979a8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird1 {
|
||||
0% {
|
||||
transform: translate3d(-120vw, -20px, -1000px) rotateY(-40deg) rotateX(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(100vw, -40vh, 1000px) rotateY(-40deg) rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird2 {
|
||||
0%,
|
||||
15% {
|
||||
transform: translate3d(100vw, -300px, -1000px) rotateY(10deg) rotateX(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, -20px, -1000px) rotateY(10deg) rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird3 {
|
||||
0% {
|
||||
transform: translate3d(100vw, -50vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, -10vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird4 {
|
||||
0% {
|
||||
transform: translate3d(100vw, 30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, -30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird5 {
|
||||
0%,
|
||||
5% {
|
||||
transform: translate3d(100vw, 30vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, 10vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird6 {
|
||||
0%,
|
||||
10% {
|
||||
transform: translate3d(-100vw, 20vh, -500px) rotateY(15deg) rotateX(10deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(100vw, 40vh, -800px) rotateY(5deg) rotateX(10deg);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 580px) {
|
||||
.container-404 {
|
||||
width: 100%;
|
||||
}
|
||||
.number {
|
||||
font-size: 100px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
padding: 0 1em;
|
||||
}
|
||||
.moon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.face {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<div>1</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<div class="login">
|
||||
<div class="main">
|
||||
<div class="logo">
|
||||
<!-- <img-->
|
||||
<!-- src="/src/assets/images/public/logo.png"-->
|
||||
<!-- alt=""-->
|
||||
<!-- width="200"-->
|
||||
<!-- height="33"-->
|
||||
<!-- />-->
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="introduce_content"></div>
|
||||
<div class="form">
|
||||
<div class="title">账号登录</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
@submit.prevent="fnLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="请输入用户名"
|
||||
tabindex="1"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon-people theme="filled" size="16" fill="#909399" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
tabindex="2"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon-lock
|
||||
theme="filled"
|
||||
size="16"
|
||||
fill="#909399"
|
||||
:stroke-width="3"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<verification v-model:verification-pass="verificationPass" />
|
||||
</el-form-item>
|
||||
<el-form-item class="button">
|
||||
<el-button native-type="submit">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import Verification from "@/components/verification/index";
|
||||
import { useUserStore } from "@/pinia/user";
|
||||
import { getUserInfo, Login } from "@/request/api";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import dayjs from "dayjs";
|
||||
import { encrypt } from "@/assets/js/aes_secret.js";
|
||||
|
||||
const router = useRouter();
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const verificationPass = ref(false);
|
||||
const userStore = useUserStore();
|
||||
const form = ref({
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const rules = {
|
||||
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
|
||||
};
|
||||
|
||||
const fnLogin = debounce(
|
||||
1000,
|
||||
() => {
|
||||
if (import.meta.env.DEV) {
|
||||
fnSubmitLogin();
|
||||
return;
|
||||
}
|
||||
fnSubmitLogin();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnSubmitLogin = async () => {
|
||||
await validate("请输入用户名密码");
|
||||
const { token } = await Login({
|
||||
username: encrypt(form.value.username),
|
||||
password: encrypt(form.value.password),
|
||||
});
|
||||
await userStore.setToken(token);
|
||||
await userStore.setTokenTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
|
||||
const { user } = await getUserInfo();
|
||||
userStore.setUserInfo({
|
||||
...userStore.getUserInfo,
|
||||
...user,
|
||||
});
|
||||
await router.replace("/index");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login {
|
||||
width: 100%;
|
||||
max-width: 1920px;
|
||||
margin: 0 auto;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
background: url("/src/assets/images/public/bg.png") no-repeat top center;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.main {
|
||||
width: 1200px;
|
||||
padding-top: 70px;
|
||||
margin: 0 auto;
|
||||
|
||||
.content {
|
||||
margin-top: 170px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.introduce_content {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 400px;
|
||||
height: 380px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 5px;
|
||||
background: #ffffff;
|
||||
padding-bottom: 50px;
|
||||
margin-right: 20px;
|
||||
box-shadow: 0 0 20px rgb(109 109 109 / 40%);
|
||||
margin-top: 20px;
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
color: #222;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #eee;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
width: 320px;
|
||||
margin: 20px auto;
|
||||
|
||||
.el-input {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
.button {
|
||||
.el-button {
|
||||
background: #0a7dfe;
|
||||
height: 45px;
|
||||
width: 100%;
|
||||
color: #ffffff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep {
|
||||
.el-input-group__prepend {
|
||||
padding-top: 5px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="type === 'edit' ? '修改' : '新增'"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="110px">
|
||||
<el-form-item label="bean名称" prop="beanName">
|
||||
<el-input v-model="form.beanName" placeholder="请输入bean名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数" prop="params">
|
||||
<el-input v-model="form.params" placeholder="参数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="cron表达式" prop="cronExpression">
|
||||
<el-input
|
||||
v-model="form.cronExpression"
|
||||
placeholder="如: 0 0 12 * * ?"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
setScheduleJobAdd,
|
||||
setScheduleJobUpdate,
|
||||
} from "@/request/schedule_job.js";
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const rules = {
|
||||
beanName: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
|
||||
cronExpression: [
|
||||
{ required: true, message: "cron表达式不能为空", trigger: "blur" },
|
||||
],
|
||||
};
|
||||
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
if (props.type === "add") {
|
||||
await setScheduleJobAdd({
|
||||
...form.value,
|
||||
});
|
||||
}
|
||||
if (props.type === "edit")
|
||||
await setScheduleJobUpdate({
|
||||
...form.value,
|
||||
});
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-table v-model:pagination="pagination" :data="list" @get-data="getData">
|
||||
<el-table-column prop="beanName" label="名称" />
|
||||
<el-table-column prop="params" label="参数" />
|
||||
<el-table-column prop="cronExpression" label="cron表达式" />
|
||||
<el-table-column prop="remark" label="备注" />
|
||||
<el-table-column label="状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status === 0" size="small">正常</el-tag>
|
||||
<el-tag v-else size="small" type="danger">暂停</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="200" label="操作">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnAddOrEdit(row.jobId, 'edit')"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button type="primary" text link @click="fnDelete(row.jobId)">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 0"
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnPause(row.jobId)"
|
||||
>
|
||||
暂停
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 1"
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnResume(row.jobId)"
|
||||
>
|
||||
恢复
|
||||
</el-button>
|
||||
<el-button type="primary" text link @click="fnRun(row.jobId)">
|
||||
立即执行
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddOrEdit('', 'add')">
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add
|
||||
v-model:form="addOrEditDialog.form"
|
||||
v-model:visible="addOrEditDialog.visible"
|
||||
:type="addOrEditDialog.type"
|
||||
@get-data="resetPagination"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, ref } from "vue";
|
||||
import {
|
||||
getScheduleJobList,
|
||||
getScheduleJobInfo,
|
||||
setScheduleJobDelete,
|
||||
setScheduleJobRun,
|
||||
setScheduleJobPause,
|
||||
setScheduleJobResume,
|
||||
} from "@/request/schedule_job.js";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import AppTable from "@/components/table/index.vue";
|
||||
import Add from "./components/add.vue";
|
||||
import useListData from "@/assets/js/useListData.js";
|
||||
|
||||
const { list, pagination, getData, resetPagination } =
|
||||
useListData(getScheduleJobList);
|
||||
const addOrEditDialog = ref({
|
||||
visible: false,
|
||||
type: "",
|
||||
form: {
|
||||
jobId: "",
|
||||
beanName: "",
|
||||
params: "",
|
||||
cronExpression: "",
|
||||
status: "",
|
||||
remark: "",
|
||||
},
|
||||
});
|
||||
|
||||
const fnAddOrEdit = async (jobId, type) => {
|
||||
addOrEditDialog.value.visible = true;
|
||||
addOrEditDialog.value.type = type;
|
||||
await nextTick();
|
||||
if (type === "edit") {
|
||||
const resData = await getScheduleJobInfo({ jobId });
|
||||
addOrEditDialog.value.form = resData.schedule;
|
||||
}
|
||||
};
|
||||
|
||||
const fnDelete = async (jobId) => {
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, { type: "warning" });
|
||||
await setScheduleJobDelete({ jobId });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
};
|
||||
const fnPause = async (jobId) => {
|
||||
await ElMessageBox.confirm(`确定要暂停吗?`, { type: "warning" });
|
||||
await setScheduleJobPause({ jobId });
|
||||
ElMessage.success("暂停成功");
|
||||
await resetPagination();
|
||||
};
|
||||
const fnResume = async (jobId) => {
|
||||
await ElMessageBox.confirm(`确定要恢复吗?`, { type: "warning" });
|
||||
await setScheduleJobResume({ jobId });
|
||||
ElMessage.success("恢复成功");
|
||||
await resetPagination();
|
||||
};
|
||||
const fnRun = async (jobId) => {
|
||||
await ElMessageBox.confirm(`确定要立即执行吗?`, { type: "warning" });
|
||||
await setScheduleJobRun({ jobId });
|
||||
ElMessage.success("立即执行成功");
|
||||
await resetPagination();
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="type === 'edit' ? '修改' : '新增'"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="上级菜单">
|
||||
<el-tag>{{ parentName }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码" prop="bianma">
|
||||
<el-input
|
||||
v-model="form.bianma"
|
||||
:disabled="type === 'edit'"
|
||||
placeholder="请输入编码"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="orderBy">
|
||||
<el-input v-model.number="form.orderBy" placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="descr">
|
||||
<el-input
|
||||
v-model="form.descr"
|
||||
:autosize="{ minRows: 1 }"
|
||||
type="textarea"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">取 消</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getDataDictionaryRepeat,
|
||||
setDataDictionaryAdd,
|
||||
setDataDictionaryEdit,
|
||||
} from "@/request/system_management.js";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parentName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parentId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: "字典名称不能为空", trigger: "change" },
|
||||
{ min: 2, max: 30, message: "长度在2到30个字符", trigger: "blur" },
|
||||
],
|
||||
bianma: [
|
||||
{ required: true, message: "字典编码名称不能为空", trigger: "change" },
|
||||
{ min: 2, max: 30, message: "长度在2到30个字符", trigger: "blur" },
|
||||
],
|
||||
orderBy: [
|
||||
{ required: true, message: "排序不能为空", trigger: ["change", "blur"] },
|
||||
{
|
||||
type: "number",
|
||||
message: "排序必须为数字",
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
],
|
||||
};
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
if (props.type === "add") {
|
||||
const { dictionaries } = await getDataDictionaryRepeat({
|
||||
bianma: form.value.bianma,
|
||||
});
|
||||
if (dictionaries) {
|
||||
ElMessage.error("添加失败,编码重复");
|
||||
return;
|
||||
}
|
||||
await setDataDictionaryAdd({
|
||||
...form.value,
|
||||
parentId: props.parentId,
|
||||
dictionariesId: undefined,
|
||||
});
|
||||
}
|
||||
if (props.type === "edit")
|
||||
await setDataDictionaryEdit({
|
||||
...form.value,
|
||||
});
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-table
|
||||
v-model:pagination="pagination"
|
||||
:data="list"
|
||||
@get-data="fnGetData"
|
||||
>
|
||||
<el-table-column label="名称">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="
|
||||
router.push({
|
||||
path: '/system_management/data_dictionary',
|
||||
query: {
|
||||
parentName: row.name,
|
||||
parentId: row.dictionariesId,
|
||||
},
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ row.name }} <el-icon><arrow-right /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bianma" label="编码" />
|
||||
<el-table-column prop="dictionariesId" label="ID" width="300" />
|
||||
<el-table-column prop="orderBy" label="排序" width="50" />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnAddOrEdit(row.dictionariesId, 'edit')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnDelete(row.dictionariesId)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddOrEdit('', 'add')">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="parentId !== '0'"
|
||||
:icon="ArrowLeft"
|
||||
@click="router.back()"
|
||||
>
|
||||
返回
|
||||
</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add
|
||||
v-model:form="addOrEditDialog.form"
|
||||
v-model:visible="addOrEditDialog.visible"
|
||||
:parent-name="parentName"
|
||||
:parent-id="parentId"
|
||||
:type="addOrEditDialog.type"
|
||||
@get-data="resetPagination"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
|
||||
import { nextTick, ref } from "vue";
|
||||
import { useRouter, onBeforeRouteUpdate, useRoute } from "vue-router";
|
||||
import AppTable from "@/components/table/index.vue";
|
||||
import {
|
||||
setDataDictionaryDelete,
|
||||
getDataDictionaryList,
|
||||
getDataDictionaryInfo,
|
||||
} from "@/request/system_management.js";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import useListData from "@/assets/js/useListData.js";
|
||||
import Add from "./components/add.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const parentIdDefault = "0";
|
||||
const parentNameDefault = "(无)此项为顶级菜单";
|
||||
const parentId = ref(route.query.parentId || parentIdDefault);
|
||||
const parentName = ref(route.query.parentName || parentNameDefault);
|
||||
const { list, pagination, resetPagination, getData } = useListData(
|
||||
getDataDictionaryList,
|
||||
{
|
||||
params: { parentId: parentId.value },
|
||||
}
|
||||
);
|
||||
const addOrEditDialog = ref({
|
||||
visible: false,
|
||||
type: "",
|
||||
form: {
|
||||
name: "",
|
||||
bianma: "",
|
||||
orderBy: "",
|
||||
descr: "",
|
||||
},
|
||||
});
|
||||
const fnGetData = () => {
|
||||
getData({ parentId: parentId.value });
|
||||
};
|
||||
onBeforeRouteUpdate((to) => {
|
||||
parentId.value = to.query.parentId || parentIdDefault;
|
||||
parentName.value = to.query.parentName || parentNameDefault;
|
||||
fnGetData();
|
||||
});
|
||||
const fnDelete = async (dictionariesId) => {
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, { type: "warning" });
|
||||
await setDataDictionaryDelete({ dictionariesId });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
};
|
||||
const fnAddOrEdit = async (dictionariesId, type) => {
|
||||
addOrEditDialog.value.visible = true;
|
||||
addOrEditDialog.value.type = type;
|
||||
await nextTick();
|
||||
if (type === "edit") {
|
||||
const resData = await getDataDictionaryInfo({ dictionariesId });
|
||||
addOrEditDialog.value.form = resData.dictionaries;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
width="60%"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="当前路由" prop="parentMenuId">
|
||||
{{ form.currentName || "首页" }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="按钮名称" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标识" prop="perms">
|
||||
<el-input v-model="form.perms" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">保存</el-button>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { setRouteAdd, setRouteEdit } from "@/request/system_management.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const formRef = ref(null);
|
||||
const rules = {
|
||||
name: [{ required: true, message: "请输入按钮名称", trigger: "blur" }],
|
||||
perms: [{ required: true, message: "请输入标识", trigger: "blur" }],
|
||||
};
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
const params = {
|
||||
...form.value,
|
||||
type: 2,
|
||||
};
|
||||
props.title === "编辑"
|
||||
? await setRouteEdit({ ...params })
|
||||
: await setRouteAdd(params);
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="设置图标"
|
||||
width="40%"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<div>
|
||||
<a href="https://iconpark.oceanengine.com/official" target="_blank">
|
||||
点击此处
|
||||
</a>
|
||||
查看所有图标
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<el-form label-width="80px" @submit.prevent="fnInit">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="图标名称">
|
||||
<el-input v-model="keywords" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label-width="10px">
|
||||
<el-button type="primary" native-type="submit">搜索</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="icon_container">
|
||||
<template v-for="(item, index) in svgList" :key="index">
|
||||
<el-tooltip :content="item.title">
|
||||
<div
|
||||
class="item"
|
||||
:class="{ active: iconIndex === index }"
|
||||
@click="iconIndex = index"
|
||||
>
|
||||
<component
|
||||
:is="'icon-' + item.name"
|
||||
theme="filled"
|
||||
fill="#a5b2c2"
|
||||
size="38"
|
||||
:stroke-width="3"
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-20 flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
size="small"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@update:current-page="fnSearch"
|
||||
@update:page-size="fnSearch"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">保存</el-button>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { setRouteIcon } from "@/request/system_management.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { paging } from "@/assets/js/utils.js";
|
||||
import { ref } from "vue";
|
||||
import icons from "@icon-park/vue-next/icons.json";
|
||||
|
||||
const props = defineProps({
|
||||
menuId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
meta: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const keywords = ref("");
|
||||
const svgList = ref([]);
|
||||
const pagination = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
total: icons.length,
|
||||
});
|
||||
const iconIndex = ref("");
|
||||
const fnSetPagination = () => {
|
||||
pagination.value = {
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
total: icons.length,
|
||||
};
|
||||
};
|
||||
const fnSearch = () => {
|
||||
const filterIcons = icons.filter(
|
||||
(item) =>
|
||||
item.name.indexOf(keywords.value) !== -1 ||
|
||||
item.title.indexOf(keywords.value) !== -1
|
||||
);
|
||||
svgList.value = paging(
|
||||
filterIcons,
|
||||
pagination.value.currentPage,
|
||||
pagination.value.pageSize
|
||||
);
|
||||
pagination.value.total = filterIcons.length;
|
||||
};
|
||||
const fnInit = () => {
|
||||
fnSetPagination();
|
||||
fnSearch();
|
||||
};
|
||||
fnInit();
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await setRouteIcon({
|
||||
menuId: props.menuId,
|
||||
meta: JSON.stringify({
|
||||
...props.meta,
|
||||
icon: iconIndex.value !== "" ? svgList.value[iconIndex.value].name : "",
|
||||
}),
|
||||
});
|
||||
ElMessage.success("设置成功");
|
||||
fnClose();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnClose = () => {
|
||||
iconIndex.value = "";
|
||||
keywords.value = "";
|
||||
fnInit();
|
||||
visible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon_container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.item {
|
||||
flex-basis: calc(5% - 10px);
|
||||
border: 1px solid var(--el-border-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-right: 13px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:nth-child(10n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
border: 1px solid #79bbff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex-end {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
width="60%"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item label="上级路由" prop="parentMenuId">
|
||||
{{ form.parentName }}
|
||||
</el-form-item>
|
||||
<el-form-item label="路由名称" prop="title">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="路由地址(从/开始)" prop="path">
|
||||
<el-input v-model="form.path" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="路由重定向地址(重定向到哪一个路由,子级路由第一个的路由地址,(没有子级路由不需要填写))"
|
||||
prop="redirect"
|
||||
>
|
||||
<el-input v-model="form.redirect" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="文件位置(路由对应的组件位置(必填,views下的文件,views和.vue不需要填写,如果是children只需要填写children))"
|
||||
prop="component"
|
||||
>
|
||||
<el-input v-model="form.component" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="model(归类到头部导航哪一级中(一级路由需要填写))"
|
||||
prop="model"
|
||||
>
|
||||
<el-select v-model="form.model" clearable>
|
||||
<el-option
|
||||
v-for="item in MENU"
|
||||
:key="item.model"
|
||||
:label="item.title"
|
||||
:value="item.model"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限标识" prop="perms">
|
||||
<el-input v-model="form.perms" />
|
||||
</el-form-item>
|
||||
<el-form-item label="序号" prop="orderNum">
|
||||
<el-input v-model.number="form.orderNum" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="选中的菜单(当前路由选中状态是哪个导航(只有当父级路由或祖先级路由’当前菜单是否显示子菜单为否‘时需要填写,设置‘当前菜单是否显示子菜单为否’的路由地址))"
|
||||
prop="activeMenu"
|
||||
>
|
||||
<el-input v-model="form.activeMenu" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示当前菜单" prop="isMenu">
|
||||
<el-select v-model="form.isMenu">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要登录才可以访问" prop="isLogin">
|
||||
<el-select v-model="form.isLogin">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前页是否显示在面包屑中" prop="breadcrumb">
|
||||
<el-select v-model="form.breadcrumb">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前页是否显示面包屑" prop="isBreadcrumb">
|
||||
<el-select v-model="form.isBreadcrumb">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="当前菜单是否显示子菜单(菜单有增删改查等等子路由的情况下需要勾选为否)"
|
||||
prop="isSubMenu"
|
||||
>
|
||||
<el-select v-model="form.isSubMenu">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前页是否显示全局返回按钮" prop="isBack">
|
||||
<el-select v-model="form.isBack">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">保存</el-button>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { MENU } from "@/assets/js/constant.js";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import { setRouteAdd, setRouteEdit } from "@/request/system_management.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const rules = {
|
||||
component: [{ required: true, message: "请输入文件位置", trigger: "blur" }],
|
||||
orderNum: [
|
||||
{ required: true, message: "请输入序号", trigger: "blur" },
|
||||
{ type: "number", message: "序号必须是数字", trigger: "blur" },
|
||||
],
|
||||
};
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
const meta = {
|
||||
title: form.value.title,
|
||||
model: form.value.model,
|
||||
activeMenu: form.value.activeMenu,
|
||||
isMenu: form.value.isMenu,
|
||||
isLogin: form.value.isLogin,
|
||||
breadcrumb: form.value.breadcrumb,
|
||||
isBreadcrumb: form.value.isBreadcrumb,
|
||||
isSubMenu: form.value.isSubMenu,
|
||||
isBack: form.value.isBack,
|
||||
icon: form.value.icon || "",
|
||||
};
|
||||
const params = {
|
||||
path: form.value.path,
|
||||
name: form.value.title,
|
||||
parentId: form.value.parentId || 0,
|
||||
orderNum: form.value.orderNum,
|
||||
component: form.value.component,
|
||||
perms: form.value.perms,
|
||||
redirect: form.value.redirect,
|
||||
meta: JSON.stringify(meta),
|
||||
type: 1,
|
||||
menuId: form.value.menuId,
|
||||
};
|
||||
props.title === "编辑"
|
||||
? await setRouteEdit({ ...params })
|
||||
: await setRouteAdd(params);
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-table
|
||||
:data="menusAll"
|
||||
:show-pagination="false"
|
||||
row-key="menuId"
|
||||
:show-index="false"
|
||||
:tree-props="{ hasChildren: 'hasChildren', children: 'list' }"
|
||||
>
|
||||
<el-table-column label="名称" show-overflow-tooltip width="300">
|
||||
<template #default="{ row }">
|
||||
{{ row?.meta?.title || row.name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="路由地址" prop="path" show-overflow-tooltip />
|
||||
<el-table-column label="文件位置" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
row.type === 1
|
||||
? row.component === "children"
|
||||
? "/src/components/children/index.vue"
|
||||
: "/src/views/" + row.component + ".vue"
|
||||
: ""
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="orderNum" width="50" />
|
||||
<el-table-column
|
||||
label="权限标识"
|
||||
prop="perms"
|
||||
width="100"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="类型" width="65">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.type === 1">路由</el-tag>
|
||||
<el-tag v-if="row.type === 2" type="danger">按钮</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否显示在菜单" width="120">
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
row.type === 1
|
||||
? row.meta?.isMenu !== false && !row.meta?.activeMenu && row.path
|
||||
? "是"
|
||||
: "否"
|
||||
: ""
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="330">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="
|
||||
row.type === 1 &&
|
||||
row.meta?.isMenu !== false &&
|
||||
!row.meta?.activeMenu &&
|
||||
row.path
|
||||
"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddIcon(row)"
|
||||
>
|
||||
图标
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 1"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddRouter(row, '编辑')"
|
||||
>
|
||||
编辑路由
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 2"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddButton(row, '编辑')"
|
||||
>
|
||||
编辑按钮
|
||||
</el-button>
|
||||
<el-button text link type="primary" @click="fnDelRouter(row)">
|
||||
删除{{ row.type === 1 ? "路由" : "按钮" }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 1 && row.component === 'children'"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddRouter(row, '新增下级')"
|
||||
>
|
||||
新增下级路由
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 1 && row.component !== 'children'"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddButton(row, '新增')"
|
||||
>
|
||||
新增按钮
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddRouter({}, '新增一级菜单')">
|
||||
新增一级路由
|
||||
</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add-menu
|
||||
v-model:visible="menuDialog.visible"
|
||||
v-model:form="menuDialog.form"
|
||||
:title="menuDialog.title"
|
||||
@get-data="getData"
|
||||
/>
|
||||
<add-button
|
||||
v-model:visible="buttonDialog.visible"
|
||||
v-model:form="buttonDialog.form"
|
||||
:title="buttonDialog.title"
|
||||
@get-data="getData"
|
||||
/>
|
||||
<icon
|
||||
v-model:visible="iconDialog.visible"
|
||||
:menu-id="iconDialog.menuId"
|
||||
:meta="iconDialog.meta"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import {
|
||||
getRouteList,
|
||||
setRouteDelete,
|
||||
getRouteView,
|
||||
} from "@/request/system_management.js";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import AppTable from "@/components/table/index.vue";
|
||||
import Icon from "./components/icon.vue";
|
||||
import AddMenu from "./components/menu.vue";
|
||||
import AddButton from "./components/button.vue";
|
||||
import conversionRouterMeta from "@/assets/js/conversion_router_meta.js";
|
||||
import useListData from "@/assets/js/useListData.js";
|
||||
|
||||
const menusAll = ref([]);
|
||||
const { getData } = useListData(getRouteList, {
|
||||
params: { parentId: 0 },
|
||||
usePagination: false,
|
||||
key: "menuList",
|
||||
callback: (list) => {
|
||||
menusAll.value = conversionRouterMeta(list);
|
||||
},
|
||||
});
|
||||
const menuDialog = ref({
|
||||
visible: false,
|
||||
title: "",
|
||||
form: {
|
||||
parentId: "",
|
||||
parentName: "",
|
||||
title: "",
|
||||
path: "",
|
||||
redirect: "",
|
||||
component: "",
|
||||
model: "",
|
||||
perms: "",
|
||||
orderNum: "",
|
||||
activeMenu: "",
|
||||
menuId: "",
|
||||
icon: "",
|
||||
isMenu: true,
|
||||
isLogin: true,
|
||||
breadcrumb: true,
|
||||
isBreadcrumb: true,
|
||||
isSubMenu: true,
|
||||
isBack: true,
|
||||
},
|
||||
});
|
||||
const iconDialog = ref({
|
||||
visible: false,
|
||||
menuId: 0,
|
||||
meta: {},
|
||||
});
|
||||
const buttonDialog = ref({
|
||||
visible: false,
|
||||
title: "",
|
||||
form: {
|
||||
currentName: "",
|
||||
parentId: "",
|
||||
name: "",
|
||||
perms: "",
|
||||
},
|
||||
});
|
||||
const fnAddRouter = async (row, type) => {
|
||||
menuDialog.value.visible = true;
|
||||
menuDialog.value.title = type;
|
||||
if (type === "编辑") {
|
||||
const resData = await getRouteView({ menuId: row.menuId });
|
||||
resData.menu.meta = JSON.parse(resData.menu.meta);
|
||||
menuDialog.value.form = {
|
||||
menuId: row.menuId,
|
||||
parentId: row.parentId,
|
||||
orderNum: row.orderNum,
|
||||
perms: row.perms,
|
||||
path: row.path,
|
||||
redirect: row.redirect,
|
||||
component: row.component,
|
||||
parentName: row.parentName || "(无)",
|
||||
title: row.meta.title,
|
||||
model: row.meta.model,
|
||||
activeMenu: row.meta.activeMenu,
|
||||
isMenu: row.meta.isMenu,
|
||||
isLogin: row.meta.isLogin,
|
||||
breadcrumb: row.meta.breadcrumb,
|
||||
isBreadcrumb: row.meta.isBreadcrumb,
|
||||
isSubMenu: row.meta.isSubMenu,
|
||||
isBack: row.meta.isBack,
|
||||
icon: row.meta.icon,
|
||||
};
|
||||
} else if (type === "新增下级") {
|
||||
menuDialog.value.form.menuId = undefined;
|
||||
menuDialog.value.form.parentId = row.menuId;
|
||||
menuDialog.value.form.parentName = row.meta.title;
|
||||
if (row.meta.isSubMenu === false)
|
||||
menuDialog.value.form.activeMenu = row.path;
|
||||
else menuDialog.value.form.activeMenu = row.meta.activeMenu || "";
|
||||
menuDialog.value.form.orderNum = row.list?.length + 1 || 1;
|
||||
} else if (type === "新增一级菜单") {
|
||||
menuDialog.value.form.menuId = undefined;
|
||||
menuDialog.value.form.parentId = 0;
|
||||
menuDialog.value.form.parentName = "(无)";
|
||||
menuDialog.value.form.orderNum = menusAll.value.length + 1;
|
||||
}
|
||||
};
|
||||
const fnDelRouter = debounce(
|
||||
1000,
|
||||
async (row) => {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除名称为【${row?.meta?.title || row.name}】的这条数据吗?`,
|
||||
"提示",
|
||||
{ type: "warning" }
|
||||
);
|
||||
await setRouteDelete({
|
||||
menuId: row.menuId,
|
||||
});
|
||||
ElMessage.success("删除成功");
|
||||
await getData();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnAddIcon = (row) => {
|
||||
iconDialog.value.visible = true;
|
||||
iconDialog.value.menuId = row.menuId;
|
||||
iconDialog.value.meta = row.meta;
|
||||
};
|
||||
const fnAddButton = async (row, type) => {
|
||||
buttonDialog.value.visible = true;
|
||||
buttonDialog.value.title = type;
|
||||
if (type === "新增") {
|
||||
buttonDialog.value.form.currentName = row.name;
|
||||
buttonDialog.value.form.parentId = row.menuId;
|
||||
buttonDialog.value.form.menuId = undefined;
|
||||
} else if (type === "编辑") {
|
||||
const resData = await getRouteView({ menuId: row.menuId });
|
||||
buttonDialog.value.form = resData.menu;
|
||||
buttonDialog.value.form.currentName = resData.menu.parentName;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep {
|
||||
.el-table .el-table__cell {
|
||||
text-align: left !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="type === 'edit' ? '修改' : '新增'"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="110px">
|
||||
<el-form-item label="名称" prop="roleName">
|
||||
<el-input v-model="form.roleName" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 1 }"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单" prop="menuIdList">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="menuId"
|
||||
:data="menusAll"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'list',
|
||||
}"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { nextTick, ref, watchEffect } from "vue";
|
||||
import {
|
||||
getRouteList,
|
||||
setRoleAdd,
|
||||
setRoleEdit,
|
||||
} from "@/request/system_management.js";
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const treeRef = ref(null);
|
||||
const menusAll = ref([]);
|
||||
const rules = {
|
||||
roleName: [
|
||||
{ required: true, message: "名称不能为空", trigger: "blur" },
|
||||
{ min: 2, max: 30, message: "长度在2到30个字符", trigger: "blur" },
|
||||
],
|
||||
};
|
||||
const fnGetData = async () => {
|
||||
const resData = await getRouteList({ parentId: 0 });
|
||||
menusAll.value = fnConversion(resData.menuList);
|
||||
};
|
||||
const fnConversion = (menuList) => {
|
||||
for (let i = 0; i < menuList.length; i++) {
|
||||
menuList[i].meta = JSON.parse(menuList[i].meta);
|
||||
if (menuList[i].type === 1) {
|
||||
menuList[i].name = (menuList[i].name || "首页") + "(菜单)";
|
||||
}
|
||||
if (menuList[i].type === 2) {
|
||||
menuList[i].name = menuList[i].name + "(按钮)";
|
||||
}
|
||||
if (menuList[i].list.length > 0) {
|
||||
fnConversion(menuList[i].list);
|
||||
}
|
||||
}
|
||||
return menuList;
|
||||
};
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
treeRef.value.setCheckedKeys([]);
|
||||
};
|
||||
watchEffect(async () => {
|
||||
if (visible.value) {
|
||||
await fnGetData();
|
||||
await nextTick();
|
||||
let checkedKeys = [];
|
||||
form.value.menuIdList.forEach((item) => {
|
||||
checkedKeys = fnGetChecked(item, menusAll.value, checkedKeys);
|
||||
});
|
||||
treeRef.value.setCheckedKeys(checkedKeys);
|
||||
}
|
||||
});
|
||||
const fnGetChecked = (menuId, menusAll, checkedKeys) => {
|
||||
for (let i = 0; i < menusAll.length; i++) {
|
||||
if (menuId === menusAll[i].menuId) {
|
||||
if (menusAll[i].list.length === 0) {
|
||||
checkedKeys.push(menusAll[i].menuId);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (menusAll[i].list.length > 0) {
|
||||
fnGetChecked(menuId, menusAll[i].list, checkedKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
return checkedKeys;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
const checkedKeys = treeRef.value.getCheckedKeys() || [];
|
||||
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys() || [];
|
||||
if (props.type === "add") {
|
||||
await setRoleAdd({
|
||||
...form.value,
|
||||
menuIdList: [...checkedKeys, ...halfCheckedKeys],
|
||||
roleId: undefined,
|
||||
});
|
||||
}
|
||||
if (props.type === "edit")
|
||||
await setRoleEdit({
|
||||
...form.value,
|
||||
menuIdList: [...checkedKeys, ...halfCheckedKeys],
|
||||
});
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-tree) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-table
|
||||
ref="tableRef"
|
||||
v-model:pagination="pagination"
|
||||
:data="list"
|
||||
row-key="roleId"
|
||||
show-selection
|
||||
@get-data="getData"
|
||||
>
|
||||
<el-table-column prop="roleName" label="名称" />
|
||||
<el-table-column prop="remark" label="备注" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnAddOrEdit(row.roleId, 'edit')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="primary" text link @click="fnDelete([row.roleId])">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddOrEdit('', 'add')">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button type="danger" @click="fnDeleteMultiple">删除</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add
|
||||
v-model:visible="addOrEditDialog.visible"
|
||||
v-model:form="addOrEditDialog.form"
|
||||
:type="addOrEditDialog.type"
|
||||
@get-data="resetPagination"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import AppTable from "@/components/table/index.vue";
|
||||
import Add from "./components/add.vue";
|
||||
import {
|
||||
getRoleList,
|
||||
getRoleView,
|
||||
setRoleDelete,
|
||||
} from "@/request/system_management.js";
|
||||
import useListData from "@/assets/js/useListData.js";
|
||||
|
||||
const { list, pagination, getData, resetPagination, tableRef } =
|
||||
useListData(getRoleList);
|
||||
const addOrEditDialog = ref({
|
||||
visible: false,
|
||||
type: "",
|
||||
form: {
|
||||
roleName: "",
|
||||
remark: "",
|
||||
menuIdList: [],
|
||||
},
|
||||
});
|
||||
const fnDelete = debounce(
|
||||
1000,
|
||||
async (roleIds) => {
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, {
|
||||
type: "warning",
|
||||
});
|
||||
await setRoleDelete({ roleIds });
|
||||
ElMessage.success("删除成功");
|
||||
resetPagination();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnDeleteMultiple = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
const selectionData = tableRef.value.getSelectionRows();
|
||||
if (selectionData.length === 0) {
|
||||
ElMessage.warning("请选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, {
|
||||
type: "warning",
|
||||
});
|
||||
const roleIds = selectionData.map((item) => item.roleId);
|
||||
await setRoleDelete({ roleIds });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnAddOrEdit = async (roleId, type) => {
|
||||
addOrEditDialog.value.visible = true;
|
||||
addOrEditDialog.value.type = type;
|
||||
await nextTick();
|
||||
if (type === "edit") {
|
||||
const resData = await getRoleView({ roleId });
|
||||
addOrEditDialog.value.form = resData.role;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="type === 'edit' ? '修改' : '新增'"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="130px">
|
||||
<el-form-item label="上级部门">
|
||||
<el-tag>{{ parentName }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门级别" prop="level">
|
||||
<el-select v-model="form.level" placeholder="请选择部门级别">
|
||||
<el-option label="公司级" value="1" />
|
||||
<el-option label="部门级" value="2" />
|
||||
<el-option label="小组级" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门负责人" prop="headman">
|
||||
<el-input v-model="form.headman" placeholder="请输入部门负责人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门负责人手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入部门负责人手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="orderBy">
|
||||
<el-input v-model.number="form.orderBy" placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {
|
||||
setDepartmentAdd,
|
||||
setDepartmentEdit,
|
||||
} from "@/request/user_management.js";
|
||||
import { PHONE } from "@/assets/js/regular.js";
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parentName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parentId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: "名称不能为空", trigger: "blur" },
|
||||
{ min: 2, max: 30, message: "长度在2到30个字符", trigger: "blur" },
|
||||
],
|
||||
level: [{ required: true, message: "部门级别不能为空", trigger: "change" }],
|
||||
phone: [{ pattern: PHONE, message: "手机号格式不正确", trigger: "blur" }],
|
||||
orderBy: [
|
||||
{ required: true, message: "排序不能为空", trigger: ["change", "blur"] },
|
||||
{ type: "number", message: "排序必须为数字", trigger: ["change", "blur"] },
|
||||
],
|
||||
};
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
if (props.type === "add") {
|
||||
await setDepartmentAdd({
|
||||
...form.value,
|
||||
parentId: props.parentId,
|
||||
departmentId: undefined,
|
||||
});
|
||||
}
|
||||
if (props.type === "edit")
|
||||
await setDepartmentEdit({
|
||||
...form.value,
|
||||
});
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-search
|
||||
v-model="searchForm"
|
||||
label-width="80px"
|
||||
@submit="resetPagination"
|
||||
>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="部门级别" prop="level">
|
||||
<el-select v-model="searchForm.level" placeholder="请选择部门级别">
|
||||
<el-option label="公司级" value="1" />
|
||||
<el-option label="部门级" value="2" />
|
||||
<el-option label="小组级" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</app-search>
|
||||
<app-table
|
||||
v-model:pagination="pagination"
|
||||
:data="list"
|
||||
@get-data="fnGetData"
|
||||
>
|
||||
<el-table-column label="名称">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="
|
||||
router.push({
|
||||
path: '/user_management/department',
|
||||
query: {
|
||||
parentName: row.name,
|
||||
parentId: row.departmentId,
|
||||
},
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ row.name }} <el-icon><arrow-right /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部门级别">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.level === '1'">公司级</span>
|
||||
<span v-if="row.level === '2'">部门级</span>
|
||||
<span v-if="row.level === '3'">小组级</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="headman" label="主管领导" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnAddOrEdit(row.departmentId, 'edit')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnDelete(row.departmentId)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddOrEdit('', 'add')">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="parentId !== '0'"
|
||||
:icon="ArrowLeft"
|
||||
@click="router.back()"
|
||||
>
|
||||
返回
|
||||
</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add
|
||||
v-model:form="addOrEditDialog.form"
|
||||
v-model:visible="addOrEditDialog.visible"
|
||||
:parent-name="parentName"
|
||||
:parent-id="parentId"
|
||||
:type="addOrEditDialog.type"
|
||||
@get-data="resetPagination"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
|
||||
import { nextTick, ref } from "vue";
|
||||
import {
|
||||
getDepartmentList,
|
||||
setDepartmentDelete,
|
||||
getDepartmentView,
|
||||
} from "@/request/user_management.js";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import AppTable from "@/components/table/index.vue";
|
||||
import AppSearch from "@/components/search/index.vue";
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
|
||||
import Add from "./components/add.vue";
|
||||
import useListData from "@/assets/js/useListData.js";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const parentIdDefault = "0";
|
||||
const parentNameDefault = "(无)此项为顶级菜单";
|
||||
const parentId = ref(route.query.parentId || parentIdDefault);
|
||||
const parentName = ref(route.query.parentName || parentNameDefault);
|
||||
const { list, pagination, searchForm, getData, resetPagination } = useListData(
|
||||
getDepartmentList,
|
||||
{
|
||||
params: { parentId: parentId.value },
|
||||
}
|
||||
);
|
||||
const addOrEditDialog = ref({
|
||||
visible: false,
|
||||
type: "",
|
||||
form: {
|
||||
name: "",
|
||||
level: "",
|
||||
headman: "",
|
||||
phone: "",
|
||||
orderBy: "",
|
||||
},
|
||||
});
|
||||
const fnGetData = () => {
|
||||
getData({ parentId: parentId.value });
|
||||
};
|
||||
onBeforeRouteUpdate((to) => {
|
||||
parentId.value = to.query.parentId || parentIdDefault;
|
||||
parentName.value = to.query.parentName || parentNameDefault;
|
||||
fnGetData();
|
||||
});
|
||||
const fnDelete = debounce(
|
||||
1000,
|
||||
async (departmentId) => {
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, {
|
||||
type: "warning",
|
||||
});
|
||||
await setDepartmentDelete({ departmentId });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnAddOrEdit = async (departmentId, type) => {
|
||||
addOrEditDialog.value.visible = true;
|
||||
addOrEditDialog.value.type = type;
|
||||
await nextTick();
|
||||
if (type === "edit") {
|
||||
const resData = await getDepartmentView({ departmentId });
|
||||
addOrEditDialog.value.form = resData.department;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="type === 'edit' ? '修改' : '新增'"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="110px">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="form.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="form.mobile" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号" prop="userIdCard">
|
||||
<el-input v-model="form.userIdCard" placeholder="请输入身份证号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学历" prop="degree">
|
||||
<el-select v-model="form.degree">
|
||||
<el-option
|
||||
v-for="item in degreeList"
|
||||
:key="item.dictionariesId"
|
||||
:label="item.name"
|
||||
:value="item.dictionariesId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门" prop="departmentId">
|
||||
<app-department v-model="form.departmentId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="人员类型" prop="type">
|
||||
<el-select v-model="form.type" multiple>
|
||||
<el-option
|
||||
v-for="item in typeList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色" prop="roleIdList">
|
||||
<el-select v-model="form.roleIdList" multiple>
|
||||
<el-option
|
||||
v-for="item in roleList"
|
||||
:key="item.roleId"
|
||||
:label="item.roleName"
|
||||
:value="item.roleId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { debounce } from "throttle-debounce";
|
||||
import useFormValidate from "@/assets/js/useFormValidate.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
import AppDepartment from "@/components/department/index.vue";
|
||||
import {
|
||||
getUserUserNameRepeat,
|
||||
setUserAdd,
|
||||
setUserEdit,
|
||||
} from "@/request/user_management.js";
|
||||
import { getRoleListAll } from "@/request/system_management.js";
|
||||
import { appFnGetDegree } from "@/assets/js/data_dictionary.js";
|
||||
import { ID_NUMBER, PHONE } from "@/assets/js/regular.js";
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate } = useFormValidate();
|
||||
const validateUserName = async (_rule, value, callback) => {
|
||||
if (props.type === "add") {
|
||||
const { user } = await getUserUserNameRepeat({ username: value });
|
||||
if (user) callback(new Error("用户名重复"));
|
||||
else callback();
|
||||
} else callback();
|
||||
};
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: "姓名不能为空", trigger: "blur" },
|
||||
{ min: 2, max: 30, message: "长度在2到30个字符", trigger: "blur" },
|
||||
],
|
||||
username: [
|
||||
{ required: true, message: "用户名不能为空", trigger: "blur" },
|
||||
{ validator: validateUserName, trigger: "blur" },
|
||||
],
|
||||
userIdCard: [
|
||||
{ pattern: ID_NUMBER, message: "请输入正确的身份证号", trigger: "blur" },
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: "手机号不能为空", trigger: "blur" },
|
||||
{ pattern: PHONE, message: "手机号格式不正确", trigger: "blur" },
|
||||
],
|
||||
email: [{ type: "email", message: "邮箱格式不正确", trigger: "blur" }],
|
||||
departmentId: [
|
||||
{ required: true, message: "部门不能为空", trigger: "change" },
|
||||
],
|
||||
type: [{ required: true, message: "人员类型不能为空", trigger: "change" }],
|
||||
roleIdList: [{ required: true, message: "角色不能为空", trigger: "change" }],
|
||||
};
|
||||
const { roleList } = await getRoleListAll();
|
||||
const degreeList = await appFnGetDegree();
|
||||
const typeList = [
|
||||
{ id: "1", name: "销售" },
|
||||
{ id: "2", name: "服务" },
|
||||
{ id: "3", name: "专家" },
|
||||
];
|
||||
const fnClose = () => {
|
||||
formRef.value.resetFields();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
form.value.type = form.value.type.join(",");
|
||||
if (props.type === "add") {
|
||||
await setUserAdd({
|
||||
...form.value,
|
||||
userId: undefined,
|
||||
});
|
||||
}
|
||||
if (props.type === "edit")
|
||||
await setUserEdit({
|
||||
...form.value,
|
||||
});
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<el-dialog v-model="visible" title="查看" :before-close="fnClose">
|
||||
<el-descriptions border :column="2">
|
||||
<el-descriptions-item label="姓名">
|
||||
{{ info.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户名">
|
||||
{{ info.username }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">
|
||||
{{ info.mobile }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
{{ info.sex }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="年龄">
|
||||
{{ info.age }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="学历">
|
||||
{{ info.degreeName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">
|
||||
{{ info.email }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="部门">
|
||||
{{ info.departmentName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="岗位">
|
||||
{{ info.postName }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
info: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const fnClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
<template>
|
||||
<div>
|
||||
<app-search
|
||||
v-model="searchForm"
|
||||
label-width="60px"
|
||||
@submit="resetPagination"
|
||||
>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</app-search>
|
||||
<app-table
|
||||
ref="tableRef"
|
||||
v-model:pagination="pagination"
|
||||
:data="list"
|
||||
row-key="userId"
|
||||
show-selection
|
||||
@get-data="getData"
|
||||
>
|
||||
<el-table-column prop="name" label="姓名" />
|
||||
<el-table-column prop="username" label="用户名" />
|
||||
<el-table-column prop="userIdCard" label="身份证号" />
|
||||
<el-table-column prop="mobile" label="手机号" />
|
||||
<el-table-column prop="departmentName" label="部门" />
|
||||
<el-table-column label="类型" width="190">
|
||||
<template #default="{ row }">
|
||||
{{ fnGetUserType(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="190">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" text link @click="fnView(row.userId)">
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnAddOrEdit(row.userId, 'edit')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnResetPassword(row.userId)"
|
||||
>
|
||||
重置密码
|
||||
</el-button>
|
||||
<el-button type="primary" text link @click="fnDelete(row.userId)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddOrEdit('', 'add')">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button type="danger" @click="fnDeleteMultiple">删除</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add
|
||||
v-model:visible="addOrEditDialog.visible"
|
||||
v-model:form="addOrEditDialog.form"
|
||||
:type="addOrEditDialog.type"
|
||||
@get-data="resetPagination"
|
||||
/>
|
||||
<view-info v-model:visible="viewDialog.visible" :info="viewDialog.info" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, ref } from "vue";
|
||||
import {
|
||||
setUserDelete,
|
||||
getUserView,
|
||||
getUserList,
|
||||
setUserResetPassword,
|
||||
} from "@/request/user_management.js";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import AppTable from "@/components/table/index.vue";
|
||||
import AppSearch from "@/components/search/index.vue";
|
||||
import Add from "./components/add.vue";
|
||||
import ViewInfo from "./components/view.vue";
|
||||
import { setRoleDelete } from "@/request/system_management.js";
|
||||
import useListData from "@/assets/js/useListData.js";
|
||||
|
||||
const { list, pagination, searchForm, getData, resetPagination, tableRef } =
|
||||
useListData(getUserList);
|
||||
const addOrEditDialog = ref({
|
||||
visible: false,
|
||||
type: "",
|
||||
form: {
|
||||
name: "",
|
||||
username: "",
|
||||
mobile: "",
|
||||
degree: "",
|
||||
type: "",
|
||||
email: "",
|
||||
departmentId: "",
|
||||
},
|
||||
});
|
||||
const viewDialog = ref({
|
||||
visible: false,
|
||||
info: {},
|
||||
});
|
||||
const fnGetUserType = (row) => {
|
||||
const type = row.type.split(",");
|
||||
const typeList = [
|
||||
{ id: "1", name: "销售" },
|
||||
{ id: "2", name: "服务" },
|
||||
{ id: "3", name: "专家" },
|
||||
];
|
||||
return typeList
|
||||
.filter((item) => type.includes(item.id.toString()))
|
||||
.map((item) => item.name)
|
||||
.join(",");
|
||||
};
|
||||
const fnResetPassword = debounce(
|
||||
1000,
|
||||
async (userId) => {
|
||||
await ElMessageBox.confirm(`是否重置密码为Aqsc@2024?`, {
|
||||
type: "warning",
|
||||
});
|
||||
await setUserResetPassword({ userId });
|
||||
ElMessage.success("重置密码成功");
|
||||
await resetPagination();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnDelete = debounce(
|
||||
1000,
|
||||
async (userId) => {
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, {
|
||||
type: "warning",
|
||||
});
|
||||
await setUserDelete({ userIds: [userId] });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnDeleteMultiple = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
const selectionData = tableRef.value.getSelectionRows();
|
||||
if (selectionData.length === 0) {
|
||||
ElMessage.warning("请选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, {
|
||||
type: "warning",
|
||||
});
|
||||
const userIds = selectionData.map((item) => item.userId);
|
||||
await setRoleDelete({ userIds });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
},
|
||||
{ atBegin: true }
|
||||
);
|
||||
const fnAddOrEdit = async (userId, type) => {
|
||||
addOrEditDialog.value.visible = true;
|
||||
addOrEditDialog.value.type = type;
|
||||
await nextTick();
|
||||
if (type === "edit") {
|
||||
const resData = await getUserView({ userId });
|
||||
resData.user.roleIdList = resData.user.rolesId.split(",");
|
||||
resData.user.type = resData.user.type.split(",");
|
||||
addOrEditDialog.value.form = resData.user;
|
||||
}
|
||||
};
|
||||
const fnView = async (userId) => {
|
||||
const resData = await getUserView({ userId });
|
||||
viewDialog.value.info = resData.user;
|
||||
viewDialog.value.visible = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import { defineConfig, loadEnv } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import eslintPlugin from "vite-plugin-eslint";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
|
||||
import removeConsole from "vite-plugin-remove-console";
|
||||
// import EnhanceLog from "vite-plugin-enhance-log";
|
||||
import { envParse } from "vite-plugin-env-parse";
|
||||
|
||||
export default ({ mode }) => {
|
||||
return defineConfig({
|
||||
base: loadEnv(mode, process.cwd()).VITE_BASE,
|
||||
plugins: [
|
||||
vue(),
|
||||
envParse(),
|
||||
eslintPlugin(),
|
||||
removeConsole({
|
||||
includes: [
|
||||
"assert",
|
||||
"clear",
|
||||
"count",
|
||||
"countReset",
|
||||
"createTask",
|
||||
"debug",
|
||||
"dir",
|
||||
"dirxml",
|
||||
"error",
|
||||
"group",
|
||||
"groupCollapsed",
|
||||
"groupEnd",
|
||||
"info",
|
||||
"log",
|
||||
"profile",
|
||||
"profileEnd",
|
||||
"table",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"timeLog",
|
||||
"timeStamp",
|
||||
"trace",
|
||||
"warn",
|
||||
],
|
||||
}),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
// EnhanceLog({
|
||||
// splitBy: "🐶",
|
||||
// preTip: "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀",
|
||||
// }),
|
||||
],
|
||||
server: {
|
||||
host: true, // 本机的局域网IP,不然其他人无法通过IP访问到,0.0.0.0或true会自动获取本机的IP
|
||||
port: 8099, // 端口号
|
||||
open: true, // 是否自动打开浏览器
|
||||
proxy: {
|
||||
[loadEnv(mode, process.cwd()).VITE_PROXY]: {
|
||||
target: loadEnv(mode, process.cwd()).VITE_BASE_URL,
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": "/src", // 别名,@代表src目录
|
||||
},
|
||||
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"], // 引入文件时哪些后缀名可以不写
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
// 打包多个入口文件
|
||||
// input: {
|
||||
// admin: path.resolve(__dirname, "index.html"),
|
||||
// 其它入口文件路径需要为src/views/*/index.html
|
||||
// },
|
||||
output: {
|
||||
chunkFileNames: "static/js/[name]-[hash].js",
|
||||
entryFileNames: "static/js/[name]-[hash].js",
|
||||
assetFileNames: "static/[ext]/name-[hash].[ext]",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
Loading…
Reference in New Issue