main
liujianan 2024-01-04 09:02:38 +08:00
commit 0cdce1c49f
82 changed files with 11630 additions and 0 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
VITE_BASE_URL=http://192.168.0.17:8001/
VITE_PROXY=/api
VITE_FILE_URL=https://file.zcloudchina.com/YTHFile

0
.env.development Normal file
View File

0
.env.production Normal file
View File

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
public
dist
package.json
!.prettierrc.cjs

34
.eslintrc.cjs Normal file
View File

@ -0,0 +1,34 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
"plugin:vue/vue3-essential",
"standard",
"@vue/prettier",
"eslint:recommended"
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module"
},
plugins: ["vue"],
rules: {
"no-console": "warn",
"vue/multi-word-component-names": "off",
camelcase: "off",
eqeqeq: "error",
"vue/eqeqeq": "error",
"no-unused-vars": [
"error",
{ vars: "all", args: "after-used", ignoreRestSiblings: false }
],
"linebreak-style": ["off",'windows'],
},
globals: {
BMapGL: "readonly",
JSEncrypt: "readonly",
}
};

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
.prettierrc.cjs Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
extends: ["@vue/prettier", "plugin:prettier/recommended"],
endOfLine: "crlf",
};

7
README.md Normal file
View File

@ -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).

19
index.html Normal file
View File

@ -0,0 +1,19 @@
<!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>管理平台</title>
</head>
<body>
<div id="app"></div>
<noscript>
<strong>很抱歉如果没有启用JavaScript网站无法正常工作请启用JavaScript使其正常工作。</strong>
</noscript>
<script type="text/javascript"
src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=OElqFYoKiAH8KFtph8ftLKF5NlNrbCUr"></script>
<script type="text/javascript" src="/jsencrypt.min.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>

10
jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
}

5766
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

60
package.json Normal file
View File

@ -0,0 +1,60 @@
{
"name": "vue3_template",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue --fix src .prettierrc.cjs"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@icon-park/vue-next": "^1.4.2",
"@vueuse/core": "^9.13.0",
"@vueuse/integrations": "^10.7.0",
"animate.css": "^4.1.1",
"axios": "^1.6.3",
"dayjs": "^1.11.10",
"element-plus": "^2.4.4",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"normalize.css": "^8.0.1",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"throttle-debounce": "^5.0.0",
"v-viewer": "^3.0.11",
"vue": "^3.3.13",
"vue-router": "^4.2.5",
"vue3-print-nb": "^0.1.4",
"vue3-puzzle-vcode": "^1.0.16"
},
"devDependencies": {
"@our-patches/postcss-px-to-viewport": "^1.2.0",
"@types/node": "^18.18.4",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"@vitejs/plugin-vue": "^4.4.0",
"@vue/eslint-config-prettier": "^7.1.0",
"autoprefixer": "^10.4.16",
"eslint": "^8.51.0",
"eslint-config-prettier": "^8.10.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.17.0",
"prettier": "^2.8.8",
"sass": "^1.69.0",
"unplugin-auto-import": "^0.12.2",
"unplugin-vue-components": "^0.22.12",
"vite": "^4.4.11",
"vite-plugin-enhance-log": "^0.5.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-remove-console": "^2.1.1",
"vue-eslint-parser": "^9.3.1"
}
}

22
postcss.config.cjs Normal file
View File

@ -0,0 +1,22 @@
const path = require('path')
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,
// }
},
};

3
public/jsencrypt.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/vite.svg Normal file
View File

@ -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

18
src/App.vue Normal file
View File

@ -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"></style>

89
src/addRouters.js Normal file
View File

@ -0,0 +1,89 @@
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 { getRouteTreeAll } from "@/request/system_management.js";
const modules = import.meta.glob("./views/**/*.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.USER_ID) {
next("/login");
return;
}
if (!storageRouter) {
// 变量里没有储存路由
// pinia里没有储存路由去后台获取路由
if (routerStore.getRouters.length === 0) {
await getAsyncRouter();
// const resData = await getRouteTreeAll();
// storageRouter = resData.menuList; // 后台请求得到的路由数据
storageRouter = asyncRouter; // 死路由
routerStore.setRouters(storageRouter); // 存储路由
routerGo(to, next); // 执行路由跳转方法
} else {
// pinia里储存了路由
storageRouter = routerStore.getRouters; // 拿到路由
routerGo(to, next); // 执行路由跳转方法
}
} else {
next();
}
} else {
// 不需要登陆,清空储存路由
storageRouter = null;
routerStore.$reset();
menuStore.$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) => {
if (route.component) {
if (route.component === "children") {
route.component = children;
} else {
route.component = modules[`./views/${route.component}.vue`];
}
}
// 如果存在children递归
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children);
}
return true;
});
}

324
src/assets/css/common.scss Normal file
View File

@ -0,0 +1,324 @@
// 5
// 使1使flexmin-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-50marginpadding
@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;
}
#app {
background-color: #030f2f;
min-height: 100vh;
background-image: url("/src/assets/images/public/bg.jpg");
background-repeat: no-repeat;
}
a {
text-decoration: none;
color: var(--el-color-primary);
}
.end {
.el-form-item__content {
justify-content: end;
}
}
.tc {
text-align: center;
}
.tr {
text-align: right;
}
.tl {
text-align: left;
}
.dn {
display: none;
}
.text-blue {
color: #3b3bff;
}
.text-yellow {
color: #bebe05;
}
.text-orange {
color: #de9004;
}
.text-red {
color: #ff0000;
}
.text-green {
color: #0bb20c;
}
.print_use {
display: none;
}
img.ml-10:first-child {
margin-left: 0;
}
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;
}
div::-webkit-scrollbar {
width: 4px;
}
div::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
opacity: 0.2;
background-color: var(--el-border-color);
}
div::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
border-radius: 0;
background-color: var(--el-border-color);
}
.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;
}
}
.w-e-bar {
--w-e-toolbar-bg-color: var(--el-fill-color-blank) !important;
border: 1px solid var(--el-border-color);
border-bottom: none;
--w-e-toolbar-border-color: var(--el-border-color);
--w-e-toolbar-color: var(--el-text-color-regular) !important;
--w-e-toolbar-disabled-color: var(--el-text-color-regular) !important;
--w-e-toolbar-active-bg-color: var(--el-fill-color-light);
--w-e-toolbar-active-color: var(--el-color-check);
.w-e-menu-tooltip-v5:before {
color: var(--el-text-color-regular) !important;
}
.w-e-select-list ul {
li:hover, .selected {
background-color: var(--el-fill-color-light);
color: var(--el-color-check);
}
}
}
.w-e-text-container {
--w-e-textarea-bg-color: var(--el-fill-color-blank) !important;
border: 1px solid var(--el-border-color);
--w-e-textarea-color: var(--el-text-color-regular) !important;
}
.w-e-bar-divider {
display: none !important;
}
.w-e-toolbar {
border-bottom: 1px dashed var(--el-border-color-darker);
}
.w-e-bar-item:has([data-menu-key="group-video"]) {
display: none !important;
}
.w-e-bar-item-menus-container .w-e-bar-item:has([data-menu-key="insertImage"]) {
display: none !important;
}
.vue-auth-box_ {
background: #020f3a !important;
border: 1px solid #223765 !important;
}
.vue-puzzle-vcode {
background-color: #0000008d !important;
}
.vue-auth-box_ .auth-control_ .range-box {
background-color: #04205f !important;
box-shadow: none !important;
}
.vue-auth-box_ .auth-control_ .range-box .range-slider .range-btn {
background: #3266cb !important;
box-shadow: none !important;
}
//
@page {
size: auto;
margin: 3mm;
}
.page_break{
page-break-after: always;
}
@media print {
.el-descriptions__label.el-descriptions__cell.is-bordered-label {
color: #000 !important;
}
.el-descriptions {
--el-text-color-primary: #000 !important;
}
.el-divider__text {
--el-text-color-regular: #000 !important;
}
.print_use {
border-collapse: collapse;
width: 100%;
display: table;
td, th {
border: 1px solid var(--el-border-color);
padding: 8px;
line-height: 1.6;
text-align: center;
}
}
.print_no_use {
display: none;
}
}

385
src/assets/css/element.scss Normal file
View File

