first commit
|
@ -0,0 +1,6 @@
|
|||
NODE_ENV = Staging
|
||||
VITE_NAME='Staging'
|
||||
VITE_BASE_URL='https://106.52.199.114:8000'
|
||||
VITE_FiLE_SYS='https://106.52.199.114:8000'
|
||||
VITE_HOME_URL='https://106.52.199.114:8000'
|
||||
VITE_CLIENT_ID='55DFB4F6-9FF1-4E26-8766-C9F1B1E2A912'
|
|
@ -0,0 +1,9 @@
|
|||
NODE_ENV = development
|
||||
VITE_NAME='开发环境'
|
||||
VITE_BASE_URL='http://localhost:19903'
|
||||
VITE_BASE_URL_SYSTEM='http://localhost:19901'
|
||||
VITE_HOME_URL='http://localhost:3000'
|
||||
VITE_CLIENT_ID="66DFB4F6-9FF1-4E26-8766-C9F1B1E2A984"
|
||||
VITE_FiLE_SYS='http://localhost:19901'
|
||||
VITE_FiLE_IMG='http://localhost:19903'
|
||||
VITE_FiLE='http://localhost:19903'
|
|
@ -0,0 +1,5 @@
|
|||
NODE_ENV = production
|
||||
VITE_NAME='生产环境'
|
||||
VITE_BASE_URL=''
|
||||
VITE_HOME_URL=''
|
||||
VITE_CLIENT_ID="66DFB4F6-9FF1-4E26-8766-C9F1B1E2A984"
|
|
@ -0,0 +1,10 @@
|
|||
NODE_ENV = test
|
||||
VITE_NAME='测试环境'
|
||||
VITE_BASE_URL=''
|
||||
VITE_BASE_URL2='https://192.168.1.100:8000'
|
||||
VITE_HOME_URL='https://192.168.1.100:8000'
|
||||
VITE_BASE_URL_SYSTEM='http://192.168.1.100:8001'
|
||||
VITE_CLIENT_ID="66DFB4F6-9FF1-4E26-8766-C9F1B1E2A984"
|
||||
VITE_FiLE='https://192.168.1.100:19902'
|
||||
VITE_FiLE_SYS='https://192.168.1.100:19901'
|
||||
VITE_FiLE_IMG='https://192.168.1.100:19903'
|
|
@ -0,0 +1,23 @@
|
|||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/essential"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"vue"
|
||||
],
|
||||
"rules": {
|
||||
"vue/no-v-model-argument": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"no-var": "error",
|
||||
}
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// Jenkinsfile for Project A
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
parameters {
|
||||
// 项目特定的默认值
|
||||
string(name: 'GIT_REPO_URL', defaultValue: 'https://your-git-server.com/project-a.git', description: 'Git仓库URL')
|
||||
string(name: 'GIT_BRANCH', defaultValue: 'main', description: '要拉取的Git分支')
|
||||
credentials(name: 'GIT_CREDENTIALS_ID', defaultValue: 'your-git-credentials-id', description: 'Git凭证ID', required: false)
|
||||
|
||||
string(name: 'BUILD_COMMAND', defaultValue: 'mvn clean package -DskipTests', description: '项目A打包命令') // project-a 的打包命令
|
||||
|
||||
string(name: 'DOCKER_REGISTRY_URL', defaultValue: 'your-docker-registry.com', description: 'Docker镜像仓库URL')
|
||||
string(name: 'DOCKER_IMAGE_NAME', defaultValue: 'project-a-app', description: 'Docker镜像名称') // project-a 的镜像名
|
||||
string(name: 'IMAGE_BASE_TAG', defaultValue: '1.0', description: '基础镜像标签')
|
||||
credentials(name: 'DOCKER_CREDENTIALS_ID', defaultValue: 'your-docker-registry-credentials-id', description: 'Docker镜像仓库凭证ID', required: true)
|
||||
booleanParam(name: 'PUSH_LATEST_TAG', defaultValue: true, description: '是否同时推送 latest 标签?')
|
||||
}
|
||||
|
||||
environment {
|
||||
FULL_IMAGE_NAME = "${params.DOCKER_REGISTRY_URL}/${params.DOCKER_IMAGE_NAME}"
|
||||
IMAGE_TAG = "" // 将在 Checkout 后动态设置
|
||||
}
|
||||
|
||||
// tools { ... } // 如果需要
|
||||
|
||||
stages {
|
||||
stage('1. Checkout Code') {
|
||||
steps {
|
||||
echo "拉取代码从 ${params.GIT_REPO_URL}, 分支: ${params.GIT_BRANCH}"
|
||||
cleanWs()
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: params.GIT_BRANCH]],
|
||||
userRemoteConfigs: [[
|
||||
url: params.GIT_REPO_URL,
|
||||
credentialsId: params.GIT_CREDENTIALS_ID
|
||||
]],
|
||||
extensions: [
|
||||
[$class: 'CloneOption', shallow: true, noTags: true, depth: 1, timeout: 20],
|
||||
[$class: 'PruneStaleBranch']
|
||||
]
|
||||
])
|
||||
script {
|
||||
def shortCommit = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
|
||||
env.IMAGE_TAG = "${params.IMAGE_BASE_TAG}-${BUILD_NUMBER}-${shortCommit}"
|
||||
echo "生成的镜像TAG: ${env.IMAGE_TAG}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('2. Build and Package Application') {
|
||||
steps {
|
||||
echo "开始打包应用: ${params.BUILD_COMMAND}"
|
||||
sh "${params.BUILD_COMMAND}"
|
||||
echo "应用打包完成."
|
||||
}
|
||||
}
|
||||
|
||||
stage('3. Build Docker Image') {
|
||||
steps {
|
||||
echo "开始构建Docker镜像: ${env.FULL_IMAGE_NAME}:${env.IMAGE_TAG}"
|
||||
script {
|
||||
docker.build("${env.FULL_IMAGE_NAME}:${env.IMAGE_TAG}", "-f Dockerfile .") // 假设 Dockerfile 在根目录
|
||||
}
|
||||
echo "Docker镜像构建完成: ${env.FULL_IMAGE_NAME}:${env.IMAGE_TAG}"
|
||||
}
|
||||
}
|
||||
|
||||
stage('4. Push Docker Image') {
|
||||
steps {
|
||||
echo "开始推送Docker镜像到 ${params.DOCKER_REGISTRY_URL}"
|
||||
script {
|
||||
docker.withRegistry(params.DOCKER_REGISTRY_URL, params.DOCKER_CREDENTIALS_ID) {
|
||||
docker.image("${env.FULL_IMAGE_NAME}:${env.IMAGE_TAG}").push()
|
||||
echo "镜像 ${env.FULL_IMAGE_NAME}:${env.IMAGE_TAG} 推送成功."
|
||||
if (params.PUSH_LATEST_TAG) {
|
||||
docker.image("${env.FULL_IMAGE_NAME}:${env.IMAGE_TAG}").push('latest')
|
||||
echo "镜像 ${env.FULL_IMAGE_NAME}:latest 推送成功."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
echo 'Pipeline 结束.'
|
||||
cleanWs()
|
||||
}
|
||||
// success { ... }
|
||||
// failure { ... }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
# vue-project
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](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).
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
yarn build
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import pluginVue from "eslint-plugin-vue";
|
||||
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{files: ["**/*.{js,mjs,cjs,vue}"]},
|
||||
{languageOptions: { globals: {...globals.browser, ...globals.node} }},
|
||||
pluginJs.configs.recommended,
|
||||
...pluginVue.configs["flat/essential"],
|
||||
];
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>集成化平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "vue-project",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite --mode development",
|
||||
"build": "vite build --mode production",
|
||||
"test": "vite build --mode test",
|
||||
"build:env": "vite build --mode development",
|
||||
"preview": "vite preview --mode production"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.4.2",
|
||||
"element-plus": "^2.9.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"oidc-client": "^1.11.5",
|
||||
"pinia": "^2.0.14",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"print-js": "^1.6.0",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"vue": "^3.2.37",
|
||||
"vue-router": "^4.1.2",
|
||||
"xlsx": "^0.18.5",
|
||||
"xlsx-style": "^0.8.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"postcss-pxtorem": "^6.1.0",
|
||||
"sass": "^1.32.13",
|
||||
"vite": "^2.9.14",
|
||||
"vite-plugin-svg-icons": "^2.0.1"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<router-view v-if="isRoutesReady"></router-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { GetDictionaryGetPage } from '@/api/system/index'
|
||||
const isRoutesReady = ref(false)
|
||||
const permissionStore = usePermissionStore()
|
||||
const router = useRouter()
|
||||
|
||||
const getDictionaryGetPage = () => {
|
||||
const params = {
|
||||
LambdaExp: 'IsDeleted==false&&dicName.Contains("")',
|
||||
SelectSorts: '[{"FieldName":"CreateTime","IsAsc":false}]',
|
||||
PageNum: 1,
|
||||
PageSize: 10000
|
||||
}
|
||||
GetDictionaryGetPage(params).then(res => {
|
||||
if (res.StatusCode == 200) {
|
||||
sessionStorage.setItem('dictionarys', JSON.stringify(res.Data))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
getDictionaryGetPage()
|
||||
try {
|
||||
if (!permissionStore.isRoutesGenerated) {
|
||||
sessionStorage.removeItem('tabs')
|
||||
await permissionStore.generateRoutes()
|
||||
}
|
||||
// 检查当前路由是否存在
|
||||
if (router.currentRoute.value.matched.length === 0 &&
|
||||
router.currentRoute.value.path !== '/404') {
|
||||
router.replace('/home')
|
||||
}
|
||||
isRoutesReady.value = true
|
||||
} catch (error) {
|
||||
console.error('Failed to generate routes:', error)
|
||||
router.replace('/404')
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,108 @@
|
|||
import service from '@/utils/request'
|
||||
|
||||
export function UploadAttachment(data, config) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/UploadAttachment',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'Custom-Header': 'CustomValue',
|
||||
},
|
||||
...config
|
||||
})
|
||||
}
|
||||
|
||||
export function UploadMultipleAttachments(data, config) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/UploadMultipleAttachments',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'Custom-Header': 'CustomValue',
|
||||
},
|
||||
...config
|
||||
})
|
||||
}
|
||||
|
||||
export function GetAttachmentById(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetAttachmentById',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function DeleteAttachment(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/DeleteAttachment',
|
||||
method: 'delete',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function GetStringObjectId(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetStringObjectId',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function MigrationFile(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/MigrationFile',
|
||||
method: 'put',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetProjectInfoList(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetProjectInfoList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetSectionList(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetSectionList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetLaborTeamPageList(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetLaborTeamPageList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetWorkerGroupPageList(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetWorkerGroupPageList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetAttacBusinessId(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetAttacBusinessId',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetLoginResourceTree(params) {
|
||||
return service({
|
||||
url: '/api/lmg/Common/GetLoginResourceTree',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
import service from "@/utils/requestSystem";
|
||||
|
||||
|
||||
export function GetLoginResourceTree(params) {
|
||||
return service({
|
||||
url: '/api/sys/Resources/GetLoginResourceTree',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetOwnerUnitPage(params) {
|
||||
return service({
|
||||
url: '/api/sys/OwnerUnit/GetPage',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function PostOwnerUnit(data) {
|
||||
return service({
|
||||
url: '/api/sys/OwnerUnit/Post',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function PutOwnerUnit(data) {
|
||||
return service({
|
||||
url: '/api/sys/OwnerUnit/Put',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function DeleteOwnerUnit(data) {
|
||||
return service({
|
||||
url: `/api/sys/OwnerUnit/Delete/${data}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function GetSectionPage(params) {
|
||||
return service({
|
||||
url: '/api/sys/Section/GetPage',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function PostSection(data) {
|
||||
return service({
|
||||
url: '/api/sys/Section/Post',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function PutSection(data) {
|
||||
return service({
|
||||
url: '/api/sys/Section/Put',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function DeleteSection(data) {
|
||||
return service({
|
||||
url: `/api/sys/Section/Delete/${data}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function UploadAttachment(data, config) {
|
||||
return service({
|
||||
url: '/api/sys/AttachmentConfig/UploadAttachment',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'Custom-Header': 'CustomValue',
|
||||
},
|
||||
...config
|
||||
})
|
||||
}
|
||||
|
||||
export function UpdateFile(params) {
|
||||
return service({
|
||||
url: '/api/sys/OtherAttachments/UpdateFile',
|
||||
method: 'put',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetAttacBusinessId(params) {
|
||||
return service({
|
||||
url: '/api/sys/OtherAttachments/GetAttacBusinessId',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function DeleteAttachment(data) {
|
||||
return service({
|
||||
url: `/api/sys/AttachmentConfig/DeleteAttachment/`,
|
||||
method: 'delete',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// export function DeleteAttachment(params) {
|
||||
// return service({
|
||||
// url: '/api/sys/OtherAttachments/DeleteAttachment',
|
||||
// method: 'delete',
|
||||
// params
|
||||
// })
|
||||
// }
|
||||
|
||||
|
||||
export function GetProjectInfoPage(params) {
|
||||
return service({
|
||||
url: '/api/sys/ProjectInfo/GetPage',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function PostProjectInfo(data) {
|
||||
return service({
|
||||
url: '/api/sys/ProjectInfo/Post',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function PutProjectInfo(data) {
|
||||
return service({
|
||||
url: '/api/sys/ProjectInfo/Put',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function DeleteProjectInfo(data) {
|
||||
return service({
|
||||
url: `/api/sys/ProjectInfo/Delete/${data}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function GetLaborTeamPage(params) {
|
||||
return service({
|
||||
url: '/api/sys/LaborTeam/GetPage',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function PostLaborTeam(data) {
|
||||
return service({
|
||||
url: '/api/sys/LaborTeam/Post',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function PutLaborTeam(data) {
|
||||
return service({
|
||||
url: '/api/sys/LaborTeam/Put',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function DeleteLaborTeam(data) {
|
||||
return service({
|
||||
url: `/api/sys/LaborTeam/Delete/${data}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function GetWorkerGroupPage(params) {
|
||||
return service({
|
||||
url: '/api/sys/WorkerGroup/GetPage',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function GetDictionaryGetPage(params) {
|
||||
return service({
|
||||
url: '/api/sys/Dictionary/GetPage',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function GetDictionaryidDicItems(params) {
|
||||
return service({
|
||||
url: '/api/sys/Dictionary/GetDictionaryidDicItems',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function ResetPassword(data) {
|
||||
return service({
|
||||
url: `/api/sys/User/ResetPassword/ResetPassword`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1736752681898" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4266" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M823.00322 24.512277l-591.737042 0c-11.307533 0-20.466124 9.15859-20.466124 20.466124l0 934.043199c0 11.2973 9.15859 20.466124 20.466124 20.466124l591.737042 0c11.307533 0 20.466124-9.168824 20.466124-20.466124L843.469344 44.978401C843.469344 33.670867 834.310753 24.512277 823.00322 24.512277zM802.537096 773.96127l-480.135268 0c-11.307533 0-20.466124 9.168824-20.466124 20.466124 0 11.307533 9.15859 20.466124 20.466124 20.466124l480.135268 0 0 143.661957-550.804794 0L251.732301 65.444525l550.804794 0L802.537096 773.96127z" p-id="4267"></path><path d="M527.134699 886.514719m-48.461735 0a47.358 47.358 0 1 0 96.92347 0 47.358 47.358 0 1 0-96.92347 0Z" p-id="4268"></path></svg>
|
After Width: | Height: | Size: 1012 B |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1746427106785" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1696" width="13" height="13" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M387.017 261.155h242.129c74.925 0 132.079-162.482 132.079-162.482 0-48.66-4.872-85.474-88.07-88.068-83.172-2.62-103.098 58.158-162.872 58.158-61.266 0-91.91-47.78-167.301-58.158-75.365-10.427-88.044 39.408-88.044 88.068 0 0.001 57.129 162.482 132.079 162.482z m255.346 50.227H387.017c-282.762 0-352.204 555.51-352.204 555.51 0 72.967 59.138 146.253 132.08 146.253h695.57c72.942 0 132.078-73.286 132.078-146.253 0 0-69.465-555.51-352.178-555.51z m28.982 426.93c17.036 0 30.842 13.83 30.842 30.867 0 17.035-13.805 30.865-30.842 30.865H546.143v80.971c0 17.035-13.805 30.867-30.817 30.867-17.036 0-30.84-13.832-30.84-30.867v-80.97h-126.01c-17.035 0-30.84-13.83-30.84-30.866 0-17.037 13.805-30.867 30.84-30.867h126.01V699.15h-126.01c-17.035 0-30.84-13.806-30.84-30.842s13.805-30.89 30.84-30.89h85.181l-87.212-154.99c-8.518-14.785-3.452-33.633 11.284-42.15 14.759-8.519 33.607-3.476 42.125 11.259l105.008 185.88h5.31l105.008-185.88c8.517-14.735 27.365-19.779 42.101-11.258 14.76 8.516 19.828 27.364 11.309 42.148l-87.213 154.99h79.968c17.036 0 30.842 13.854 30.842 30.891 0 17.035-13.805 30.842-30.842 30.842H546.143v39.162h125.202z" fill="#bfcbd9" p-id="1697"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 653 KiB |
After Width: | Height: | Size: 186 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 563 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 308 B |
|
@ -0,0 +1,35 @@
|
|||
/* @import "./base.css"; */
|
||||
|
||||
/* #app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
} */
|
|
@ -0,0 +1,31 @@
|
|||
@use 'variables' as v;
|
||||
|
||||
// 混入
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@mixin text-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@mixin multi-ellipsis($lines) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $lines;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
// 响应式混入
|
||||
@mixin respond-to($breakpoint) {
|
||||
@if map-has-key(v.$breakpoints, $breakpoint) {
|
||||
@media (min-width: map-get(v.$breakpoints, $breakpoint)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// 颜色系统
|
||||
$colors: (
|
||||
'primary': #409EFF,
|
||||
'success': #67C23A,
|
||||
'warning': #E6A23C,
|
||||
'danger': #F56C6C,
|
||||
'info': #909399,
|
||||
'blue': #3385ff,
|
||||
'white': #FFFFFF,
|
||||
'black': #000000,
|
||||
'theme1': #0A397D
|
||||
);
|
||||
|
||||
// 中性色
|
||||
$gray: (
|
||||
'100': #f7f7f7,
|
||||
'200': #f2f2f2,
|
||||
'300': #e6e6e6,
|
||||
'400': #d9d9d9,
|
||||
'500': #bfbfbf,
|
||||
'600': #8c8c8c,
|
||||
'700': #595959,
|
||||
'800': #434343,
|
||||
'900': #262626
|
||||
);
|
||||
|
||||
// 字体家族
|
||||
$font-family: (
|
||||
'base': -apple-system,
|
||||
'BlinkMacSystemFont': 'Segoe UI',
|
||||
'Roboto': 'Helvetica Neue',
|
||||
'Arial': sans-serif
|
||||
);
|
||||
|
||||
// 字体大小
|
||||
$font-size: (
|
||||
'xs': 12px,
|
||||
'sm': 14px,
|
||||
'base': 16px,
|
||||
'lg': 18px,
|
||||
'xl': 20px,
|
||||
'2xl': 24px,
|
||||
'3xl': 30px,
|
||||
'4xl': 36px
|
||||
);
|
||||
|
||||
// 字重
|
||||
$font-weight: (
|
||||
'light': 300,
|
||||
'regular': 400,
|
||||
'medium': 500,
|
||||
'semibold': 600,
|
||||
'bold': 700
|
||||
);
|
||||
|
||||
// 行高
|
||||
$line-height: (
|
||||
'tight': 1.25,
|
||||
'normal': 1.5,
|
||||
'relaxed': 1.75,
|
||||
'loose': 2
|
||||
);
|
||||
|
||||
// 间距
|
||||
$spacing: (
|
||||
'none': 0,
|
||||
'xs': 4px,
|
||||
'sm': 8px,
|
||||
'md': 16px,
|
||||
'lg': 24px,
|
||||
'xl': 32px,
|
||||
'2xl': 48px
|
||||
);
|
||||
|
||||
// 圆角
|
||||
$border-radius: (
|
||||
'none': 0,
|
||||
'sm': 2px,
|
||||
'base': 4px,
|
||||
'md': 6px,
|
||||
'lg': 8px,
|
||||
'xl': 12px,
|
||||
'full': 9999px
|
||||
);
|
||||
|
||||
// 阴影
|
||||
$box-shadow: (
|
||||
'sm': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||
'base': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
||||
'md': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
||||
'xl': '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)'
|
||||
);
|
||||
|
||||
// 断点
|
||||
$breakpoints: (
|
||||
'sm': 640px,
|
||||
'md': 768px,
|
||||
'lg': 1024px,
|
||||
'xl': 1280px,
|
||||
'2xl': 1536px
|
||||
);
|
||||
|
||||
// z-index管理
|
||||
$z-index: (
|
||||
'dropdown': 1000,
|
||||
'sticky': 1020,
|
||||
'fixed': 1030,
|
||||
'modal': 1040,
|
||||
'popover': 1050,
|
||||
'tooltip': 1060
|
||||
);
|
||||
|
||||
// 主题
|
||||
$themes: (
|
||||
// 默认主题
|
||||
'default': (
|
||||
'bg1': #0A397D,
|
||||
'color1': #FFFFFF,
|
||||
),
|
||||
// 主题1
|
||||
'theme1': (
|
||||
'bg1': #FF5733,
|
||||
'color1': #FFFFFF,
|
||||
),
|
||||
// 主题2
|
||||
'theme2': (
|
||||
'bg1': #33FF57,
|
||||
'color1': #000000,
|
||||
)
|
||||
);
|
||||
|
||||
// 导出变量到 CSS 变量
|
||||
:root {
|
||||
@each $name, $color in $colors {
|
||||
--color-#{$name}: #{$color};
|
||||
}
|
||||
|
||||
@each $key, $value in $spacing {
|
||||
--spacing-#{$key}: #{$value};
|
||||
}
|
||||
|
||||
@each $key, $value in $font-size {
|
||||
--font-size-#{$key}: #{$value};
|
||||
}
|
||||
|
||||
@each $theme-name, $theme-colors in $themes {
|
||||
@each $name, $color in $theme-colors {
|
||||
--color-#{$theme-name}-#{$name}: #{$color};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
@use '../variables' as v;
|
||||
@use '../mixins' as m;
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: map-get(v.$spacing, 'md');
|
||||
|
||||
&-cols-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
&-cols-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
// @use 'variables' as v;
|
||||
// @use 'mixins' as m;
|
||||
// @use 'modules/button';
|
||||
// @use 'modules/form';
|
||||
// @use 'pages/dashboard';
|
||||
// @use 'pages/login';
|
||||
|
||||
// 重置样式
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 全局样式
|
||||
body {
|
||||
// font-family: map-get(v.$font-family, 'base');
|
||||
// font-size: map-get(v.$font-size, 'base');
|
||||
// line-height: map-get(v.$line-height, 'normal');
|
||||
// color: map-get(v.$colors, 'black');
|
||||
}
|
||||
|
||||
// 通用类
|
||||
.text-ellipsis {
|
||||
@include m.text-ellipsis;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
@include m.flex-center;
|
||||
}
|
||||
|
||||
.el-upload-list__item.el-icon--close-tip {
|
||||
display: none !important;
|
||||
}
|
||||
.el-upload-list__item.is-success:focus:not(:hover) .el-icon--close-tip {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// 响应式容器
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
// padding: 0 map-get(v.$spacing, 'md');
|
||||
|
||||
// @include m.respond-to('sm') {
|
||||
// max-width: map-get(v.$breakpoints, 'sm');
|
||||
// }
|
||||
|
||||
// @include m.respond-to('md') {
|
||||
// max-width: map-get(v.$breakpoints, 'md');
|
||||
// }
|
||||
|
||||
// @include m.respond-to('lg') {
|
||||
// max-width: map-get(v.$breakpoints, 'lg');
|
||||
// }
|
||||
|
||||
// @include m.respond-to('xl') {
|
||||
// max-width: map-get(v.$breakpoints, 'xl');
|
||||
// }
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #abaeb4;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
|
||||
.overall-container {
|
||||
height: 80vh !important;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
overflow: auto !important;
|
||||
|
||||
.search-container {
|
||||
|
||||
.search-container-from {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout-pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.layout-w150 {
|
||||
width: 150px;
|
||||
}
|
||||
.layout-w180 {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.layout-w200 {
|
||||
width: 200px;
|
||||
}
|
||||
.layout-w250{
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.layout-pr10 {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.layout-mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.layout_flex {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.layout_form {
|
||||
padding: 0 20px;
|
||||
overflow: auto;
|
||||
.el-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.layout_footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layout_form {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.layout_header_menu .el-menu--collapse .el-menu .el-submenu, .el-menu--popup {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
|
||||
.el-popover.el-popper.layout_attendance_popover {
|
||||
text-align: center !important;
|
||||
min-width: 85px !important;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
@use '../variables' as v;
|
||||
@use '../mixins' as m;
|
||||
|
||||
// 按钮样式
|
||||
.btn {
|
||||
padding: map-get(v.$spacing, 'sm') map-get(v.$spacing, 'md');
|
||||
border-radius: map-get(v.$border-radius, 'base');
|
||||
font-size: map-get(v.$font-size, 'base');
|
||||
|
||||
&-primary {
|
||||
background-color: map-get(v.$colors, 'primary');
|
||||
color: map-get(v.$colors, 'white');
|
||||
}
|
||||
|
||||
&-success {
|
||||
background-color: map-get(v.$colors, 'success');
|
||||
color: map-get(v.$colors, 'white');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
@use '../variables' as v;
|
||||
@use '../mixins' as m;
|
||||
|
||||
.dashboard {
|
||||
&-header {
|
||||
@include m.flex-center;
|
||||
padding: map-get(v.$spacing, 'lg');
|
||||
}
|
||||
|
||||
&-content {
|
||||
margin: map-get(v.$spacing, 'xl');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
$layout-title-height: 30px;
|
||||
$layout-title-fontsize: 16px;
|
||||
$layout-padding-horizontal: 2%;
|
||||
$layout-top-height: 70%;
|
||||
$layout-bottom-height: 30%;
|
||||
$sidebar-width: 33%;
|
||||
$section-padding: 10px;
|
||||
$white-color: #FFFFFF;
|
||||
|
||||
.layout_wapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
|
||||
|
||||
.layout_content {
|
||||
@extend .layout_wapper;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: $white-color;
|
||||
|
||||
.layout_m10 {
|
||||
padding: $section-padding $section-padding $section-padding 0;
|
||||
}
|
||||
|
||||
.layout_title {
|
||||
background: url(@/assets/images/largeScreen/preview.png) center/100% 100%;
|
||||
height: $layout-title-height;
|
||||
width: 90%;
|
||||
line-height: $layout-title-height;
|
||||
|
||||
span {
|
||||
padding-left: $section-padding * 3;
|
||||
font-size: $layout-title-fontsize;
|
||||
}
|
||||
}
|
||||
|
||||
.layout_top {
|
||||
width: 100%;
|
||||
height: $layout-top-height;
|
||||
display: flex;
|
||||
padding: 0 $layout-padding-horizontal;
|
||||
|
||||
.layout_top_left {
|
||||
width: 33%;
|
||||
|
||||
.layout_top_left_one {
|
||||
width: 100%;
|
||||
height: 55%;
|
||||
}
|
||||
|
||||
.layout_top_left_two {
|
||||
width: 100%;
|
||||
height: 45%;
|
||||
}
|
||||
}
|
||||
|
||||
.layout_top_center {
|
||||
width: $sidebar-width;
|
||||
height: 100%;
|
||||
margin: 0 $layout-padding-horizontal;
|
||||
}
|
||||
|
||||
.layout_top_right {
|
||||
width: 33%;
|
||||
|
||||
.layout_top_right_one {
|
||||
width: 100%;
|
||||
height: 55%;
|
||||
}
|
||||
|
||||
.layout_top_right_two {
|
||||
width: 100%;
|
||||
height: 45%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout_bottom {
|
||||
padding-left: $layout-padding-horizontal;
|
||||
width: 100%;
|
||||
height: $layout-bottom-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout_btn_back {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: $layout-padding-horizontal;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 6px;
|
||||
background: #f3f3f3;
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar-thumb {
|
||||
// border-radius: 6px;
|
||||
// background: #e3e3e3;
|
||||
// }
|
||||
|
||||
// ::-webkit-scrollbar-track {
|
||||
// border-radius: 6px;
|
||||
// background-color: transparent !important;
|
||||
// }
|
|
@ -0,0 +1,173 @@
|
|||
$layout-title-height: 30px;
|
||||
$layout-title-fontsize: 16px;
|
||||
$sidebar-width: 33%;
|
||||
$section-padding: 10px;
|
||||
$content-padding: 1%;
|
||||
$bottom-padding: 1.5%;
|
||||
$white-color: #FFFFFF;
|
||||
|
||||
|
||||
.layout_wapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.layout_content {
|
||||
@extend .layout_wapper;
|
||||
display: flex;
|
||||
color: $white-color;
|
||||
}
|
||||
|
||||
|
||||
.layout_m10 {
|
||||
padding: $section-padding 0 0 0;
|
||||
}
|
||||
|
||||
.layout_title {
|
||||
background: url(@/assets/images/largeScreen/preview.png) center/100% 100%;
|
||||
height: $layout-title-height;
|
||||
width: 90%;
|
||||
line-height: $layout-title-height;
|
||||
|
||||
span {
|
||||
padding-left: $section-padding * 3;
|
||||
font-size: $layout-title-fontsize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
%sidebar-layout {
|
||||
width: $sidebar-width;
|
||||
height: 100%;
|
||||
padding-bottom: $bottom-padding;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
.layout_left {
|
||||
@extend %sidebar-layout;
|
||||
padding-left: $content-padding;
|
||||
|
||||
.left_top { height: 37%; }
|
||||
.left_center { height: 32%; }
|
||||
.left_bottom { height: 31%; }
|
||||
}
|
||||
|
||||
|
||||
.layout_center {
|
||||
@extend %sidebar-layout;
|
||||
|
||||
.center_top { height: 37%; }
|
||||
.center_center { height: 32%; }
|
||||
.center_bottom { height: 31%; }
|
||||
}
|
||||
|
||||
|
||||
.layout_right {
|
||||
@extend %sidebar-layout;
|
||||
|
||||
.right_top { height: 64%; }
|
||||
.right_bottom { height: 35%; }
|
||||
}
|
||||
|
||||
// .layout_wapper {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// font-size: 12px;
|
||||
|
||||
// .layout_content {
|
||||
// display: flex;
|
||||
// color: #FFFFFF;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
|
||||
// .layout_m10 {
|
||||
// padding: 10px 0 0 0;
|
||||
// }
|
||||
|
||||
// .layout_title {
|
||||
// background-image: url(@/assets/images/largeScreen/preview.png);
|
||||
// background-size: 100% 100%;
|
||||
// height: 30px;
|
||||
// width: 90%;
|
||||
// line-height: 30px;
|
||||
|
||||
// span {
|
||||
// padding-left: 30px;
|
||||
// font-size: 16px;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .layout_left {
|
||||
// width: 33%;
|
||||
// height: 100%;
|
||||
// padding-left: 1%;
|
||||
// padding-bottom: 1.5%;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// justify-content: space-between;
|
||||
|
||||
// .left_top {
|
||||
// width: 100%;
|
||||
// height: 37%;
|
||||
// }
|
||||
|
||||
// .left_center {
|
||||
// width: 100%;
|
||||
// height: 32%;
|
||||
// }
|
||||
|
||||
// .left_bottom {
|
||||
// width: 100%;
|
||||
// height: 31%;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .layout_center {
|
||||
// width: 33%;
|
||||
// height: 100%;
|
||||
// padding-bottom: 1.5%;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// justify-content: space-between;
|
||||
|
||||
|
||||
// .center_top {
|
||||
// width: 100%;
|
||||
// height: 37%;
|
||||
// }
|
||||
|
||||
// .center_center {
|
||||
// width: 100%;
|
||||
// height: 32%;
|
||||
// }
|
||||
|
||||
// .center_bottom {
|
||||
// width: 100%;
|
||||
// height: 31%;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .layout_right {
|
||||
// width: 33%;
|
||||
// height: 100%;
|
||||
// padding-bottom: 1.5%;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// justify-content: space-between;
|
||||
|
||||
// .right_top {
|
||||
// width: 100%;
|
||||
// height: 64%;
|
||||
// }
|
||||
|
||||
// .right_bottom {
|
||||
// width: 100%;
|
||||
// height: 35%;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,14 @@
|
|||
@use '../variables' as v;
|
||||
|
||||
$theme-colors: (
|
||||
'background': #ffffff,
|
||||
'text': #333333,
|
||||
'border': #e5e5e5
|
||||
);
|
||||
|
||||
// 导出主题变量
|
||||
:root[data-theme='light'] {
|
||||
@each $name, $color in $theme-colors {
|
||||
--theme-#{$name}: #{$color};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { ElMessage } from 'element-plus';
|
||||
|
||||
export const showMessage = (type, message, options = {}) => {
|
||||
const windowHeight = window.innerHeight;
|
||||
const messageHeight = 60;
|
||||
const offset = (windowHeight - messageHeight) / 2;
|
||||
|
||||
ElMessage({
|
||||
message,
|
||||
type,
|
||||
plain: true,
|
||||
offset: offset,
|
||||
duration: 2000,
|
||||
// showClose: true,
|
||||
...options
|
||||
});
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
import { ElNotification } from 'element-plus';
|
||||
|
||||
const DURATION = 1500;
|
||||
|
||||
const NotificationService = {
|
||||
success(message, title = '成功', options = {}) {
|
||||
return ElNotification({
|
||||
title,
|
||||
message,
|
||||
duration:DURATION,
|
||||
type: 'success',
|
||||
...options,
|
||||
});
|
||||
},
|
||||
warning(message, title = '警告', options = {}) {
|
||||
return ElNotification({
|
||||
title,
|
||||
message,
|
||||
duration:DURATION,
|
||||
type: 'warning',
|
||||
...options,
|
||||
});
|
||||
},
|
||||
info(message, title = '信息', options = {}) {
|
||||
return ElNotification({
|
||||
title,
|
||||
message,
|
||||
duration:DURATION,
|
||||
type: 'info',
|
||||
...options,
|
||||
});
|
||||
},
|
||||
error(message, title = '错误', options = {}) {
|
||||
return ElNotification({
|
||||
title,
|
||||
message,
|
||||
duration:DURATION,
|
||||
type: 'error',
|
||||
...options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default NotificationService;
|
|
@ -0,0 +1,217 @@
|
|||
import { ref } from 'vue'
|
||||
import { GetProjectInfoList, GetSectionList, GetLaborTeamPageList, GetWorkerGroupPageList } from '@/api/common/index'
|
||||
|
||||
const commonParams = {
|
||||
Skip: 0,
|
||||
Take: 1000,
|
||||
RequireTotalCount: true,
|
||||
Sort: JSON.stringify([{ Selector: "CreateTime", Desc: true }]),
|
||||
Filter: JSON.stringify(["IsDeleted", "=", false])
|
||||
}
|
||||
|
||||
const CACHE_KEYS = {
|
||||
PROJECTS: 'cached_projects',
|
||||
SECTIONS: 'cached_sections',
|
||||
LABOR_TEAMS: 'cached_labor_teams',
|
||||
WORKER_GROUPS: 'cached_worker_groups',
|
||||
LAST_CACHE_TIME: 'last_cache_time'
|
||||
}
|
||||
|
||||
const CACHE_DURATION = 5 * 60 * 1000
|
||||
|
||||
export const useCascadeSelect = (fetchOptions) => {
|
||||
const projectState = ref('')
|
||||
const projectListAll = ref([])
|
||||
const projectList = ref([])
|
||||
|
||||
const sectionListAll = ref([])
|
||||
const sectionState = ref('')
|
||||
const sectionList = ref([])
|
||||
|
||||
const laborTeamListAll = ref([])
|
||||
const laborTeamState = ref('')
|
||||
const laborTeamList = ref([])
|
||||
|
||||
const teamsGroupsState = ref('')
|
||||
const teamsGroupsList = ref([])
|
||||
const teamsGroupsListAll = ref([])
|
||||
|
||||
const isCacheValid = (cacheKey) => {
|
||||
const lastCacheTime = sessionStorage.getItem(CACHE_KEYS.LAST_CACHE_TIME)
|
||||
if (!lastCacheTime) return false
|
||||
|
||||
const cachedData = sessionStorage.getItem(cacheKey)
|
||||
return cachedData &&
|
||||
(Date.now() - parseInt(lastCacheTime)) < CACHE_DURATION
|
||||
}
|
||||
|
||||
const getFromCache = (cacheKey) => {
|
||||
const cachedData = sessionStorage.getItem(cacheKey)
|
||||
return cachedData ? JSON.parse(cachedData) : null
|
||||
}
|
||||
|
||||
const saveToCache = (cacheKey, data) => {
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(data))
|
||||
sessionStorage.setItem(CACHE_KEYS.LAST_CACHE_TIME, Date.now().toString())
|
||||
}
|
||||
|
||||
const fetchData = async (apiFunc, refValue, cacheKey) => {
|
||||
try {
|
||||
if (isCacheValid(cacheKey)) {
|
||||
const cachedData = getFromCache(cacheKey)
|
||||
if (cachedData) {
|
||||
refValue.value = cachedData
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const res = await apiFunc(commonParams)
|
||||
if (res.StatusCode === 200) {
|
||||
const FIELD_FILTERS = {
|
||||
[CACHE_KEYS.PROJECTS]: item => ({
|
||||
Id: item.Id,
|
||||
AC001: item.AC001,
|
||||
AC002: item.AC002
|
||||
}),
|
||||
[CACHE_KEYS.SECTIONS]: item => ({
|
||||
Id: item.Id,
|
||||
AID: item.AID,
|
||||
BC01: item.BC01,
|
||||
BC02: item.BC02
|
||||
}),
|
||||
[CACHE_KEYS.LABOR_TEAMS]: item => ({
|
||||
Id: item.Id,
|
||||
BID: item.BID,
|
||||
DC01: item.DC01
|
||||
}),
|
||||
[CACHE_KEYS.WORKER_GROUPS]: item => ({
|
||||
Id: item.Id,
|
||||
BID: item.BID,
|
||||
DID: item.DID,
|
||||
EC01: item.EC01
|
||||
})
|
||||
};
|
||||
|
||||
const filteredData = res.Data.map(FIELD_FILTERS[cacheKey] || (item => ({ Id: item.Id })));
|
||||
refValue.value = filteredData;
|
||||
saveToCache(cacheKey, filteredData);
|
||||
} else {
|
||||
refValue.value = []
|
||||
}
|
||||
} catch {
|
||||
refValue.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const initSelect = () => {
|
||||
if (projectListAll.value?.length > 0) {
|
||||
projectList.value = projectListAll.value
|
||||
projectState.value = projectListAll.value[0]?.Id || ''
|
||||
|
||||
sectionList.value = sectionListAll.value?.filter(item => item.AID === projectState.value) || []
|
||||
sectionState.value = sectionList.value[0]?.Id || ''
|
||||
|
||||
laborTeamList.value = laborTeamListAll.value?.filter(item => item.BID === sectionState.value) || []
|
||||
laborTeamState.value = laborTeamList.value[0]?.Id || ''
|
||||
|
||||
teamsGroupsList.value = teamsGroupsListAll.value?.filter(item => item.DID === laborTeamState.value) || []
|
||||
teamsGroupsState.value = teamsGroupsList.value[0]?.Id || ''
|
||||
return
|
||||
}
|
||||
|
||||
if (sectionListAll.value?.length > 0) {
|
||||
sectionList.value = sectionListAll.value
|
||||
sectionState.value = sectionListAll.value[0]?.Id || ''
|
||||
|
||||
laborTeamList.value = laborTeamListAll.value?.filter(item => item.BID === sectionState.value) || []
|
||||
laborTeamState.value = laborTeamList.value[0]?.Id || ''
|
||||
|
||||
teamsGroupsList.value = teamsGroupsListAll.value?.filter(item => item.DID === laborTeamState.value) || []
|
||||
teamsGroupsState.value = teamsGroupsList.value[0]?.Id || ''
|
||||
return
|
||||
}
|
||||
|
||||
if (laborTeamListAll.value?.length > 0) {
|
||||
laborTeamList.value = laborTeamListAll.value
|
||||
laborTeamState.value = laborTeamList.value[0]?.Id || ''
|
||||
|
||||
teamsGroupsList.value = teamsGroupsListAll.value?.filter(item => item.DID === laborTeamState.value) || []
|
||||
teamsGroupsState.value = teamsGroupsList.value[0]?.Id || ''
|
||||
return
|
||||
}
|
||||
|
||||
if (teamsGroupsListAll.value?.length > 0) {
|
||||
teamsGroupsList.value = teamsGroupsListAll.value
|
||||
teamsGroupsState.value = teamsGroupsList.value[0]?.Id || ''
|
||||
}
|
||||
}
|
||||
|
||||
const projectChange = () => {
|
||||
sectionList.value = sectionListAll.value?.filter(item => item.AID === projectState.value) || []
|
||||
sectionState.value = sectionList.value[0]?.Id || ''
|
||||
|
||||
laborTeamList.value = laborTeamListAll.value?.filter(item => item.BID === sectionState.value) || []
|
||||
laborTeamState.value = laborTeamList.value[0]?.Id || ''
|
||||
|
||||
teamsGroupsList.value = teamsGroupsListAll.value?.filter(item => item.DID === laborTeamState.value) || []
|
||||
teamsGroupsState.value = teamsGroupsList.value[0]?.Id || ''
|
||||
|
||||
fetchOptions.onChange?.()
|
||||
}
|
||||
|
||||
const sectionChange = () => {
|
||||
laborTeamList.value = laborTeamListAll.value?.filter(item => item.BID === sectionState.value) || []
|
||||
laborTeamState.value = laborTeamList.value[0]?.Id || ''
|
||||
|
||||
teamsGroupsList.value = teamsGroupsListAll.value?.filter(item => item.DID === laborTeamState.value) || []
|
||||
teamsGroupsState.value = teamsGroupsList.value[0]?.Id || ''
|
||||
|
||||
fetchOptions.onChange?.()
|
||||
}
|
||||
const laborTeamChange = () => {
|
||||
teamsGroupsList.value = teamsGroupsListAll.value?.filter(item => item.DID === laborTeamState.value) || []
|
||||
teamsGroupsState.value = teamsGroupsList.value[0]?.Id || ''
|
||||
fetchOptions.onChange?.()
|
||||
}
|
||||
|
||||
const teamsGroupsChange = () => {
|
||||
fetchOptions.onChange?.()
|
||||
}
|
||||
|
||||
const initData = async () => {
|
||||
await Promise.all([
|
||||
fetchData(GetProjectInfoList, projectListAll, CACHE_KEYS.PROJECTS),
|
||||
fetchData(GetSectionList, sectionListAll, CACHE_KEYS.SECTIONS),
|
||||
fetchData(GetLaborTeamPageList, laborTeamListAll, CACHE_KEYS.LABOR_TEAMS),
|
||||
fetchData(GetWorkerGroupPageList, teamsGroupsListAll, CACHE_KEYS.WORKER_GROUPS)
|
||||
])
|
||||
initSelect()
|
||||
}
|
||||
|
||||
const clearCache = () => {
|
||||
Object.values(CACHE_KEYS).forEach(key => {
|
||||
sessionStorage.removeItem(key)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
projectState,
|
||||
projectList,
|
||||
projectListAll,
|
||||
sectionState,
|
||||
sectionList,
|
||||
sectionListAll,
|
||||
laborTeamState,
|
||||
laborTeamList,
|
||||
laborTeamListAll,
|
||||
teamsGroupsState,
|
||||
teamsGroupsList,
|
||||
teamsGroupsListAll,
|
||||
projectChange,
|
||||
sectionChange,
|
||||
laborTeamChange,
|
||||
teamsGroupsChange,
|
||||
initData,
|
||||
clearCache
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<svg :class="svgClass" :style="{ color: color }" aria-hidden="true">
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const iconName = computed(() => `#icon-${props.name}`)
|
||||
const svgClass = computed(() => {
|
||||
if (props.customClass) {
|
||||
return `svg-icon ${props.customClass}`
|
||||
}
|
||||
return 'el-icon'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,54 @@
|
|||
<style lang="scss" scoped>
|
||||
.layout_container {
|
||||
height: 600px !important;
|
||||
padding: 0 20px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<el-dialog v-model="drawerVisible" align-center :title="title" width="800" @close="handleClose"
|
||||
:close-on-click-modal="showDrawerClose" :close-on-press-escape="showDrawerClose" :show-close="true">
|
||||
<div class="layout_container">
|
||||
<iframe :src="iframeSrc" frameborder="0" width="100%" height="100%" style="width: 100%; height: 100%;"></iframe>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="layout_footer">
|
||||
<el-button type="info" @click="handleClose">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
store: Object
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const showDrawerClose = ref(false)
|
||||
const drawerVisible = ref(false)
|
||||
const title = ref('附件')
|
||||
const iframeSrc = ref('')
|
||||
|
||||
watch(() => props.store, (newValue) => {
|
||||
if (newValue && newValue.openDditFlag !== undefined) {
|
||||
drawerVisible.value = newValue.openDditFlag
|
||||
}
|
||||
if (newValue && newValue.src !== undefined) {
|
||||
iframeSrc.value = newValue.src
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
const handleClose = () => {
|
||||
iframeSrc.value = ''
|
||||
emit('close')
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
</script>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,19 @@
|
|||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<el-container class="layout-container">
|
||||
<el-header>
|
||||
<nav-header />
|
||||
</el-header>
|
||||
<el-container>
|
||||
<el-aside :class="{ 'collapsed': isCollapse, 'expanded': !isCollapse }">
|
||||
<div class="scrollable_menu">
|
||||
<nav-menu :is-collapse="isCollapse" @toggleCollapse="toggleCollapse" />
|
||||
</div>
|
||||
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<Tabs />
|
||||
<Home v-show="showDefaultContent" />
|
||||
<router-view />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import NavHeader from '@/components/layouts/navHeader.vue'
|
||||
import NavMenu from '@/components/layouts/navMenu.vue'
|
||||
import Tabs from '@/components/layouts/tabs.vue'
|
||||
import Home from '@/views/home/index.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const showDefaultContent = ref(true)
|
||||
const isCollapse = ref(false)
|
||||
// 监听路由变化
|
||||
watch(() => route.path, (newPath) => {
|
||||
showDefaultContent.value = newPath === '/home'
|
||||
}, { immediate: true })
|
||||
const toggleCollapse = () => {
|
||||
isCollapse.value = !isCollapse.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-container {
|
||||
height: 100vh;
|
||||
|
||||
:deep(.el-header) {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-aside) {
|
||||
// background-color: #304156;
|
||||
background: #fff;
|
||||
color: #fff;
|
||||
transition: width 0.3s;
|
||||
height: calc(100vh - 60px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:deep(.el-main) {
|
||||
background-color: #f0f2f5;
|
||||
padding: 20px;
|
||||
transition: margin-left 0.3s;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.expanded {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollable_menu {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: var(--color-bg1)
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,83 @@
|
|||
<style lang="scss" scoped>
|
||||
.layout_menu {
|
||||
border: none;
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user {
|
||||
margin: 5px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--horizontal>.el-menu-item:nth-child(1) {
|
||||
margin-right: auto;
|
||||
}
|
||||
.el-menu--horizontal > .el-menu-item {
|
||||
border-bottom: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<el-menu :default-active="activeIndex" class="layout_menu" mode="horizontal" :ellipsis="false" @select="handleSelect"
|
||||
background-color="#0A397D" text-color="#fff" active-text-color="#fff">
|
||||
<el-menu-item index=" 0">
|
||||
<span class="title">{{ systemName }}</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="1" @click="handleLogout">系统切换</el-menu-item>
|
||||
<el-sub-menu index="2">
|
||||
<template #title>{{ userName }}</template>
|
||||
<el-menu-item index="2-1" @click="reset">密码重置</el-menu-item>
|
||||
<el-menu-item index="2-2" @click="onExit">退出系统</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<ResetDetail v-if="resetDetailFlag" :resetDetailFlag = resetDetailFlag @close="handleClose" />
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import ResetDetail from "./resetDetail.vue";
|
||||
|
||||
const userStore = useUserStore()
|
||||
const systemName = JSON.parse(sessionStorage.getItem('system')).systemName
|
||||
const userName = JSON.parse(sessionStorage.getItem('userInfo')).idp
|
||||
const resetDetailFlag = ref(false)
|
||||
|
||||
const reset = () => {
|
||||
resetDetailFlag.value = true
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
resetDetailFlag.value = false
|
||||
}
|
||||
const handleLogout = () => {
|
||||
userStore.logout()
|
||||
sessionStorage.clear()
|
||||
window.open(import.meta.env.VITE_HOME_URL, '_self')
|
||||
}
|
||||
const activeIndex = ref('1')
|
||||
const currentTheme = ref('default')
|
||||
|
||||
const toggleTheme = (type = 'default') => {
|
||||
currentTheme.value = type
|
||||
document.documentElement.style.setProperty('--color-bg1', `var(--color-${currentTheme.value}-bg1)`)
|
||||
document.documentElement.style.setProperty('--color-color1', `var(--color-${currentTheme.value}-color1)`)
|
||||
}
|
||||
const handleSelect = () => { }
|
||||
|
||||
const onExit = () => {
|
||||
userStore.logout()
|
||||
sessionStorage.clear()
|
||||
window.open(import.meta.env.VITE_HOME_URL + '?access=exit', '_self');
|
||||
}
|
||||
onMounted(() => {
|
||||
toggleTheme()
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<el-menu v-if="permissionStore.isRoutesGenerated"
|
||||
:default-active="route.path"
|
||||
class="nav-menu"
|
||||
background-color="#04285C"
|
||||
text-color="#bfcbd9"
|
||||
active-text-color="#409EFF"
|
||||
@select="handleSelect"
|
||||
:collapse="isCollapse"
|
||||
unique-opened>
|
||||
<recursive-menu-item
|
||||
v-for="routeItem in menuRoutes"
|
||||
:key="routeItem.path"
|
||||
:route-item="routeItem"
|
||||
:base-path="routeItem.path"
|
||||
/>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
import RecursiveMenuItem from './recursiveMenuItem.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const { isCollapse } = defineProps({
|
||||
isCollapse: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 过滤出需要显示在菜单中的路由
|
||||
const menuRoutes = computed(() => {
|
||||
return permissionStore.routes.filter(route => {
|
||||
return !route.meta?.hidden && (route.children?.length > 0 || route.meta?.title)
|
||||
})
|
||||
})
|
||||
|
||||
// 菜单选择事件
|
||||
const handleSelect = (index) => {
|
||||
try {
|
||||
router.push(index)
|
||||
.then(() => {
|
||||
// console.log('Navigation successful to:', index)
|
||||
})
|
||||
.catch(() => {
|
||||
// console.error('Navigation failed:', error)
|
||||
router.push('/home')
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Navigation error:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
height: 100%;
|
||||
border-right: none;
|
||||
background: #0A397D;
|
||||
|
||||
:deep(.el-menu-item) {
|
||||
&.is-active {
|
||||
background-color: #04285C;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
background-color: #103261;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout_menu {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<el-menu-item v-if="!hasChildren" :index="fullPath">
|
||||
<el-icon v-if="routeItem.meta?.icon && !isSvgIcon">
|
||||
<component :is="routeItem.meta.icon" />
|
||||
</el-icon>
|
||||
<svg-icon v-else-if="routeItem.meta?.icon && isSvgIcon" :name="routeItem.meta.icon" />
|
||||
<span>{{ routeItem.meta?.title }}</span>
|
||||
</el-menu-item>
|
||||
<el-sub-menu v-else :index="fullPath">
|
||||
<template #title>
|
||||
<el-icon v-if="routeItem.meta?.icon && !isSvgIcon">
|
||||
<component :is="routeItem.meta.icon" />
|
||||
</el-icon>
|
||||
<svg-icon v-else-if="routeItem.meta?.icon && isSvgIcon" :name="routeItem.meta.icon" />
|
||||
<span>{{ routeItem.meta?.title }}</span>
|
||||
</template>
|
||||
<recursive-menu-item v-for="child in routeItem.children" :key="child.path" :route-item="child"
|
||||
:base-path="fullPath" />
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
routeItem: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const hasChildren = computed(() => {
|
||||
return props.routeItem.children && props.routeItem.children.length > 0
|
||||
})
|
||||
|
||||
const isSvgIcon = computed(() => {
|
||||
return props.routeItem.meta?.icon?.startsWith('svg-')
|
||||
})
|
||||
|
||||
const fullPath = computed(() => {
|
||||
// 如果 basePath 已经以当前路径结尾,直接返回 basePath
|
||||
if (props.basePath.endsWith(props.routeItem.path)) {
|
||||
return props.basePath
|
||||
}
|
||||
// 否则拼接路径,并确保没有重复的斜杠
|
||||
return `${props.basePath}/${props.routeItem.path}`.replace(/\/+/g, '/')
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<el-dialog v-model="drawerVisible" title="重置密码" width="650" align-center @close="handleClose()"
|
||||
:close-on-click-modal="showDrawerClose" :close-on-press-escape="showDrawerClose">
|
||||
<el-form ref="ruleFormRef" label-width="150" :model="form" :rules="rules" status-icon class="layout_form">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="原密码" prop="password">
|
||||
<el-input v-model="form.password" name="password" clearable
|
||||
placeholder="请输入原密码" maxlength="16" show-word-limit show-password />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="form.newPassword" name="newPassword" clearable show-password
|
||||
placeholder="请输入新密码" maxlength="16" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="再次输入新密码" prop="qrNewPassword">
|
||||
<el-input v-model="form.qrNewPassword" name="qrNewPassword" clearable show-password
|
||||
placeholder="请输入再次输入新密码" maxlength="16" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="layout_footer">
|
||||
<el-button type="info" @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { showMessage } from '@/common/message'
|
||||
import regexPatterns from '@/utils/regexp.js';
|
||||
import { ResetPassword } from '@/api/system'
|
||||
|
||||
const props = defineProps({
|
||||
resetDetailFlag: Boolean
|
||||
});
|
||||
const showDrawerClose = ref(false)
|
||||
const emit = defineEmits(['close'])
|
||||
const drawerVisible = ref(false)
|
||||
const form = ref({
|
||||
password: '',
|
||||
newPassword: '',
|
||||
qrNewPassword: '',
|
||||
id: '',
|
||||
})
|
||||
const ruleFormRef = ref()
|
||||
|
||||
watch(() => props.resetDetailFlag, (newValue) => {
|
||||
if (newValue && newValue !== undefined) {
|
||||
drawerVisible.value = newValue
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
const checkidPasswordDifficult = (rule, value, callback) => {
|
||||
if (!regexPatterns.passwordDifficult.test(value)) {
|
||||
callback(new Error('请输入大写字母、小写字母、数字、特殊字符组成的8-16位密码'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const checkidPasswordsDifficult = (rule, value, callback) => {
|
||||
if (value !== form.value.qrNewPassword) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
}
|
||||
if (!regexPatterns.passwordDifficult.test(value)) {
|
||||
callback(new Error('请输入大写字母、小写字母、数字、特殊字符组成的8-16位密码'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = {
|
||||
password: [
|
||||
{ required: true, validator: checkidPasswordDifficult, trigger: 'change' },
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, validator: checkidPasswordDifficult, trigger: 'change' },
|
||||
],
|
||||
qrNewPassword: [
|
||||
{ required: true, validator: checkidPasswordsDifficult, trigger: 'change' },
|
||||
],
|
||||
}
|
||||
|
||||
const resetPassword = () => {
|
||||
ResetPassword(form.value).then(res => {
|
||||
if (res.StatusCode === 200) {
|
||||
showMessage("success", "重置密码成功");
|
||||
handleClose("refresh");
|
||||
} else {
|
||||
showMessage("error", res.Message);
|
||||
}
|
||||
}).catch(error => {
|
||||
showMessage("error", error.Message);
|
||||
})
|
||||
}
|
||||
const submitForm = async () => {
|
||||
const userInfo = JSON.parse(sessionStorage.getItem('userInfo'));
|
||||
if (userInfo) {
|
||||
form.value.id = userInfo.sub
|
||||
ruleFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
resetPassword()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
showMessage('error', '用户信息获取失败,请重新登录')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false
|
||||
form.value = {
|
||||
password: '',
|
||||
newPassword: '',
|
||||
qrNewPassword: '',
|
||||
id: '',
|
||||
}
|
||||
emit('close')
|
||||
}
|
||||
|
||||
</script>
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="tabs-container">
|
||||
<el-tabs
|
||||
v-model="tabsStore.activeTab"
|
||||
type="card"
|
||||
@tab-click="handleTabClick"
|
||||
@tab-remove="handleTabRemove"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="item in tabsStore.tabs"
|
||||
:key="item.path"
|
||||
:label="item.title"
|
||||
:name="item.path"
|
||||
:closable="!item.fixed"
|
||||
>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useTabsStore } from '@/stores/tabs'
|
||||
import { watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const tabsStore = useTabsStore()
|
||||
|
||||
// 添加标签页
|
||||
watch(() => route, (newRoute) => {
|
||||
if (newRoute.meta?.title && !newRoute.meta?.noTab) {
|
||||
const fromMenu = newRoute.query.fromMenu || false
|
||||
tabsStore.addTab({
|
||||
...newRoute,
|
||||
meta: {
|
||||
...newRoute.meta,
|
||||
fromMenu
|
||||
}
|
||||
})
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
// 标签页点击事件
|
||||
const handleTabClick = (tab) => {
|
||||
tabsStore.switchTab(tab.props.name)
|
||||
}
|
||||
|
||||
// 关闭标签页事件
|
||||
const handleTabRemove = (targetPath) => {
|
||||
tabsStore.removeTab(targetPath)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tabs-container {
|
||||
background: #fff;
|
||||
padding: 6px 6px 0;
|
||||
// border-radius: 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,34 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './utils/rem'
|
||||
import './assets/scss/main.scss';
|
||||
import ElementPlus from 'element-plus'//引入elmentplus的组件
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
import 'element-plus/dist/index.css'//引入elmentplus的组件的样式
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue' //图标
|
||||
import 'virtual:svg-icons-register'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue' // 导入 SVG 组件
|
||||
|
||||
//需要npm install pinia-plugin-persist npm install pinia-plugin-persist@1.0.0 -- 支持本地数据持久化保存
|
||||
import piniaPluginPersist from 'pinia-plugin-persist'
|
||||
|
||||
import NotificationService from './common/notification.js';
|
||||
|
||||
import '@/stores/login'
|
||||
const app = createApp(App)
|
||||
|
||||
//让ElementPlus 图标文件生效
|
||||
for (const[key,component] of Object.entries(ElementPlusIconsVue)){
|
||||
app.component(key,component)
|
||||
}
|
||||
|
||||
app.component('svg-icon', SvgIcon)
|
||||
app.use(createPinia().use(piniaPluginPersist));
|
||||
|
||||
app.provide('notification', NotificationService);
|
||||
app.use(router)
|
||||
app.use(ElementPlus,{locale: zhCn});//全局引入ElementPlus
|
||||
|
||||
app.mount('#app')
|
|
@ -0,0 +1,118 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
|
||||
export const constantRoutes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/home',
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: () => import('@/components/layouts/defaultLayout.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '首页',
|
||||
icon: 'House'
|
||||
},
|
||||
},
|
||||
// {
|
||||
// path: '/personnePostPhysicalExamination',
|
||||
// name: 'personnePostPhysicalExamination',
|
||||
// meta: {
|
||||
// requiresAuth: true,
|
||||
// title: '人员岗前体检',
|
||||
// icon: 'User'
|
||||
// },
|
||||
// component: () => import('@/components/layouts/defaultLayout.vue')
|
||||
// },
|
||||
// {
|
||||
// path: '/personnelHealthMonitoring',
|
||||
// name: 'personnelHealthMonitoring',
|
||||
// meta: {
|
||||
// requiresAuth: true,
|
||||
// title: '人员岗前体检',
|
||||
// icon: 'User'
|
||||
// },
|
||||
// component: () => import('@/components/layouts/defaultLayout.vue')
|
||||
// },
|
||||
{
|
||||
path: '/404',
|
||||
name: '404',
|
||||
meta: {
|
||||
title: '404',
|
||||
hidden: true,
|
||||
noTab: true
|
||||
},
|
||||
component: () => import('@/views/error/404.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/404',
|
||||
hidden: true
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: constantRoutes
|
||||
})
|
||||
|
||||
export function resetRouter() {
|
||||
const newRouter = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: constantRoutes
|
||||
})
|
||||
router.matcher = newRouter.matcher
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const token = sessionStorage.getItem('token')
|
||||
if (!token) {
|
||||
window.open(import.meta.env.VITE_HOME_URL, '_self');
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取权限store
|
||||
const permissionStore = usePermissionStore()
|
||||
//permissionStore.generateRoutes()
|
||||
|
||||
// 如果路由还没有生成,先生成路由
|
||||
if (!permissionStore.isRoutesGenerated) {
|
||||
try {
|
||||
await permissionStore.generateRoutes()
|
||||
// 重新进入当前路由
|
||||
if (to.path === '/404') {
|
||||
next('/home')
|
||||
} else {
|
||||
next({ ...to, replace: true })
|
||||
}
|
||||
return
|
||||
} catch (error) {
|
||||
console.error('Failed to generate routes:', error)
|
||||
next('/404')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查路由是否存在
|
||||
if (to.matched.length === 0 && to.path !== '/404') {
|
||||
next('/404')
|
||||
return
|
||||
}
|
||||
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (token) {
|
||||
next()
|
||||
} else {
|
||||
window.open(import.meta.env.VITE_HOME_URL, '_self');
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
|
@ -0,0 +1,5 @@
|
|||
import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
export default pinia
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
import { createPinia } from 'pinia'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
const pinia = createPinia()
|
||||
const permissionStore = usePermissionStore(pinia)
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
if (urlParams.get('token')) {
|
||||
sessionStorage.setItem('token', urlParams.get('token'))
|
||||
sessionStorage.setItem('system', urlParams.get('system'))
|
||||
sessionStorage.setItem('userInfo', urlParams.get('userInfo'))
|
||||
const token = sessionStorage.getItem('token')
|
||||
|
||||
if (!token) {
|
||||
window.open(import.meta.env.VITE_HOME_URL, '_self');
|
||||
} else {
|
||||
permissionStore.generateRoutes()
|
||||
}
|
||||
} else {
|
||||
if (sessionStorage.getItem('token')) {
|
||||
permissionStore.generateRoutes()
|
||||
|
||||
} else {
|
||||
window.open(import.meta.env.VITE_HOME_URL, '_self');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { GetLoginResourceTree } from '@/api/common/index'
|
||||
import router, { constantRoutes } from '@/router'
|
||||
const modules = import.meta.glob('../views/**/index.vue')
|
||||
const layoutModule = import.meta.glob('../components/layouts/defaultLayout.vue')
|
||||
|
||||
export const usePermissionStore = defineStore('permission', () => {
|
||||
const routes = ref([...constantRoutes])
|
||||
const dynamicRoutes = ref([])
|
||||
const isRoutesGenerated = ref(false)
|
||||
|
||||
// 处理组件路径
|
||||
const loadComponent = (path) => {
|
||||
try {
|
||||
const formattedPath = path.replace(/^\/+/, '').replace(/\/+/g, '/')
|
||||
const componentPath = `../views/${formattedPath}/index.vue`
|
||||
if (modules[componentPath]) {
|
||||
return modules[componentPath]
|
||||
}
|
||||
return modules['../views/error/404.vue']
|
||||
} catch (error) {
|
||||
console.error('Failed to load component:', error)
|
||||
return modules['../views/error/404.vue']
|
||||
}
|
||||
}
|
||||
|
||||
const getLayoutComponent = () => {
|
||||
const layoutPath = '../components/layouts/defaultLayout.vue'
|
||||
return layoutModule[layoutPath]
|
||||
}
|
||||
|
||||
// 生成动态路由
|
||||
const generateRoutes = async () => {
|
||||
const token = sessionStorage.getItem('token')
|
||||
if(!token) return
|
||||
|
||||
try {
|
||||
// 如果路由已经生成过,直接返回
|
||||
if (isRoutesGenerated.value) {
|
||||
return dynamicRoutes.value
|
||||
}
|
||||
|
||||
const system = JSON.parse(sessionStorage.getItem('system'))
|
||||
if (!system.systemId) {
|
||||
console.error('未找到系统信息')
|
||||
isRoutesGenerated.value = true
|
||||
return []
|
||||
}
|
||||
|
||||
const params = {
|
||||
serviceID: system.systemId
|
||||
}
|
||||
|
||||
const response = await GetLoginResourceTree(params)
|
||||
isRoutesGenerated.value = true
|
||||
|
||||
|
||||
if (response.StatusCode === 200 && response.Data) {
|
||||
sessionStorage.setItem('routers', JSON.stringify(response.Data))
|
||||
const routerData = response.Data
|
||||
const newRoutes = []
|
||||
|
||||
// 处理路由数据
|
||||
routerData.forEach(item => {
|
||||
if (item.ResType === '菜单') {
|
||||
const route = {
|
||||
path: '/' + item.Path,
|
||||
name: item.Path,
|
||||
meta: {
|
||||
title: item.Name,
|
||||
icon: item.IconPath,
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: getLayoutComponent(),
|
||||
children: item.Children ? item.Children.filter(child => child.ResType === '菜单').map(child => {
|
||||
const childPath = `${item.Path}/${child.Path}`
|
||||
return {
|
||||
path: child.Path,
|
||||
name: childPath,
|
||||
meta: {
|
||||
title: child.Name,
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: loadComponent(childPath),
|
||||
children: child.Children ? child.Children.filter(grandChild => grandChild.ResType === '菜单').map(grandChild => {
|
||||
const grandChildPath = `${childPath}/${grandChild.Path}`
|
||||
return {
|
||||
path: grandChild.Path,
|
||||
name: grandChildPath,
|
||||
meta: {
|
||||
title: grandChild.Name,
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: loadComponent(grandChildPath)
|
||||
}
|
||||
}) : []
|
||||
}
|
||||
}) : []
|
||||
}
|
||||
newRoutes.push(route)
|
||||
}
|
||||
})
|
||||
|
||||
// 清除现有的动态路由
|
||||
dynamicRoutes.value.forEach(route => {
|
||||
if (router.hasRoute(route.name)) {
|
||||
router.removeRoute(route.name)
|
||||
}
|
||||
})
|
||||
|
||||
// 添加新的动态路由
|
||||
newRoutes.forEach(route => {
|
||||
if (!router.hasRoute(route.name)) {
|
||||
router.addRoute(route)
|
||||
}
|
||||
})
|
||||
|
||||
// 更新路由状态
|
||||
dynamicRoutes.value = newRoutes
|
||||
routes.value = [...constantRoutes, ...newRoutes]
|
||||
return newRoutes
|
||||
}
|
||||
return []
|
||||
} catch (error) {
|
||||
console.error('获取路由数据失败:', error)
|
||||
window.open(import.meta.env.VITE_HOME_URL, '_self')
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
routes,
|
||||
dynamicRoutes,
|
||||
isRoutesGenerated,
|
||||
generateRoutes
|
||||
}
|
||||
})
|
|
@ -0,0 +1,81 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import router from '@/router'
|
||||
|
||||
export const useTabsStore = defineStore('tabs', () => {
|
||||
const defaultTabs = [{
|
||||
title: '首页',
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
fixed: true
|
||||
}]
|
||||
|
||||
const tabs = ref(JSON.parse(sessionStorage.getItem('tabs')) || defaultTabs)
|
||||
const activeTab = ref(sessionStorage.getItem('activeTab') || '/home')
|
||||
|
||||
// 添加标签页
|
||||
const addTab = (route) => {
|
||||
if (route.meta?.noTab || route.meta?.hidden) {
|
||||
return
|
||||
}
|
||||
|
||||
const isExist = tabs.value.some(tab => tab.path === route.path)
|
||||
if (!isExist) {
|
||||
tabs.value.push({
|
||||
title: route.meta.title,
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
fixed: false
|
||||
})
|
||||
}
|
||||
activeTab.value = route.path
|
||||
saveToStorage()
|
||||
}
|
||||
|
||||
// 移除标签页
|
||||
const removeTab = (targetPath) => {
|
||||
const targetIndex = tabs.value.findIndex(tab => tab.path === targetPath)
|
||||
if (targetIndex === -1 || tabs.value[targetIndex].fixed) return
|
||||
if (activeTab.value === targetPath) {
|
||||
const nextTab = tabs.value[targetIndex + 1] || tabs.value[targetIndex - 1]
|
||||
if (nextTab) {
|
||||
activeTab.value = nextTab.path
|
||||
router.push(nextTab.path)
|
||||
}
|
||||
}
|
||||
|
||||
tabs.value.splice(targetIndex, 1)
|
||||
saveToStorage()
|
||||
}
|
||||
|
||||
// 切换标签页
|
||||
const switchTab = (path) => {
|
||||
activeTab.value = path
|
||||
router.push(path)
|
||||
saveToStorage()
|
||||
}
|
||||
|
||||
|
||||
// 保存到sessionStorage
|
||||
const saveToStorage = () => {
|
||||
sessionStorage.setItem('tabs', JSON.stringify(tabs.value))
|
||||
sessionStorage.setItem('activeTab', activeTab.value)
|
||||
}
|
||||
|
||||
// 清空标签页
|
||||
const clearTabs = () => {
|
||||
tabs.value = [...defaultTabs]
|
||||
activeTab.value = '/home'
|
||||
sessionStorage.removeItem('tabs')
|
||||
sessionStorage.removeItem('activeTab')
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
activeTab,
|
||||
addTab,
|
||||
removeTab,
|
||||
switchTab,
|
||||
clearTabs
|
||||
}
|
||||
})
|
|
@ -0,0 +1,34 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useTabsStore } from '@/stores/tabs'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const isLoggedIn = ref(sessionStorage.getItem('isLoggedIn') === 'true')
|
||||
const userInfo = ref(JSON.parse(sessionStorage.getItem('userInfo')) || null)
|
||||
|
||||
// 登录
|
||||
const login = (info) => {
|
||||
const tabsStore = useTabsStore()
|
||||
tabsStore.clearTabs()
|
||||
isLoggedIn.value = true
|
||||
userInfo.value = info
|
||||
sessionStorage.setItem('isLoggedIn', 'true')
|
||||
sessionStorage.setItem('userInfo', JSON.stringify(info))
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logout = () => {
|
||||
isLoggedIn.value = false
|
||||
userInfo.value = null
|
||||
sessionStorage.clear()
|
||||
const tabsStore = useTabsStore()
|
||||
tabsStore.clearTabs()
|
||||
}
|
||||
|
||||
return {
|
||||
isLoggedIn,
|
||||
userInfo,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
|
@ -0,0 +1,316 @@
|
|||
* {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
ul,
|
||||
li {
|
||||
list-style-type: none;
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
font-size: 14px;
|
||||
background-color: #11145C;
|
||||
color: #fff;
|
||||
}
|
||||
body,html{
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
#module {
|
||||
height:100%;
|
||||
}
|
||||
#app {
|
||||
height:100%;
|
||||
}
|
||||
/*登录*/
|
||||
.login {
|
||||
background: url(./assets/img/bg.jpg) no-repeat;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-size: cover;
|
||||
perspective: 800px;
|
||||
}
|
||||
.login-content {
|
||||
display: flex;
|
||||
align-items:center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate(0,-50%);
|
||||
background: rgba(255,255,255,.3);
|
||||
padding: 40px;
|
||||
}
|
||||
.login-box {
|
||||
width: 250px;
|
||||
height: 200px;
|
||||
margin: 0 10px;
|
||||
box-shadow: 0 3px 20px 2px rgba(0, 0, 0, .3);
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
.login-box i{
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 45vh;
|
||||
padding: 20px;
|
||||
box-shadow: 0 3px 20px 2px rgba(0, 0, 0, .3);
|
||||
/* position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%); */
|
||||
text-align: center;
|
||||
}
|
||||
.login-form .el-form-item__content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
line-height: 32px;
|
||||
position: relative;
|
||||
font-size: var(--font-size);
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.login-form .el-form-item__content .el-button {
|
||||
margin-left: -70px;
|
||||
}
|
||||
.login-form .login-title,
|
||||
.login-form .el-form-item__label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.login .login-title {
|
||||
font-size: 25px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.login .login-button {
|
||||
margin-left: -70px;
|
||||
width: 100px;
|
||||
}
|
||||
.exit {
|
||||
float: right;
|
||||
margin:10px;
|
||||
padding: 2px 10px;
|
||||
background: #233444;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
/* 首页 */
|
||||
#module .el-container {
|
||||
height:100%;
|
||||
}
|
||||
#module .el-container .el-menu-item ,
|
||||
#module .el-container .el-sub-menu .el-sub-menu__title{
|
||||
color:#f9f9f9;
|
||||
}
|
||||
#module .el-container .el-menu-item:hover ,
|
||||
#module .el-container .el-sub-menu .el-sub-menu__title:hover{
|
||||
color:#fff;
|
||||
background:#30377d;
|
||||
}
|
||||
#module .el-menu-item.is-active,
|
||||
#module .el-sub-menu.is-active {
|
||||
color: #fff;
|
||||
background:#30377d;
|
||||
}
|
||||
|
||||
#module .el-container .el-sub-menu.is-active.is-opened,
|
||||
#module .el-container .el-sub-menu.is-active.is-opened .el-sub-menu__title {
|
||||
color: #f9f9f9;
|
||||
background:#20266d;
|
||||
}
|
||||
#module .el-container .el-header {
|
||||
/* border-bottom: 1px solid #f1f1f1; */
|
||||
background: #20266d;
|
||||
}
|
||||
#module .el-container .el-header .exit{
|
||||
margin-top:10px;
|
||||
}
|
||||
#module .aside {
|
||||
background: #20266d;
|
||||
}
|
||||
#module .aside .el-menu {
|
||||
border-right: solid 1px #20266d;
|
||||
background: #20266d;
|
||||
}
|
||||
#module .el-submenu__title {
|
||||
color:#f9f9f9;
|
||||
}
|
||||
|
||||
/* 面包屑 */
|
||||
#module .breadcrumb {
|
||||
padding:20px;
|
||||
display: inline-block;
|
||||
}
|
||||
#module .breadcrumb ul li {
|
||||
display: inline-block;
|
||||
}
|
||||
#module .breadcrumb ul li span{
|
||||
padding: 0 5px;
|
||||
}
|
||||
#module .el-carousel__item img {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
opacity: 0.75;
|
||||
line-height: 200px;
|
||||
margin: 0;
|
||||
max-width:100%;
|
||||
}
|
||||
#module .breadcrumb .el-breadcrumb__item .el-breadcrumb__inner ,
|
||||
#module .breadcrumb .el-breadcrumb__item .el-breadcrumb__inner a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#module .el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
#module .el-carousel__item:nth-child(2n+1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
|
||||
/* 首页主体部分 */
|
||||
#module .index-content {
|
||||
display: flex;
|
||||
width:100%;
|
||||
}
|
||||
#module .index-content .content-left {
|
||||
flex:30%;
|
||||
}
|
||||
#module .index-content .content-right {
|
||||
flex:70%;
|
||||
}
|
||||
#module .index-content .content {
|
||||
margin: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
|
||||
#module .el-main .el-card {
|
||||
border:none;
|
||||
background: #20266d;
|
||||
}
|
||||
|
||||
#module .el-main .el-card .el-card__body {
|
||||
color: #fff;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
.index-title li {
|
||||
width: 18%;
|
||||
}
|
||||
}
|
||||
.index-title li:nth-child(1) {
|
||||
background: #fe8688;
|
||||
}
|
||||
.index-title li:nth-child(2) {
|
||||
background: #feba35;
|
||||
}
|
||||
.index-title li:nth-child(3) {
|
||||
background: #1bc6bd;
|
||||
}
|
||||
.index-title li:nth-child(4) {
|
||||
background: #ba99ff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.banner {
|
||||
position: relative;
|
||||
float:left;
|
||||
}
|
||||
.banner .banner-circle {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
color: #fff;
|
||||
}
|
||||
.banner .banner-circle li{
|
||||
display:inline-block;
|
||||
background: rgba(0,0,0,.3);
|
||||
border-radius: 50%;
|
||||
padding:5px;
|
||||
margin:2px;
|
||||
}
|
||||
.banner .banner-circle ul {
|
||||
text-align: center;
|
||||
}
|
||||
.banner .banner-circle .selected {
|
||||
background: rgba(0,0,0,.8);
|
||||
}
|
||||
.banner img {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.table{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.table tbody {
|
||||
background:#fff;
|
||||
}
|
||||
.table td,
|
||||
.table th{
|
||||
border: 1px solid #1890ff;
|
||||
padding: 10px;
|
||||
}
|
||||
.table thead tr {
|
||||
background:#1f76b3;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.module-box {
|
||||
display:flex; /*弹性布局*/
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.list-box{
|
||||
margin: 0 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 3px 20px 2px rgb(0 0 0 / 30%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
.list-box i {
|
||||
font-size: 40px;
|
||||
}
|
||||
.box:nth-child(1) .list-box {
|
||||
background-image:linear-gradient(#2277ce,#0356ab);
|
||||
}
|
||||
.box:nth-child(2) .list-box {
|
||||
background-image:linear-gradient(#ba5bd8,#9f3fbd);
|
||||
}
|
||||
.box:nth-child(3) .list-box{
|
||||
background-image:linear-gradient(#ea8052,#cf673a);
|
||||
}
|
||||
.box:nth-child(4) .list-box {
|
||||
background-image:linear-gradient(#84f395,#71c24a);
|
||||
}
|
||||
.box:nth-child(5) .list-box {
|
||||
background-image:linear-gradient(#A44A38,#902569);
|
||||
}
|
||||
.marginBottom {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.pagination{
|
||||
margin: 10px 0;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { GetDictionaryidDicItems } from '@/api/system/index'
|
||||
export const getDictionaryItems = async (dicName, refArray, type) => {
|
||||
const dictionarys = JSON.parse(sessionStorage.getItem('dictionarys'))
|
||||
if (dictionarys) {
|
||||
const dictionary = dictionarys.find(item => item.DicName === dicName)
|
||||
if (dictionary) {
|
||||
const params = {
|
||||
DictionaryId: dictionary.Id,
|
||||
LambdaExp: `IsDeleted==false`,
|
||||
SelectSorts: '[{ "FieldName": "CreateTime", "IsAsc": false }]',
|
||||
}
|
||||
try {
|
||||
const res = await GetDictionaryidDicItems(params)
|
||||
if (res.StatusCode === 200) {
|
||||
if (type === 'number') {
|
||||
res.Data.forEach(item => {
|
||||
item.Value = Number(item.Value)
|
||||
})
|
||||
}
|
||||
refArray.value = res.Data
|
||||
} else {
|
||||
refArray.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
refArray.value = []
|
||||
}
|
||||
} else {
|
||||
refArray.value = []
|
||||
}
|
||||
} else {
|
||||
refArray.value = []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { computed } from 'vue'
|
||||
const now = computed(() => new Date())
|
||||
export function disabledDateAge(time) {
|
||||
const year = now.value.getFullYear() - 100
|
||||
const minDate = new Date(Date.UTC(year, 0))
|
||||
const maxDate = new Date(now.value.getTime())
|
||||
const isBeforeMin = time.getTime() < minDate.getTime()
|
||||
const isAfterMax = time.getTime() > maxDate.getTime()
|
||||
return isBeforeMin || isAfterMax
|
||||
}
|
||||
|
||||
export function disabledDate2020Start(time) {
|
||||
return 2020 - time.getFullYear() > 0 || time.getTime() > Date.now()
|
||||
}
|
||||
|
||||
export function disabledDate(time, month = 0) {
|
||||
const minDate = new Date(Date.UTC(1901, 0));
|
||||
const currentDate = new Date(now.value.getTime());
|
||||
const year = currentDate.getFullYear();
|
||||
const monthValue = currentDate.getMonth();
|
||||
const newMonth = monthValue - month;
|
||||
const adjustedYear = year + Math.floor(newMonth / 12);
|
||||
const adjustedMonth = ((newMonth % 12) + 12) % 12;
|
||||
const maxDate = new Date(Date.UTC(adjustedYear, adjustedMonth + 1, 0));
|
||||
return time.getTime() < minDate.getTime() || time.getTime() > maxDate.getTime();
|
||||
}
|
||||
|
||||
export function disabledDayRange(time, beforeDays = 0, afterDays = 0) {
|
||||
const currentTime = now.value.getTime();
|
||||
const minDate = beforeDays > 0
|
||||
? new Date(currentTime - beforeDays * 86400000)
|
||||
: new Date(Date.UTC(1901, 0, 1));
|
||||
|
||||
const maxDate = afterDays > 0
|
||||
? new Date(currentTime + afterDays * 86400000)
|
||||
: new Date(currentTime);
|
||||
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
maxDate.setHours(23, 59, 59, 999);
|
||||
const checkDate = new Date(time);
|
||||
checkDate.setHours(0, 0, 0, 0);
|
||||
|
||||
return checkDate.getTime() < minDate.getTime() ||
|
||||
checkDate.getTime() > maxDate.getTime();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import axios from 'axios';
|
||||
export default async function fetchPublicIp() {
|
||||
try {
|
||||
const response = await axios.get('https://ipinfo.io/json');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
export default function formatDate(date, format) {
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = d.getDate().toString().padStart(2, '0');
|
||||
const hours = d.getHours().toString().padStart(2, '0');
|
||||
const minutes = d.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = d.getSeconds().toString().padStart(2, '0');
|
||||
|
||||
switch (format) {
|
||||
case 'YYYY-MM-DD':
|
||||
return `${year}-${month}-${day}`;
|
||||
case 'YYYY':
|
||||
return `${year}`;
|
||||
case 'YYYY-MM':
|
||||
return `${year}-${month}`;
|
||||
case 'YYYY-MM-DD HH:MM:SS':
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
default:
|
||||
return date;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// src/utils/permission.js
|
||||
export function extractButtonPermissions(routers) {
|
||||
const buttonPermissions = {};
|
||||
function traverse(nodes) {
|
||||
nodes.forEach(node => {
|
||||
if (node.ResType === "按钮") {
|
||||
const parentPath = findParentPath(routers, node.ParentId);
|
||||
if (parentPath) {
|
||||
if (!buttonPermissions[parentPath]) {
|
||||
buttonPermissions[parentPath] = [];
|
||||
}
|
||||
buttonPermissions[parentPath].push(node.AliasName);
|
||||
}
|
||||
}
|
||||
if (node.Children && node.Children.length > 0) {
|
||||
traverse(node.Children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
traverse(routers);
|
||||
return buttonPermissions;
|
||||
}
|
||||
|
||||
|
||||
function findParentPath(nodes, parentId) {
|
||||
for (const node of nodes) {
|
||||
if (node.Id === parentId) {
|
||||
return node.Path;
|
||||
}
|
||||
if (node.Children && node.Children.length > 0) {
|
||||
const path = findParentPath(node.Children, parentId);
|
||||
if (path) return path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function hasPermission(buttonPermissions, pagePath, buttonAlias) {
|
||||
return buttonPermissions[pagePath]?.includes(buttonAlias) || false;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// 正则表达式
|
||||
const regexPatterns = {
|
||||
phone: /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1589]))\d{8}$/,
|
||||
passwordDifficult: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,16}$/,
|
||||
passwordMedium: /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^\w\s\d])([^\s]){8,16}$/,
|
||||
passwordSimple: /^(?=.*[a-zA-Z])(?=.*\d)[A-Za-z\d]{8,16}$/,
|
||||
name: /^([\u4E00-\u9FA5]{2,10})$/,
|
||||
idCard: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dX]$/,
|
||||
number: /^\d{1,10}$/,
|
||||
numberDate: /^([1-9]|[12][0-9]|3[01])$/,
|
||||
numberDecimal: /^-?\d{1,10}(\.\d{1,2})?$/,
|
||||
bankCard: /^\d{16,19}$/
|
||||
};
|
||||
|
||||
|
||||
export default regexPatterns;
|
|
@ -0,0 +1,10 @@
|
|||
const baseSize = 16
|
||||
function setRem () {
|
||||
const scale = document.documentElement.clientWidth / 1920
|
||||
let fontSize = (baseSize * Math.min(scale, 2))>12 ? (baseSize * Math.min(scale, 2)): 12
|
||||
document.documentElement.style.fontSize = fontSize + 'px'
|
||||
}
|
||||
setRem()
|
||||
window.onresize = function () {
|
||||
setRem()
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import axios from "axios";
|
||||
import { showMessage } from '@/common/message'
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
|
||||
const baseURL = import.meta.env.MODE === 'development' ? '' : import.meta.env.VITE_BASE_URL;
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: baseURL,
|
||||
withCredentials: true, // 必须携带 Cookie
|
||||
headers: {
|
||||
'Content-Type': 'application/json' // 避免触发复杂预检
|
||||
}
|
||||
});
|
||||
service.interceptors.request.use(
|
||||
async (config) => {
|
||||
|
||||
const tokenStr = sessionStorage.getItem('token');
|
||||
const token = tokenStr ? tokenStr : '';
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
//响应拦截器--从服务器响应之后--得到的结果,优先处理
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
const handle401Error = () => {
|
||||
ElMessageBox.alert('登录已过期,请重新登录,点击确认将退出系统。', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
showClose: false,
|
||||
callback: () => {
|
||||
window.open(import.meta.env.VITE_HOME_URL + '?access=exit', '_self');
|
||||
},
|
||||
});
|
||||
return Promise.reject(new Error(`请求失败,状态码: 401`));
|
||||
};
|
||||
|
||||
if (response.status !== 200) {
|
||||
return Promise.reject(new Error(`请求失败,状态码: ${response.status}`));
|
||||
}
|
||||
|
||||
if (response.data.StatusCode === 401 ||
|
||||
(response.data.Message && response.data.Message.includes('401'))) {
|
||||
return handle401Error();
|
||||
}
|
||||
|
||||
return response.data;
|
||||
},
|
||||
async error => {
|
||||
if (error.response?.status === 401) {
|
||||
ElMessageBox.alert('登录已过期,请重新登录,点击确认将退出系统。', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
callback: () => {
|
||||
window.open(import.meta.env.VITE_HOME_URL + '?access=exit', '_self');
|
||||
},
|
||||
});
|
||||
}
|
||||
showMessage('error', error.message || "请求错误");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
export default service;
|
|
@ -0,0 +1,59 @@
|
|||
import axios from "axios";
|
||||
import { showMessage } from '@/common/message'
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
const baseURL = import.meta.env.MODE === 'development' ? '' : import.meta.env.VITE_BASE_URL;
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: baseURL,
|
||||
withCredentials: true, // 必须携带 Cookie
|
||||
headers: {
|
||||
'Content-Type': 'application/json' // 避免触发复杂预检
|
||||
}
|
||||
});
|
||||
|
||||
service.interceptors.request.use(
|
||||
async (config) => {
|
||||
const tokenStr = sessionStorage.getItem('token');
|
||||
const token = tokenStr ? tokenStr : '';
|
||||
if (token) {
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
|
||||
|
||||
service.interceptors.response.use(response => {
|
||||
if (response.status === 200) {
|
||||
if (response.data.StatusCode === 401) {
|
||||
ElMessageBox.alert('登录已过期,请重新登录,点击确认将退出系统。', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
callback: () => {
|
||||
window.open(import.meta.env.VITE_HOME_URL + '?access=exit', '_self')
|
||||
},
|
||||
})
|
||||
return Promise.reject(new Error(`请求失败,状态码: ${response.data.StatusCode}`));
|
||||
}
|
||||
return response.data;
|
||||
} else {
|
||||
return Promise.reject(new Error(`请求失败,状态码: ${response.status}`));
|
||||
}
|
||||
|
||||
}, async (error) => {
|
||||
if (error.response && error.response.status === 401) {
|
||||
ElMessageBox.alert('登录已过期,请重新登录,点击确认将退出系统。', '提示', {
|
||||
confirmButtonText: '确认',
|
||||
callback: () => {
|
||||
window.open(import.meta.env.VITE_HOME_URL + '?access=exit', '_self')
|
||||
},
|
||||
})
|
||||
}
|
||||
showMessage('error', error.message || "请求错误");
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
|
||||
export default service;
|
|
@ -0,0 +1,40 @@
|
|||
import { readFileSync, readdirSync } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
|
||||
/**
|
||||
* 获取所有 SVG 图标
|
||||
* @param dir 图标目录
|
||||
*/
|
||||
export function getSvgIcons(dir) {
|
||||
try {
|
||||
const icons = []
|
||||
const files = readdirSync(dir)
|
||||
files.forEach((file) => {
|
||||
if (file.endsWith('.svg')) {
|
||||
const content = readFileSync(resolve(dir, file), 'utf-8')
|
||||
icons.push({
|
||||
name: file.replace('.svg', ''),
|
||||
content
|
||||
})
|
||||
}
|
||||
})
|
||||
return icons
|
||||
} catch (error) {
|
||||
console.error('Error reading SVG files:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite SVG 插件配置
|
||||
* @param {string} iconDirs 图标目录
|
||||
*/
|
||||
export function configSvgIconsPlugin(iconDirs) {
|
||||
return createSvgIconsPlugin({
|
||||
iconDirs: [iconDirs],
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
inject: 'body-last',
|
||||
customDomId: '__svg__icons__dom__'
|
||||
})
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
export const scrollToFirstError = () => {
|
||||
const isError = document.getElementsByClassName('is-error');
|
||||
if (isError[0]) {
|
||||
isError[0].scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="not-found">
|
||||
<img class="layout_img" src="@/assets/images/error/404_1.png" alt="404" />
|
||||
<el-button type="primary" @click="router.push('/home')">返回首页</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.not-found {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.layout_img {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,181 @@
|
|||
<style lang="scss" scoped>
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active {
|
||||
-webkit-box-shadow: 0 0 0 30px white inset !important;
|
||||
-webkit-text-fill-color: #000 !important;
|
||||
transition: background-color 5000s ease-in-out 0s !important;
|
||||
}
|
||||
|
||||
.card_title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
}
|
||||
|
||||
.card_header_count {
|
||||
margin: 5px;
|
||||
padding: 5px 6px;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
}
|
||||
|
||||
.todos_card {
|
||||
.card_content {
|
||||
padding: 0 10px;
|
||||
height: 65vh;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.card_content_item {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.card_content_item_left {
|
||||
.card_content_item_left_top {
|
||||
display: flex;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 10, 26, 0.89);
|
||||
}
|
||||
|
||||
.text {
|
||||
padding-left: 10px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
.card_content_item_left_bottom {
|
||||
margin-top: 10px;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice_card {
|
||||
.card_content {
|
||||
padding: 0 10px;
|
||||
height: 65vh;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.card_content_item {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.card_content_item_left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.card_content_item_tag {
|
||||
width: 40px;
|
||||
margin-right: 10px;
|
||||
padding: 2px 8px;
|
||||
background: #f5f5f5;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.card_content_item_text {
|
||||
width: 280px;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.card_content_item_right {
|
||||
width: 90px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #141414;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="overall-container">
|
||||
<!-- <el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-card class="todos_card" :style="{ height: cardHeight }">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card_title">代办事项</span>
|
||||
<span class="card_header_count">{{ toDos.length }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="card_content">
|
||||
<div class="card_content_item" v-for="(item, index) in toDos" :key="index">
|
||||
<div class="card_content_item_left">
|
||||
<div class="card_content_item_left_top">
|
||||
<div class="title">{{ item.title }}</div>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
<div class="card_content_item_left_bottom">{{ item.date }}</div>
|
||||
</div>
|
||||
<div class="card_content_item_right">
|
||||
<el-button type="primary" plain>去处理</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card class="notice_card" :style="{ height: cardHeight }">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card_title">通知</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="card_content">
|
||||
<div class="card_content_item" v-for="(item, index) in notice" :key="index">
|
||||
<div class="card_content_item_left">
|
||||
<span class="card_content_item_tag">{{ item.tag }}</span>
|
||||
<span class="card_content_item_text">{{ item.text }}</span>
|
||||
</div>
|
||||
<div class="card_content_item_right">{{ item.date }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import { ref } from 'vue'
|
||||
// const cardHeight = ref('75vh')
|
||||
// const toDos = ref([])
|
||||
|
||||
// const notice = ref([
|
||||
// {
|
||||
// tag: '保障平台',
|
||||
// text: '测试版本0.1试运行',
|
||||
// date: '2025-05-20',
|
||||
// },
|
||||
// ])
|
||||
|
||||
</script>
|
|
@ -0,0 +1,105 @@
|
|||
import { fileURLToPath, URL } from 'url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import postCssPxToRem from 'postcss-pxtorem'
|
||||
import { resolve } from 'path'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
}),
|
||||
],
|
||||
productionSourceMap: false,
|
||||
configureWebpack: {
|
||||
devtool: false
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3002,
|
||||
host: '0.0.0.0',
|
||||
https: false,
|
||||
proxy: {
|
||||
// 系统管理服务19901
|
||||
'/api/sys': {
|
||||
target: env.VITE_BASE_URL_SYSTEM,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/sys/, '/api'),
|
||||
secure: false,
|
||||
onProxyReq: (proxyReq, req, res) => {
|
||||
console.log('Proxying request:');
|
||||
console.log(` Method: ${req.method}`);
|
||||
console.log(` Path: ${proxyReq.path}`);
|
||||
console.log(' Request Headers:', req.headers);
|
||||
}
|
||||
},
|
||||
'/auth': {
|
||||
target: env.VITE_BASE_URL_SYSTEM,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/auth/, ''),
|
||||
secure: false,
|
||||
onProxyReq: (proxyReq, req, res) => {
|
||||
console.log('Proxying request:');
|
||||
console.log(` Method: ${req.method}`);
|
||||
console.log(` Path: ${proxyReq.path}`);
|
||||
console.log(' Request Headers:', req.headers);
|
||||
}
|
||||
},
|
||||
// 业务系统服务19903
|
||||
'/api/lmg': {
|
||||
target: env.VITE_BASE_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/lmg/, '/api'),
|
||||
secure: false,
|
||||
onProxyReq: (proxyReq, req, res) => {
|
||||
console.log('Proxying request:');
|
||||
console.log(` Method: ${req.method}`);
|
||||
console.log(` Path: ${proxyReq.path}`);
|
||||
console.log(' Request Headers:', req.headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
additionalData: `
|
||||
@use "@/assets/scss/variables" as v;
|
||||
@use "@/assets/scss/mixins" as m;
|
||||
`
|
||||
}
|
||||
},
|
||||
postcss: {
|
||||
plugins: [
|
||||
postCssPxToRem({
|
||||
rootValue: 16,
|
||||
propList: ['*'],
|
||||
selectorBlackList: ['no-rem'],
|
||||
replace: true,
|
||||
mediaQuery: false,
|
||||
minPixelValue: 0
|
||||
}),
|
||||
]
|
||||
}
|
||||
},
|
||||
define: {
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(false),
|
||||
},
|
||||
esbuild: {
|
||||
drop: ['console', 'debugger']
|
||||
}
|
||||
};
|
||||
});
|