@ -0,0 +1,385 @@
:root {
--el-header-height: 69px;
--el-aside-scrollbar-height: calc(100vh - var(--el-header-height));
--el-main-scrollbar-height: calc(100vh - var(--el-header-height) - 60px);
--el-border-color: #273868 !important; //
--el-text-color-regular: #fff !important;
--el-fill-color-light: #0e1d44 !important; //hover
--el-color-check: #14affe !important; //hover
--el-aside-bg-color: #081e42 !important;
--el-input-bg-color: #081435 !important;
--el-fill-color-blank: #081435 !important; //
--el-border-color-lighter: var(--el-border-color) !important;
--el-bullet-frame-bg-color: #08163b !important;
}
.el-button:focus, .el-button:hover {
--el-button-hover-text-color: var(--el-button-text-color);
--el-button-hover-border-color: var(--el-button-border-color);
--el-button-hover-bg-color: var(--el-button-bg-color);
}
.el-header {
--el-header-padding: 0 !important;
--el-header-height: 69px !important;
}
.el-main {
--el-main-padding: 0 !important;
}
.el-aside {
--el-aside-width: 250px;
background-color: var(--el-aside-bg-color);
}
.el-card {
margin: 10px 20px;
--el-card-padding: 18px 18px 0 18px !important;
--el-card-bg-color: rgba(8, 24, 58, 0.5) !important;
--el-card-border-color: var(--el-border-color) !important;
color: var(--el-text-color-regular) !important;
position: relative;
}
.el-popper {
--el-bg-color-overlay: var(--el-fill-color-blank) !important;
--el-border-color-light: var(--el-border-color) !important;
}
.el-cascader-node:not(.is-disabled):focus, .el-cascader-node:not(.is-disabled):hover {
--el-cascader-node-background-hover: var(--el-fill-color-light);
color: var(--el-color-check);
}
.el-cascader {
--el-cascader-tag-background: #214082 !important;
--el-color-info: var(--el-text-color-regular);
}
.el-select-dropdown__item.hover, .el-select-dropdown__item:hover {
color: var(--el-color-check);
}
.el-dropdown-menu__item {
&:not(.is-disabled):focus {
--el-dropdown-menuItem-hover-fill: var(--el-fill-color-light);
--el-dropdown-menuItem-hover-color: var(--el-color-check);
}
}
.el-menu {
border-right: none !important;
--el-menu-bg-color: var(--el-aside-bg-color);
--el-menu-text-color: var(--el-text-color-regular);
--el-menu-hover-text-color: var(--el-text-color-regular);
--el-menu-active-color: var(--el-text-color-regular);
--el-menu-hover-bg-color: transparent;
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.el-sub-menu__title *, .el-menu-item * {
vertical-align: middle !important;
}
.el-menu-item {
&:hover, &.is-active {
background-image: url("/src/assets/images/public/list_on.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}
}
//.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item {
// padding-left: calc(var(--el-menu-base-level-padding) + var(--el-menu-level) * var(--el-menu-level-padding) + 20px) !important;
//}
.el-breadcrumb {
margin-left: 10px;
height: 30px;
line-height: 30px !important;
.el-breadcrumb__inner {
--el-text-color-regular: #cacbce;
a {
font-weight: normal;
--el-text-color-primary: #cacbce;
}
}
.el-breadcrumb__separator {
--el-text-color-placeholder: #cacbce;
}
}
.el-select, .el-cascader, .el-date-editor.el-input, .el-date-editor.el-input__wrapper, .el-input__wrapper, .el-input-number {
width: 100% !important;
}
.el-table {
--el-table-bg-color: #071a43 !important;
--el-bg-color: #071a43 !important;
--el-table-header-bg-color: #0f2049 !important;
--el-table-border-color: var(--el-border-color) !important;
--el-table-tr-bg-color: #071a43 !important;
--el-fill-color-lighter: #111e40 !important;
--el-table-text-color: #e0e0e0 !important;
--el-table-header-text-color: var(--el-text-color-regular) !important;
--el-table-row-hover-bg-color: #0d5aa3 !important;
--el-table-current-row-bg-color: #0d5aa3 !important;
.el-table__cell {
text-align: center !important;
}
.el-table__empty-text {
--el-text-color-secondary: var(--el-text-color-regular) !important;
}
}
.el-pagination {
--el-pagination-button-color: var(--el-text-color-regular) !important;
--el-pagination-button-disabled-bg-color: var(--el-fill-color-blank) !important;
--el-pagination-bg-color: var(--el-fill-color-blank) !important;
}
.el-radio {
--el-radio-input-bg-color: #091839 !important;
}
.el-radio__input.is-checked .el-radio__inner {
background: var(--el-radio-input-bg-color) !important;
border-color: var(--el-color-check);
}
.el-radio__inner {
&::after {
--el-color-white: var(--el-color-check);
}
}
.el-radio__input.is-disabled.is-checked .el-radio__inner {
--el-disabled-border-color: var(--el-color-check);
&::after {
--el-text-color-placeholder: var(--el-color-check);
}
}
.el-radio__input.is-disabled .el-radio__inner {
--el-disabled-bg-color: var(--el-radio-input-bg-color);
--el-disabled-border-color: var(--el-border-color);
}
.el-checkbox {
--el-checkbox-bg-color: #091839 !important;
--el-checkbox-checked-bg-color: #091839 !important;
--el-checkbox-checked-input-border-color: var(--el-color-check) !important;
}
.el-checkbox__input.is-disabled .el-checkbox__inner {
--el-checkbox-disabled-input-fill: var(--el-checkbox-bg-color);
--el-checkbox-disabled-border-color: var(--el-border-color);
}
.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner {
--el-checkbox-disabled-checked-input-fill: var(--el-checkbox-bg-color);
--el-checkbox-disabled-checked-input-border-color: var(--el-color-check);
}
.el-picker__popper, .el-date-picker {
--el-datepicker-border-color: var(--el-border-color) !important;
}
.el-time-panel__btn {
--el-text-color-primary: var(--el-text-color-regular);
}
.el-time-spinner__item:hover:not(.is-disabled):not(.is-active) {
color: var(--el-color-check);
}
.el-button.is-plain {
--el-fill-color-blank: var(--el-fill-color-blank);
}
.el-button.is-text:not(.is-disabled):active {
background-color: var(--el-fill-color-light) !important;
}
.el-button.is-text:not(.is-disabled):focus, .el-button.is-text:not(.is-disabled):hover {
color: var(--el-color-check);
}
.el-time-spinner__item.is-active:not(.is-disabled) {
--el-text-color-primary: var(--el-color-check);
}
.el-picker-panel__icon-btn {
color: var(--el-text-color-regular);
.el-icon {
color: var(--el-text-color-regular);
}
}
.el-date-range-picker {
--el-datepicker-border-color: var(--el-fill-color-light) !important;
--el-datepicker-inrange-bg-color: var(--el-fill-color-light) !important;
--el-datepicker-inrange-hover-bg-color: var(--el-fill-color-light) !important;
}
.el-date-editor .el-range-separator {
color: var(--el-text-color-regular) !important;
}
.el-input.is-disabled .el-input__wrapper {
box-shadow: 0 0 0 1px var(--el-border-color) inset !important;
}
.el-select__tags .el-tag--info {
background-color: #214082 !important;
--el-tag-text-color: var(--el-text-color-regular) !important;
}
.el-tag .el-tag__close:hover {
color: var(--el-color-check) !important;
background-color: var(--el-fill-color-light) !important;
}
.el-upload--picture-card {
--el-fill-color-lighter: var(--el-fill-color-blank) !important;
border: 1px solid var(--el-border-color) !important;
}
.el-collapse {
--el-collapse-header-text-color: var(--el-text-color-regular) !important;
--el-collapse-content-text-color: var(--el-text-color-regular) !important;
}
.el-tabs__item {
color: var(--el-text-color-regular) !important;
&.is-active {
color: var(--el-color-primary) !important;
}
}
.el-tabs__nav-wrap::after {
--el-border-color-light: var(--el-border-color);
}
.el-divider__text {
font-size: 16px !important;
font-weight: 700 !important;
}
.el-dialog {
background: transparent !important;
--el-dialog-margin-top: 50px !important;
.el-dialog__header {
background-image: url("/src/assets/images/public/tctitlebg.png");
height: 31px;
line-height: 31px;
padding-left: 30px;
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 0 !important;
--el-dialog-padding-primary: 0 !important;
padding-bottom: 0;
.el-dialog__title {
color: var(--el-text-color-regular);
--el-dialog-title-font-size: 14px;
}
.el-dialog__headerbtn {
height: 31px;
top: 0;
}
}
.el-dialog__body, .el-dialog__footer {
background-color: var(--el-bullet-frame-bg-color);
}
}
.el-drawer {
--el-drawer-bg-color: var(--el-bullet-frame-bg-color) !important;
.el-drawer__header {
color: var(--el-text-color-regular);
}
}
.el-popper.is-dark {
background: var(--el-fill-color-blank) !important;
border: 1px solid var(--el-border-color) !important;
color: var(--el-text-color-regular) !important;
.el-popper__arrow::before {
background: var(--el-fill-color-blank) !important;
border: 1px solid var(--el-border-color) !important;
}
}
.el-message-box {
background-color: var(--el-fill-color-blank) !important;
--el-messagebox-title-color: var(--el-text-color-regular) !important;
}
.el-divider__text {
background-color: var(--el-fill-color-blank) !important;
color: var(--el-text-color-regular) !important;
}
.el-calendar {
--el-calendar-selected-bg-color: var(--el-fill-color-light) !important;
}
.el-steps {
--el-text-color-primary: var(--el-text-color-regular);
.el-step__icon {
background: var(--el-fill-color-blank);
}
}
.el-descriptions {
--el-text-color-primary: var(--el-text-color-regular);
}
.el-descriptions__label {
width: 200px;
}
.el-descriptions__content {
width: auto;
}
.el-descriptions__title {
&:before {
content: '';
display: inline-block;
width: 4px;
height: 19px;
background-color: #04a9f5;
margin-right: 10px;
vertical-align: text-bottom;
}
}
.el-statistic__content {
color: var(--el-text-color-regular) !important;
}
.el-step__title{
font-size: 14px !important;;
}

View File

@ -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: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -0,0 +1,78 @@
import { MODEL } from "@/assets/js/constant";
export default [
{
path: "/enterprise_management",
redirect: "/enterprise_management/information",
meta: { title: "企业管理", model: MODEL["1"] },
component: "children",
children: [
{
path: "/enterprise_management/information",
redirect: "/enterprise_management/information/info",
meta: { title: "企业信息" },
component: "children",
children: [
{
path: "/enterprise_management/information/info",
meta: { title: "企业信息", isSubMenu: false },
component: "children",
children: [
{
path: "",
component: "enterprise_management/information/info",
},
{
path: "/enterprise_management/information/info/edit",
meta: {
title: "编辑",
activeMenu: "/enterprise_management/information/info",
},
component: "enterprise_management/information/edit",
},
],
},
{
path: "/enterprise_management/information/industry_qualifications",
meta: { title: "行业资质", isSubMenu: false },
component: "children",
children: [
{
path: "",
component:
"enterprise_management/industry_qualifications/index",
},
{
path: "/enterprise_management/information/industry_qualifications/add",
meta: {
title: "新增",
activeMenu:
"/enterprise_management/information/industry_qualifications",
},
component: "enterprise_management/industry_qualifications/add",
},
{
path: "/enterprise_management/information/industry_qualifications/update",
meta: {
title: "修改",
activeMenu:
"/enterprise_management/information/industry_qualifications",
},
component: "enterprise_management/industry_qualifications/add",
},
{
path: "/enterprise_management/information/industry_qualifications/view",
meta: {
title: "查看",
activeMenu:
"/enterprise_management/information/industry_qualifications",
},
component: "enterprise_management/industry_qualifications/view",
},
],
},
],
},
],
},
];

18
src/assets/js/button.js Normal file
View File

@ -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("参数无效,请联系管理员");
},
});
},
};

23
src/assets/js/constant.js Normal file
View File

@ -0,0 +1,23 @@
// 将常用的值储存成常量,防止重复使用写错
// 头部导航条切换的model
export const MODEL = {
1: "prevention",
2: "educationAndTraining",
3: "highRisk",
4: "monitor",
5: "comprehensive",
};
// 头部导航条
export const MENU = [
{ title: "双重预防", model: MODEL["1"] },
{ title: "教育培训", model: MODEL["2"] },
{ title: "高危作业管理", model: MODEL["3"] },
{ title: "监测预警", model: MODEL["4"] },
{ title: "综合管理", model: MODEL["5"] },
];
export const styleText =
'<style type="text/css" media="print">\n' +
" @page { size: landscape; }\n" +
"</style>";

View File

@ -0,0 +1,65 @@
import {
getLearningTrainType,
getLevels,
getLevelsAndChildrenNumber,
getRegulatoryType,
} from "@/request/data_dictionary.js";
import { ref } from "vue";
// 监管类型
export const layoutFnGetRegulatoryType = async (params) => {
const resData = await getRegulatoryType(params);
return ref(JSON.parse(resData.varOList.zTreeNodes));
};
// 企业状态
export const layoutFnGetEnterpriseStatus = async () => {
const resData = await getLevels({
DICTIONARIES_ID: "4d4862f9863b4b0da67f754c49e67ea3",
});
return ref(resData.list);
};
// 隶属关系
export const layoutFnGetSubordination = async () => {
const resData = await getLevels({
DICTIONARIES_ID: "1a13f574d4c44cd2ac2034f8e3259f9b",
});
return ref(resData.list);
};
// 企业规模
export const layoutFnGetEnterpriseScale = async () => {
const resData = await getLevels({
DICTIONARIES_ID: "37b045e160c04ddba851073b4e510cc9",
});
return ref(resData.list);
};
// 培训行业类型
export const layoutFnGetTrainingIndustryType = async () => {
const resData = await getLearningTrainType({
parentId: "052369aa22d242118236cde52d0c67ea",
});
return ref(JSON.parse(resData.zTreeNodes));
};
// 培训岗位类型
export const layoutFnGetTrainingPostType = async () => {
const resData = await getLearningTrainType({
parentId: "f6a7c4f5602f46e291d06b1390a3f820",
});
return ref(JSON.parse(resData.zTreeNodes));
};
// 培训板块类型
export const layoutFnGetTrainingPlateType = async () => {
const resData = await getLearningTrainType({
parentId: "d538d11e4eec409ab428f5d2f3c67c24",
});
return ref(JSON.parse(resData.zTreeNodes));
};
// 无法确定DICTIONARIES_ID的数据字典
export const layoutFnGetLevels = async (DICTIONARIES_ID) => {
const resData = await getLevels({ DICTIONARIES_ID });
return ref(resData.list);
};
// 无法确定DICTIONARIES_ID的数据字典包括子级数量
export const layoutFnGetLevelsAndChildrenNumber = async (DICTIONARIES_ID) => {
const resData = await getLevelsAndChildrenNumber({ DICTIONARIES_ID });
return ref(resData.list);
};

2
src/assets/js/mitt.js Normal file
View File

@ -0,0 +1,2 @@
import mitt from "mitt";
export default mitt();

39
src/assets/js/print.js Normal file
View File

@ -0,0 +1,39 @@
import { ref } from "vue";
const buttonRef = ref(null);
const THEAD_HEIGHT = 81.59;
const A4_HEIGHT_MM = 297;
const A4_HEIGHT_MM_TO_PX_PROPORTION = 3.78;
let A4_HEIGHT_PX = A4_HEIGHT_MM * A4_HEIGHT_MM_TO_PX_PROPORTION - THEAD_HEIGHT;
let elements = [];
const printObj = {
id: "printContent",
closeCallback() {
document.querySelector("#printContent").style.overflow = "hidden";
},
};
const fnPrint = () => {
document.querySelector("#printContent").style.overflow = "visible";
elements = document.querySelectorAll("#printContent > table > tr");
if (!document.querySelector("#printContent > table thead")) {
A4_HEIGHT_PX = A4_HEIGHT_PX + THEAD_HEIGHT;
}
fnIsPaging();
buttonRef.value.$el.click();
};
const fnIsPaging = (index = 0) => {
for (let i = index; i < elements.length; i++) {
if (
elements[i].offsetHeight +
elements[i].offsetTop -
elements[index].offsetTop >=
A4_HEIGHT_PX
) {
elements[i - 1].setAttribute("class", "page_break");
fnIsPaging(i === index ? i + 1 : i);
break;
}
}
};
export { fnPrint, printObj, buttonRef };

View File

@ -0,0 +1,16 @@
import { post } from "@/request/axios";
// 按钮权限
export const useButtonJurisdiction = async (type) => {
const keys = `${type}:add,${type}:del,${type}:edit,toExcel`;
const resData = await post("/api/head/hasButton", {
loading: false,
keys,
});
return {
add: resData[`${type}fhadminadd`],
del: resData[`${type}fhadmindel`],
edit: resData[`${type}fhadminedit`],
excel: resData.toExcel,
};
};

View File

@ -0,0 +1,25 @@
import { ElMessage } from "element-plus";
import { getDataType } from "@/assets/js/utils.js";
export default function (ref, message = "请补全必填项!") {
const type = ["Function", "AsyncFunction"];
if (!type.includes(getDataType(ref?.value?.validate)))
throw new Error("不是有效的Element-Plus Form组件ref!");
return new Promise((resolve, reject) => {
ref.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);
}
});
});
}

View File

@ -0,0 +1,77 @@
import { nextTick, ref } from "vue";
import { getDataType } from "@/assets/js/utils.js";
/**
* @param api {Function} 接口函数
* @param options {Object?: {callbackFn, otherParams, immediate, usePagination}} 配置项
* @param options.callbackFn {Function?} 回调函数
* @param options.otherParams {Object?} 其它接口参数
* @param options.immediate {Boolean?} 是否立即执行接口函数默认是
* @param options.usePagination {Boolean?} 是否使用分页默认是
* @return {Object} 返回对象包含以下属性list 表格数据pagination 分页数据searchForm 搜索表单数据tableRef 表格实例fnGetData 获取数据函数fnResetPagination 重置分页函数
*/
export default function useListData(api, options = {}) {
if (getDataType(api) !== "Function") throw new Error("api必须是一个函数");
if (options.immediate && getDataType(options.immediate) !== "Boolean")
throw new Error("options.immediate必须是一个布尔值");
if (options.usePagination && getDataType(options.usePagination) !== "Boolean")
throw new Error("options.usePagination必须是一个布尔值");
const immediate = options.immediate ?? true;
const usePagination = options.usePagination ?? true;
if (!immediate && options.otherParams)
throw new Error("options.otherParams只有在immediate为true时才有效");
if (
immediate &&
options.otherParams &&
getDataType(options.otherParams) !== "Object"
)
throw new Error("options.otherParams必须是一个对象");
if (options.callbackFn && getDataType(options.callbackFn) !== "Function")
throw new Error("options.callbackFn必须是一个函数");
const list = ref([]);
const pagination = ref({
currentPage: 1,
pageSize: 10,
total: 0,
});
const searchForm = ref({});
const tableRef = ref(null);
const fnGetData = async (otherParams = {}) => {
const resData = await api({
...(usePagination
? {
currentPage: pagination.value.currentPage,
showCount: pagination.value.pageSize,
}
: {}),
...searchForm.value,
...(options.otherParams || {}),
...(getDataType(otherParams) === "Object" ? otherParams : {}),
});
list.value = resData.varList;
pagination.value.total = resData.page.totalResult;
options.callbackFn && options.callbackFn(list.value);
};
immediate && fnGetData().then();
const fnResetPagination = async (otherParams) => {
list.value = [];
pagination.value = {
currentPage: 1,
pageSize: 10,
total: 0,
};
await nextTick();
await fnGetData(otherParams);
tableRef.value && tableRef.value.clearSelection();
};
return {
list,
pagination,
searchForm,
tableRef,
fnGetData: async (otherParams) => await fnGetData(otherParams),
fnResetPagination: async (otherParams) =>
await fnResetPagination(otherParams),
};
}

320
src/assets/js/utils.js Normal file
View File

@ -0,0 +1,320 @@
import { ElMessage, ElMessageBox } 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);
for (let i = 0; i < num - 1; i++) {
index = str.indexOf(char, index + 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) {
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) % 24, 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 {string} filePah 下载路径
**/
export async function downloadFile(filePah) {
const FILE_URL = import.meta.env.VITE_FILE_URL;
await ElMessageBox.confirm("确定要下载此文件吗?", { type: "warning" });
window.open(FILE_URL + filePah, "_blank");
}
/**
* @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 function verifyDuplicateSelection(list, index, key, id) {
if (list.some((item) => item[key] === id)) {
ElMessage.warning("不能重复选择");
} else {
list[index][key] = id;
}
}
/**
* @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;
}
}
}

View File

@ -0,0 +1,43 @@
<template>
<el-card>
<div class="top_bg" />
<div class="content">
<slot />
</div>
<div class="bottom_bg" />
</el-card>
</template>
<script setup>
defineOptions({
name: "LayoutCard",
});
</script>
<style scoped lang="scss">
.el-card {
--el-card-padding: 0 !important;
}
.top_bg {
margin: auto;
width: 1250px;
height: 15px;
background-image: url("/src/assets/images/public/topguang.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.bottom_bg {
margin: auto;
width: 1218px;
height: 30px;
background-image: url("/src/assets/images/public/bottomguang.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.content {
padding: 10px;
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<el-cascader
ref="cascaderRef"
v-model="modelValue"
:props="cascaderProps"
:show-all-levels="showAllLevels"
/>
</template>
<script setup>
import { useVModel } from "@vueuse/core";
import { ref } from "vue";
import {
layoutFnGetLevels,
layoutFnGetLevelsAndChildrenNumber,
} from "@/assets/js/data_dictionary.js";
defineOptions({
name: "LayoutCascader",
});
const props = defineProps({
modelValue: {
type: Array,
default: () => [],
required: true,
},
id: {
type: String,
required: true,
default: "",
},
level: {
type: [Number, String],
default: 3,
},
checkStrictly: {
type: Boolean,
default: true,
},
showAllLevels: {
type: Boolean,
default: true,
},
value: {
type: String,
default: "DICTIONARIES_ID",
},
joinSeparator: {
type: String,
default: "/",
},
});
const emits = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emits);
const cascaderRef = ref(null);
const cascaderProps = {
lazy: true,
lazyLoad: async (node, resolve) => {
let resData;
if (props.checkStrictly)
resData = await layoutFnGetLevels(node.data.DICTIONARIES_ID || props.id);
else
resData = await layoutFnGetLevelsAndChildrenNumber(
node.data.DICTIONARIES_ID || props.id
);
resolve(
resData.value.map((item) => {
return {
DICTIONARIES_ID: item.DICTIONARIES_ID,
BIANMA: item.BIANMA,
NAME: item.NAME,
leaf: props.checkStrictly
? node.level >= props.level
: item.zcount === 0,
};
})
);
},
value: props.value,
id: "DICTIONARIES_ID",
label: "NAME",
children: "children",
checkStrictly: props.checkStrictly,
};
const getCheckedNodes = () => {
return cascaderRef.value
.getCheckedNodes()[0]
.pathLabels.join(props.joinSeparator);
};
defineExpose({
getCheckedNodes,
});
</script>
<style scoped></style>

View File

@ -0,0 +1,9 @@
<template>
<router-view />
</template>
<script setup>
defineOptions({
name: "children",
});
</script>

View File

@ -0,0 +1,37 @@
<template>
<layout-cascader
id="688d2cf1c6cd4dab999a0106e09aec83"
v-model="modelValue"
ref="cascaderRef"
:check-strictly="false"
:show-all-levels="false"
/>
</template>
<script setup>
import { ref } from "vue";
import { useVModel } from "@vueuse/core";
import LayoutCascader from "@/components/cascader/index.vue";
defineOptions({
name: "LayoutEconomicType",
});
const props = defineProps({
modelValue: {
type: Array,
required: true,
default: () => [],
},
});
const emits = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emits);
const cascaderRef = ref(null);
const getCheckedNodes = () => {
return cascaderRef.value.getCheckedNodes();
};
defineExpose({
getCheckedNodes,
});
</script>
<style scoped></style>

View File

@ -0,0 +1,37 @@
<template>
<layout-cascader
id="f2598ba72e864eadabf0ca4b664d26b9"
v-model="modelValue"
ref="cascaderRef"
:check-strictly="false"
:show-all-levels="false"
/>
</template>
<script setup>
import { ref } from "vue";
import { useVModel } from "@vueuse/core";
import LayoutCascader from "@/components/cascader/index.vue";
defineOptions({
name: "LayoutIndustry",
});
const props = defineProps({
modelValue: {
type: Array,
required: true,
default: () => [],
},
});
const emits = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emits);
const cascaderRef = ref(null);
const getCheckedNodes = () => {
return cascaderRef.value.getCheckedNodes();
};
defineExpose({
getCheckedNodes,
});
</script>
<style scoped></style>

View File

@ -0,0 +1,83 @@
<template>
<div
style="display: flex; justify-content: space-between; align-items: center"
>
<div class="breadcrumb">
<icon-local theme="filled" size="22" fill="#10a7e5" :strokeWidth="3" />
<el-breadcrumb class="app-breadcrumb" separator=">">
<transition-group name="breadcrumb">
<el-breadcrumb-item
v-for="(item, index) in data.breadcrumbList"
:key="item.path"
>
<router-link v-if="index === 0" :to="item.path">
{{ item.meta.title }}
</router-link>
<span v-else-if="index !== data.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 class="return">
<el-button
type="primary"
text
link
@click="$router.back()"
v-if="
route.meta?.activeMenu &&
route.path &&
route.meta?.activeMenu !== route.path
"
>
返回上一页
</el-button>
</div>
</div>
</template>
<script setup>
import { reactive, watch } from "vue";
import { useRoute } from "vue-router";
defineOptions({
name: "LayoutBreadcrumb",
});
const route = useRoute();
const data = reactive({
breadcrumbList: [],
});
const fnGetBreadcrumb = () => {
const matched = route.matched.filter((item) => item.meta?.title);
data.breadcrumbList = matched.filter(
(item) => item.meta?.title && item.meta?.breadcrumb !== false
);
};
fnGetBreadcrumb();
watch(
() => route,
() => fnGetBreadcrumb(),
{ deep: true }
);
</script>
<style lang="scss" scoped>
.breadcrumb {
margin-top: 10px;
margin-left: 20px;
display: flex;
align-items: center;
.no-redirect {
color: #ffffff;
cursor: text;
}
}
.return {
margin-right: 20px;
}
</style>

View File

@ -0,0 +1,79 @@
<template>
<el-dialog
v-model="visible"
title="修改头像"
width="600px"
:before-close="fnClose"
>
<el-form :model="form" :rules="rules" label-width="100px" ref="formRef">
<el-form-item label="头像" prop="file">
<layout-upload
v-model:file-list="form.file"
accept=".jpg,.jpeg,.png"
list-type="picture-card"
/>
</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 LayoutUpload from "@/components/upload/index.vue";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/assets/js/useFormValidate.js";
import { setAvatar } from "@/request/api.js";
import { ElMessage } from "element-plus";
import { ref } from "vue";
import { useVModels } from "@vueuse/core";
import { useUserStore } from "@/pinia/user.js";
const userStore = useUserStore();
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
form: {
type: Object,
required: true,
default: () => ({}),
},
});
const emits = defineEmits(["update:visible", "update:form"]);
const { visible, form } = useVModels(props, emits);
const formRef = ref(null);
const rules = {
file: [{ required: true, message: "请上传头像", trigger: "change" }],
};
const fnSubmit = debounce(
1000,
async () => {
await useFormValidate(formRef);
if (!form.value.file[0].raw) {
ElMessage.warning("没有修改图片,不需要保存");
return;
}
const formData = new FormData();
formData.append("FFILE", form.value.file[0].raw);
const resData = await setAvatar(formData);
fnClose();
ElMessage.success("修改成功");
userStore.setUserInfo({
...userStore.getUserInfo,
userPhoto: resData.userPhoto,
});
},
{ atBegin: true }
);
const fnClose = () => {
formRef.value.resetFields();
visible.value = false;
};
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,130 @@
<template>
<el-dialog
v-model="visible"
title="修改信息"
width="600px"
:before-close="fnClose"
>
<el-form :model="form" :rules="rules" label-width="100px" ref="formRef">
<el-form-item label="用户名" prop="USERNAME">
<el-input
v-model="form.USERNAME"
disabled
placeholder="默认用户手机号码..."
/>
</el-form-item>
<el-form-item label="新密码" prop="newpwd">
<el-input v-model="form.newpwd" type="password" autocomplete="off" />
</el-form-item>
<el-form-item label="确认密码" prop="newpassword1">
<el-input
v-model="form.newpassword1"
type="password"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="姓名" prop="NAME">
<el-input v-model="form.NAME" placeholder="这里输入姓名..." />
</el-form-item>
<el-form-item label="邮箱" prop="EMAIL">
<el-input v-model="form.EMAIL" placeholder="这里输入邮箱..." />
</el-form-item>
<el-form-item label="备注" prop="BZ">
<el-input
:rows="3"
v-model="form.BZ"
type="textarea"
placeholder="这里输入备注..."
/>
</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 { getVerifyDuplicateEmail, setUserInfo } from "@/request/api.js";
import { useVModels } from "@vueuse/core";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/assets/js/useFormValidate.js";
import { ElMessage } from "element-plus";
import { useUserStore } from "@/pinia/user.js";
import { ref } from "vue";
const userStore = useUserStore();
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
form: {
type: Object,
required: true,
default: () => ({}),
},
});
const emits = defineEmits(["update:visible", "update:form"]);
const { visible, form } = useVModels(props, emits);
const formRef = ref(null);
const validatePass = (rule, value, callback) => {
if (form.value.newpwd && !value) {
callback(new Error("请再次输入新密码"));
} else if (form.value.newpwd && value && value !== form.value.newpwd) {
callback(new Error("两次输入密码不一致!"));
} else callback();
};
const validateEmail = async (rule, value, callback) => {
if (value) {
try {
await getVerifyDuplicateEmail({
EMAIL: value,
USERNAME: form.value.USERNAME,
});
callback();
} catch {
callback(new Error("邮箱重复"));
}
} else callback();
};
const rules = {
NAME: [{ required: true, message: "姓名不能为空", trigger: "blur" }],
EMAIL: [
{ required: false, message: "请输入邮箱", trigger: "blur" },
{
pattern: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/,
message: "请输入正确的邮箱",
},
{ validator: validateEmail, trigger: "blur" },
],
newpwd: [
{ required: false, message: "请输入新密码", trigger: "blur" },
{ min: 6, max: 18, message: "密码长度为6-18位", trigger: "blur" },
],
newpassword1: [{ required: false, validator: validatePass, trigger: "blur" }],
};
const fnSubmit = debounce(
1000,
async () => {
await useFormValidate(formRef);
await setUserInfo({
USER_ID: userStore.getUserInfo.USER_ID,
OPERATIONTYPE: "1",
...form.value,
PASSWORD: form.value.newpwd,
});
fnClose();
ElMessage.success("修改成功");
},
{ atBegin: true }
);
const fnClose = () => {
formRef.value.resetFields();
visible.value = false;
};
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,270 @@
<template>
<div class="header">
<div class="left">
<div class="logo">管理平台</div>
<div class="menu">
<ul>
<template v-for="(item, index) in MENU" :key="index">
<li
@click="switchMenu(item.model)"
:class="{ active: item.model === menuStore.getModel }"
>
<div v-for="item1 in 4" :key="item1" :class="'horn' + item1" />
<div class="title">{{ item.title }}</div>
</li>
</template>
</ul>
</div>
</div>
<div class="user">
<el-dropdown
trigger="click"
placement="bottom-end"
@command="dropdownCommand"
>
<div class="user_info">
<el-avatar :size="23" fit="fill" :src="data.avatar" />
<span>{{ userStore.getUserInfo.NAME }}</span>
<icon-down theme="filled" size="16" fill="#a2c2d3" :strokeWidth="3" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="modifyInformation">
修改信息
</el-dropdown-item>
<el-dropdown-item command="modifyAvatar">
修改头像
</el-dropdown-item>
<el-dropdown-item command="signOut">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<update-info
v-model:visible="data.userDialog.visible"
v-model:form="data.userDialog.form"
/>
<update-avatar
v-model:visible="data.avatarDialog.visible"
v-model:form="data.avatarDialog.form"
/>
</div>
</template>
<script setup>
import { onBeforeUnmount, reactive } from "vue";
import { useRouter } from "vue-router";
import { useMenuStore } from "@/pinia/menu";
import { useUserStore } from "@/pinia/user";
import { MENU } from "@/assets/js/constant";
import { getInfo, getUserInfo, logout } from "@/request/api";
import { useMiscellaneousStore } from "@/pinia/miscellaneous.js";
import UpdateInfo from "./components/update_info.vue";
import UpdateAvatar from "./components/update_avatar.vue";
import { checkImgExists, addingPrefixToFile } from "@/assets/js/utils.js";
defineOptions({
name: "LayoutHeader",
});
const FILE_URL = import.meta.env.VITE_FILE_URL;
let webSocket;
const router = useRouter();
const menuStore = useMenuStore();
const userStore = useUserStore();
const miscellaneousStore = useMiscellaneousStore();
const data = reactive({
avatar: "",
userDialog: {
visible: false,
form: {},
},
avatarDialog: {
visible: false,
form: {},
},
});
const dropdownCommand = async (command) => {
if (command === "signOut") {
await fnSignOut();
}
if (command === "modifyInformation") {
await fnGetUserInfo();
}
if (command === "modifyAvatar") {
data.avatarDialog.visible = true;
data.avatarDialog.form.file = addingPrefixToFile([
{ FILEPATH: userStore.getUserInfo.userPhoto },
]);
}
};
const fnGetUserInfo = async () => {
const resData = await getUserInfo({
USER_ID: userStore.getUserInfo.USER_ID,
});
data.userDialog.visible = true;
data.userDialog.form = resData.pd;
};
const switchMenu = (model) => {
menuStore.setModel(model);
};
const fnGetInfo = async () => {
const resData = await getInfo();
try {
await checkImgExists(FILE_URL + resData.userPhoto);
data.avatar = FILE_URL + resData.userPhoto;
} catch {
data.avatar = new URL(
"../../../assets/images/public/tx.png",
import.meta.url
).href;
}
userStore.setUserInfo({
...userStore.getUserInfo,
...resData,
});
miscellaneousStore.setOnlineAddress(resData.onlineAdress);
webSocket = new WebSocket(encodeURI("ws://" + resData.onlineAdress));
webSocket.onopen = () => {
webSocket.send("[join]" + resData.USERNAME);
};
webSocket.onmessage = (message) => {
const messageMsg = JSON.parse(message.data);
if (messageMsg.type === "goOut") {
fnSignOut();
} else if (messageMsg.type === "thegoout") {
fnSignOut();
}
};
};
fnGetInfo();
const fnSignOut = async () => {
await logout();
webSocket && webSocket.close();
userStore.$reset();
await router.replace("/login");
};
onBeforeUnmount(() => {
webSocket && webSocket.close();
webSocket = null;
});
</script>
<style lang="scss" scoped>
.header {
background-image: url("/src/assets/images/public/headerbg.png");
background-repeat: no-repeat;
background-size: 660px 70px;
background-color: #030f2f;
border-bottom: 1px solid #1f3869;
position: relative;
.left {
display: flex;
align-items: center;
position: relative;
z-index: 99;
width: max-content;
.logo {
width: 500px;
height: var(--el-header-height);
line-height: var(--el-header-height);
padding-left: 20px;
color: #fff;
font-size: 20px;
font-weight: 700;
font-family: cursive;
}
.menu {
ul {
display: flex;
li {
width: 120px;
height: 40px;
line-height: 40px;
text-align: center;
cursor: pointer;
background-color: #051748;
clip-path: polygon(0% 0%, 80% 0%, 100% 100%, 20% 100%);
position: relative;
box-shadow: rgba(11, 62, 110, 0.7) 0 0 13px inset;
color: #fff;
&.active {
background-image: url("/src/assets/images/public/tguang.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}
@for $i from 1 through 4 {
.horn#{$i} {
width: 8px;
height: 8px;
background-image: url("/src/assets/images/public/menu_horn#{$i}.png");
background-repeat: no-repeat;
background-size: 100% 100%;
position: absolute;
z-index: 2;
@if $i == 1 {
top: 0;
left: 2px;
}
@if $i == 2 {
top: 0;
right: 21px;
}
@if $i == 3 {
bottom: 0;
left: 21px;
}
@if $i == 4 {
bottom: 0;
right: 2px;
}
}
}
.title {
padding-left: 3px;
}
}
}
}
}
.user {
width: 500px;
height: 70px;
background-image: url("/src/assets/images/public/userbg.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: flex-end;
align-items: center;
position: absolute;
right: 0;
top: 0;
border-bottom: 1px solid #1f3869;
.el-dropdown {
margin-right: 55px;
cursor: pointer;
position: absolute;
z-index: 999;
}
.user_info {
display: flex;
align-items: center;
span:nth-child(2) {
color: #ece8e8;
margin-left: 10px;
margin-right: 10px;
}
}
}
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<el-container>
<el-header>
<layout-header />
</el-header>
<el-container>
<el-aside>
<el-scrollbar style="height: var(--el-aside-scrollbar-height)">
<el-menu
router
unique-opened
:default-active="$route.meta.activeMenu || $route.path"
>
<layout-menu :menus="routes" />
</el-menu>
</el-scrollbar>
</el-aside>
<el-main>
<layout-breadcrumb v-if="route.meta.isBreadcrumb !== false" />
<el-scrollbar style="height: var(--el-main-scrollbar-height)">
<router-view v-slot="{ Component }">
<transition name="view" mode="out-in">
<div :key="$route.path">
<component :is="Component"></component>
</div>
</transition>
</router-view>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
</template>
<script setup>
import LayoutBreadcrumb from "@/components/layout/breadcrumb/index";
import LayoutHeader from "@/components/layout/header/index";
import LayoutMenu from "@/components/layout/menu/index";
import { computed } from "vue";
import { useMenuStore } from "@/pinia/menu";
import { useRoute } from "vue-router";
defineOptions({
name: "layout",
});
const menuStore = useMenuStore();
const route = useRoute();
const routes = computed(() => menuStore.getMenus);
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,70 @@
<template>
<template v-for="menu in props.menus" :key="menu.path">
<!-- 没有二级导航-->
<el-menu-item
v-if="fnIsShowMenuItem(menu)"
:index="menu.path"
:route="{ path: menu.path }"
>
<component
:is="'icon-application-menu'"
theme="filled"
fill="#a5b2c2"
size="18"
:strokeWidth="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-application-menu'"
theme="filled"
fill="#a5b2c2"
size="18"
:strokeWidth="3"
style="margin-right: 15px"
/>
<span>{{ menu.meta?.title }}</span>
</template>
<!-- 递归调用当前组件生成导航-->
<layout-menu :menus="menu.children" />
</el-sub-menu>
</template>
</template>
<script setup>
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;
}
};
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,70 @@
<template>
<el-tree-select
ref="treeSelectRef"
v-model="modelValue"
:data="data"
:props="{
value: 'id',
children: 'nodes',
label: 'name',
}"
node-key="id"
:render-after-expand="false"
accordion
check-strictly
:clearable="true"
:disabled="disabled"
/>
</template>
<script setup>
import {
layoutFnGetTrainingIndustryType,
layoutFnGetTrainingPlateType,
layoutFnGetTrainingPostType,
} from "@/assets/js/data_dictionary";
import { ref } from "vue";
import { useVModel } from "@vueuse/core";
defineOptions({
name: "LayoutLearningTrainType",
});
const props = defineProps({
modelValue: {
type: String,
required: true,
default: "",
},
type: {
type: String,
required: true,
default: "",
validator: (value) => {
if (["industry", "post", "plate"].includes(value)) {
return true;
} else {
throw new Error("type必须是industry、post、plate之一");
}
},
},
disabled: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emits);
const treeSelectRef = ref(null);
const getCurrentNode = () => {
return treeSelectRef.value.getCurrentNode();
};
defineExpose({
getCurrentNode,
});
let data = [];
if (props.type === "industry") data = await layoutFnGetTrainingIndustryType();
if (props.type === "post") data = await layoutFnGetTrainingPostType();
if (props.type === "plate") data = await layoutFnGetTrainingPlateType();
</script>
<style scoped></style>

View File

@ -0,0 +1,55 @@
<template>
<el-tree-select
ref="treeSelectRef"
v-model="modelValue"
:data="data"
node-key="id"
:props="{
children: 'children',
label: 'NAME',
}"
:render-after-expand="false"
accordion
check-strictly
:clearable="true"
/>
</template>
<script setup>
import { layoutFnGetRegulatoryType } from "@/assets/js/data_dictionary";
import { ref, watchEffect } from "vue";
import { useVModel } from "@vueuse/core";
defineOptions({
name: "LayoutRegulatoryType",
});
const props = defineProps({
modelValue: {
type: String,
required: true,
default: "",
},
territory: {
type: Array,
required: true,
default: () => [],
},
});
const emits = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emits);
const treeSelectRef = ref(null);
const data = ref([]);
const fnGetData = async () => {
const { value } = await layoutFnGetRegulatoryType({
PROVINCE: props.territory[0],
CITY: props.territory[1],
COUNTY: props.territory[2],
});
data.value = value;
};
watchEffect(() => {
if (props.territory.length > 0) fnGetData();
});
</script>
<style scoped></style>

View File

@ -0,0 +1,152 @@
<template>
<el-table
ref="tableRef"
size="small"
:data="props.data"
:border="border"
:stripe="stripe"
:height="height"
:max-height="maxHeight"
:highlight-current-row="highlightCurrentRow"
:row-key="rowKey"
:row-class-name="rowClassName"
:show-header="showHeader"
:show-summary="showSummary"
:summary-method="summaryMethod"
@row-click="rowClick"
@row-dblclick="rowDblclick"
>
<slot></slot>
</el-table>
<div class="table_footer">
<div>
<slot name="button"></slot>
</div>
<el-pagination
v-if="props.showPagination"
small
:current-page="props.pagination.currentPage || 1"
:page-size="props.pagination.pageSize || 10"
layout="total, sizes, prev, pager, next, jumper"
:total="props.pagination.total || 0"
@update:current-page="handleCurrentChange"
@update:page-size="handleSizeChange"
/>
</div>
</template>
<script setup>
import { ref } from "vue";
defineOptions({
name: "LayoutTable",
});
const props = defineProps({
data: {
type: Array,
default: () => [],
},
pagination: {
type: Object,
default: () => ({
currentPage: 1,
pageSize: 10,
total: 0,
}),
},
showPagination: {
type: Boolean,
default: true,
},
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,
},
rowKey: {
type: [String, Function],
},
maxHeight: {
type: [String, Number],
},
height: {
type: [String, Number],
},
rowClassName: {
type: Function,
},
summaryMethod: {
type: Function,
},
});
const emits = defineEmits([
"update:pagination",
"get-data",
"row-click",
"row-dblclick",
]);
const tableRef = ref(null);
const handleCurrentChange = (val) => {
emits("update:pagination", {
currentPage: val,
pageSize: props.pagination.pageSize,
total: props.pagination.total,
});
emits("get-data");
};
const handleSizeChange = (val) => {
emits("update:pagination", {
currentPage: 1,
pageSize: val,
total: props.pagination.total,
});
emits("get-data");
};
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 = (id, selected) => {
for (let i = 0; i < props.data.length; i++) {
if (props.data[i][props.rowKey] === id) {
tableRef.value.toggleRowSelection(props.data[i], selected);
}
}
};
defineExpose({
getSelectionRows,
clearSelection,
toggleRowSelection,
});
</script>
<style lang="scss" scoped>
.table_footer {
margin-top: 20px;
display: flex;
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<layout-cascader
id="e725d2a91b8248f4b8f49889038df7de"
v-model="modelValue"
ref="cascaderRef"
:check-strictly="checkStrictly"
:level="level"
value="BIANMA"
/>
</template>
<script setup>
import { useVModel } from "@vueuse/core";
import { ref } from "vue";
import LayoutCascader from "@/components/cascader/index.vue";
defineOptions({
name: "LayoutTerritory",
});
const props = defineProps({
modelValue: {
type: Array,
default: () => [],
required: true,
},
level: {
type: [Number, String],
default: 3,
},
checkStrictly: {
type: Boolean,
default: true,
},
});
const emits = defineEmits(["update:modelValue"]);
const modelValue = useVModel(props, "modelValue", emits);
const cascaderRef = ref(null);
const getCheckedNodes = () => {
return cascaderRef.value.getCheckedNodes();
};
defineExpose({
getCheckedNodes,
});
</script>
<style scoped></style>

View File

@ -0,0 +1,170 @@
<template>
<el-upload
style="width: 100%"
ref="uploadRef"
: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 }"
>
<icon-plus
v-if="listType === 'picture-card'"
theme="filled"
size="32"
fill="#fff"
:strokeWidth="3"
/>
<el-button v-else type="primary">点击选择文件上传</el-button>
<template #tip>
<div class="ml 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 } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { setDeleteImg } from "@/request/api";
defineOptions({
name: "LayoutUpload",
});
const props = defineProps({
fileList: {
type: Array,
default: () => [],
required: true,
},
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 emits = defineEmits(["update:file-list", "preview"]);
const uploadRef = ref(null);
const onExceed = () => {
ElMessage.warning(`最多上传${props.limit}个文件`);
};
const beforeRemove = async (uploadFile) => {
if (props.deleteToServer && uploadFile.IMGFILES_ID) {
await ElMessageBox.confirm("确定要删除吗?", {
type: "warning",
});
await setDeleteImg({
IMGFILES_ID: uploadFile.IMGFILES_ID,
});
}
};
const onRemove = (uploadFile, uploadFiles) => {
emits("update:file-list", 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);
}
};
}
if (size) {
if (uploadFile.size > size) {
ElMessage.warning(`文件大小不能超过${props.size}M`);
uploadRef.value.handleRemove(uploadFile);
}
}
if (accept) {
if (accept.includes(suffix)) {
emits("update:file-list", uploadFiles);
} else {
ElMessage.warning(`只能上传${props.accept}格式的文件`);
uploadRef.value.handleRemove(uploadFile);
}
} else {
emits("update:file-list", 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>

View File

@ -0,0 +1,212 @@
<template>
<div class="mi-captcha">
<div class="mi-captcha-content">
<!-- 没有进行验证-->
<div
class="mi-captcha-radar"
v-if="!verificationPass"
@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
class="mi-captcha-radar mi-captcha-radar-pass"
v-if="verificationPass"
>
<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: "LayoutVerification",
});
defineProps({
verificationPass: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(["update:verificationPass"]);
const verificationShow = ref(false);
const verificationClose = () => {
verificationShow.value = false;
};
const verificationSuccess = () => {
emits("update:verificationPass", 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, #2e63d8 0%, #21438e 74%);
border: 1px solid #2056cf;
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>

41
src/main.js Normal file
View File

@ -0,0 +1,41 @@
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 "element-plus/es/components/notification/style/css";
import { install } from "@icon-park/vue-next/es/all";
import LayoutTable from "@/components/table/index.vue";
import LayoutCard from "@/components/card/index.vue";
import ElDialog from "element-plus/es/components/dialog/index";
ElDialog.props.closeOnClickModal.default = false;
ElDialog.props.closeOnPressEscape.default = false;
const app = createApp(App);
app.component("layout-table", LayoutTable);
app.component("layout-card", LayoutCard);
install(app, "icon");
app
.use(pinia)
.use(router)
.use(VueViewer, {
defaultOptions: {
zIndex: 9999,
},
})
.use(print)
.use(button)
.mount("#app");

7
src/pinia/index.js Normal file
View File

@ -0,0 +1,7 @@
import { createPinia } from "pinia";
import piniaPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPersistedstate);
export default pinia;

30
src/pinia/menu.js Normal file
View File

@ -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,
},
});

View File

@ -0,0 +1,18 @@
import { defineStore } from "pinia";
export const useMiscellaneousStore = defineStore("miscellaneousStore", {
state: () => ({
onlineAddress: false,
}),
getters: {
getOnlineAddress: (state) => state.onlineAddress,
},
actions: {
setOnlineAddress(onlineAddress) {
this.onlineAddress = onlineAddress;
},
},
persist: {
storage: window.sessionStorage,
},
});

18
src/pinia/router.js Normal file
View File

@ -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,
},
});

18
src/pinia/user.js Normal file
View File

@ -0,0 +1,18 @@
import { defineStore } from "pinia";
export const useUserStore = defineStore("userStore", {
state: () => ({
userInfo: {},
}),
getters: {
getUserInfo: (state) => state.userInfo,
},
actions: {
setUserInfo(userInfo) {
this.userInfo = userInfo;
},
},
persist: {
storage: window.sessionStorage,
},
});

17
src/request/api.js Normal file
View File

@ -0,0 +1,17 @@
import { post, upload } from "./axios";
export const Login = (params) => post("/admin/check", params); // 登录
export const logout = (params) => post("/main/logout", params); // 退出登录
export const getAsyncRouter = (params) => post("/main/index", params); // 获取动态路由
export const getUserInfo = (params) => post("/user/goEditMyInfo", params); // 获取用户信息
export const setUserInfo = (params) => post("/user/editUserOwn", params); // 修改用户信息
export const getVerifyDuplicateEmail = (params) =>
post("/user/hasEmail", params); // 验证邮箱重复
export const getVerifyDeduplicationUser = (params) =>
post("/user/hasUser", params); // 用户名去重
export const setAvatar = (params) => upload("/photo/saveNew", params); // 修改头像
export const getInfo = (params) =>
post("/head/getInfo", { loading: false, ...params }); // 获取用户信息
export const setUploadImg = (params) => upload("/imgfiles/add", params); // 上传附件
export const setDeleteImg = (params) => post("/imgfiles/delete", params); // 删除附件
export const getViewImg = (params) => post("/imgfiles/listImgs", params); // 查看图片

153
src/request/axios.js Normal file
View File

@ -0,0 +1,153 @@
import axios from "axios";
import { ElLoading, ElMessage } from "element-plus";
import router from "../router";
import QS from "qs";
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.timeout = 1000 * 60 * 10;
// axios.defaults.withCredentials = true;
axios.interceptors.request.use(
(config) => {
if (config.method === "get" || config.method === "GET") {
if (QS.parse(config.params)?.loading !== "false") startLoading();
}
if (config.method === "post" || config.method === "POST") {
if (QS.parse(config.data)?.loading !== "false") startLoading();
}
return config;
},
(error) => Promise.reject(error)
);
axios.interceptors.response.use(
(config) => {
if (config.config.method === "get" || config.config.method === "GET") {
if (QS.parse(config.config.params)?.loading !== "false") endLoading();
}
if (config.config.method === "post" || config.config.method === "POST") {
if (QS.parse(config.config.data)?.loading !== "false") endLoading();
}
return config;
},
(error) => {
console.log(error);
if (error && error.response) {
switch (error.response.status) {
case 0:
case 302:
endLoading();
ElMessage.error("登录失效,请重新登陆");
router.push("/login").then();
break;
default:
error.message = `连接错误${error.response.status}`;
import.meta.env.DEV &&
ElMessage.error(`连接错误${error.response.status}`);
}
} else {
error.message = "连接到服务器失败";
endLoading();
ElMessage.error("登录失效,请重新登陆");
router.push("/login").then();
}
return Promise.reject(error.message);
}
);
export function post(url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, QS.stringify(params), {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
.then((res) => {
if (res.data.result === "success") {
resolve(res.data);
} else {
ElMessage.error(
res.data.msg ||
res.data.resMsg ||
res.data.resultStr ||
"系统开小差了"
);
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}
export function get(url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
params: QS.stringify(params),
})
.then((res) => {
if (res.data.result === "success") {
resolve(res.data);
} else {
ElMessage.error(
res.data.msg ||
res.data.resMsg ||
res.data.resultStr ||
"系统开小差了"
);
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 ||
res.data.resMsg ||
res.data.resultStr ||
"系统开小差了"
);
reject(res.data);
}
})
.catch((err) => {
reject(err);
});
});
}

View File

@ -0,0 +1,26 @@
import { post } from "@/request/axios.js";
// 获取数据字典
export const getLevels = (params) =>
post("/dictionaries/getLevels", {
loading: false,
...params,
});
// 获取数据字典包括子级数量
export const getLevelsAndChildrenNumber = (params) =>
post("/dictionaries/getLevelsAndSCount", {
loading: false,
...params,
});
// 监管类型
export const getRegulatoryType = (params) =>
post("/corptype/corptypelist", {
loading: false,
...params,
});
// 获取在线学习培训类型
export const getLearningTrainType = (params) =>
post("/dictionaries/listDictToParId", {
loading: false,
...params,
});

View File

@ -0,0 +1,14 @@
import { post, upload } from "@/request/axios.js";
export const getEnterpriseInfo = (params) => post("/corpinfo/goEdit", params); // 获取企业信息
export const setEnterpriseInfo = (params) => upload("/corpinfo/edit", params); // 修改企业信息
export const getIndustryQualificationsList = (params) =>
post("/qualifications/list", params); // 行业资质列表
export const setIndustryQualificationsDelete = (params) =>
post("/qualifications/delete", params); // 行业资质删除
export const getIndustryQualificationsView = (params) =>
post("/qualifications/goEdit", params); // 行业资质查看
export const setIndustryQualificationsAdd = (params) =>
post("/qualifications/add", params); // 行业资质添加
export const setIndustryQualificationsEdit = (params) =>
post("/qualifications/edit", params); // 行业资质修改

51
src/router/index.js Normal file
View File

@ -0,0 +1,51 @@
import { createRouter, createWebHashHistory } from "vue-router";
import layout from "../components/layout/index.vue";
// import children from "../components/children/index.vue";
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,
},
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;

565
src/views/404.vue Normal file
View File

@ -0,0 +1,565 @@
<template>
<div class="containerer">
<div class="container container-star">
<div class="star-1" v-for="item in 30" :key="item"></div>
<div class="star-2" v-for="item in 30" :key="item"></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>

View File

@ -0,0 +1,107 @@
<template>
<layout-card>
<el-form
ref="formRef"
:rules="data.rules"
:model="data.form"
label-width="110px"
>
<el-form-item label="证书名称" prop="NAME">
<el-input v-model="data.form.NAME" />
</el-form-item>
<el-form-item label="证书有效期" prop="VALIDITYTIME">
<el-date-picker
v-model="data.form.VALIDITYTIME"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="证书编号" prop="NUMBER">
<el-input v-model="data.form.NUMBER" />
</el-form-item>
<el-form-item label="备注" prop="DESCR">
<el-input autosize v-model="data.form.DESCR" type="textarea" />
</el-form-item>
<el-form-item label="证书图片" prop="file">
<layout-upload
v-model:file-list="data.form.file"
accept=".jpg,.jpeg,.png"
list-type="picture-card"
delete-to-server
/>
</el-form-item>
</el-form>
<div class="tc mt">
<el-button type="primary" @click="fnSubmit"></el-button>
</div>
</layout-card>
</template>
<script setup>
import LayoutCard from "@/components/card/index.vue";
import LayoutUpload from "@/components/upload/index.vue";
import { reactive, ref } from "vue";
import {
getIndustryQualificationsView,
setIndustryQualificationsAdd,
setIndustryQualificationsEdit,
} from "@/request/prevention/enterprise_management.js";
import { debounce } from "throttle-debounce";
import { ElMessage } from "element-plus";
import { setUploadImg } from "@/request/api.js";
import { useRouter, useRoute } from "vue-router";
import { addingPrefixToFile } from "@/assets/js/utils.js";
import useFormValidate from "@/assets/js/useFormValidate.js";
const router = useRouter();
const route = useRoute();
const { QUALIFICATIONS_ID } = route.query;
const formRef = ref(null);
const data = reactive({
form: {
NAME: "",
VALIDITYTIME: "",
NUMBER: "",
DESCR: "",
file: [],
verifyFileUrl: "12",
},
rules: {
NAME: [{ required: true, message: "名称不能为空", trigger: "blur" }],
VALIDITYTIME: [
{ required: true, message: "证书有效期不能为空", trigger: "blur" },
],
NUMBER: [{ required: true, message: "证书编号不能为空", trigger: "blur" }],
file: [{ required: true, message: "证书图片不能为空", trigger: "blur" }],
},
});
const fnGetData = async () => {
if (!QUALIFICATIONS_ID) return;
const resData = await getIndustryQualificationsView({ QUALIFICATIONS_ID });
data.form = resData.pd;
data.form.file = addingPrefixToFile(resData.hImgs);
};
fnGetData();
const fnSubmit = debounce(
1000,
async () => {
await useFormValidate(formRef);
const resData = !QUALIFICATIONS_ID
? await setIndustryQualificationsAdd({ ...data.form })
: await setIndustryQualificationsEdit({ ...data.form });
const formData = new FormData();
for (let i = 0; i < data.form.file.length; i++) {
if (data.form.file[i].raw)
formData.append("FFILE", data.form.file[i].raw);
}
formData.append("FOREIGN_KEY", resData.pd.QUALIFICATIONS_ID);
formData.append("TYPE", 6);
await setUploadImg(formData);
ElMessage.success("保存成功");
router.back();
},
{ atBegin: true }
);
</script>
<style scoped></style>

View File

@ -0,0 +1,170 @@
<template>
<div>
<el-card>
<el-form
:model="searchForm"
label-width="130px"
@submit.prevent="fnResetPaginationTransfer"
>
<el-row>
<el-col :span="6">
<el-form-item label="证书名称/证书编号" prop="KEYWORDS">
<el-input
v-model="searchForm.KEYWORDS"
placeholder="请输入关键字"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="证书有效期" prop="dates">
<el-date-picker
v-model="searchForm.dates"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</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-button native-type="reset" @click="fnResetPagination">
重置
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<layout-card>
<layout-table
:data="list"
v-model:pagination="pagination"
@get-data="fnGetDataTransfer"
>
<el-table-column label="序号" width="70">
<template v-slot="{ $index }">
{{ serialNumber(pagination, $index) }}
</template>
</el-table-column>
<el-table-column label="证书名称" prop="NAME" />
<el-table-column label="证书有效期" prop="VALIDITYTIME" />
<el-table-column label="证书编号" prop="NUMBER" />
<el-table-column label="照片">
<template v-slot="{ row }">
<el-tooltip placement="top">
<template #content>
<template v-if="row.imgs.length > 0">
<img
v-for="item in row.imgs"
:key="item.IMGFILES_ID"
:src="FILE_URL + item.FILEPATH"
width="100"
height="100"
alt=""
/>
</template>
<span v-else></span>
</template>
<el-tag>预览</el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template v-slot="{ row }">
<el-button
type="primary"
text
link
@click="
router.push({
path: '/enterprise_management/information/industry_qualifications/view',
query: { QUALIFICATIONS_ID: row.QUALIFICATIONS_ID },
})
"
>
查看
</el-button>
<el-button
type="primary"
text
link
@click="
router.push({
path: '/enterprise_management/information/industry_qualifications/update',
query: { QUALIFICATIONS_ID: row.QUALIFICATIONS_ID },
})
"
>
修改
</el-button>
<el-button
type="primary"
text
link
@click="fnDelete(row.QUALIFICATIONS_ID)"
>
删除
</el-button>
</template>
</el-table-column>
<template #button>
<el-button
type="primary"
@click="
router.push({
path: '/enterprise_management/information/industry_qualifications/add',
})
"
>
新增
</el-button>
</template>
</layout-table>
</layout-card>
</div>
</template>
<script setup>
import { serialNumber } from "@/assets/js/utils.js";
import { debounce } from "throttle-debounce";
import { ElMessage, ElMessageBox } from "element-plus";
import {
getIndustryQualificationsList,
setIndustryQualificationsDelete,
} from "@/request/prevention/enterprise_management.js";
import { useRouter } from "vue-router";
import useListData from "@/assets/js/useListData.js";
const FILE_URL = import.meta.env.VITE_FILE_URL;
const router = useRouter();
const listData = useListData(getIndustryQualificationsList);
const { list, pagination, searchForm, fnGetData, fnResetPagination } = listData;
const fnGetDataTransfer = async () => {
await fnGetData({
STARTTIME: searchForm.value.dates?.[0],
ENDTIME: searchForm.value.dates?.[1],
});
};
const fnResetPaginationTransfer = async () => {
await fnResetPagination({
STARTTIME: searchForm.value.dates?.[0],
ENDTIME: searchForm.value.dates?.[1],
});
};
const fnDelete = debounce(
1000,
async (QUALIFICATIONS_ID) => {
await ElMessageBox.confirm("确定要删除吗?", { type: "warning" });
await setIndustryQualificationsDelete({ QUALIFICATIONS_ID });
ElMessage.success("删除成功");
await fnResetPaginationTransfer();
},
{ atBegin: true }
);
</script>
<style scoped></style>

View File

@ -0,0 +1,51 @@
<template>
<layout-card>
<el-descriptions :column="2" border>
<el-descriptions-item label="证书名称">
{{ data.info.NAME }}
</el-descriptions-item>
<el-descriptions-item label="证书有效期">
{{ data.info.VALIDITYTIME }}
</el-descriptions-item>
<el-descriptions-item label="证书编号">
{{ data.info.NUMBER }}
</el-descriptions-item>
<el-descriptions-item label="备注">
{{ data.info.DESCR }}
</el-descriptions-item>
<el-descriptions-item label="证书图片">
<img
v-viewer
v-for="item in data.info.file"
:key="item.FILEPATH"
:src="item.url"
width="100"
height="100"
class="ml"
/>
</el-descriptions-item>
</el-descriptions>
</layout-card>
</template>
<script setup>
import LayoutCard from "@/components/card/index.vue";
import { getIndustryQualificationsView } from "@/request/prevention/enterprise_management.js";
import { reactive } from "vue";
import { useRoute } from "vue-router";
import { addingPrefixToFile } from "@/assets/js/utils.js";
const route = useRoute();
const { QUALIFICATIONS_ID } = route.query;
const data = reactive({
info: {},
});
const fnGetData = async () => {
const resData = await getIndustryQualificationsView({ QUALIFICATIONS_ID });
data.info = resData.pd;
data.info.file = addingPrefixToFile(resData.hImgs);
};
fnGetData();
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,721 @@
<template>
<div>
<layout-card>
<el-form
ref="formRef"
:model="data.form"
:rules="data.rules"
label-width="210px"
>
<el-row>
<el-col :span="24">
<el-divider content-position="left">基本信息</el-divider>
</el-col>
<el-col :span="12">
<el-form-item label="企业名称" prop="CORP_NAME">
<el-input v-model="data.form.CORP_NAME" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮政编码">
<el-input
v-model="data.form.POSTAL_CODE"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="统一社会信用代码" prop="CODE">
<el-input v-model="data.form.CODE" placeholder="请输入内容" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="市行业监管部门" prop="INDUSTRY_DEPARTMENTName">
<el-input
disabled
v-model="data.form.INDUSTRY_DEPARTMENTName"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="属地" prop="POSSESSION">
<layout-territory
ref="territoryCascaderRef"
v-model="data.form.POSSESSION"
:level="4"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="监管类型" prop="REGULARTYPE">
<layout-regulatory-type
v-model="data.form.REGULARTYPE"
:territory="data.form.POSSESSION"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属行业" prop="INDUSTRYALL">
<layout-industry
ref="industryCascaderRef"
v-model="data.form.INDUSTRYALL"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="经济类型" prop="ECO_TYPEALL">
<layout-economic-type
ref="economicTypeCascaderRef"
v-model="data.form.ECO_TYPEALL"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企事业单位经营地址" prop="ADDRESS_BUSINESS">
<el-input
v-model="data.form.ADDRESS_BUSINESS"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业状态" prop="CORP_STATE">
<el-select v-model="data.form.CORP_STATE">
<el-option
v-for="item in enterpriseStatus"
:key="item.DICTIONARIES_ID"
:label="item.NAME"
:value="item.DICTIONARIES_ID"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="经度/纬度" prop="LONGITUDE">
<div style="flex: 1; display: flex">
<el-input
:model-value="
(data.form.LONGITUDE ?? '') +
'-' +
(data.form.LATITUDE ?? '')
"
disabled
style="flex: 1"
/>
<el-button
type="primary"
class="ml-10"
@click="fnMapDialogVisible"
>
地图定位
</el-button>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主要负责人" prop="CONTACTS">
<el-input v-model="data.form.CONTACTS" placeholder="请输入内容" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主要负责人电话" prop="CONTACTS_PHONE">
<el-input
v-model="data.form.CONTACTS_PHONE"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="法定代表人">
<el-input v-model="data.form.LR_NAME" placeholder="请输入内容" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="法人手机号" prop="LR_MOBILE">
<el-input v-model="data.form.LR_PHONE" placeholder="请输入内容" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="占地面积(㎡)">
<el-input
v-model="data.form.AREA_COVERED"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职工人数(人)">
<el-input
v-model="data.form.EMPLOYEES"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成立时间">
<el-date-picker
v-model="data.form.CREATE_DATE"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
type="date"
placeholder="请选择"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="注册资金(万元)">
<el-input
v-model="data.form.REGCAPITAL"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="资产总额(万元)">
<el-input
v-model="data.form.TOTALASSETS"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="隶属关系">
<el-select v-model="data.form.SUBORDINATION">
<el-option
v-for="item in subordination"
:key="item.DICTIONARIES_ID"
:label="item.NAME"
:value="item.DICTIONARIES_ID"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规模" prop="SCALE">
<el-select v-model="data.form.SCALE" clearable>
<el-option
v-for="item in enterpriseScale"
:key="item.DICTIONARIES_ID"
:label="item.NAME"
:value="item.DICTIONARIES_ID"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否规模以上" prop="SCALE_TYPE">
<el-radio-group v-model="data.form.SCALE_TYPE">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训行业类型" prop="TRAINTYPE">
<layout-learning-train-type
v-model="data.form.TRAINTYPE"
type="industry"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="企业可新建用户数量" prop="USERS_NUM">
<el-input
v-model="data.form.USERS_NUM"
disabled
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="四色图类型">
<el-radio v-model="data.form.FOURTYPE" :label="1">
平面四色图
</el-radio>
<el-radio
v-model="data.form.FOURTYPE"
:label="2"
@change="fnProhibitSelect"
>
3D图
</el-radio>
</el-form-item>
</el-col>
<el-col :span="24" v-if="data.form.FOURTYPE === 1">
<el-form-item label="四色图" prop="four_images">
<layout-upload
v-model:file-list="data.form.four_images"
accept=".jpg,.jpeg,.png"
list-type="picture-card"
ratio="1480*640"
delete-to-server
>
<template #tip> 四色图请上传1480*640分辨率的图片 </template>
</layout-upload>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="营业执照" prop="bus_images">
<layout-upload
v-model:file-list="data.form.bus_images"
accept=".jpg,.jpeg,.png"
list-type="picture-card"
:limit="99"
delete-to-server
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-divider content-position="left">安全负责人信息</el-divider>
</el-col>
<el-col :span="12">
<el-form-item label="姓名" prop="SAFETY_NAME">
<el-input
v-model="data.form.SAFETY_NAME"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职务" prop="SAFETY_POST">
<el-input
v-model="data.form.SAFETY_POST"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单位电话" prop="SAFETY_NUMBER">
<el-input
v-model="data.form.SAFETY_NUMBER"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手机号码" prop="SAFETY_PHONE">
<el-input
v-model="data.form.SAFETY_PHONE"
placeholder="请输入内容"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-divider content-position="left">企业相关属性</el-divider>
</el-col>
<el-col :span="12">
<el-form-item label="有无职业卫生信息" prop="WHETHER_HYGIENE">
<el-radio-group v-model="data.form.WHETHER_HYGIENE">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="有无重大危险源" prop="WHETHER_HAZARDS">
<el-radio-group v-model="data.form.WHETHER_HAZARDS">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="是否有稀缺大型应急物资或设施"
prop="WHETHER_SCARCE"
>
<el-radio-group v-model="data.form.WHETHER_SCARCE">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否涉及危化品" prop="WHETHER_CHEMICALS">
<el-radio-group v-model="data.form.WHETHER_CHEMICALS">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="有无特种设备" prop="WHETHER_SPECIALEQUIPMENT">
<el-radio-group v-model="data.form.WHETHER_SPECIALEQUIPMENT">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="有无特种作业人员" prop="WHETHER_SPECIALPEOPLE">
<el-radio-group v-model="data.form.WHETHER_SPECIALPEOPLE">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否涉及煤气" prop="WHETHER_COALGAS">
<el-radio-group v-model="data.form.WHETHER_COALGAS">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否属于消防重点单位" prop="WHETHER_FIRE">
<el-radio-group v-model="data.form.WHETHER_FIRE">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否在受限空间作业" prop="WHETHER_CONFINED">
<el-radio-group v-model="data.form.WHETHER_CONFINED">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否存在涉爆粉尘作业" prop="WHETHER_POWDER">
<el-radio-group v-model="data.form.WHETHER_POWDER">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否涉及防雷防静电" prop="WHETHER_LIGHTNING">
<el-radio-group v-model="data.form.WHETHER_LIGHTNING">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否持有放射源" prop="WHETHER_ACTINOGEN">
<el-radio-group v-model="data.form.WHETHER_ACTINOGEN">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否涉及液氨制冷" prop="WHETHER_LIQUIDAMMONIA">
<el-radio-group v-model="data.form.WHETHER_LIQUIDAMMONIA">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否涉及危化品管道" prop="WHETHER_PIPELINE">
<el-radio-group v-model="data.form.WHETHER_PIPELINE">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="tc mt-10">
<el-button @click="back"> </el-button>
<el-button type="primary" @click="fnSubmit"> </el-button>
</div>
</layout-card>
<el-dialog title="请选择位置" v-model="data.mapDialog.visible">
<el-form label-position="right" label-width="50px">
<el-row>
<el-col :span="12">
<el-form-item label="坐标">
<el-input disabled v-model="data.mapDialog.lng" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label-width="10px">
<el-input disabled v-model="data.mapDialog.lat" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div
v-loading="data.mapDialog.loading"
element-loading-text="地图正在加载中..."
element-loading-background="rgba(0, 0, 0, 0.5)"
>
<div style="width: 100%; height: 500px" id="map_container" />
</div>
<template #footer>
<el-button @click="fnMapDialogVisible"></el-button>
<el-button type="primary" @click="fnMapDialogConfirm"></el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { nextTick, onBeforeUnmount, reactive, ref } from "vue";
import LayoutTerritory from "@/components/territory/index.vue";
import LayoutRegulatoryType from "@/components/regulatory_type/index.vue";
import LayoutIndustry from "@/components/industry/index.vue";
import LayoutEconomicType from "@/components/economic_type/index.vue";
import LayoutLearningTrainType from "@/components/learning_train_type/index.vue";
import LayoutUpload from "@/components/upload/index.vue";
import {
layoutFnGetEnterpriseScale,
layoutFnGetEnterpriseStatus,
layoutFnGetSubordination,
} from "@/assets/js/data_dictionary.js";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/assets/js/useFormValidate.js";
import {
getEnterpriseInfo,
setEnterpriseInfo,
} from "@/request/prevention/enterprise_management.js";
import { addingPrefixToFile } from "@/assets/js/utils.js";
let mapInstance;
const router = useRouter();
const formRef = ref(null);
const territoryCascaderRef = ref(null);
const industryCascaderRef = ref(null);
const economicTypeCascaderRef = ref(null);
const data = reactive({
form: {
CORP_NAME: "",
POSTAL_CODE: "",
CODE: "",
INDUSTRY_DEPARTMENTName: "",
INDUSTRY_DEPARTMENT: "",
POSSESSION: [],
REGULARTYPE: "",
INDUSTRYALL: [],
ECO_TYPEALL: [],
ADDRESS_BUSINESS: "",
CORP_STATE: "",
LONGITUDE: "",
LATITUDE: "",
CONTACTS: "",
CONTACTS_PHONE: "",
LR_NAME: "",
LR_MOBILE: "",
AREA_COVERED: "",
EMPLOYEES: "",
CREATE_DATE: "",
REGCAPITAL: "",
TOTALASSETS: "",
SUBORDINATION: "",
SCALE: "",
SCALE_TYPE: "",
TRAINTYPE: [],
USERS_NUM: "",
FOURTYPE: "",
four_images: [],
bus_images: [],
SAFETY_NAME: "",
SAFETY_POST: "",
SAFETY_NUMBER: "",
SAFETY_PHONE: "",
WHETHER_HYGIENE: "",
WHETHER_HAZARDS: "",
WHETHER_SCARCE: "",
WHETHER_CHEMICALS: "",
WHETHER_SPECIALEQUIPMENT: "",
WHETHER_SPECIALPEOPLE: "",
WHETHER_COALGAS: "",
WHETHER_FIRE: "",
WHETHER_CONFINED: "",
WHETHER_POWDER: "",
WHETHER_LIGHTNING: "",
WHETHER_ACTINOGEN: "",
WHETHER_LIQUIDAMMONIA: "",
WHETHER_PIPELINE: "",
},
rules: {
CORP_NAME: [
{ required: true, message: "企业名称不能为空", trigger: "blur" },
],
CODE: [
{ required: true, message: "统一社会信用代码不能为空", trigger: "blur" },
{
pattern: /^[^_IOZSVa-z\W]{2}\d{6}[^_IOZSVa-z\W]{10}$/,
message: "请输入正确的统一社会信用代码",
},
],
POSSESSION: [{ required: true, message: "属地不能为空", trigger: "blur" }],
INDUSTRYALL: [
{ required: true, message: "所属行业不能为空", trigger: "blur" },
],
ECO_TYPEALL: [
{ required: true, message: "经济类型不能为空", trigger: "blur" },
],
ADDRESS_BUSINESS: [
{
required: true,
message: "企事业单位经营地址不能为空",
trigger: "blur",
},
],
CORP_STATE: [
{ required: true, message: "企业状态不能为空", trigger: "blur" },
],
LONGITUDE: [{ required: true, message: "经度不能为空", trigger: "blur" }],
LATITUDE: [{ required: true, message: "纬度不能为空", trigger: "blur" }],
CONTACTS: [
{ required: true, message: "主要负责人不能为空", trigger: "blur" },
],
CONTACTS_PHONE: [
{ required: true, message: "主要负责人电话不能为空", trigger: "blur" },
{ min: 11, max: 11, message: "请输入11位手机号码", trigger: "blur" },
{
pattern:
/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
message: "请输入正确的手机号码",
},
],
TRAINTYPE: [
{ required: true, message: "培训行业类型不能为空", trigger: "blur" },
],
bus_images: [
{ required: true, message: "营业执照不能为空", trigger: "blur" },
],
LR_MOBILE: [
{ min: 11, max: 11, message: "请输入11位手机号码", trigger: "blur" },
{
pattern:
/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
message: "请输入正确的手机号码",
},
],
SAFETY_PHONE: [
{ min: 11, max: 11, message: "请输入11位手机号码", trigger: "blur" },
{
pattern:
/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
message: "请输入正确的手机号码",
},
],
SAFETY_NUMBER: [
{ min: 11, max: 11, message: "请输入11位手机号码", trigger: "blur" },
{
pattern:
/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
message: "请输入正确的手机号码",
},
],
},
mapDialog: {
visible: false,
loading: true,
lat: "",
lng: "",
},
});
const fnGetData = async () => {
const resData = await getEnterpriseInfo();
data.form = resData.pd;
data.form.bus_images = addingPrefixToFile(resData.busImgs);
data.form.four_images = addingPrefixToFile(resData.fourImgs);
data.form.OLDFOURTYPE = resData.pd.FOURTYPE;
data.form.FOURTYPE = resData.pd.FOURTYPE || 1;
};
fnGetData();
const enterpriseStatus = await layoutFnGetEnterpriseStatus();
const subordination = await layoutFnGetSubordination();
const enterpriseScale = await layoutFnGetEnterpriseScale();
const fnMapInit = () => {
mapInstance = new window.BMapGL.Map("map_container");
mapInstance.centerAndZoom(
new window.BMapGL.Point(
data.form.LONGITUDE || "116.3972282409668",
data.form.LATITUDE || "39.90960456049752"
),
16
);
mapInstance.enableScrollWheelZoom(true);
mapInstance.setMapStyleV2({
styleId: "6f501abeb2a0cc0d961d110b9407b127",
});
data.mapDialog.loading = false;
mapInstance.addEventListener("click", function (event) {
data.mapDialog.lat = event.latlng.lat;
data.mapDialog.lng = event.latlng.lng;
});
};
const fnMapDialogVisible = async () => {
data.mapDialog.visible = !data.mapDialog.visible;
data.mapDialog.lat = data.form.LATITUDE;
data.mapDialog.lng = data.form.LONGITUDE;
if (!mapInstance) {
await nextTick();
fnMapInit();
}
};
const fnMapDialogConfirm = () => {
data.form.LATITUDE = data.mapDialog.lat;
data.form.LONGITUDE = data.mapDialog.lng;
fnMapDialogVisible();
};
const fnProhibitSelect = () => {
ElMessage.error("请联系管理员制作3D图");
data.form.FOURTYPE = 1;
};
const back = () => {
router.back();
};
const fnSubmit = debounce(
1000,
async () => {
await useFormValidate(formRef);
data.form.PROVINCE = data.form.POSSESSION[0] || "";
data.form.CITY = data.form.POSSESSION[1] || "";
data.form.COUNTY = data.form.POSSESSION[2] || "";
data.form.VILLAGE = data.form.POSSESSION[3] || "";
data.form.STREET = data.form.POSSESSION[4] || "";
data.form.COMPANY_AREA = territoryCascaderRef.value.getCheckedNodes();
data.form.SMALL = "2";
data.form.ECO_TYPE = data.form.ECO_TYPEALL[0] || "";
data.form.ECO_TYPE2 = data.form.ECO_TYPEALL[1] || "";
data.form.ECO_TYPE_NAME =
economicTypeCascaderRef.value.getCheckedNodes()[0];
data.form.CORP_TYPE = data.form.INDUSTRYALL[0] || "";
data.form.CORP_TYPE2 = data.form.INDUSTRYALL[1] || "";
data.form.CORP_TYPE3 = data.form.INDUSTRYALL[2] || "";
data.form.CORP_TYPE4 = data.form.INDUSTRYALL[3] || "";
data.form.CORP_TYPE_NAME = industryCascaderRef.value.getCheckedNodes()[0];
const formData = new FormData();
Object.keys(data.form).forEach((key) => {
formData.append(key, data.form[key]);
});
for (let i = 0; i < data.form.bus_images.length; i++) {
data.form.bus_images[i].raw &&
formData.append("imgFiles", data.form.bus_images[i].raw);
}
for (let i = 0; i < data.form.four_images.length; i++) {
data.form.four_images[i].raw &&
formData.append("fourFiles", data.form.four_images[i].raw);
}
formData.append(
"EDITFOURTYPE",
data.form.FOURTYPE === data.form.OLDFOURTYPE
);
await setEnterpriseInfo(formData);
back();
},
{ atBegin: true }
);
onBeforeUnmount(() => {
if (mapInstance) {
mapInstance.destroy();
mapInstance = null;
}
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,259 @@
<template>
<layout-card>
<el-divider content-position="left">基本信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="企业名称">
{{ data.info.CORP_NAME }}
</el-descriptions-item>
<el-descriptions-item label="邮政编码">
{{ data.info.POSTAL_CODE }}
</el-descriptions-item>
<el-descriptions-item label="统一社会信用代码">
{{ data.info.CODE }}
</el-descriptions-item>
<el-descriptions-item label="市行业监管部门">
{{ data.info.INDUSTRY_DEPARTMENTName }}
</el-descriptions-item>
<el-descriptions-item label="属地">
{{ data.info.COMPANY_AREA }}
</el-descriptions-item>
<el-descriptions-item label="监管类型">
{{ data.info.REGULARTYPE_NAME }}
</el-descriptions-item>
<el-descriptions-item label="经济类型">
{{ data.info.ECO_TYPE_NAME }}
</el-descriptions-item>
<el-descriptions-item label="所属行业">
{{ data.info.CORP_TYPE_NAME }}
</el-descriptions-item>
<el-descriptions-item label="企事业单位经营地址">
{{ data.info.ADDRESS_BUSINESS }}
</el-descriptions-item>
<el-descriptions-item label="企业状态">
{{ data.info.CORP_STATE_NAME }}
</el-descriptions-item>
<el-descriptions-item label="经度">
{{ data.info.LONGITUDE }}
</el-descriptions-item>
<el-descriptions-item label="纬度">
{{ data.info.LATITUDE }}
</el-descriptions-item>
<el-descriptions-item label="主要负责人">
{{ data.info.CONTACTS }}
</el-descriptions-item>
<el-descriptions-item label="主要负责人电话">
{{ data.info.CONTACTS_PHONE }}
</el-descriptions-item>
<el-descriptions-item label="法定代表人">
{{ data.info.LR_NAME }}
</el-descriptions-item>
<el-descriptions-item label="法人手机号">
{{ data.info.LR_PHONE }}
</el-descriptions-item>
<el-descriptions-item label="占地面积(㎡)">
{{ data.info.AREA_COVERED }}
</el-descriptions-item>
<el-descriptions-item label="职工人数(人)">
{{ data.info.EMPLOYEES }}
</el-descriptions-item>
<el-descriptions-item label="成立时间">
{{ data.info.CREATE_DATE }}
</el-descriptions-item>
<el-descriptions-item label="注册资金(万元)">
{{ data.info.REGCAPITAL }}
</el-descriptions-item>
<el-descriptions-item label="资产总额(万元)">
{{ data.info.TOTALASSETS }}
</el-descriptions-item>
<el-descriptions-item label="隶属关系">
{{ data.info.SUBORDINATIONNAME }}
</el-descriptions-item>
<el-descriptions-item label="规模">
{{ data.info.SCALE_NAME }}
</el-descriptions-item>
<el-descriptions-item label="是否规模以上">
<template v-if="data.info.SCALE_TYPE === 0"></template>
<template v-if="data.info.SCALE_TYPE === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="培训行业类型">
{{ data.info.TRAINTYPE_NAME }}
</el-descriptions-item>
<el-descriptions-item label="企业可新建用户数量">
{{ data.info.USERS_NUM }}
</el-descriptions-item>
<el-descriptions-item
label="四色图"
v-if="data.info.FOURTYPE === 1"
:span="2"
>
<img
v-viewer
v-for="item in data.four_images"
:src="item.url"
:key="item.IMGFILES_ID"
width="100"
height="100"
class="ml-10"
/>
</el-descriptions-item>
<el-descriptions-item label="营业执照" :span="2">
<img
v-viewer
v-for="item in data.bus_images"
:src="item.url"
:key="item.IMGFILES_ID"
width="100"
height="100"
class="ml-10"
/>
</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">安全负责人信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="姓名">
{{ data.info.SAFETY_NAME }}
</el-descriptions-item>
<el-descriptions-item label="职务">
{{ data.info.SAFETY_POST }}
</el-descriptions-item>
<el-descriptions-item label="单位电话">
{{ data.info.SAFETY_NUMBER }}
</el-descriptions-item>
<el-descriptions-item label="手机号码">
{{ data.info.SAFETY_PHONE }}
</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">企业相关属性</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="有无职业卫生信息">
<template v-if="data.info.WHETHER_HYGIENE === 0"></template>
<template v-if="data.info.WHETHER_HYGIENE === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="有无重大危险源">
<template v-if="data.info.WHETHER_HAZARDS === 0"></template>
<template v-if="data.info.WHETHER_HAZARDS === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否有稀缺大型应急物资或设施">
<template v-if="data.info.WHETHER_SCARCE === 0"></template>
<template v-if="data.info.WHETHER_SCARCE === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否涉及危化品">
<template v-if="data.info.WHETHER_CHEMICALS === 0"></template>
<template v-if="data.info.WHETHER_CHEMICALS === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="有无特种设备">
<template v-if="data.info.WHETHER_SPECIALEQUIPMENT === 0"></template>
<template v-if="data.info.WHETHER_SPECIALEQUIPMENT === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="有无特种作业人员">
<template v-if="data.info.WHETHER_SPECIALPEOPLE === 0"></template>
<template v-if="data.info.WHETHER_SPECIALPEOPLE === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否涉及煤气">
<template v-if="data.info.WHETHER_COALGAS === 0"></template>
<template v-if="data.info.WHETHER_COALGAS === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否属于消防重点单位">
<template v-if="data.info.WHETHER_FIRE === 0"></template>
<template v-if="data.info.WHETHER_FIRE === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否在受限空间作业">
<template v-if="data.info.WHETHER_CONFINED === 0"></template>
<template v-if="data.info.WHETHER_CONFINED === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否存在涉爆粉尘作业">
<template v-if="data.info.WHETHER_POWDER === 0"></template>
<template v-if="data.info.WHETHER_POWDER === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否涉及防雷防静电">
<template v-if="data.info.WHETHER_LIGHTNING === 0"></template>
<template v-if="data.info.WHETHER_LIGHTNING === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否持有放射源">
<template v-if="data.info.WHETHER_ACTINOGEN === 0"></template>
<template v-if="data.info.WHETHER_ACTINOGEN === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否涉及液氨制冷">
<template v-if="data.info.WHETHER_LIQUIDAMMONIA === 0"></template>
<template v-if="data.info.WHETHER_LIQUIDAMMONIA === 1"></template>
</el-descriptions-item>
<el-descriptions-item label="是否涉及危化品管道">
<template v-if="data.info.WHETHER_PIPELINE === 0"></template>
<template v-if="data.info.WHETHER_PIPELINE === 1"></template>
</el-descriptions-item>
</el-descriptions>
<div class="tc mt-10">
<el-button
type="primary"
@click="
router.push({ path: '/enterprise_management/information/info/edit' })
"
>
修改
</el-button>
<el-button @click="fnQrCode"></el-button>
</div>
<el-dialog
v-model="data.qrCodeDialog.visible"
title="查看二维码"
width="600px"
>
<div id="printContainer" style="border: 1px dashed #ccc">
<div class="tc mt-20">
<h1>{{ data.info.CORP_NAME }}</h1>
</div>
<div class="tc mt-20 mb-20">
<img :src="data.qrCodeDialog.src" alt="" />
</div>
</div>
<template #footer>
<el-button @click="fnQrCodeDialogChangeShow"> </el-button>
<el-button v-print="'#printContainer'" type="primary"> </el-button>
</template>
</el-dialog>
</layout-card>
</template>
<script setup>
import { getEnterpriseInfo } from "@/request/prevention/enterprise_management.js";
import { addingPrefixToFile } from "@/assets/js/utils.js";
import { reactive } from "vue";
import { useRouter } from "vue-router";
import { useQRCode } from "@vueuse/integrations/useQRCode";
import { ElMessage } from "element-plus";
const router = useRouter();
const data = reactive({
info: {},
bus_images: [],
four_images: [],
qrCodeDialog: {
visible: false,
src: "",
},
});
const fnGetData = async () => {
const resData = await getEnterpriseInfo();
data.info = resData.pd;
data.bus_images = addingPrefixToFile(resData.busImgs);
data.four_images = addingPrefixToFile(resData.fourImgs);
};
fnGetData();
const fnQrCodeDialogChangeShow = () => {
data.qrCodeDialog.visible = !data.qrCodeDialog.visible;
};
const fnQrCode = () => {
if (data.info.CORPINFO_ID) {
fnQrCodeDialogChangeShow();
// TODO:
data.qrCodeDialog.src = useQRCode("https://vueuse.org", {
width: 200,
height: 200,
margin: 1,
correctLevel: "H",
});
} else ElMessage.error("请重新获取二维码");
};
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<template>
<div></div>
</template>
<script setup></script>
<style lang="scss" scoped></style>

184
src/views/login/index.vue Normal file
View File

@ -0,0 +1,184 @@
<template>
<div class="login">
<div class="main">
<div class="title">管理平台</div>
<div class="form">
<el-form
ref="formRef"
:model="data.form"
:rules="data.rules"
@submit.prevent="fnLogin"
>
<el-form-item prop="username">
<el-input
v-model="data.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
type="password"
v-model="data.form.password"
placeholder="请输入密码"
tabindex="2"
>
<template #prepend>
<icon-lock
theme="filled"
size="16"
fill="#909399"
:strokeWidth="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>
</template>
<script setup>
import { reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import Verification from "@/components/verification/index";
import { useUserStore } from "@/pinia/user";
import { Login } from "@/request/api";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/assets/js/useFormValidate.js";
const router = useRouter();
const formRef = ref(null);
const verificationPass = ref(false);
const userStore = useUserStore();
const data = reactive({
form: {
username: "",
password: "",
},
rules: {
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
},
});
const fnLogin = debounce(
1000,
() => {
if (import.meta.env.DEV) {
fnSubmitLogin();
return;
}
if (verificationPass.value) {
fnSubmitLogin();
} else {
ElMessage.warning("请进行登录验证");
}
},
{ atBegin: true }
);
const fnSubmitLogin = async () => {
await useFormValidate(formRef, "请输入用户名密码");
// eslint-disable-next-line no-undef
const jsencrypt = new JSEncrypt();
jsencrypt.setPublicKey(
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUoHAavCikaZxjlDM6Km8cX+ye78F4oF39AcEfnE1p2Yn9pJ9WFxYZ4Vkh6F8SKMi7k4nYsKceqB1RwG996SvHQ5C3pM3nbXCP4K15ad6QhN4a7lzlbLhiJcyIKszvvK8ncUDw8mVQ0j/2mwxv05yH6LN9OKU6Hzm1ninpWeE+awIDAQAB"
);
const KEYDATA = jsencrypt.encrypt(
"zcloudchina" + data.form.username + ",zy," + data.form.password
);
const resData = await Login({
KEYDATA,
SOURCE: 1,
});
await userStore.setUserInfo({
...userStore.getUserInfo,
...resData,
});
await router.replace("/index");
};
</script>
<style scoped lang="scss">
.login {
width: 100%;
max-width: 1920px;
margin: 0 auto;
height: 100vh;
position: relative;
background: url("../../assets/images/public/loginbg.jpg") no-repeat top center;
background-size: 100% 100%;
.main {
width: 600px;
padding-top: 280px;
margin: 0 auto;
.title {
width: 100%;
text-align: center;
font-size: 36px;
color: #ffffff;
font-weight: bold;
}
.form {
width: 478px;
min-height: 290px;
padding: 20px 40px;
border-radius: 5px;
background: rgba(8, 22, 59, 0.36);
border: 1px solid rgba(31, 58, 136, 0.9);
margin: 60px auto 0;
box-shadow: 0 0 20px rgba(47, 85, 124, 0.2) inset;
.title {
color: #666;
text-align: center;
font-weight: 700;
font-size: 18px;
margin-bottom: 20px;
}
.el-form-item {
width: 325px;
margin: 20px auto;
.el-input {
height: 40px;
}
}
.button {
.el-button {
background: #0948d5;
border: 1px solid #276aff;
height: 45px;
width: 100%;
color: #ffffff;
margin-top: 10px;
}
}
}
}
}
:deep {
.el-input-group__prepend {
padding-top: 5px !important;
}
}
</style>

92
vite.config.js Normal file
View File

@ -0,0 +1,92 @@
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,
VantResolver,
} from "unplugin-vue-components/resolvers";
// import basicSsl from "@vitejs/plugin-basic-ssl";
import removeConsole from "vite-plugin-remove-console";
import EnhanceLog from "vite-plugin-enhance-log";
export default ({ mode }) => {
return defineConfig({
plugins: [
vue(),
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(), VantResolver()],
}),
EnhanceLog({
preTip: "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀",
}),
// basicSsl(),
],
server: {
// https: true,
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]",
},
},
},
});
};