Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f58e15fe60 | ||
|
|
64e6a9ce31 | ||
|
|
babbee9c62 | ||
|
|
5f59baf186 | ||
|
|
edf4894719 | ||
|
|
49855f198a | ||
|
|
1fb7d94353 | ||
|
|
c71e5a6b01 | ||
|
|
9c7223483a | ||
|
|
0326018db6 | ||
|
|
44bca173aa | ||
|
|
3a6620b4e9 | ||
|
|
e3da2f821d | ||
|
|
e69cd3af6c | ||
|
|
435b683cfc | ||
|
|
8de5841fb5 | ||
|
|
8f036e9be7 | ||
|
|
b159ccd420 |
@@ -0,0 +1,46 @@
|
||||
"use strict";
|
||||
module.exports = {
|
||||
types: [
|
||||
{ value: "✨新增", name: "新增: 新的内容" },
|
||||
{ value: "🐛修复", name: "修复: 修复一个Bug" },
|
||||
{ value: "📝文档", name: "文档: 变更的只有文档" },
|
||||
{ value: "💄格式", name: "格式: 空格, 分号等格式修复" },
|
||||
{ value: "♻️重构", name: "重构: 代码重构,注意和特性、修复区分开" },
|
||||
{ value: "⚡️性能", name: "性能: 提升性能" },
|
||||
{ value: "✅测试", name: "测试: 添加一个测试" },
|
||||
{ value: "🔧工具", name: "工具: 开发工具变动(构建、脚手架工具等)" },
|
||||
{ value: "⏪回滚", name: "回滚: 代码回退" }
|
||||
],
|
||||
scopes: [
|
||||
{ name: "leetcode" },
|
||||
{ name: "javascript" },
|
||||
{ name: "typescript" },
|
||||
{ name: "Vue" },
|
||||
{ name: "node" }
|
||||
],
|
||||
// it needs to match the value for field type. Eg.: 'fix'
|
||||
/* scopeOverrides: {
|
||||
fix: [
|
||||
{name: 'merge'},
|
||||
{name: 'style'},
|
||||
{name: 'e2eTest'},
|
||||
{name: 'unitTest'}
|
||||
]
|
||||
}, */
|
||||
// override the messages, defaults are as follows
|
||||
messages: {
|
||||
type: "选择一种你的提交类型:",
|
||||
scope: "选择一个scope (可选):",
|
||||
// used if allowCustomScopes is true
|
||||
customScope: "Denote the SCOPE of this change:",
|
||||
subject: "短说明:\n",
|
||||
body: '长说明,使用"|"换行(可选):\n',
|
||||
breaking: "非兼容性说明 (可选):\n",
|
||||
footer: "关联关闭的issue,例如:#31, #34(可选):\n",
|
||||
confirmCommit: "确定提交说明?(yes/no)"
|
||||
},
|
||||
allowCustomScopes: true,
|
||||
allowBreakingChanges: ["特性", "修复"],
|
||||
// limit subject length
|
||||
subjectLimit: 100
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE='Qomo Ai'
|
||||
|
||||
# 开发环境配置
|
||||
VITE_APP_ENV='development'
|
||||
|
||||
# 子应用路径配置
|
||||
VITE_APP_PATH='/layoutApp/newEnergy'
|
||||
|
||||
# 子应用名称
|
||||
VITE_APP_NAME='newEnergy'
|
||||
|
||||
# 开发环境
|
||||
VITE_APP_BASE_API=http://172.16.1.141:8082
|
||||
@@ -0,0 +1,15 @@
|
||||
# 页面标题
|
||||
VITE_APP_TITLE='Qomo Ai'
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV='production'
|
||||
|
||||
# 子应用路径配置
|
||||
VITE_APP_PATH='/layoutApp/newEnergy'
|
||||
|
||||
# 子应用名称
|
||||
VITE_APP_NAME='newEnergy'
|
||||
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS=gzip
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:vue/vue3-essential", "prettier "],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"plugins": ["vue"],
|
||||
"rules": {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
**/*.log
|
||||
|
||||
tests/**/coverage/
|
||||
tests/e2e/reports
|
||||
selenium-debug.log
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.local
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"*.{js,jsx,vue}": ["prettier --write ."],
|
||||
"*.md": ["prettier --write"]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# pnpm 配置
|
||||
shamefully-hoist=true
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
@qomo:registry=https://packages.aliyun.com/669a1b3bbedb1acc325c96c0/npm/npm-registry/
|
||||
@qomo-platform:registry=https://packages.aliyun.com/669a1b3bbedb1acc325c96c0/npm/npm-registry/
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"quoteProps": "as-needed",
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"rangeStart": 0,
|
||||
"endOfLine": "crlf"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 RuoYi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
extends: ["cz"],
|
||||
rules: {}
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
|
||||
<meta content="webkit" name="renderer" />
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport" />
|
||||
<link href="/favicon.ico" rel="icon" />
|
||||
<title>QoMo 子应用</title>
|
||||
<script>
|
||||
window._CONFIG = {
|
||||
domianURL: 'http://172.16.0.87:8095',
|
||||
// 其他配置项...
|
||||
};
|
||||
</script>
|
||||
<!--[if lt IE 11
|
||||
]><script>
|
||||
window.location.href = "/html/ie.html";
|
||||
</script><!
|
||||
[endif]-->
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#subApp {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.chromeframe {
|
||||
margin: 0.2em 0;
|
||||
background: #ccc;
|
||||
color: #000;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
||||
#loader-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
#loader {
|
||||
display: block;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #fff;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
-ms-animation: spin 2s linear infinite;
|
||||
-moz-animation: spin 2s linear infinite;
|
||||
-o-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
#loader:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #fff;
|
||||
-webkit-animation: spin 3s linear infinite;
|
||||
-moz-animation: spin 3s linear infinite;
|
||||
-o-animation: spin 3s linear infinite;
|
||||
-ms-animation: spin 3s linear infinite;
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
#loader:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #fff;
|
||||
-moz-animation: spin 1.5s linear infinite;
|
||||
-o-animation: spin 1.5s linear infinite;
|
||||
-ms-animation: spin 1.5s linear infinite;
|
||||
-webkit-animation: spin 1.5s linear infinite;
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 51%;
|
||||
height: 100%;
|
||||
background: #7171c6;
|
||||
z-index: 1000;
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-left {
|
||||
-webkit-transform: translateX(-100%);
|
||||
-ms-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-right {
|
||||
-webkit-transform: translateX(100%);
|
||||
-ms-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.loaded #loader {
|
||||
opacity: 0;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper {
|
||||
visibility: hidden;
|
||||
-webkit-transform: translateY(-100%);
|
||||
-ms-transform: translateY(-100%);
|
||||
transform: translateY(-100%);
|
||||
-webkit-transition: all 0.3s 1s ease-out;
|
||||
transition: all 0.3s 1s ease-out;
|
||||
}
|
||||
|
||||
.no-js #loader-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-js h1 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title {
|
||||
font-family: "Open Sans";
|
||||
color: #fff;
|
||||
font-size: 19px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
z-index: 9999999999999;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
opacity: 1;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title span {
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="subApp"></div>
|
||||
<script src="/src/main.js" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "project",
|
||||
"version": "1.0.0",
|
||||
"description": "QomoAi 子应用",
|
||||
"author": "lqy",
|
||||
"license": "MIT",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build:prod": "vite build",
|
||||
"build:stage": "vite build --mode staging",
|
||||
"preview": "vite preview",
|
||||
"prepare": "husky install",
|
||||
"pre-commit": "lint-staged",
|
||||
"commit": "git-cz"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.3.1",
|
||||
"@qomo-platform/core": "^1.0.151",
|
||||
"@vueup/vue-quill": "1.2.0",
|
||||
"@vueuse/core": "10.6.1",
|
||||
"axios": "1.6.7",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "2.4.3",
|
||||
"file-saver": "2.0.5",
|
||||
"js-cookie": "3.0.5",
|
||||
"jsencrypt": "3.3.2",
|
||||
"less": "^4.2.0",
|
||||
"md5": "^2.3.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"postcss-pxtorem": "^6.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"uuid": "^10.0.0",
|
||||
"vite-plugin-qiankun": "^1.0.15",
|
||||
"vue": "3.3.9",
|
||||
"vue-router": "4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/config-conventional": "^19.0.3",
|
||||
"@vitejs/plugin-vue": "4.5.0",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue/compiler-sfc": "3.3.9",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"commitizen": "^4.3.0",
|
||||
"commitlint": "^19.0.3",
|
||||
"commitlint-config-cz": "^0.13.3",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"cz-customizable": "^7.0.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.22.0",
|
||||
"husky": "^8.0.0",
|
||||
"husky-init": "^8.0.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"postcss": "^8.4.35",
|
||||
"sass": "1.69.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"unplugin-auto-import": "0.17.1",
|
||||
"unplugin-vue-setup-extend-plus": "1.0.0",
|
||||
"vite": "5.1.4",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vite-plugin-svg-icons": "2.0.1"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-customizable"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
tailwindcss: {}
|
||||
}
|
||||
};
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
import { handleThemeStyle } from "@/utils/theme";
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// 初始化主题样式
|
||||
handleThemeStyle(useSettingsStore().theme);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
import request from "@/service/request";
|
||||
|
||||
/*新增版本号*/
|
||||
export function costStandardVersionAdd(data) {
|
||||
return request.post("/costCount/addCostCountTask", {}, data, {
|
||||
message: {
|
||||
success: "生成成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
// 查询列表
|
||||
export function costStandardList(params) {
|
||||
return request.get(`/costCount/list`, params);
|
||||
}
|
||||
//删除
|
||||
export function deleteCostStandard(data) {
|
||||
return request.delete("/costStandard/delete", data,{
|
||||
message: {
|
||||
success: "删除成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import request from "@/service/request";
|
||||
/*获取小时费率表格内容*/
|
||||
export function hourRateList(params) {
|
||||
return request.get(`/hourRate/list`, params);
|
||||
}
|
||||
/*新增小时费率*/
|
||||
export function addHourRate(data) {
|
||||
return request.post("/hourRate/add", {}, data, {
|
||||
message: {
|
||||
success: "新增成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*编辑小时费率*/
|
||||
export function editHourRate(data) {
|
||||
return request.post("/hourRate/edit", {}, data, {
|
||||
message: {
|
||||
success: "编辑成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*删除小时费率*/
|
||||
export function deleteHourRate(data) {
|
||||
return request.delete("/hourRate/delete", data,{
|
||||
message: {
|
||||
success: "删除成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*批量删除小时费率*/
|
||||
export function deleteBatchHourRate(data) {
|
||||
return request.delete("/hourRate/deleteBatch", data, {
|
||||
message: {
|
||||
success: "删除成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import request from "@/service/request";
|
||||
export function getDictCode(params) {
|
||||
return request.get(`/sys/dictItem/type`, params);
|
||||
}
|
||||
|
||||
//保存流程图
|
||||
export function saveFlowModel(data) {
|
||||
return request.post("/api/flowDefinition/saveModel", {}, data, {
|
||||
message: {
|
||||
success: `保存成功`,
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
//执行流程
|
||||
export function flowInstanceExecute(id) {
|
||||
return request.post(`/api/dataIntegration/flowInstance/execute/${id}`, {}, {}, {
|
||||
message: {
|
||||
success: `流程开始运行`,
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
//查询数据源
|
||||
export function dataSourceList(params) {
|
||||
return request.get("/api/group/treeList", params, {
|
||||
error: true
|
||||
});
|
||||
}
|
||||
//查询组件
|
||||
export function getComponentTree(params) {
|
||||
return request.get("/api/componentGroup/getComponentTree", params, {
|
||||
error: true
|
||||
});
|
||||
}
|
||||
//查询数据库列表
|
||||
export function databaseListSchemas(params) {
|
||||
return request.get('/api/database/listSchemas', params, {
|
||||
error: true
|
||||
});
|
||||
}
|
||||
|
||||
//查询表
|
||||
export function listTablesBySchemaPage(params) {
|
||||
return request.get('/api/database/listTablesBySchemaPage', params);
|
||||
}
|
||||
|
||||
// 获取表字段
|
||||
export function listColumns(params) {
|
||||
return request.get('/api/database/listColumns', params);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import request from "@/service/request";
|
||||
|
||||
/**
|
||||
* @description 登录方法
|
||||
*/
|
||||
export function login(data) {
|
||||
return request.post("/sys/login", {}, data, {
|
||||
headers: {
|
||||
isToken: false,
|
||||
repeatSubmit: false
|
||||
},
|
||||
message: {
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取用户详细信息
|
||||
*/
|
||||
export function getInfo() {
|
||||
return request.get("/sys/user/getUserInfo");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取验证码
|
||||
*/
|
||||
export function getCodeImg(data) {
|
||||
return request.get(
|
||||
`/sys/randomImage/${data}`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: "get",
|
||||
timeout: 20000
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import request from "@/service/request";
|
||||
|
||||
export const listByApp = params => {
|
||||
return request.get('/sys/permission/listByApp',params)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import request from "@/service/request";
|
||||
/*获取模型列表*/
|
||||
export function modelList(params) {
|
||||
return request.get(`/cost-model/pageList`, params);
|
||||
}
|
||||
/*新增*/
|
||||
export function addModel(data) {
|
||||
return request.post("/cost-model/add", {}, data, {
|
||||
message: {
|
||||
success: "新增成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*编辑*/
|
||||
export function editCostModel(data) {
|
||||
return request.post("/cost-model/edit", {}, data, {
|
||||
message: {
|
||||
success: "编辑成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*删除*/
|
||||
export function deleteModel(data) {
|
||||
return request.delete("/cost-model/delete", data,{
|
||||
message: {
|
||||
success: "删除成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*获取流程管理员列表*/
|
||||
export function listAll() {
|
||||
return request.get(`/sys/user/listAll`);
|
||||
}
|
||||
/*获取环境配置*/
|
||||
export function environmentList(params) {
|
||||
return request.get(`/api/environment/list`, params);
|
||||
}
|
||||
/*获取操作系统租户*/
|
||||
export function tenantList(params) {
|
||||
return request.get(`/api/tenant/list`, params);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import request from "@/service/request";
|
||||
|
||||
// 查询列表
|
||||
export function costStandardDetailList(params) {
|
||||
return request.get(`/costStandardDetail/list`, params);
|
||||
}
|
||||
/*导出excel*/
|
||||
export function exportcostStandardDetail(params) {
|
||||
return request.get("/costStandardDetail/exportXls", params,{ responseType:"blob"}, {
|
||||
message: {
|
||||
success: "导出成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import request from "@/service/request";
|
||||
|
||||
// 查询列表
|
||||
export function queryBomTree(params) {
|
||||
return request.get(`/bomTree/queryBomTree`, params);
|
||||
}
|
||||
// 查询物料编号接口
|
||||
export function costMaterialBomList(params) {
|
||||
return request.get(`/cost-material-bom/list`, params);
|
||||
}
|
||||
// 查询版本号接口
|
||||
export function costStandardVersionList(params) {
|
||||
return request.get(`/costStandardVersion/list`, params);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import request from "@/service/request";
|
||||
/*获取小时费率表格内容*/
|
||||
export function c7525Costist(params) {
|
||||
return request.get(`/c7525cost/list`, params);
|
||||
}
|
||||
/*新增小时费率*/
|
||||
export function addC7525cost(data) {
|
||||
return request.post("/c7525cost/add", {}, data, {
|
||||
message: {
|
||||
success: "新增成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*编辑小时费率*/
|
||||
export function editC7525cost(data) {
|
||||
return request.post("/c7525cost/edit", {}, data, {
|
||||
message: {
|
||||
success: "编辑成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*删除小时费率*/
|
||||
export function deleteC7525cost(data) {
|
||||
return request.delete("/c7525cost/delete", data,{
|
||||
message: {
|
||||
success: "删除成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*批量删除小时费率*/
|
||||
export function deleteBatchC7525cost(data) {
|
||||
return request.delete("/c7525cost/deleteBatch", data,{
|
||||
message: {
|
||||
success: "删除成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
/*导出excel*/
|
||||
export function exportExcelC7525cost(data) {
|
||||
return request.get("c7525cost/exportXls", data, {
|
||||
message: {
|
||||
success: "导出成功",
|
||||
error: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
|
||||
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
|
||||
<g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
|
||||
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
|
||||
<mask id="mask-3" fill="white">
|
||||
<use xlink:href="#path-2"></use>
|
||||
</mask>
|
||||
<g id="Rectangle-18">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
|
||||
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
|
||||
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
|
||||
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
|
||||
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
|
||||
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
|
||||
<mask id="mask-3" fill="white">
|
||||
<use xlink:href="#path-2"></use>
|
||||
</mask>
|
||||
<g id="Rectangle-18">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
|
||||
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
|
||||
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 509 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 588 B |
|
After Width: | Height: | Size: 868 B |
|
After Width: | Height: | Size: 720 B |
|
After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,99 @@
|
||||
@import "./variables.module.scss";
|
||||
|
||||
@mixin colorBtn($color) {
|
||||
background: $color;
|
||||
|
||||
&:hover {
|
||||
color: $color;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
background: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blue-btn {
|
||||
@include colorBtn($blue);
|
||||
}
|
||||
|
||||
.light-blue-btn {
|
||||
@include colorBtn($light-blue);
|
||||
}
|
||||
|
||||
.red-btn {
|
||||
@include colorBtn($red);
|
||||
}
|
||||
|
||||
.pink-btn {
|
||||
@include colorBtn($pink);
|
||||
}
|
||||
|
||||
.green-btn {
|
||||
@include colorBtn($green);
|
||||
}
|
||||
|
||||
.tiffany-btn {
|
||||
@include colorBtn($tiffany);
|
||||
}
|
||||
|
||||
.yellow-btn {
|
||||
@include colorBtn($yellow);
|
||||
}
|
||||
|
||||
.pan-btn {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
padding: 14px 36px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: 600ms ease all;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&:hover {
|
||||
background: #fff;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
width: 100%;
|
||||
transition: 600ms ease all;
|
||||
}
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
width: 0;
|
||||
transition: 400ms ease all;
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: inherit;
|
||||
top: inherit;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
color: #fff;
|
||||
-webkit-appearance: none;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
margin: 0;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// cover some element-ui styles
|
||||
|
||||
.el-breadcrumb__inner,
|
||||
.el-breadcrumb__inner a {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
input[type="file"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-upload__input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cell {
|
||||
.el-tag {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.small-padding {
|
||||
.cell {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-width {
|
||||
.el-button--mini {
|
||||
padding: 7px 10px;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-col {
|
||||
.cell {
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
|
||||
.el-tag {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// to fixed https://github.com/ElemeFE/element/issues/2461
|
||||
.el-dialog {
|
||||
transform: none;
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
// refine element ui upload
|
||||
.upload-container {
|
||||
.el-upload {
|
||||
width: 100%;
|
||||
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dropdown
|
||||
.el-dropdown-menu {
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// fix date-picker ui bug in filter-item
|
||||
.el-range-editor.el-input__inner {
|
||||
display: inline-flex !important;
|
||||
}
|
||||
|
||||
// to fix el-date-picker css style
|
||||
.el-range-separator {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.el-menu--collapse
|
||||
> div
|
||||
> .el-submenu
|
||||
> .el-submenu__title
|
||||
.el-submenu__icon-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-dropdown .el-dropdown-link {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
.el-form-item {
|
||||
.el-select,
|
||||
.el-cascader,
|
||||
.el-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
:root {
|
||||
--el-color-primary: #2474ff;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
@import "./variables.module.scss";
|
||||
@import "./mixin.scss";
|
||||
@import "./transition.scss";
|
||||
@import "./element-ui.scss";
|
||||
@import "./sidebar.scss";
|
||||
@import "./btn.scss";
|
||||
@import "./ruoyi.scss";
|
||||
@import "element-plus/theme-chalk/src/index";
|
||||
@import "@qomo-platform/core/src/index.scss";
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
PingFang SC,
|
||||
Hiragino Sans GB,
|
||||
Microsoft YaHei,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#subApp {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.padding-content {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.pr-5 {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.pl-5 {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inlineBlock {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
&:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
aside {
|
||||
background: #eef1f6;
|
||||
padding: 8px 24px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 2px;
|
||||
display: block;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
color: #2c3e50;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
a {
|
||||
color: #337ab7;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: rgb(32, 160, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//main-container全局样式
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.components-container {
|
||||
margin: 30px 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sub-navbar {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
transition: 600ms ease position;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(32, 182, 249, 1) 0%,
|
||||
rgba(32, 182, 249, 1) 0%,
|
||||
rgba(33, 120, 241, 1) 100%,
|
||||
rgba(33, 120, 241, 1) 100%
|
||||
);
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.draft {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
|
||||
&.deleted {
|
||||
background: #d0d0d0;
|
||||
}
|
||||
}
|
||||
|
||||
.link-type,
|
||||
.link-type:focus {
|
||||
color: #337ab7;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: rgb(32, 160, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
padding-bottom: 10px;
|
||||
|
||||
.filter-item {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@mixin clearfix {
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin scrollBar {
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: #d3dce6;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #99a9bf;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin relative {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@mixin pct($pct) {
|
||||
width: #{$pct};
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@mixin triangle($width, $height, $color, $direction) {
|
||||
$width: $width/2;
|
||||
$color-border-style: $height solid $color;
|
||||
$transparent-border-style: $width solid transparent;
|
||||
height: 0;
|
||||
width: 0;
|
||||
|
||||
@if $direction==up {
|
||||
border-bottom: $color-border-style;
|
||||
border-left: $transparent-border-style;
|
||||
border-right: $transparent-border-style;
|
||||
} @else if $direction==right {
|
||||
border-left: $color-border-style;
|
||||
border-top: $transparent-border-style;
|
||||
border-bottom: $transparent-border-style;
|
||||
} @else if $direction==down {
|
||||
border-top: $color-border-style;
|
||||
border-left: $transparent-border-style;
|
||||
border-right: $transparent-border-style;
|
||||
} @else if $direction==left {
|
||||
border-right: $color-border-style;
|
||||
border-top: $transparent-border-style;
|
||||
border-bottom: $transparent-border-style;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* 通用css样式布局处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
|
||||
/** 基础通用 **/
|
||||
.pt5 {
|
||||
padding-top: 5px;
|
||||
}
|
||||
.pr5 {
|
||||
padding-right: 5px;
|
||||
}
|
||||
.pb5 {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.mt5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.mr5 {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.mb5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.ml5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.mt10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.mr10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.mb10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.mt20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.mr20 {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.ml20 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.el-form .el-form-item__label {
|
||||
font-weight: 700;
|
||||
}
|
||||
.el-dialog:not(.is-fullscreen) {
|
||||
margin-top: 6vh !important;
|
||||
}
|
||||
|
||||
.el-dialog.scrollbar .el-dialog__body {
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 70vh;
|
||||
padding: 10px 20px 0;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
.el-table__header-wrapper,
|
||||
.el-table__fixed-header-wrapper {
|
||||
th {
|
||||
word-break: break-word;
|
||||
background-color: #f8f8f9 !important;
|
||||
color: #515a6e;
|
||||
height: 40px !important;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.el-table__body-wrapper {
|
||||
.el-button [class*="el-icon-"] + span {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 表单布局 **/
|
||||
.form-header {
|
||||
font-size: 15px;
|
||||
color: #6379bb;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 8px 10px 25px 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
/** 表格布局 **/
|
||||
.pagination-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 15px;
|
||||
padding: 10px 20px !important;
|
||||
}
|
||||
|
||||
.el-dialog .pagination-container {
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
/* tree border */
|
||||
.tree-border {
|
||||
margin-top: 5px;
|
||||
border: 1px solid #e5e6e7;
|
||||
background: #ffffff none;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
right: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pagination-container .el-pagination > .el-pagination__jump {
|
||||
display: none !important;
|
||||
}
|
||||
.pagination-container .el-pagination > .el-pagination__sizes {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table .fixed-width .el-button--small {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
/** 表格更多操作下拉样式 */
|
||||
.el-table .el-dropdown-link {
|
||||
cursor: pointer;
|
||||
color: #2474ff;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.el-table .el-dropdown,
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-tree-node__content > .el-checkbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.list-group-striped > .list-group-item {
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-radius: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
padding-left: 0px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
border-bottom: 1px solid #e7eaec;
|
||||
border-top: 1px solid #e7eaec;
|
||||
margin-bottom: -1px;
|
||||
padding: 11px 0px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
||||
|
||||
.el-card__header {
|
||||
padding: 14px 15px 7px !important;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 15px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
.card-box {
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* button color */
|
||||
.el-button--cyan.is-active,
|
||||
.el-button--cyan:active {
|
||||
background: #20b2aa;
|
||||
border-color: #20b2aa;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.el-button--cyan:focus,
|
||||
.el-button--cyan:hover {
|
||||
background: #48d1cc;
|
||||
border-color: #48d1cc;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.el-button--cyan {
|
||||
background-color: #20b2aa;
|
||||
border-color: #20b2aa;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* text color */
|
||||
.text-navy {
|
||||
color: #1ab394;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #1c84c6;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #23c6c8;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #f8ac59;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #ed5565;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
/* image */
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.img-lg {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.avatar-upload-preview {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(50%, -50%);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 4px #ccc;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 拖拽列样式 */
|
||||
.sortable-ghost {
|
||||
opacity: 0.8;
|
||||
color: #fff !important;
|
||||
background: #42b983 !important;
|
||||
}
|
||||
|
||||
/* 表格右侧工具栏样式 */
|
||||
.top-right-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
#subApp {
|
||||
.main-container {
|
||||
height: calc(100% - 94px);
|
||||
transition: margin-left 0.28s;
|
||||
margin-left: $base-sidebar-width;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebarHide {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
-webkit-transition: width 0.28s;
|
||||
transition: width 0.28s;
|
||||
width: $base-sidebar-width !important;
|
||||
background-color: $base-menu-background;
|
||||
height: calc(100% - 60px) !important;
|
||||
position: fixed;
|
||||
font-size: 0px;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
overflow: hidden;
|
||||
-webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||
|
||||
// reset element-ui css
|
||||
.horizontal-collapse-transition {
|
||||
transition:
|
||||
0s width ease-in-out,
|
||||
0s padding-left ease-in-out,
|
||||
0s padding-right ease-in-out;
|
||||
}
|
||||
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.el-scrollbar__bar.is-vertical {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.has-logo {
|
||||
.el-scrollbar {
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
|
||||
.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.menu-title {
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.el-menu-item .el-menu-tooltip__trigger {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
// menu hover
|
||||
.sub-menu-title-noDropdown,
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.06) !important;
|
||||
}
|
||||
}
|
||||
|
||||
& .theme-dark .is-active > .el-sub-menu__title {
|
||||
color: $base-menu-color-active !important;
|
||||
}
|
||||
|
||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .el-sub-menu .el-menu-item {
|
||||
min-width: $base-sidebar-width !important;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.06) !important;
|
||||
}
|
||||
}
|
||||
|
||||
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
& .theme-dark .el-sub-menu .el-menu-item {
|
||||
background-color: $base-sub-menu-background !important;
|
||||
|
||||
&:hover {
|
||||
background-color: $base-sub-menu-hover !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hideSidebar {
|
||||
.sidebar-container {
|
||||
width: 54px !important;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
margin-left: 54px;
|
||||
}
|
||||
|
||||
.sub-menu-title-noDropdown {
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
|
||||
.el-tooltip {
|
||||
padding: 0 !important;
|
||||
|
||||
.svg-icon {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
overflow: hidden;
|
||||
|
||||
& > .el-sub-menu__title {
|
||||
padding: 0 !important;
|
||||
|
||||
.svg-icon {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--collapse {
|
||||
.el-sub-menu {
|
||||
& > .el-sub-menu__title {
|
||||
& > span {
|
||||
height: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
& > i {
|
||||
height: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--collapse .el-menu .el-sub-menu {
|
||||
min-width: $base-sidebar-width !important;
|
||||
}
|
||||
|
||||
// mobile responsive
|
||||
.mobile {
|
||||
.main-container {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
transition: transform 0.28s;
|
||||
width: $base-sidebar-width !important;
|
||||
}
|
||||
|
||||
&.hideSidebar {
|
||||
.sidebar-container {
|
||||
pointer-events: none;
|
||||
transition-duration: 0.3s;
|
||||
transform: translate3d(-$base-sidebar-width, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.withoutAnimation {
|
||||
.main-container,
|
||||
.sidebar-container {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when menu collapsed
|
||||
.el-menu--vertical {
|
||||
& > .el-menu {
|
||||
.svg-icon {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
&:hover {
|
||||
// you can use $sub-menuHover
|
||||
background-color: rgba(0, 0, 0, 0.06) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// the scroll bar appears when the sub-menu is too long
|
||||
> .el-menu--popup {
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: #d3dce6;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #99a9bf;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,49 @@
|
||||
// global transition css
|
||||
|
||||
/* fade */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.28s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* fade-transform */
|
||||
.fade-transform--move,
|
||||
.fade-transform-leave-active,
|
||||
.fade-transform-enter-active {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.fade-transform-enter {
|
||||
opacity: 0;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.fade-transform-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
/* breadcrumb transition */
|
||||
.breadcrumb-enter-active,
|
||||
.breadcrumb-leave-active {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.breadcrumb-move {
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// base color
|
||||
$blue: #324157;
|
||||
$light-blue: #3a71a8;
|
||||
$red: #c03639;
|
||||
$pink: #e65d6e;
|
||||
$green: #30b08f;
|
||||
$tiffany: #4ab7bd;
|
||||
$yellow: #fec171;
|
||||
$panGreen: #30b08f;
|
||||
|
||||
// 默认菜单主题风格
|
||||
$base-menu-color: #bfcbd9;
|
||||
$base-menu-color-active: #f4f4f5;
|
||||
$base-menu-background: #304156;
|
||||
$base-logo-title-color: #ffffff;
|
||||
|
||||
$base-menu-light-color: rgba(0, 0, 0, 0.7);
|
||||
$base-menu-light-background: #ffffff;
|
||||
$base-logo-light-title-color: #001529;
|
||||
|
||||
$base-sub-menu-background: #1f2d3d;
|
||||
$base-sub-menu-hover: #001528;
|
||||
|
||||
// 自定义暗色菜单风格
|
||||
/**
|
||||
$base-menu-color:hsla(0,0%,100%,.65);
|
||||
$base-menu-color-active:#fff;
|
||||
$base-menu-background:#001529;
|
||||
$base-logo-title-color: #ffffff;
|
||||
|
||||
$base-menu-light-color:rgba(0,0,0,.70);
|
||||
$base-menu-light-background:#ffffff;
|
||||
$base-logo-light-title-color: #001529;
|
||||
|
||||
$base-sub-menu-background:#000c17;
|
||||
$base-sub-menu-hover:#001528;
|
||||
*/
|
||||
|
||||
$--color-primary: #2474ff;
|
||||
$--color-success: #67c23a;
|
||||
$--color-warning: #e6a23c;
|
||||
$--color-danger: #f56c6c;
|
||||
$--color-info: #909399;
|
||||
|
||||
$base-sidebar-width: 200px;
|
||||
|
||||
// the :export directive is the magic sauce for webpack
|
||||
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
|
||||
:export {
|
||||
menuColor: $base-menu-color;
|
||||
menuLightColor: $base-menu-light-color;
|
||||
menuColorActive: $base-menu-color-active;
|
||||
menuBackground: $base-menu-background;
|
||||
menuLightBackground: $base-menu-light-background;
|
||||
subMenuBackground: $base-sub-menu-background;
|
||||
subMenuHover: $base-sub-menu-hover;
|
||||
sideBarWidth: $base-sidebar-width;
|
||||
logoTitleColor: $base-logo-title-color;
|
||||
logoLightTitleColor: $base-logo-light-title-color;
|
||||
primaryColor: $--color-primary;
|
||||
successColor: $--color-success;
|
||||
dangerColor: $--color-danger;
|
||||
infoColor: $--color-info;
|
||||
warningColor: $--color-warning;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
|
||||
<span
|
||||
v-if="
|
||||
item.redirect === 'noRedirect' || index === levelList.length - 1
|
||||
"
|
||||
class="no-redirect"
|
||||
>{{ item.meta.title }}</span
|
||||
>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const levelList = ref([]);
|
||||
|
||||
function getBreadcrumb() {
|
||||
// only show routes with meta.title
|
||||
let matched = route.matched.filter(item => item.meta && item.meta.title);
|
||||
const first = matched[0];
|
||||
// 判断是否为首页
|
||||
if (!isDashboard(first)) {
|
||||
matched = [{ path: "/index", meta: { title: "首页" } }].concat(matched);
|
||||
}
|
||||
|
||||
levelList.value = matched.filter(
|
||||
item => item.meta && item.meta.title && item.meta.breadcrumb !== false
|
||||
);
|
||||
}
|
||||
function isDashboard(route) {
|
||||
const name = route && route.name;
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return name.trim() === "Index";
|
||||
}
|
||||
function handleLink(item) {
|
||||
const { redirect, path } = item;
|
||||
if (redirect) {
|
||||
router.push(redirect);
|
||||
return;
|
||||
}
|
||||
router.push(path);
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
// if you go to the redirect page, do not update the breadcrumbs
|
||||
if (route.path.startsWith("/redirect/")) {
|
||||
return;
|
||||
}
|
||||
getBreadcrumb();
|
||||
});
|
||||
getBreadcrumb();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 8px;
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="tsx">
|
||||
//表格
|
||||
import { defineComponent, ref, onMounted, watch } from "vue";
|
||||
import * as echarts from 'echarts';
|
||||
export default defineComponent({
|
||||
props: {
|
||||
option:Object
|
||||
},
|
||||
setup(props) {
|
||||
const chartRef = ref();
|
||||
const render = () => {
|
||||
return (
|
||||
<div ref={chartRef} style="width: 100%; height: 100%;">
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
onMounted(() => {
|
||||
// 初始化图表
|
||||
const chartInstance = echarts.init(chartRef.value);
|
||||
chartInstance.setOption(props.option);
|
||||
window.addEventListener('resize', () => {
|
||||
chartInstance.resize();
|
||||
});
|
||||
});
|
||||
watch(props, (newVal, oldVal) => {
|
||||
if (newVal.option) {
|
||||
const chartInstance = echarts.init(chartRef.value);
|
||||
chartInstance.setOption(props.option);
|
||||
}
|
||||
});
|
||||
return render;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
@@ -0,0 +1,245 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import { useRequest } from "vue-request";
|
||||
import {
|
||||
dataSourceList,
|
||||
databaseListSchemas,
|
||||
listTablesBySchemaPage,
|
||||
listColumns
|
||||
} from "@/api";
|
||||
import icon_report from "@/assets/imgs/icon_report.png";
|
||||
import icon_folder from "@/assets/imgs/icon_folder.png";
|
||||
import dateIcon from "@/assets/imgs/dateIcon.png";
|
||||
import numberIcon from "@/assets/imgs/numberIcon.png";
|
||||
import stringIcon from "@/assets/imgs/stringIcon.png";
|
||||
import databaseIcon from "@/assets/imgs/database.png";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { da } from "element-plus/es/locale";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SqlStatement",
|
||||
props: {
|
||||
model: {
|
||||
type: Object
|
||||
},
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const typeNumber = [
|
||||
"int",
|
||||
"smallint",
|
||||
"bigint",
|
||||
"float",
|
||||
"double",
|
||||
"decimal",
|
||||
"bit"
|
||||
],
|
||||
typeDate = ["date", "datetime", "time", "timestamp"],
|
||||
typeString = ["char", "varchar", "varchar2", "text"];
|
||||
const { data, error } = useRequest(async () => await dataSourceList());
|
||||
|
||||
const {
|
||||
run,
|
||||
data: _schemaData_,
|
||||
loading
|
||||
} = useRequest(databaseListSchemas, {
|
||||
manual: true
|
||||
});
|
||||
|
||||
const {
|
||||
run: listTablesBySchemaPageRun,
|
||||
data: tablesData,
|
||||
loading: listTablesLoading
|
||||
} = useRequest(listTablesBySchemaPage, {
|
||||
manual: true
|
||||
});
|
||||
|
||||
const {
|
||||
run: listColumnsRun,
|
||||
data: listColumnsData,
|
||||
loading: listColumnsLoading
|
||||
} = useRequest(listColumns, {
|
||||
manual: true
|
||||
});
|
||||
|
||||
const schemaData = computed(() =>
|
||||
(_schemaData_.value || []).map(item => ({
|
||||
label: item.name,
|
||||
value: item.name
|
||||
}))
|
||||
);
|
||||
|
||||
const handleNodeClick = async node => {
|
||||
if (node.nodeType) {
|
||||
props.model.dataSource = node.name;
|
||||
props.model.url = node.url;
|
||||
props.model.username = node.userName;
|
||||
props.model.password = node.password;
|
||||
props.model.currentNode = node;
|
||||
run({ id: node.id });
|
||||
} else ElMessage.warning("选中的不是数据源,请重新选择");
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.model.schema,
|
||||
val => {
|
||||
if (val)
|
||||
listTablesBySchemaPageRun({
|
||||
id: props.model.currentNode.id,
|
||||
schemaName: props.model.schema,
|
||||
tableName: "",
|
||||
typeList: "table,view",
|
||||
pageNo: 1,
|
||||
pageSize: 1000
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const filterNodeMethod = (value, data) => data.name.includes(value);
|
||||
|
||||
const loadNode = async (node, resolve) => {
|
||||
const { data } = node;
|
||||
if (node?.level !== 0) {
|
||||
await listColumnsRun({
|
||||
id: props.model.currentNode.id,
|
||||
tableName: data.name,
|
||||
schemaName: props.model.schema
|
||||
});
|
||||
resolve(listColumnsData.value);
|
||||
}
|
||||
};
|
||||
|
||||
const filterSearch = async value => {
|
||||
listTablesBySchemaPageRun({
|
||||
id: props.model.currentNode.id,
|
||||
schemaName: props.model.schema,
|
||||
tableName: value,
|
||||
typeList: "table,view",
|
||||
pageNo: 1,
|
||||
pageSize: 1000
|
||||
});
|
||||
};
|
||||
|
||||
const handleTableClick = node => {
|
||||
props.model.sqlString = `select * from ${node.name}`;
|
||||
// this.$refs.codemirrorTic.$refs.mycode.__vue__.focus();
|
||||
};
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<base-form-item label="数据源:" prop="dataSource" span={8}>
|
||||
<el-tree-select
|
||||
v-model={props.model.dataSource}
|
||||
data={data.value}
|
||||
value-key="name"
|
||||
filter-node-method={filterNodeMethod}
|
||||
filterable
|
||||
render-after-expand={false}
|
||||
style="width: 240px"
|
||||
v-slots={{
|
||||
default: ({ data }) => (
|
||||
<div
|
||||
class="flex items-center gap-[5px]"
|
||||
onClick={() => handleNodeClick(data)}
|
||||
>
|
||||
{data.children && !data.parentId && (
|
||||
<img class="w-[15px] h-[15px]" src={icon_report} alt="" />
|
||||
)}
|
||||
{data.children && data.parentId && (
|
||||
<img class="w-[15px] h-[15px]" src={icon_folder} alt="" />
|
||||
)}
|
||||
<span class={"text-[12px] text-[#26314a] font-[400]"}>
|
||||
{data.name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</base-form-item>
|
||||
<base-form-item label="schema:" prop="schema" span={8}>
|
||||
<base-select
|
||||
disabled={loading.value}
|
||||
placeholder={loading.value ? "加载中" : "请选择"}
|
||||
v-model={props.model.schema}
|
||||
options={schemaData.value}
|
||||
></base-select>
|
||||
</base-form-item>
|
||||
<div
|
||||
class={
|
||||
"max-h-[400px] overflow-auto border-solid border-[1px] border-[#ccc] p-[10px]"
|
||||
}
|
||||
>
|
||||
<base-tree
|
||||
data={tablesData.value?.records}
|
||||
filterables={{
|
||||
size: "small"
|
||||
}}
|
||||
props={{
|
||||
label: "name"
|
||||
}}
|
||||
load={loadNode}
|
||||
filterSearch={filterSearch}
|
||||
lazy
|
||||
v-slots={{
|
||||
default: ({ node, data }) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => handleTableClick(data)}
|
||||
class={"flex items-center gap-[4px] text-[12px]"}
|
||||
>
|
||||
{node.level === 1 && (
|
||||
<img
|
||||
class="w-[15px] h-[15px]"
|
||||
src={databaseIcon}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
{!data.columns &&
|
||||
typeNumber.includes(
|
||||
(data?.type || "").toLocaleLowerCase()
|
||||
) && (
|
||||
<img
|
||||
class="w-[15px] h-[15px]"
|
||||
src={numberIcon}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
{!data.columns &&
|
||||
typeDate.includes(
|
||||
(data?.type || "").toLocaleLowerCase()
|
||||
) && (
|
||||
<img class="w-[15px] h-[15px]" src={dateIcon} alt="" />
|
||||
)}
|
||||
{!data.columns &&
|
||||
typeString.includes(
|
||||
(data?.type || "").toLocaleLowerCase()
|
||||
) && (
|
||||
<img
|
||||
class="w-[15px] h-[15px]"
|
||||
src={stringIcon}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
<span class="text-[12px]">
|
||||
{data.name}
|
||||
{data.comment && <span>({data.comment})</span>}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,273 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import { useRequest } from "vue-request";
|
||||
import {
|
||||
dataSourceList,
|
||||
databaseListSchemas,
|
||||
listTablesBySchemaPage,
|
||||
listColumns
|
||||
} from "@/api";
|
||||
import icon_report from "@/assets/imgs/icon_report.png";
|
||||
import icon_folder from "@/assets/imgs/icon_folder.png";
|
||||
import dateIcon from "@/assets/imgs/dateIcon.png";
|
||||
import numberIcon from "@/assets/imgs/numberIcon.png";
|
||||
import stringIcon from "@/assets/imgs/stringIcon.png";
|
||||
import databaseIcon from "@/assets/imgs/database.png";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { da } from "element-plus/es/locale";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SqlStatement",
|
||||
props: {
|
||||
model: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const typeNumber = [
|
||||
"int",
|
||||
"smallint",
|
||||
"bigint",
|
||||
"float",
|
||||
"double",
|
||||
"decimal",
|
||||
"bit"
|
||||
],
|
||||
typeDate = ["date", "datetime", "time", "timestamp"],
|
||||
typeString = ["char", "varchar", "varchar2", "text"];
|
||||
const { data, error } = useRequest(async () => await dataSourceList());
|
||||
|
||||
const {
|
||||
run,
|
||||
data: _schemaData_,
|
||||
loading
|
||||
} = useRequest(databaseListSchemas, {
|
||||
manual: true
|
||||
});
|
||||
|
||||
const {
|
||||
run: listTablesBySchemaPageRun,
|
||||
data: tablesData,
|
||||
loading: listTablesLoading
|
||||
} = useRequest(listTablesBySchemaPage, {
|
||||
manual: true
|
||||
});
|
||||
|
||||
const {
|
||||
run: listColumnsRun,
|
||||
data: listColumnsData,
|
||||
loading: listColumnsLoading
|
||||
} = useRequest(listColumns, {
|
||||
manual: true
|
||||
});
|
||||
|
||||
const schemaData = computed(() =>
|
||||
(_schemaData_.value || []).map(item => ({
|
||||
label: item.name,
|
||||
value: item.name
|
||||
}))
|
||||
);
|
||||
|
||||
const handleNodeClick = async node => {
|
||||
if (node.nodeType) {
|
||||
props.model.dataSource = node.name;
|
||||
props.model.url = node.url;
|
||||
props.model.username = node.userName;
|
||||
props.model.password = node.password;
|
||||
props.model.currentNode = node;
|
||||
run({ id: node.id });
|
||||
} else ElMessage.warning("选中的不是数据源,请重新选择");
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.model.schema,
|
||||
val => {
|
||||
if (val)
|
||||
listTablesBySchemaPageRun({
|
||||
id: props.model.currentNode.id,
|
||||
schemaName: props.model.schema,
|
||||
tableName: "",
|
||||
typeList: "table,view",
|
||||
pageNo: 1,
|
||||
pageSize: 1000
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const filterNodeMethod = (value, data) => data.name.includes(value);
|
||||
|
||||
const loadNode = async (node, resolve) => {
|
||||
const { data } = node;
|
||||
if (node?.level !== 0) {
|
||||
await listColumnsRun({
|
||||
id: props.model.currentNode.id,
|
||||
tableName: data.name,
|
||||
schemaName: props.model.schema
|
||||
});
|
||||
resolve(listColumnsData.value);
|
||||
}
|
||||
};
|
||||
|
||||
const filterSearch = async value => {
|
||||
listTablesBySchemaPageRun({
|
||||
id: props.model.currentNode.id,
|
||||
schemaName: props.model.schema,
|
||||
tableName: value,
|
||||
typeList: "table,view",
|
||||
pageNo: 1,
|
||||
pageSize: 1000
|
||||
});
|
||||
};
|
||||
|
||||
const handleTableClick = node => {
|
||||
props.model.sqlString = `select * from ${node.name}`;
|
||||
// this.$refs.codemirrorTic.$refs.mycode.__vue__.focus();
|
||||
};
|
||||
const feeTypeList = [
|
||||
{
|
||||
label: "机器折旧费",
|
||||
value: "EquipmentFee"
|
||||
},
|
||||
{
|
||||
label: "辅料费",
|
||||
value: "SupplyMaterialFee"
|
||||
},
|
||||
{
|
||||
label: "水电费",
|
||||
value: "DriverFee"
|
||||
},
|
||||
{
|
||||
label: "其他费用",
|
||||
value: "OtherFee"
|
||||
},
|
||||
{
|
||||
label: "物流费",
|
||||
value: "LogisticsFee"
|
||||
}
|
||||
];
|
||||
return () => (
|
||||
<>
|
||||
<base-form-item label="费用类型:" prop="feeType" span={8}>
|
||||
<base-select
|
||||
placeholder={loading.value ? "加载中" : "请选择"}
|
||||
v-model={props.model.feeType}
|
||||
options={feeTypeList}
|
||||
></base-select>
|
||||
</base-form-item>
|
||||
<base-form-item label="数据源:" prop="dataSource" span={8}>
|
||||
<el-tree-select
|
||||
v-model={props.model.dataSource}
|
||||
data={data.value}
|
||||
value-key="name"
|
||||
filter-node-method={filterNodeMethod}
|
||||
filterable
|
||||
render-after-expand={false}
|
||||
style="width: 240px"
|
||||
v-slots={{
|
||||
default: ({ data }) => (
|
||||
<div
|
||||
class="flex items-center gap-[5px]"
|
||||
onClick={() => handleNodeClick(data)}
|
||||
>
|
||||
{data.children && !data.parentId && (
|
||||
<img class="w-[15px] h-[15px]" src={icon_report} alt="" />
|
||||
)}
|
||||
{data.children && data.parentId && (
|
||||
<img class="w-[15px] h-[15px]" src={icon_folder} alt="" />
|
||||
)}
|
||||
<span class={"text-[12px] text-[#26314a] font-[400]"}>
|
||||
{data.name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</base-form-item>
|
||||
<base-form-item label="schema:" prop="schema" span={8}>
|
||||
<base-select
|
||||
disabled={loading.value}
|
||||
placeholder={loading.value ? "加载中" : "请选择"}
|
||||
v-model={props.model.schema}
|
||||
options={schemaData.value}
|
||||
></base-select>
|
||||
</base-form-item>
|
||||
<div
|
||||
class={
|
||||
"max-h-[400px] overflow-auto border-solid border-[1px] border-[#ccc] p-[10px]"
|
||||
}
|
||||
>
|
||||
<base-tree
|
||||
data={tablesData.value?.records}
|
||||
filterables={{
|
||||
size: "small"
|
||||
}}
|
||||
props={{
|
||||
label: "name"
|
||||
}}
|
||||
load={loadNode}
|
||||
filterSearch={filterSearch}
|
||||
lazy
|
||||
v-slots={{
|
||||
default: ({ node, data }) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => handleTableClick(data)}
|
||||
class={"flex items-center gap-[4px] text-[12px]"}
|
||||
>
|
||||
{node.level === 1 && (
|
||||
<img
|
||||
class="w-[15px] h-[15px]"
|
||||
src={databaseIcon}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
{!data.columns &&
|
||||
typeNumber.includes(
|
||||
(data?.type || "").toLocaleLowerCase()
|
||||
) && (
|
||||
<img
|
||||
class="w-[15px] h-[15px]"
|
||||
src={numberIcon}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
{!data.columns &&
|
||||
typeDate.includes(
|
||||
(data?.type || "").toLocaleLowerCase()
|
||||
) && (
|
||||
<img class="w-[15px] h-[15px]" src={dateIcon} alt="" />
|
||||
)}
|
||||
{!data.columns &&
|
||||
typeString.includes(
|
||||
(data?.type || "").toLocaleLowerCase()
|
||||
) && (
|
||||
<img
|
||||
class="w-[15px] h-[15px]"
|
||||
src={stringIcon}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
<span class="text-[12px]">
|
||||
{data.name}
|
||||
{data.comment && <span>({data.comment})</span>}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div v-loading="loading" :style="'height:' + height">
|
||||
<iframe
|
||||
:src="url"
|
||||
frameborder="no"
|
||||
style="width: 100%; height: 100%"
|
||||
scrolling="auto"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const height = ref(document.documentElement.clientHeight - 94.5 + "px;");
|
||||
const loading = ref(true);
|
||||
const url = computed(() => props.src);
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 300);
|
||||
window.onresize = function temp() {
|
||||
height.value = document.documentElement.clientHeight - 94.5 + "px;";
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* v-copyText 复制文本内容
|
||||
* Copyright (c) 2022 ruoyi
|
||||
*/
|
||||
|
||||
export default {
|
||||
beforeMount(el, { value, arg }) {
|
||||
if (arg === "callback") {
|
||||
el.$copyCallback = value;
|
||||
} else {
|
||||
el.$copyValue = value;
|
||||
const handler = () => {
|
||||
copyTextToClipboard(el.$copyValue);
|
||||
if (el.$copyCallback) {
|
||||
el.$copyCallback(el.$copyValue);
|
||||
}
|
||||
};
|
||||
el.addEventListener("click", handler);
|
||||
el.$destroyCopy = () => el.removeEventListener("click", handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function copyTextToClipboard(input, { target = document.body } = {}) {
|
||||
const element = document.createElement("textarea");
|
||||
const previouslyFocusedElement = document.activeElement;
|
||||
|
||||
element.value = input;
|
||||
|
||||
// Prevent keyboard from showing on mobile
|
||||
element.setAttribute("readonly", "");
|
||||
|
||||
element.style.contain = "strict";
|
||||
element.style.position = "absolute";
|
||||
element.style.left = "-9999px";
|
||||
element.style.fontSize = "12pt"; // Prevent zooming on iOS
|
||||
|
||||
const selection = document.getSelection();
|
||||
const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0);
|
||||
|
||||
target.append(element);
|
||||
element.select();
|
||||
|
||||
// Explicit selection workaround for iOS
|
||||
element.selectionStart = 0;
|
||||
element.selectionEnd = input.length;
|
||||
|
||||
let isSuccess = false;
|
||||
try {
|
||||
isSuccess = document.execCommand("copy");
|
||||
} catch {}
|
||||
|
||||
element.remove();
|
||||
|
||||
if (originalRange) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(originalRange);
|
||||
}
|
||||
|
||||
// Get the focus back on the previously focused element, if any
|
||||
if (previouslyFocusedElement) {
|
||||
previouslyFocusedElement.focus();
|
||||
}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import hasRole from "./permission/hasRole";
|
||||
import hasPermi from "./permission/hasPermi";
|
||||
import copyText from "./common/copyText";
|
||||
|
||||
export default function directive(app) {
|
||||
app.directive("hasRole", hasRole);
|
||||
app.directive("hasPermi", hasPermi);
|
||||
app.directive("copyText", copyText);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* v-hasPermi 操作权限处理
|
||||
*/
|
||||
|
||||
import useUserStore from "@/store/modules/user";
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
const { value } = binding;
|
||||
const all_permission = "*:*:*";
|
||||
const permissions = useUserStore().permissions;
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const permissionFlag = value;
|
||||
|
||||
const hasPermissions = permissions.some(permission => {
|
||||
return (
|
||||
all_permission === permission || permissionFlag.includes(permission)
|
||||
);
|
||||
});
|
||||
|
||||
if (!hasPermissions) {
|
||||
el.parentNode && el.parentNode.removeChild(el);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`请设置操作权限标签值`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* v-hasRole 角色权限处理
|
||||
* Copyright (c) 2019 ruoyi
|
||||
*/
|
||||
|
||||
import useUserStore from "@/store/modules/user";
|
||||
|
||||
export default {
|
||||
mounted(el, binding, vnode) {
|
||||
const { value } = binding;
|
||||
const super_admin = "admin";
|
||||
const roles = useUserStore().roles;
|
||||
|
||||
if (value && value instanceof Array && value.length > 0) {
|
||||
const roleFlag = value;
|
||||
|
||||
const hasRole = roles.some(role => {
|
||||
return super_admin === role || roleFlag.includes(role);
|
||||
});
|
||||
|
||||
if (!hasRole) {
|
||||
el.parentNode && el.parentNode.removeChild(el);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`请设置角色权限标签值`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import SqlStatement from "@/components/SqlStatement";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dbOutput",
|
||||
props: {
|
||||
nodeDetail: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const nodeDetail = computed(() => props?.nodeDetail);
|
||||
const baseFormRef = ref({});
|
||||
const baseMonacoEditorRef = ref();
|
||||
const { findNodesPrev, findNodesNext, getNodeData } = attrs;
|
||||
const submit = () => {
|
||||
let params = baseFormRef.value.formData;
|
||||
return {
|
||||
params
|
||||
};
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
init(nodeDetail.value);
|
||||
baseMonacoEditorRef.value?.restLayout();
|
||||
};
|
||||
|
||||
// onMounted(()=>{
|
||||
// init(nodeDetail.value)
|
||||
// })
|
||||
|
||||
// watch(nodeDetail,(v)=>{
|
||||
// init(v)
|
||||
// },{
|
||||
// deep:true
|
||||
// })
|
||||
watch(
|
||||
() => baseFormRef.value.formData,
|
||||
val => {},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const init = v => {
|
||||
const data = findNodesNext(props?.nodeDetail.id);
|
||||
baseFormRef.value.setFormData(
|
||||
JSON.parse(v?.taskParams || "{}")?.params || {}
|
||||
);
|
||||
};
|
||||
|
||||
const handleOK = value => {};
|
||||
|
||||
expose({
|
||||
submit,
|
||||
open
|
||||
});
|
||||
|
||||
return () => (
|
||||
<base-form
|
||||
ref={baseFormRef}
|
||||
v-slots={{
|
||||
default: ({ model }) => (
|
||||
<div class={"flex gap-[10px] w-full"}>
|
||||
<div class={"w-[300px] overflow-auto flex flex-col"}>
|
||||
<SqlStatement
|
||||
baseFormRef={baseFormRef}
|
||||
model={model}
|
||||
></SqlStatement>
|
||||
</div>
|
||||
<div class={"flex flex-1 flex-col"}>
|
||||
<base-form-item label="SQL语句:" prop="test" span={8}>
|
||||
<div class={"w-full h-[200px]"}>
|
||||
<BaseMonacoEditor
|
||||
ref={baseMonacoEditorRef}
|
||||
v-model={model.sqlString}
|
||||
language="sql"
|
||||
customReminders={{
|
||||
test: ["liaoqiuyu"]
|
||||
}}
|
||||
></BaseMonacoEditor>
|
||||
</div>
|
||||
</base-form-item>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import SqlStatementType from "@/components/SqlStatementType";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dbOutput",
|
||||
props: {
|
||||
nodeDetail: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const nodeDetail = computed(() => props?.nodeDetail);
|
||||
const baseFormRef = ref({});
|
||||
const baseMonacoEditorRef = ref();
|
||||
const { findNodesPrev, findNodesNext, getNodeData } = attrs;
|
||||
const submit = () => {
|
||||
let params = baseFormRef.value.formData;
|
||||
return {
|
||||
params
|
||||
};
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
init(nodeDetail.value);
|
||||
baseMonacoEditorRef.value?.restLayout();
|
||||
};
|
||||
|
||||
// onMounted(()=>{
|
||||
// init(nodeDetail.value)
|
||||
// })
|
||||
|
||||
// watch(nodeDetail,(v)=>{
|
||||
// init(v)
|
||||
// },{
|
||||
// deep:true
|
||||
// })
|
||||
watch(
|
||||
() => baseFormRef.value.formData,
|
||||
val => {},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const init = v => {
|
||||
const data = findNodesNext(props?.nodeDetail.id);
|
||||
baseFormRef.value.setFormData(
|
||||
JSON.parse(v?.taskParams || "{}")?.params || {}
|
||||
);
|
||||
};
|
||||
|
||||
const handleOK = value => {};
|
||||
|
||||
expose({
|
||||
submit,
|
||||
open
|
||||
});
|
||||
|
||||
return () => (
|
||||
<base-form
|
||||
ref={baseFormRef}
|
||||
v-slots={{
|
||||
default: ({ model }) => (
|
||||
<div class={"flex gap-[10px] w-full"}>
|
||||
<div class={"w-[300px] overflow-auto flex flex-col"}>
|
||||
<SqlStatementType
|
||||
baseFormRef={baseFormRef}
|
||||
model={model}
|
||||
></SqlStatementType>
|
||||
</div>
|
||||
<div class={"flex flex-1 flex-col"}>
|
||||
<base-form-item label="SQL语句:" prop="test" span={8}>
|
||||
<div class={"w-full h-[200px]"}>
|
||||
<BaseMonacoEditor
|
||||
ref={baseMonacoEditorRef}
|
||||
v-model={model.sqlString}
|
||||
language="sql"
|
||||
customReminders={{
|
||||
test: ["liaoqiuyu"]
|
||||
}}
|
||||
></BaseMonacoEditor>
|
||||
</div>
|
||||
</base-form-item>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import SqlStatement from "@/components/SqlStatement";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dbOutput",
|
||||
props: {
|
||||
nodeDetail: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const nodeDetail = computed(() => props?.nodeDetail);
|
||||
const baseFormRef = ref({});
|
||||
const baseMonacoEditorRef = ref();
|
||||
const { findNodesPrev, findNodesNext, getNodeData } = attrs;
|
||||
const submit = () => {
|
||||
let params = baseFormRef.value.formData;
|
||||
return {
|
||||
params
|
||||
};
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
init(nodeDetail.value);
|
||||
baseMonacoEditorRef.value?.restLayout();
|
||||
};
|
||||
|
||||
// onMounted(()=>{
|
||||
// init(nodeDetail.value)
|
||||
// })
|
||||
|
||||
// watch(nodeDetail,(v)=>{
|
||||
// init(v)
|
||||
// },{
|
||||
// deep:true
|
||||
// })
|
||||
watch(
|
||||
() => baseFormRef.value.formData,
|
||||
val => {},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const init = v => {
|
||||
const data = findNodesNext(props?.nodeDetail.id);
|
||||
baseFormRef.value.setFormData(
|
||||
JSON.parse(v?.taskParams || "{}")?.params || {}
|
||||
);
|
||||
};
|
||||
|
||||
const handleOK = value => {};
|
||||
|
||||
expose({
|
||||
submit,
|
||||
open
|
||||
});
|
||||
|
||||
return () => (
|
||||
<base-form
|
||||
ref={baseFormRef}
|
||||
v-slots={{
|
||||
default: ({ model }) => (
|
||||
<div class={"flex gap-[10px] w-full"}>
|
||||
<div class={"w-[300px] overflow-auto flex flex-col"}>
|
||||
<SqlStatement
|
||||
baseFormRef={baseFormRef}
|
||||
model={model}
|
||||
></SqlStatement>
|
||||
</div>
|
||||
<div class={"flex flex-1 flex-col"}>
|
||||
<base-form-item label="SQL语句:" prop="test" span={8}>
|
||||
<div class={"w-full h-[200px]"}>
|
||||
<BaseMonacoEditor
|
||||
ref={baseMonacoEditorRef}
|
||||
v-model={model.sqlString}
|
||||
language="sql"
|
||||
customReminders={{
|
||||
test: ["liaoqiuyu"]
|
||||
}}
|
||||
></BaseMonacoEditor>
|
||||
</div>
|
||||
</base-form-item>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import SqlStatement from "@/components/SqlStatement";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dbOutput",
|
||||
props: {
|
||||
nodeDetail: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const nodeDetail = computed(() => props?.nodeDetail);
|
||||
const baseFormRef = ref({});
|
||||
const baseMonacoEditorRef = ref();
|
||||
const { findNodesPrev, findNodesNext, getNodeData } = attrs;
|
||||
const submit = () => {
|
||||
let params = baseFormRef.value.formData;
|
||||
return {
|
||||
params
|
||||
};
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
init(nodeDetail.value);
|
||||
baseMonacoEditorRef.value?.restLayout();
|
||||
};
|
||||
|
||||
// onMounted(()=>{
|
||||
// init(nodeDetail.value)
|
||||
// })
|
||||
|
||||
// watch(nodeDetail,(v)=>{
|
||||
// init(v)
|
||||
// },{
|
||||
// deep:true
|
||||
// })
|
||||
watch(
|
||||
() => baseFormRef.value.formData,
|
||||
val => {},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const init = v => {
|
||||
const data = findNodesNext(props?.nodeDetail.id);
|
||||
baseFormRef.value.setFormData(
|
||||
JSON.parse(v?.taskParams || "{}")?.params || {}
|
||||
);
|
||||
};
|
||||
|
||||
const handleOK = value => {};
|
||||
|
||||
expose({
|
||||
submit,
|
||||
open
|
||||
});
|
||||
|
||||
return () => (
|
||||
<base-form
|
||||
ref={baseFormRef}
|
||||
v-slots={{
|
||||
default: ({ model }) => (
|
||||
<div class={"flex gap-[10px] w-full"}>
|
||||
<div class={"w-[300px] overflow-auto flex flex-col"}>
|
||||
<SqlStatement
|
||||
baseFormRef={baseFormRef}
|
||||
model={model}
|
||||
></SqlStatement>
|
||||
</div>
|
||||
<div class={"flex flex-1 flex-col"}>
|
||||
<base-form-item label="SQL语句:" prop="test" span={8}>
|
||||
<div class={"w-full h-[200px]"}>
|
||||
<BaseMonacoEditor
|
||||
ref={baseMonacoEditorRef}
|
||||
v-model={model.sqlString}
|
||||
language="sql"
|
||||
customReminders={{
|
||||
test: ["liaoqiuyu"]
|
||||
}}
|
||||
></BaseMonacoEditor>
|
||||
</div>
|
||||
</base-form-item>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import SqlStatement from "@/components/SqlStatement";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dbOutput",
|
||||
props: {
|
||||
nodeDetail: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const nodeDetail = computed(() => props?.nodeDetail);
|
||||
const baseFormRef = ref({});
|
||||
const baseMonacoEditorRef = ref();
|
||||
const { findNodesPrev, findNodesNext, getNodeData } = attrs;
|
||||
const submit = () => {
|
||||
let params = baseFormRef.value.formData;
|
||||
return {
|
||||
params
|
||||
};
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
init(nodeDetail.value);
|
||||
baseMonacoEditorRef.value?.restLayout();
|
||||
};
|
||||
|
||||
// onMounted(()=>{
|
||||
// init(nodeDetail.value)
|
||||
// })
|
||||
|
||||
// watch(nodeDetail,(v)=>{
|
||||
// init(v)
|
||||
// },{
|
||||
// deep:true
|
||||
// })
|
||||
watch(
|
||||
() => baseFormRef.value.formData,
|
||||
val => {},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const init = v => {
|
||||
const data = findNodesNext(props?.nodeDetail.id);
|
||||
baseFormRef.value.setFormData(
|
||||
JSON.parse(v?.taskParams || "{}")?.params || {}
|
||||
);
|
||||
};
|
||||
|
||||
const handleOK = value => {};
|
||||
|
||||
expose({
|
||||
submit,
|
||||
open
|
||||
});
|
||||
|
||||
return () => (
|
||||
<base-form
|
||||
ref={baseFormRef}
|
||||
v-slots={{
|
||||
default: ({ model }) => (
|
||||
<div class={"flex gap-[10px] w-full"}>
|
||||
<div class={"w-[300px] overflow-auto flex flex-col"}>
|
||||
<SqlStatement
|
||||
baseFormRef={baseFormRef}
|
||||
model={model}
|
||||
></SqlStatement>
|
||||
</div>
|
||||
<div class={"flex flex-1 flex-col"}>
|
||||
<base-form-item label="SQL语句:" prop="test" span={8}>
|
||||
<div class={"w-full h-[200px]"}>
|
||||
<BaseMonacoEditor
|
||||
ref={baseMonacoEditorRef}
|
||||
v-model={model.sqlString}
|
||||
language="sql"
|
||||
customReminders={{
|
||||
test: ["liaoqiuyu"]
|
||||
}}
|
||||
></BaseMonacoEditor>
|
||||
</div>
|
||||
</base-form-item>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue";
|
||||
import { useExposeDialogArgs } from "@qomo-platform/core";
|
||||
import SqlStatement from "@/components/SqlStatement";
|
||||
|
||||
export default defineComponent({
|
||||
name: "dbOutput",
|
||||
props: {
|
||||
nodeDetail: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, expose, slots = {} }) {
|
||||
const nodeDetail = computed(() => props?.nodeDetail);
|
||||
const baseFormRef = ref({});
|
||||
const baseMonacoEditorRef = ref();
|
||||
const { findNodesPrev, findNodesNext, getNodeData } = attrs;
|
||||
const submit = () => {
|
||||
let params = baseFormRef.value.formData;
|
||||
return {
|
||||
params
|
||||
};
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
init(nodeDetail.value);
|
||||
baseMonacoEditorRef.value?.restLayout();
|
||||
};
|
||||
|
||||
// onMounted(()=>{
|
||||
// init(nodeDetail.value)
|
||||
// })
|
||||
|
||||
// watch(nodeDetail,(v)=>{
|
||||
// init(v)
|
||||
// },{
|
||||
// deep:true
|
||||
// })
|
||||
watch(
|
||||
() => baseFormRef.value.formData,
|
||||
val => {},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const init = v => {
|
||||
const data = findNodesNext(props?.nodeDetail.id);
|
||||
baseFormRef.value.setFormData(
|
||||
JSON.parse(v?.taskParams || "{}")?.params || {}
|
||||
);
|
||||
};
|
||||
|
||||
const handleOK = value => {};
|
||||
|
||||
expose({
|
||||
submit,
|
||||
open
|
||||
});
|
||||
|
||||
return () => (
|
||||
<base-form
|
||||
ref={baseFormRef}
|
||||
v-slots={{
|
||||
default: ({ model }) => (
|
||||
<div class={"flex gap-[10px] w-full"}>
|
||||
<div class={"w-[300px] overflow-auto flex flex-col"}>
|
||||
<SqlStatement
|
||||
baseFormRef={baseFormRef}
|
||||
model={model}
|
||||
></SqlStatement>
|
||||
</div>
|
||||
<div class={"flex flex-1 flex-col"}>
|
||||
<base-form-item label="SQL语句:" prop="test" span={8}>
|
||||
<div class={"w-full h-[200px]"}>
|
||||
<BaseMonacoEditor
|
||||
ref={baseMonacoEditorRef}
|
||||
v-model={model.code}
|
||||
language="sql"
|
||||
customReminders={{
|
||||
test: ["liaoqiuyu"]
|
||||
}}
|
||||
></BaseMonacoEditor>
|
||||
</div>
|
||||
</base-form-item>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { App, defineAsyncComponent } from "vue";
|
||||
import { getComponentNameByPath } from "@qomo-platform/core";
|
||||
|
||||
//组件
|
||||
const components = Object.assign(
|
||||
{},
|
||||
import.meta.glob("/src/flowableComponents/**/*.(vue|tsx|jsx)")
|
||||
);
|
||||
|
||||
const FlowableComponents = {
|
||||
install(app: App) {
|
||||
Object.entries(components).map(([componentPath, component]) =>
|
||||
app.component(
|
||||
getComponentNameByPath(componentPath),
|
||||
defineAsyncComponent(component)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
export default FlowableComponents;
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<section class="app-main">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition mode="out-in" name="fade-transform">
|
||||
<keep-alive :include="tagsViewStore.cachedViews">
|
||||
<component
|
||||
:is="Component"
|
||||
v-if="!route.meta.link"
|
||||
:key="route.path"
|
||||
/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
<iframe-toggle />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import iframeToggle from "./IframeToggle/index";
|
||||
import useTagsViewStore from "@/store/modules/tagsView";
|
||||
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-main {
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
background: #f5f7fa;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
.hasTagsView {
|
||||
.app-main {
|
||||
/* 84 = navbar + tags-view = 50 + 34 */
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 84px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
// fix css style bug in open el-dialog
|
||||
.el-popup-parent--hidden {
|
||||
.fixed-header {
|
||||
padding-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #c0c0c0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<inner-link
|
||||
v-for="(item, index) in tagsViewStore.iframeViews"
|
||||
:key="item.path"
|
||||
:iframeId="'iframe' + index"
|
||||
v-show="route.path === item.path"
|
||||
:src="iframeUrl(item.meta.link, item.query)"
|
||||
></inner-link>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import InnerLink from "../InnerLink/index";
|
||||
import useTagsViewStore from "@/store/modules/tagsView";
|
||||
|
||||
const route = useRoute();
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
|
||||
function iframeUrl(url, query) {
|
||||
if (Object.keys(query).length > 0) {
|
||||
let params = Object.keys(query)
|
||||
.map(key => key + "=" + query[key])
|
||||
.join("&");
|
||||
return url + "?" + params;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div :style="'height:' + height">
|
||||
<iframe
|
||||
:id="iframeId"
|
||||
style="width: 100%; height: 100%"
|
||||
:src="src"
|
||||
frameborder="no"
|
||||
></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
default: "/"
|
||||
},
|
||||
iframeId: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
|
||||
</script>
|
||||
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<!-- -->
|
||||
<div
|
||||
:style="{ background: settingsStore.theme }"
|
||||
class="navbar h-[60px] flex justify-between"
|
||||
>
|
||||
<div class="left-box flex items-center">
|
||||
<div class="logo-box h-full flex items-center pl-4 cursor-pointer">
|
||||
<div class="logo w-[180px] h-[40px]"></div>
|
||||
<div class="home w-[14px] h-[14px]"></div>
|
||||
</div>
|
||||
<hamburger
|
||||
id="hamburger-container"
|
||||
:is-active="appStore.sidebar.opened"
|
||||
class="hamburger-container"
|
||||
@toggleClick="toggleSideBar"
|
||||
/>
|
||||
<!-- <breadcrumb id="breadcrumb-container" class="breadcrumb-container" />-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import useAppStore from "@/store/modules/app";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
import useUserStore from "@/store/modules/user";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
defineProps({
|
||||
showBreadcrumb: { type: Boolean, default: true } // 是否显示左侧面包屑
|
||||
});
|
||||
|
||||
function toggleSideBar() {
|
||||
appStore.toggleSideBar();
|
||||
}
|
||||
|
||||
function handleCommand(command) {
|
||||
switch (command) {
|
||||
case "setLayout":
|
||||
setLayout();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const emits = defineEmits(["setLayout"]);
|
||||
function setLayout() {
|
||||
emits("setLayout");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
.logo {
|
||||
background: url("@/assets/images/logo.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
.home {
|
||||
background: url("@/assets/images/home.png") no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 60px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
color: #fff;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.topmenu-container {
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.errLog-container {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
float: right;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.right-menu-item {
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
vertical-align: text-bottom;
|
||||
|
||||
&.hover-effect {
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
margin-right: 40px;
|
||||
.avatar-wrapper {
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 25px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="showSettings"
|
||||
:withHeader="false"
|
||||
direction="rtl"
|
||||
size="300px"
|
||||
>
|
||||
<div class="setting-drawer-title">
|
||||
<h3 class="drawer-title">主题风格设置</h3>
|
||||
</div>
|
||||
<div class="setting-drawer-block-checbox">
|
||||
<div
|
||||
class="setting-drawer-block-checbox-item"
|
||||
@click="handleTheme('theme-dark')"
|
||||
>
|
||||
<img alt="dark" src="@/assets/images/dark.svg" />
|
||||
<div
|
||||
v-if="sideTheme === 'theme-dark'"
|
||||
class="setting-drawer-block-checbox-selectIcon"
|
||||
style="display: block"
|
||||
>
|
||||
<i aria-label="图标: check" class="anticon anticon-check">
|
||||
<svg
|
||||
:fill="theme"
|
||||
aria-hidden="true"
|
||||
class
|
||||
data-icon="check"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="setting-drawer-block-checbox-item"
|
||||
@click="handleTheme('theme-light')"
|
||||
>
|
||||
<img alt="light" src="@/assets/images/light.svg" />
|
||||
<div
|
||||
v-if="sideTheme === 'theme-light'"
|
||||
class="setting-drawer-block-checbox-selectIcon"
|
||||
style="display: block"
|
||||
>
|
||||
<i aria-label="图标: check" class="anticon anticon-check">
|
||||
<svg
|
||||
:fill="theme"
|
||||
aria-hidden="true"
|
||||
class
|
||||
data-icon="check"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drawer-item">
|
||||
<span>主题颜色</span>
|
||||
<span class="comp-style">
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
:predefine="predefineColors"
|
||||
@change="themeChange"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<el-divider />
|
||||
|
||||
<h3 class="drawer-title">系统布局配置</h3>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>开启 TopNav</span>
|
||||
<span class="comp-style">
|
||||
<el-switch
|
||||
v-model="settingsStore.topNav"
|
||||
class="drawer-switch"
|
||||
@change="topNavChange"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>开启 Tags-Views</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>固定 Header</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.fixedHeader" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>显示 Logo</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>动态标题</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="settingsStore.dynamicTitle" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-button icon="DocumentAdd" plain type="primary" @click="saveSetting"
|
||||
>保存配置</el-button
|
||||
>
|
||||
<el-button icon="Refresh" plain @click="resetSetting">重置配置</el-button>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import variables from "@/assets/styles/variables.module.scss";
|
||||
import axios from "axios";
|
||||
import { ElLoading, ElMessage } from "element-plus";
|
||||
import { useDynamicTitle } from "@/utils/dynamicTitle";
|
||||
import useAppStore from "@/store/modules/app";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
import usePermissionStore from "@/store/modules/permission";
|
||||
import { handleThemeStyle } from "@/utils/theme";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
const showSettings = ref(false);
|
||||
const theme = ref(settingsStore.theme);
|
||||
const sideTheme = ref(settingsStore.sideTheme);
|
||||
const storeSettings = computed(() => settingsStore);
|
||||
const predefineColors = ref([
|
||||
"#2474ff",
|
||||
"#ff4500",
|
||||
"#ff8c00",
|
||||
"#ffd700",
|
||||
"#90ee90",
|
||||
"#00ced1",
|
||||
"#1e90ff",
|
||||
"#c71585"
|
||||
]);
|
||||
|
||||
/** 是否需要topnav */
|
||||
function topNavChange(val) {
|
||||
if (!val) {
|
||||
appStore.toggleSideBarHide(false);
|
||||
permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
function themeChange(val) {
|
||||
settingsStore.theme = val;
|
||||
handleThemeStyle(val);
|
||||
}
|
||||
function handleTheme(val) {
|
||||
settingsStore.sideTheme = val;
|
||||
sideTheme.value = val;
|
||||
}
|
||||
function saveSetting() {
|
||||
proxy.$modal.loading("正在保存到本地,请稍候...");
|
||||
let layoutSetting = {
|
||||
topNav: storeSettings.value.topNav,
|
||||
tagsView: storeSettings.value.tagsView,
|
||||
fixedHeader: storeSettings.value.fixedHeader,
|
||||
sidebarLogo: storeSettings.value.sidebarLogo,
|
||||
dynamicTitle: storeSettings.value.dynamicTitle,
|
||||
sideTheme: storeSettings.value.sideTheme,
|
||||
theme: storeSettings.value.theme
|
||||
};
|
||||
localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
|
||||
setTimeout(proxy.$modal.closeLoading(), 1000);
|
||||
}
|
||||
function resetSetting() {
|
||||
proxy.$modal.loading("正在清除设置缓存并刷新,请稍候...");
|
||||
localStorage.removeItem("layout-setting");
|
||||
setTimeout("window.location.reload()", 1000);
|
||||
}
|
||||
function openSetting() {
|
||||
showSettings.value = true;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openSetting
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.setting-drawer-title {
|
||||
margin-bottom: 12px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 22px;
|
||||
font-weight: bold;
|
||||
.drawer-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.setting-drawer-block-checbox {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.setting-drawer-block-checbox-item {
|
||||
position: relative;
|
||||
margin-right: 16px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.custom-img {
|
||||
width: 48px;
|
||||
height: 38px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 2px #898484;
|
||||
}
|
||||
|
||||
.setting-drawer-block-checbox-selectIcon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 15px;
|
||||
padding-left: 24px;
|
||||
color: #1890ff;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-item {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
|
||||
.comp-style {
|
||||
float: right;
|
||||
margin: -3px 8px 0px 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<component :is="type" v-bind="linkProps()">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { isExternal } from "@/utils/validate";
|
||||
|
||||
const props = defineProps({
|
||||
to: {
|
||||
type: [String, Object],
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const isExt = computed(() => {
|
||||
return isExternal(props.to);
|
||||
});
|
||||
|
||||
const type = computed(() => {
|
||||
if (isExt.value) {
|
||||
return "a";
|
||||
}
|
||||
return "router-link";
|
||||
});
|
||||
|
||||
function linkProps() {
|
||||
if (isExt.value) {
|
||||
return {
|
||||
href: props.to,
|
||||
target: "_blank",
|
||||
rel: "noopener"
|
||||
};
|
||||
}
|
||||
return {
|
||||
to: props.to
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{ collapse: collapse }"
|
||||
:style="{
|
||||
backgroundColor:
|
||||
sideTheme === 'theme-dark'
|
||||
? variables.menuBackground
|
||||
: variables.menuLightBackground
|
||||
}"
|
||||
class="sidebar-logo-container"
|
||||
>
|
||||
<transition name="sidebarLogoFade">
|
||||
<router-link
|
||||
v-if="collapse"
|
||||
key="collapse"
|
||||
class="sidebar-logo-link"
|
||||
to="/"
|
||||
>
|
||||
<div v-if="logo" class="sidebar-logo"></div>
|
||||
<!-- <img v-if="logo" :src="logo" alt="" class="sidebar-logo" />-->
|
||||
<h1
|
||||
v-else
|
||||
:style="{
|
||||
color:
|
||||
sideTheme === 'theme-dark'
|
||||
? variables.logoTitleColor
|
||||
: variables.logoLightTitleColor
|
||||
}"
|
||||
class="sidebar-title"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
</router-link>
|
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||
<h1
|
||||
:style="{
|
||||
color:
|
||||
sideTheme === 'theme-dark'
|
||||
? variables.logoTitleColor
|
||||
: variables.logoLightTitleColor
|
||||
}"
|
||||
class="sidebar-title"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
</router-link>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import variables from "@/assets/styles/variables.module.scss";
|
||||
import logo from "@/assets/logo/logo.png";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
|
||||
defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const title = import.meta.env.VITE_APP_TITLE;
|
||||
const settingsStore = useSettingsStore();
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebarLogoFade-enter-active {
|
||||
transition: opacity 1.5s;
|
||||
}
|
||||
|
||||
.sidebarLogoFade-enter,
|
||||
.sidebarLogoFade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sidebar-logo-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
background: #2b2f3a;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
background: url("@/assets/images/logo.png") no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
& .sidebar-logo-link {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
& .sidebar-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
& .sidebar-title {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
line-height: 50px;
|
||||
font-size: 14px;
|
||||
font-family:
|
||||
Avenir,
|
||||
Helvetica Neue,
|
||||
Arial,
|
||||
Helvetica,
|
||||
sans-serif;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapse {
|
||||
.sidebar-logo {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div v-if="!item.hidden">
|
||||
<template
|
||||
v-if="
|
||||
hasOneShowingChild(item.children, item) &&
|
||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
|
||||
!item.alwaysShow
|
||||
"
|
||||
>
|
||||
<app-link
|
||||
v-if="onlyOneChild.meta"
|
||||
:to="resolvePath(onlyOneChild.path, onlyOneChild.query)"
|
||||
>
|
||||
<el-menu-item
|
||||
:index="resolvePath(onlyOneChild.path)"
|
||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||
>
|
||||
<base-svg
|
||||
:icon-class="
|
||||
onlyOneChild.meta.icon || (item.meta && item.meta.icon)
|
||||
"
|
||||
/>
|
||||
<template #title
|
||||
><span
|
||||
class="menu-title"
|
||||
:title="hasTitle(onlyOneChild.meta.title)"
|
||||
>{{ onlyOneChild.meta.title }}</span
|
||||
></template
|
||||
>
|
||||
</el-menu-item>
|
||||
</app-link>
|
||||
</template>
|
||||
|
||||
<el-sub-menu
|
||||
v-else
|
||||
ref="subMenu"
|
||||
:index="resolvePath(item.path)"
|
||||
teleported
|
||||
>
|
||||
<template v-if="item.meta" #title>
|
||||
<base-svg :icon-class="item.meta && item.meta.icon" />
|
||||
<span class="menu-title" :title="hasTitle(item.meta.title)">{{
|
||||
item.meta.title
|
||||
}}</span>
|
||||
</template>
|
||||
|
||||
<sidebar-item
|
||||
v-for="(child, index) in item.children"
|
||||
:key="child.path + index"
|
||||
:is-nest="true"
|
||||
:item="child"
|
||||
:base-path="resolvePath(child.path)"
|
||||
class="nest-menu"
|
||||
/>
|
||||
</el-sub-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { isExternal } from "@/utils/validate";
|
||||
import AppLink from "./Link";
|
||||
import { getNormalPath } from "@/utils/ruoyi";
|
||||
|
||||
const props = defineProps({
|
||||
// route object
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isNest: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
|
||||
const onlyOneChild = ref({});
|
||||
|
||||
function hasOneShowingChild(children = [], parent) {
|
||||
if (!children) {
|
||||
children = [];
|
||||
}
|
||||
const showingChildren = children.filter(item => {
|
||||
if (item.hidden) {
|
||||
return false;
|
||||
} else {
|
||||
// Temp set(will be used if only has one showing child)
|
||||
onlyOneChild.value = item;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// When there is only one child router, the child router is displayed by default
|
||||
if (showingChildren.length === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show parent if there are no child router to display
|
||||
if (showingChildren.length === 0) {
|
||||
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolvePath(routePath, routeQuery) {
|
||||
if (isExternal(routePath)) {
|
||||
return routePath;
|
||||
}
|
||||
if (isExternal(props.basePath)) {
|
||||
return props.basePath;
|
||||
}
|
||||
if (routeQuery) {
|
||||
let query = JSON.parse(routeQuery);
|
||||
return {
|
||||
path: getNormalPath(props.basePath + "/" + routePath),
|
||||
query: query
|
||||
};
|
||||
}
|
||||
return getNormalPath(props.basePath + "/" + routePath);
|
||||
}
|
||||
|
||||
function hasTitle(title) {
|
||||
if (title.length > 5) {
|
||||
return title;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{ 'has-logo': showLogo }"
|
||||
:style="{
|
||||
backgroundColor:
|
||||
sideTheme === 'theme-dark'
|
||||
? variables.menuBackground
|
||||
: variables.menuLightBackground
|
||||
}"
|
||||
>
|
||||
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
|
||||
<el-menu
|
||||
:active-text-color="theme"
|
||||
:background-color="
|
||||
sideTheme === 'theme-dark'
|
||||
? variables.menuBackground
|
||||
: variables.menuLightBackground
|
||||
"
|
||||
:collapse="isCollapse"
|
||||
:collapse-transition="false"
|
||||
:default-active="activeMenu"
|
||||
:text-color="
|
||||
sideTheme === 'theme-dark'
|
||||
? variables.menuColor
|
||||
: variables.menuLightColor
|
||||
"
|
||||
:unique-opened="true"
|
||||
mode="vertical"
|
||||
>
|
||||
<sidebar-item
|
||||
v-for="(route, index) in sidebarRouters"
|
||||
:key="route.path + index"
|
||||
:base-path="route.path"
|
||||
:item="route"
|
||||
/>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Logo from "./Logo";
|
||||
import SidebarItem from "./SidebarItem";
|
||||
import variables from "@/assets/styles/variables.module.scss";
|
||||
import useAppStore from "@/store/modules/app";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
import usePermissionStore from "@/store/modules/permission";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
|
||||
const showLogo = computed(() => settingsStore.sidebarLogo);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const isCollapse = computed(() => !appStore.sidebar.opened);
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route;
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu;
|
||||
}
|
||||
return path;
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<el-scrollbar
|
||||
ref="scrollContainer"
|
||||
:vertical="false"
|
||||
class="scroll-container"
|
||||
@wheel.prevent="handleScroll"
|
||||
>
|
||||
<slot />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useTagsViewStore from "@/store/modules/tagsView";
|
||||
|
||||
const tagAndTagSpacing = ref(4);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrapRef);
|
||||
|
||||
onMounted(() => {
|
||||
scrollWrapper.value.addEventListener("scroll", emitScroll, true);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
scrollWrapper.value.removeEventListener("scroll", emitScroll);
|
||||
});
|
||||
|
||||
function handleScroll(e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40;
|
||||
const $scrollWrapper = scrollWrapper.value;
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4;
|
||||
}
|
||||
const emits = defineEmits();
|
||||
const emitScroll = () => {
|
||||
emits("scroll");
|
||||
};
|
||||
|
||||
const tagsViewStore = useTagsViewStore();
|
||||
const visitedViews = computed(() => tagsViewStore.visitedViews);
|
||||
|
||||
function moveToTarget(currentTag) {
|
||||
const $container = proxy.$refs.scrollContainer.$el;
|
||||
const $containerWidth = $container.offsetWidth;
|
||||
const $scrollWrapper = scrollWrapper.value;
|
||||
|
||||
let firstTag = null;
|
||||
let lastTag = null;
|
||||
|
||||
// find first tag and last tag
|
||||
if (visitedViews.value.length > 0) {
|
||||
firstTag = visitedViews.value[0];
|
||||
lastTag = visitedViews.value[visitedViews.value.length - 1];
|
||||
}
|
||||
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0;
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
|
||||
} else {
|
||||
const tagListDom = document.getElementsByClassName("tags-view-item");
|
||||
const currentIndex = visitedViews.value.findIndex(
|
||||
item => item === currentTag
|
||||
);
|
||||
let prevTag = null;
|
||||
let nextTag = null;
|
||||
for (const k in tagListDom) {
|
||||
if (k !== "length" && Object.hasOwnProperty.call(tagListDom, k)) {
|
||||
if (
|
||||
tagListDom[k].dataset.path ===
|
||||
visitedViews.value[currentIndex - 1].path
|
||||
) {
|
||||
prevTag = tagListDom[k];
|
||||
}
|
||||
if (
|
||||
tagListDom[k].dataset.path ===
|
||||
visitedViews.value[currentIndex + 1].path
|
||||
) {
|
||||
nextTag = tagListDom[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the tag's offsetLeft after of nextTag
|
||||
const afterNextTagOffsetLeft =
|
||||
nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value;
|
||||
|
||||
// the tag's offsetLeft before of prevTag
|
||||
const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value;
|
||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
|
||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
moveToTarget
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scroll-container {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
:deep(.el-scrollbar__bar) {
|
||||
bottom: 0px;
|
||||
}
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
height: 39px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<div id="tags-view-container" class="tags-view-container">
|
||||
<scroll-pane
|
||||
ref="scrollPaneRef"
|
||||
class="tags-view-wrapper"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<router-link
|
||||
v-for="tag in visitedViews"
|
||||
:key="tag.path"
|
||||
:data-path="tag.path"
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
class="tags-view-item"
|
||||
:style="activeStyle(tag)"
|
||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
{{ tag.title }}
|
||||
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<close
|
||||
class="el-icon-close"
|
||||
style="width: 1em; height: 1em; vertical-align: middle"
|
||||
/>
|
||||
</span>
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul
|
||||
v-show="visible"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
class="contextmenu"
|
||||
>
|
||||
<li @click="refreshSelectedTag(selectedTag)">
|
||||
<refresh-right style="width: 1em; height: 1em" /> 刷新页面
|
||||
</li>
|
||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
||||
<close style="width: 1em; height: 1em" /> 关闭当前
|
||||
</li>
|
||||
<li @click="closeOthersTags">
|
||||
<circle-close style="width: 1em; height: 1em" /> 关闭其他
|
||||
</li>
|
||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
||||
<back style="width: 1em; height: 1em" /> 关闭左侧
|
||||
</li>
|
||||
<li v-if="!isLastView()" @click="closeRightTags">
|
||||
<right style="width: 1em; height: 1em" /> 关闭右侧
|
||||
</li>
|
||||
<li @click="closeAllTags(selectedTag)">
|
||||
<circle-close style="width: 1em; height: 1em" /> 全部关闭
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ScrollPane from "./ScrollPane";
|
||||
import { getNormalPath } from "@/utils/ruoyi";
|
||||
import useTagsViewStore from "@/store/modules/tagsView";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
import usePermissionStore from "@/store/modules/permission";
|
||||
|
||||
const visible = ref(false);
|
||||
const top = ref(0);
|
||||
const left = ref(0);
|
||||
const selectedTag = ref({});
|
||||
const affixTags = ref([]);
|
||||
const scrollPaneRef = ref(null);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const visitedViews = computed(() => useTagsViewStore().visitedViews);
|
||||
const routes = computed(() => usePermissionStore().routes);
|
||||
const theme = computed(() => useSettingsStore().theme);
|
||||
|
||||
watch(route, () => {
|
||||
addTags();
|
||||
moveToCurrentTag();
|
||||
});
|
||||
watch(visible, value => {
|
||||
if (value) {
|
||||
document.body.addEventListener("click", closeMenu);
|
||||
} else {
|
||||
document.body.removeEventListener("click", closeMenu);
|
||||
}
|
||||
});
|
||||
onMounted(() => {
|
||||
initTags();
|
||||
addTags();
|
||||
});
|
||||
|
||||
function isActive(r) {
|
||||
return r.path === route.path;
|
||||
}
|
||||
function activeStyle(tag) {
|
||||
if (!isActive(tag)) return {};
|
||||
return {
|
||||
"background-color": theme.value,
|
||||
"border-color": theme.value
|
||||
};
|
||||
}
|
||||
function isAffix(tag) {
|
||||
return tag.meta && tag.meta.affix;
|
||||
}
|
||||
function isFirstView() {
|
||||
try {
|
||||
return (
|
||||
selectedTag.value.fullPath === "/index" ||
|
||||
selectedTag.value.fullPath === visitedViews.value[1].fullPath
|
||||
);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function isLastView() {
|
||||
try {
|
||||
return (
|
||||
selectedTag.value.fullPath ===
|
||||
visitedViews.value[visitedViews.value.length - 1].fullPath
|
||||
);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function filterAffixTags(routes, basePath = "") {
|
||||
let tags = [];
|
||||
routes.forEach(route => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
const tagPath = getNormalPath(basePath + "/" + route.path);
|
||||
tags.push({
|
||||
fullPath: tagPath,
|
||||
path: tagPath,
|
||||
name: route.name,
|
||||
meta: { ...route.meta }
|
||||
});
|
||||
}
|
||||
if (route.children) {
|
||||
const tempTags = filterAffixTags(route.children, route.path);
|
||||
if (tempTags.length >= 1) {
|
||||
tags = [...tags, ...tempTags];
|
||||
}
|
||||
}
|
||||
});
|
||||
return tags;
|
||||
}
|
||||
function initTags() {
|
||||
const res = filterAffixTags(routes.value);
|
||||
affixTags.value = res;
|
||||
for (const tag of res) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
useTagsViewStore().addVisitedView(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
function addTags() {
|
||||
const { name } = route;
|
||||
if (name) {
|
||||
useTagsViewStore().addView(route);
|
||||
if (route.meta.link) {
|
||||
useTagsViewStore().addIframeView(route);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function moveToCurrentTag() {
|
||||
nextTick(() => {
|
||||
for (const r of visitedViews.value) {
|
||||
if (r.path === route.path) {
|
||||
scrollPaneRef.value.moveToTarget(r);
|
||||
// when query is different then update
|
||||
if (r.fullPath !== route.fullPath) {
|
||||
useTagsViewStore().updateVisitedView(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function refreshSelectedTag(view) {
|
||||
proxy.$tab.refreshPage(view);
|
||||
if (route.meta.link) {
|
||||
useTagsViewStore().delIframeView(route);
|
||||
}
|
||||
}
|
||||
function closeSelectedTag(view) {
|
||||
proxy.$tab.closePage(view).then(({ visitedViews }) => {
|
||||
if (isActive(view)) {
|
||||
toLastView(visitedViews, view);
|
||||
}
|
||||
});
|
||||
}
|
||||
function closeRightTags() {
|
||||
proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
toLastView(visitedViews);
|
||||
}
|
||||
});
|
||||
}
|
||||
function closeLeftTags() {
|
||||
proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
toLastView(visitedViews);
|
||||
}
|
||||
});
|
||||
}
|
||||
function closeOthersTags() {
|
||||
router.push(selectedTag.value).catch(() => {});
|
||||
proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
|
||||
moveToCurrentTag();
|
||||
});
|
||||
}
|
||||
function closeAllTags(view) {
|
||||
proxy.$tab.closeAllPage().then(({ visitedViews }) => {
|
||||
if (affixTags.value.some(tag => tag.path === route.path)) {
|
||||
return;
|
||||
}
|
||||
toLastView(visitedViews, view);
|
||||
});
|
||||
}
|
||||
function toLastView(visitedViews, view) {
|
||||
const latestView = visitedViews.slice(-1)[0];
|
||||
if (latestView) {
|
||||
router.push(latestView.fullPath);
|
||||
} else {
|
||||
// now the default is to redirect to the home page if there is no tags-view,
|
||||
// you can adjust it according to your needs.
|
||||
if (view.name === "Dashboard") {
|
||||
// to reload home page
|
||||
router.replace({ path: "/redirect" + view.fullPath });
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
function openMenu(tag, e) {
|
||||
const menuMinWidth = 105;
|
||||
const offsetLeft = proxy.$el.getBoundingClientRect().left; // container margin left
|
||||
const offsetWidth = proxy.$el.offsetWidth; // container width
|
||||
const maxLeft = offsetWidth - menuMinWidth; // left boundary
|
||||
const l = e.clientX - offsetLeft + 15; // 15: margin right
|
||||
|
||||
if (l > maxLeft) {
|
||||
left.value = maxLeft;
|
||||
} else {
|
||||
left.value = l;
|
||||
}
|
||||
|
||||
top.value = e.clientY;
|
||||
visible.value = true;
|
||||
selectedTag.value = tag;
|
||||
}
|
||||
function closeMenu() {
|
||||
visible.value = false;
|
||||
}
|
||||
function handleScroll() {
|
||||
closeMenu();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tags-view-container {
|
||||
height: 34px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #d8dce5;
|
||||
box-shadow:
|
||||
0 1px 3px 0 rgba(0, 0, 0, 0.12),
|
||||
0 0 3px 0 rgba(0, 0, 0, 0.04);
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
border: 1px solid #d8dce5;
|
||||
color: #495060;
|
||||
background: #fff;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-right: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #42b983;
|
||||
color: #fff;
|
||||
border-color: #42b983;
|
||||
&::before {
|
||||
content: "";
|
||||
background: #fff;
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
//reset element css of el-icon-close
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
.el-icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: 2px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
transform-origin: 100% 50%;
|
||||
&:before {
|
||||
transform: scale(0.6);
|
||||
display: inline-block;
|
||||
vertical-align: -3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #b4bccc;
|
||||
color: #fff;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as AppMain } from "./AppMain";
|
||||
export { default as Navbar } from "./Navbar";
|
||||
export { default as Settings } from "./Settings";
|
||||
export { default as TagsView } from "./TagsView/index.vue";
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div
|
||||
:class="classObj"
|
||||
:style="{ '--current-color': theme }"
|
||||
class="app-wrapper"
|
||||
v-if="!isQIANKUN"
|
||||
>
|
||||
<div
|
||||
v-if="device === 'mobile' && sidebar.opened"
|
||||
class="drawer-bg"
|
||||
@click="handleClickOutside"
|
||||
/>
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar @setLayout="setLayout" />
|
||||
<div class="tags-view-container-box">
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }"
|
||||
class="main-container"
|
||||
>
|
||||
<app-main />
|
||||
<settings ref="settingRef" />
|
||||
</div>
|
||||
</div>
|
||||
<app-main v-else />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
import Sidebar from "./components/Sidebar/index.vue";
|
||||
import { AppMain, Navbar, Settings, TagsView } from "./components";
|
||||
|
||||
import useAppStore from "@/store/modules/app";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
const sidebar = computed(() => useAppStore().sidebar);
|
||||
const device = computed(() => useAppStore().device);
|
||||
const needTagsView = computed(() => settingsStore.tagsView);
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
withoutAnimation: sidebar.value.withoutAnimation,
|
||||
mobile: device.value === "mobile"
|
||||
}));
|
||||
|
||||
const { width, height } = useWindowSize();
|
||||
const WIDTH = 992; // refer to Bootstrap's responsive design
|
||||
const isQIANKUN = computed(() => qiankunWindow.__POWERED_BY_QIANKUN__);
|
||||
watchEffect(() => {
|
||||
if (device.value === "mobile" && sidebar.value.opened) {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false });
|
||||
}
|
||||
if (width.value - 1 < WIDTH) {
|
||||
useAppStore().toggleDevice("mobile");
|
||||
useAppStore().closeSideBar({ withoutAnimation: true });
|
||||
} else {
|
||||
useAppStore().toggleDevice("desktop");
|
||||
}
|
||||
});
|
||||
|
||||
function handleClickOutside() {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false });
|
||||
}
|
||||
|
||||
const settingRef = ref(null);
|
||||
function setLayout() {
|
||||
settingRef.value.openSetting();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <style lang="scss" scoped>
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
.hideSidebar .tags-view-container-box {
|
||||
width: calc(100% - 54px);
|
||||
margin-left: 54px;
|
||||
}
|
||||
|
||||
.tags-view-container-box {
|
||||
width: calc(100% - 200px);
|
||||
margin-left: #{$base-sidebar-width};
|
||||
transition: margin-left 0.28s;
|
||||
}
|
||||
.sidebarHide .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style> -->
|
||||
@@ -0,0 +1,87 @@
|
||||
import { createApp } from "vue";
|
||||
import { isHttp } from "@/utils/validate";
|
||||
import "./assets/styles/tailwind.css";
|
||||
import Cookies from "js-cookie";
|
||||
import "@/utils/flexible.js";
|
||||
import ElementPlus from "element-plus";
|
||||
import "element-plus/dist/index.css";
|
||||
import locale from "element-plus/es/locale/lang/zh-cn";
|
||||
|
||||
import "@/assets/styles/index.scss";
|
||||
|
||||
import App from "./App";
|
||||
import store from "./store";
|
||||
import router from "./router";
|
||||
import directive from "./directive"; // directive
|
||||
import { qomoPlatformOptions } from "./platform";
|
||||
import QomoPlatform from "@qomo-platform/core";
|
||||
|
||||
// 注册指令
|
||||
import plugins from "./plugins"; // plugins
|
||||
|
||||
import {
|
||||
renderWithQiankun,
|
||||
qiankunWindow
|
||||
} from "vite-plugin-qiankun/dist/helper";
|
||||
|
||||
// svg图标
|
||||
import "virtual:svg-icons-register";
|
||||
import usePermissionStore from "@/store/modules/permission";
|
||||
import "@/assets/styles/global.css";
|
||||
import FlowableComponents from "@/flowableComponents/index.ts"
|
||||
let app;
|
||||
|
||||
function render(props = {}) {
|
||||
const { container } = props;
|
||||
|
||||
app = createApp(App);
|
||||
app.use(store);
|
||||
usePermissionStore()
|
||||
.generateRoutes()
|
||||
.then(routes => {
|
||||
routes.forEach(route => {
|
||||
if (!isHttp(route.path)) {
|
||||
router.addRoute(route); // 动态添加可访问路由表
|
||||
}
|
||||
});
|
||||
app.use(router);
|
||||
app.use(plugins);
|
||||
|
||||
app.use(QomoPlatform, qomoPlatformOptions);
|
||||
app.use(FlowableComponents);
|
||||
directive(app);
|
||||
|
||||
// 使用element-plus 并且设置全局的大小
|
||||
app.use(ElementPlus, {
|
||||
name:'testApp',
|
||||
locale: locale,
|
||||
// 支持 large、default、small
|
||||
size: Cookies.get("size") || "default"
|
||||
});
|
||||
|
||||
app.mount(container ? container.querySelector("#subApp") : "#subApp");
|
||||
});
|
||||
}
|
||||
|
||||
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
|
||||
render();
|
||||
} else {
|
||||
renderWithQiankun({
|
||||
mount(props) {
|
||||
console.log("===mount===");
|
||||
render(props);
|
||||
},
|
||||
bootstrap() {
|
||||
console.log("===bootstrap===");
|
||||
},
|
||||
unmount(props) {
|
||||
console.log(app);
|
||||
console.log("===unmount===");
|
||||
const { container } = props;
|
||||
app.unmount();
|
||||
},
|
||||
update(props) {
|
||||
console.log("===update===", props);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form
|
||||
ref="loginRef"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
>
|
||||
<h3 class="title">Qomo Ai</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
auto-complete="off"
|
||||
placeholder="账号"
|
||||
size="large"
|
||||
type="text"
|
||||
>
|
||||
<template #prefix
|
||||
><base-svg class="el-input__icon input-icon" icon-class="user"
|
||||
/></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
size="large"
|
||||
type="password"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix
|
||||
><base-svg class="el-input__icon input-icon" icon-class="password"
|
||||
/></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="captchaEnabled" prop="code">
|
||||
<el-input
|
||||
v-model="loginForm.captcha"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
size="large"
|
||||
style="width: 63%"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prefix
|
||||
><base-svg class="el-input__icon input-icon" icon-class="validCode"
|
||||
/></template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" class="login-code-img" @click="getCode" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0"
|
||||
>记住密码</el-checkbox
|
||||
>
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
type="primary"
|
||||
@click.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
<div v-if="register" style="float: right">
|
||||
<router-link :to="'/register'" class="link-type"
|
||||
>立即注册</router-link
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>Copyright © 2018-2023 ruoyi.vip All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCodeImg } from "@/api/login";
|
||||
import Cookies from "js-cookie";
|
||||
import { encrypt, decrypt } from "@/utils/jsencrypt";
|
||||
import useUserStore from "@/store/modules/user";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const loginForm = ref({
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
rememberMe: false,
|
||||
captcha: ""
|
||||
});
|
||||
|
||||
const loginRules = {
|
||||
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
|
||||
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
|
||||
captcha: [{ required: true, trigger: "change", message: "请输入验证码" }]
|
||||
};
|
||||
|
||||
const codeUrl = ref("");
|
||||
const loading = ref(false);
|
||||
// 验证码开关
|
||||
const captchaEnabled = ref(true);
|
||||
// 注册开关
|
||||
const register = ref(false);
|
||||
const redirect = ref(undefined);
|
||||
const currDateTime = ref();
|
||||
const loginRef = ref(null);
|
||||
|
||||
watch(
|
||||
route,
|
||||
newRoute => {
|
||||
console.log(newRoute.query.redirect, "newRoute.query.redirect");
|
||||
console.log(redirect.value, " redirect.value");
|
||||
|
||||
redirect.value = newRoute.query && newRoute.query.redirect;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function handleLogin() {
|
||||
loginRef.value.validate(valid => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
|
||||
if (loginForm.value.rememberMe) {
|
||||
Cookies.set("username", loginForm.value.username, { expires: 30 });
|
||||
Cookies.set("password", encrypt(loginForm.value.password), {
|
||||
expires: 30
|
||||
});
|
||||
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
|
||||
} else {
|
||||
// 否则移除
|
||||
Cookies.remove("username");
|
||||
Cookies.remove("password");
|
||||
Cookies.remove("rememberMe");
|
||||
}
|
||||
// 调用action的登录方法
|
||||
userStore
|
||||
.login({ ...loginForm.value, checkKey: currDateTime.value })
|
||||
.then(() => {
|
||||
const query = route.query;
|
||||
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
|
||||
if (cur !== "redirect") {
|
||||
acc[cur] = query[cur];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
router.push({
|
||||
path: redirect.value || "/main",
|
||||
query: otherQueryParams
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
// 重新获取验证码
|
||||
if (captchaEnabled.value) {
|
||||
getCode();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取验证码
|
||||
*/
|
||||
async function getCode() {
|
||||
currDateTime.value = new Date().getTime();
|
||||
const result = await getCodeImg(currDateTime.value);
|
||||
codeUrl.value = result;
|
||||
}
|
||||
|
||||
function getCookie() {
|
||||
const username = Cookies.get("username");
|
||||
const password = Cookies.get("password");
|
||||
const rememberMe = Cookies.get("rememberMe");
|
||||
loginForm.value = {
|
||||
username: username === undefined ? loginForm.value.username : username,
|
||||
password:
|
||||
password === undefined ? loginForm.value.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
};
|
||||
}
|
||||
|
||||
getCode();
|
||||
getCookie();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("@/assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
.el-input {
|
||||
height: 40px;
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
.login-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
.login-code {
|
||||
width: 33%;
|
||||
height: 40px;
|
||||
float: right;
|
||||
img {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.login-code-img {
|
||||
height: 40px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup>
|
||||
const leftMenu = ref([
|
||||
{
|
||||
title: "首页",
|
||||
icon: "home",
|
||||
path: "/index"
|
||||
},
|
||||
{
|
||||
title: "系统管理",
|
||||
icon: "setting",
|
||||
path: "/system"
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="left-menu-box w-[120px] bg-white h-full flex flex-col items-center"
|
||||
>
|
||||
<template v-for="item in leftMenu" :key="item.path">
|
||||
<div
|
||||
class="menu-box w-[100px] flex flex-col items-center p-[11px] rounded cursor-pointer transition-all duration-300 ease-in-out mt-[8px]"
|
||||
>
|
||||
<base-svg
|
||||
:icon-class="item.icon"
|
||||
class-name="text-[#A1A7B2] text-[22px] icon-box"
|
||||
/>
|
||||
<span class="text-[#A1A7B2] text-[14px] inline-block mt-2">{{
|
||||
item.title
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-box {
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary);
|
||||
color: white;
|
||||
span,
|
||||
.icon-box {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div
|
||||
:class="classObj"
|
||||
:style="{ '--current-color': theme }"
|
||||
class="app-wrapper"
|
||||
>
|
||||
<div
|
||||
v-if="device === 'mobile' && sidebar.opened"
|
||||
class="drawer-bg"
|
||||
@click="handleClickOutside"
|
||||
/>
|
||||
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar :show-breadcrumb="false" @setLayout="setLayout" />
|
||||
</div>
|
||||
<div class="main-box">
|
||||
<left-menu></left-menu>
|
||||
</div>
|
||||
<settings ref="settingRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
import { AppMain, Navbar, Settings, TagsView } from "@/layout/components";
|
||||
import LeftMenu from "./components/LeftMenu.vue";
|
||||
|
||||
import useAppStore from "@/store/modules/app";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
const sidebar = computed(() => useAppStore().sidebar);
|
||||
const device = computed(() => useAppStore().device);
|
||||
const needTagsView = computed(() => settingsStore.tagsView);
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
withoutAnimation: sidebar.value.withoutAnimation,
|
||||
mobile: device.value === "mobile"
|
||||
}));
|
||||
|
||||
const { width, height } = useWindowSize();
|
||||
const WIDTH = 992; // refer to Bootstrap's responsive design
|
||||
|
||||
watchEffect(() => {
|
||||
if (device.value === "mobile" && sidebar.value.opened) {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false });
|
||||
}
|
||||
if (width.value - 1 < WIDTH) {
|
||||
useAppStore().toggleDevice("mobile");
|
||||
useAppStore().closeSideBar({ withoutAnimation: true });
|
||||
} else {
|
||||
useAppStore().toggleDevice("desktop");
|
||||
}
|
||||
});
|
||||
|
||||
function handleClickOutside() {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false });
|
||||
}
|
||||
|
||||
const settingRef = ref(null);
|
||||
function setLayout() {
|
||||
settingRef.value.openSetting();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.main-box {
|
||||
height: calc(100vh - 60px);
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.sidebarHide .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="errPage-container">
|
||||
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
|
||||
返回
|
||||
</el-button>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<h1 class="text-jumbo text-ginormous">401错误!</h1>
|
||||
<h2>您没有访问权限!</h2>
|
||||
<h6>对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="link-type">
|
||||
<router-link to="/"> 回首页 </router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img
|
||||
:src="errGif"
|
||||
width="313"
|
||||
height="428"
|
||||
alt="Girl has dropped her ice cream."
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import errImage from "@/assets/401_images/401.gif";
|
||||
|
||||
let { proxy } = getCurrentInstance();
|
||||
|
||||
const errGif = ref(errImage + "?" + +new Date());
|
||||
|
||||
function back() {
|
||||
if (proxy.$route.query.noGoBack) {
|
||||
proxy.$router.push({ path: "/" });
|
||||
} else {
|
||||
proxy.$router.go(-1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.errPage-container {
|
||||
width: 800px;
|
||||
max-width: 100%;
|
||||
margin: 100px auto;
|
||||
.pan-back-btn {
|
||||
background: #008489;
|
||||
color: #fff;
|
||||
border: none !important;
|
||||
}
|
||||
.pan-gif {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
.pan-img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
.text-jumbo {
|
||||
font-size: 60px;
|
||||
font-weight: 700;
|
||||
color: #484848;
|
||||
}
|
||||
.list-unstyled {
|
||||
font-size: 14px;
|
||||
li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
a {
|
||||
color: #008489;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="wscn-http404-container">
|
||||
<div class="wscn-http404">
|
||||
<div class="pic-404">
|
||||
<img
|
||||
class="pic-404__parent"
|
||||
src="../../assets/404_images/404.png"
|
||||
alt="404"
|
||||
/>
|
||||
<img
|
||||
class="pic-404__child left"
|
||||
src="../../assets/404_images/404_cloud.png"
|
||||
alt="404"
|
||||
/>
|
||||
<img
|
||||
class="pic-404__child mid"
|
||||
src="../../assets/404_images/404_cloud.png"
|
||||
alt="404"
|
||||
/>
|
||||
<img
|
||||
class="pic-404__child right"
|
||||
src="../../assets/404_images/404_cloud.png"
|
||||
alt="404"
|
||||
/>
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops">404错误!</div>
|
||||
<div class="bullshit__headline">
|
||||
{{ message }}
|
||||
</div>
|
||||
<div class="bullshit__info">
|
||||
对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
|
||||
</div>
|
||||
<router-link to="/index" class="bullshit__return-home">
|
||||
返回首页
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let message = computed(() => {
|
||||
return "找不到网页!";
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wscn-http404-container {
|
||||
transform: translate(-50%, -50%);
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
}
|
||||
.wscn-http404 {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
padding: 0 50px;
|
||||
overflow: hidden;
|
||||
.pic-404 {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
&__parent {
|
||||
width: 100%;
|
||||
}
|
||||
&__child {
|
||||
position: absolute;
|
||||
&.left {
|
||||
width: 80px;
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
&.mid {
|
||||
width: 46px;
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
&.right {
|
||||
width: 62px;
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bullshit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
&__oops {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: #1482f0;
|
||||
opacity: 0;
|
||||
margin-bottom: 20px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__headline {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
opacity: 0;
|
||||
margin-bottom: 10px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__info {
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: grey;
|
||||
opacity: 0;
|
||||
margin-bottom: 30px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
background: #1482f0;
|
||||
border-radius: 100px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
opacity: 0;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(60px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { params, query } = route;
|
||||
const { path } = params;
|
||||
|
||||
router.replace({ path: "/" + path, query });
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
import router from "./router";
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { isHttp } from "@/utils/validate";
|
||||
import { isRelogin } from "@/service/request";
|
||||
import useUserStore from "@/store/modules/user";
|
||||
import useSettingsStore from "@/store/modules/settings";
|
||||
import usePermissionStore from "@/store/modules/permission";
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
// const whiteList = ["/login", "/register"];
|
||||
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// NProgress.start();
|
||||
// if (getToken()) {
|
||||
// to.meta.title && useSettingsStore().setTitle(to.meta.title);
|
||||
// /* has token*/
|
||||
// if (to.path === "/login") {
|
||||
// next({ path: "/" });
|
||||
// NProgress.done();
|
||||
// } else if (whiteList.indexOf(to.path) !== -1) {
|
||||
// next();
|
||||
// } else {
|
||||
// if (useUserStore().roles.length === 0) {
|
||||
// isRelogin.show = true;
|
||||
// useUserStore()
|
||||
// .getInfo()
|
||||
// .then(() => {
|
||||
// isRelogin.show = true;
|
||||
// usePermissionStore()
|
||||
// .generateRoutes()
|
||||
// .then(routes => {
|
||||
// routes.forEach(route => {
|
||||
// if (!isHttp(route.path)) {
|
||||
// router.addRoute(route); // 动态添加可访问路由表
|
||||
// }
|
||||
// });
|
||||
// next({ ...to, replace: true });
|
||||
// });
|
||||
// });
|
||||
// } else {
|
||||
// next();
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // 没有token
|
||||
// if (whiteList.indexOf(to.path) !== -1) {
|
||||
// // 在免登录白名单,直接进入
|
||||
// next();
|
||||
// } else {
|
||||
// next(`/login?redirect=${to.fullPath}`); // 否则全部重定向到登录页
|
||||
// NProgress.done();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import { App } from "vue";
|
||||
import QomoPlatform, { QomoPlatformInstallOptions } from "@qomo-platform/core";
|
||||
import { getDictCode } from "@/api/index";
|
||||
|
||||
export const qomoPlatformOptions = {
|
||||
//字典配置
|
||||
dict: {
|
||||
//静态字典
|
||||
staticDictMaps: {
|
||||
stageData: [
|
||||
{
|
||||
label: "初始",
|
||||
value: "A"
|
||||
},
|
||||
{
|
||||
label: "量产",
|
||||
value: "B"
|
||||
},
|
||||
{
|
||||
label: "首批",
|
||||
value: "C"
|
||||
}
|
||||
],
|
||||
modelStatus: [
|
||||
{
|
||||
label: "草稿",
|
||||
value: "1"
|
||||
},
|
||||
{
|
||||
label: "已发布",
|
||||
value: "2"
|
||||
},
|
||||
{
|
||||
label: "运行中",
|
||||
value: "3"
|
||||
},
|
||||
{
|
||||
label: "已完成",
|
||||
value: "4"
|
||||
}
|
||||
],
|
||||
feeTypeList:[
|
||||
{
|
||||
label: "机器折旧费",
|
||||
value: "EquipmentFee"
|
||||
},
|
||||
{
|
||||
label: "辅料费",
|
||||
value: "SupplyMaterialFee"
|
||||
},
|
||||
{
|
||||
label: "水电费",
|
||||
value: "DriverFee"
|
||||
},
|
||||
{
|
||||
label: "其他费用",
|
||||
value: "OtherFee"
|
||||
},
|
||||
{
|
||||
label: "物流费",
|
||||
value: "LogisticsFee"
|
||||
}
|
||||
],
|
||||
statusList:[
|
||||
{
|
||||
label: "未执行",
|
||||
value: "0"
|
||||
},
|
||||
{
|
||||
label: "计算中",
|
||||
value: "1"
|
||||
},
|
||||
{
|
||||
label: "计算完成",
|
||||
value: "2"
|
||||
},
|
||||
{
|
||||
label: "出错中止",
|
||||
value: "3"
|
||||
},
|
||||
]
|
||||
},
|
||||
async provider(dictType) {
|
||||
const result = await getDictCode({
|
||||
dictCode: dictType
|
||||
});
|
||||
return result.map(item =>
|
||||
Object.assign(item, {
|
||||
label: item.itemText,
|
||||
value: item.itemValue
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
component: {
|
||||
//配置全局文件上传
|
||||
async fileUpload(options) {
|
||||
const formData = new FormData();
|
||||
formData.append("object", options.file);
|
||||
return await uploadFile(formData);
|
||||
},
|
||||
getFileDetail: async files => ({
|
||||
...files,
|
||||
relativePath: files.name,
|
||||
name: files.originalName,
|
||||
url: location.origin + files?.fileurl
|
||||
}),
|
||||
namespace: "testApp"
|
||||
}
|
||||
} as QomoPlatformInstallOptions;
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
app.use(QomoPlatform, qomoPlatformOptions);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import useUserStore from "@/store/modules/user";
|
||||
|
||||
function authPermission(permission) {
|
||||
const all_permission = "*:*:*";
|
||||
const permissions = useUserStore().permissions;
|
||||
if (permission && permission.length > 0) {
|
||||
return permissions.some(v => {
|
||||
return all_permission === v || v === permission;
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function authRole(role) {
|
||||
const super_admin = "admin";
|
||||
const roles = useUserStore().roles;
|
||||
if (role && role.length > 0) {
|
||||
return roles.some(v => {
|
||||
return super_admin === v || v === role;
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
// 验证用户是否具备某权限
|
||||
hasPermi(permission) {
|
||||
return authPermission(permission);
|
||||
},
|
||||
// 验证用户是否含有指定权限,只需包含其中一个
|
||||
hasPermiOr(permissions) {
|
||||
return permissions.some(item => {
|
||||
return authPermission(item);
|
||||
});
|
||||
},
|
||||
// 验证用户是否含有指定权限,必须全部拥有
|
||||
hasPermiAnd(permissions) {
|
||||
return permissions.every(item => {
|
||||
return authPermission(item);
|
||||
});
|
||||
},
|
||||
// 验证用户是否具备某角色
|
||||
hasRole(role) {
|
||||
return authRole(role);
|
||||
},
|
||||
// 验证用户是否含有指定角色,只需包含其中一个
|
||||
hasRoleOr(roles) {
|
||||
return roles.some(item => {
|
||||
return authRole(item);
|
||||
});
|
||||
},
|
||||
// 验证用户是否含有指定角色,必须全部拥有
|
||||
hasRoleAnd(roles) {
|
||||
return roles.every(item => {
|
||||
return authRole(item);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
const sessionCache = {
|
||||
set(key, value) {
|
||||
if (!sessionStorage) {
|
||||
return;
|
||||
}
|
||||
if (key != null && value != null) {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
},
|
||||
get(key) {
|
||||
if (!sessionStorage) {
|
||||
return null;
|
||||
}
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
return sessionStorage.getItem(key);
|
||||
},
|
||||
setJSON(key, jsonValue) {
|
||||
if (jsonValue != null) {
|
||||
this.set(key, JSON.stringify(jsonValue));
|
||||
}
|
||||
},
|
||||
getJSON(key) {
|
||||
const value = this.get(key);
|
||||
if (value != null) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
},
|
||||
remove(key) {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
const localCache = {
|
||||
set(key, value) {
|
||||
if (!localStorage) {
|
||||
return;
|
||||
}
|
||||
if (key != null && value != null) {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
},
|
||||
get(key) {
|
||||
if (!localStorage) {
|
||||
return null;
|
||||
}
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
return localStorage.getItem(key);
|
||||
},
|
||||
setJSON(key, jsonValue) {
|
||||
if (jsonValue != null) {
|
||||
this.set(key, JSON.stringify(jsonValue));
|
||||
}
|
||||
},
|
||||
getJSON(key) {
|
||||
const value = this.get(key);
|
||||
if (value != null) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
},
|
||||
remove(key) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 会话级缓存
|
||||
*/
|
||||
session: sessionCache,
|
||||
/**
|
||||
* 本地缓存
|
||||
*/
|
||||
local: localCache
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import tab from "./tab";
|
||||
import auth from "./auth";
|
||||
import cache from "./cache";
|
||||
|
||||
export default function installPlugins(app) {
|
||||
// 页签操作
|
||||
app.config.globalProperties.$tab = tab;
|
||||
// 认证对象
|
||||
app.config.globalProperties.$auth = auth;
|
||||
// 缓存对象
|
||||
app.config.globalProperties.$cache = cache;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import useTagsViewStore from "@/store/modules/tagsView";
|
||||
import router from "@/router";
|
||||
|
||||
export default {
|
||||
// 刷新当前tab页签
|
||||
refreshPage(obj) {
|
||||
const { path, query, matched } = router.currentRoute.value;
|
||||
if (obj === undefined) {
|
||||
matched.forEach(m => {
|
||||
if (m.components && m.components.default && m.components.default.name) {
|
||||
if (!["Layout", "ParentView"].includes(m.components.default.name)) {
|
||||
obj = { name: m.components.default.name, path: path, query: query };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return useTagsViewStore()
|
||||
.delCachedView(obj)
|
||||
.then(() => {
|
||||
const { path, query } = obj;
|
||||
router.replace({
|
||||
path: "/redirect" + path,
|
||||
query: query
|
||||
});
|
||||
});
|
||||
},
|
||||
// 关闭当前tab页签,打开新页签
|
||||
closeOpenPage(obj) {
|
||||
useTagsViewStore().delView(router.currentRoute.value);
|
||||
if (obj !== undefined) {
|
||||
return router.push(obj);
|
||||
}
|
||||
},
|
||||
// 关闭指定tab页签
|
||||
closePage(obj) {
|
||||
if (obj === undefined) {
|
||||
return useTagsViewStore()
|
||||
.delView(router.currentRoute.value)
|
||||
.then(({ visitedViews }) => {
|
||||
const latestView = visitedViews.slice(-1)[0];
|
||||
if (latestView) {
|
||||
return router.push(latestView.fullPath);
|
||||
}
|
||||
return router.push("/");
|
||||
});
|
||||
}
|
||||
return useTagsViewStore().delView(obj);
|
||||
},
|
||||
// 关闭所有tab页签
|
||||
closeAllPage() {
|
||||
return useTagsViewStore().delAllViews();
|
||||
},
|
||||
// 关闭左侧tab页签
|
||||
closeLeftPage(obj) {
|
||||
return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
|
||||
},
|
||||
// 关闭右侧tab页签
|
||||
closeRightPage(obj) {
|
||||
return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
|
||||
},
|
||||
// 关闭其他tab页签
|
||||
closeOtherPage(obj) {
|
||||
return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
|
||||
},
|
||||
// 打开tab页签
|
||||
openPage(url) {
|
||||
return router.push(url);
|
||||
},
|
||||
// 修改tab页签
|
||||
updatePage(obj) {
|
||||
return useTagsViewStore().updateVisitedView(obj);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,285 @@
|
||||
import { createWebHistory, createRouter } from "vue-router";
|
||||
/* Layout */
|
||||
import Layout from "@/layout";
|
||||
import NProgress from "nprogress";
|
||||
import usePermissionStore from "@/store/modules/permission";
|
||||
import { isHttp } from "@/utils/validate";
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";
|
||||
|
||||
/**
|
||||
* Note: 路由配置项
|
||||
*
|
||||
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
|
||||
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
|
||||
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
|
||||
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
|
||||
* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
|
||||
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
|
||||
* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
||||
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
|
||||
* roles: ['admin', 'common'] // 访问路由的角色权限
|
||||
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
|
||||
* meta : {
|
||||
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
|
||||
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
|
||||
icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg
|
||||
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
|
||||
activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
|
||||
}
|
||||
*/
|
||||
|
||||
// 公共路由
|
||||
export const constantRoutes = [
|
||||
{
|
||||
path: "/redirect",
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: "/redirect/:path(.*)",
|
||||
component: () => import("@/pages/redirect/index.vue")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
component: () => import("@/pages/Login"),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: "/401",
|
||||
component: () => import("@/pages/error/401"),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
redirect: "/index"
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: Layout,
|
||||
redirect: "/index",
|
||||
children: [
|
||||
{
|
||||
path: "/index",
|
||||
component: () => import("@/views/index"),
|
||||
name: "Index",
|
||||
meta: { title: "首页", icon: "home", affix: true }
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "/hour",
|
||||
component: () => import("@/views/hour/index"),
|
||||
name: "Hour",
|
||||
meta: { title: "小时费率", icon: "money", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: '',
|
||||
// component: Layout,
|
||||
// children: [
|
||||
// {
|
||||
// path: "/unitPrice",
|
||||
// component: () => import("@/views/unitPrice/index"),
|
||||
// name: "UnitPrice",
|
||||
// meta: { title: "7525单价", icon: "money", affix: true }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "convolution/costCalculation",
|
||||
component: () => import("@/views/convolution/costCalculation/index"),
|
||||
name: "CostCalculation",
|
||||
meta: { title: "成本计算", icon: "money", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "convolution/singleLineQuery",
|
||||
component: () => import("@/views/convolution/singleLineQuery/index"),
|
||||
name: "SingleLineQuery",
|
||||
meta: { title: "单行查询", icon: "money", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "convolution/treeQuery",
|
||||
component: () => import("@/views/convolution/treeQuery/index"),
|
||||
name: "TreeQuery",
|
||||
meta: { title: "结构树查询", icon: "tree", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: '',
|
||||
// component: Layout,
|
||||
// children: [
|
||||
// {
|
||||
// path: "miningAnalysis/statistics",
|
||||
// component: () => import("@/views/miningAnalysis/statistics/index"),
|
||||
// name: "Statistics",
|
||||
// meta: { title: "挖掘统计", icon: "tree", affix: true }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "test/index",
|
||||
component: () => import("@/views/test/index"),
|
||||
name: "Test",
|
||||
meta: { title: "测试组件", icon: "tree", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "modelManagement/index",
|
||||
component: () => import("@/views/modelManagement/index"),
|
||||
name: "Modelmanagement",
|
||||
meta: { title: "成本计算模型管理", icon: "tree", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "standardComparison/index",
|
||||
component: () => import("@/views/standardComparison/index"),
|
||||
name: "Standardcomparison",
|
||||
meta: { title: "标准成本对比", icon: "tree", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "standardComparison/manHour",
|
||||
component: () => import("@/views/standardComparison/component/manHour"),
|
||||
name: "ManHour",
|
||||
hidden: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "standardComparison/artificial",
|
||||
component: () => import("@/views/standardComparison/component/artificial"),
|
||||
name: "Artificial",
|
||||
hidden: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "standardComparison/materials",
|
||||
component: () => import("@/views/standardComparison/component/materials"),
|
||||
name: "Materials",
|
||||
hidden: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "stageComparison/index",
|
||||
component: () => import("@/views/stageComparison/index"),
|
||||
name: "StageComparison",
|
||||
meta: { title: "阶段成本对比", icon: "tree", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
//成本挖掘
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "costMining",
|
||||
component: () => import("@/views/costMining/index"),
|
||||
name: "CostMining",
|
||||
meta: { title: "成本挖掘", icon: "tree", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
// Bom,工序,采购价格
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "actualCost",
|
||||
component: () => import("@/views/actualCost/index"),
|
||||
name: "ActualCost",
|
||||
meta: { title: "实际成本", icon: "tree", affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
//本地调试默认路由
|
||||
const defaultRoutes = [];
|
||||
|
||||
// 动态路由,基于用户权限动态去加载
|
||||
export const dynamicRoutes = [...defaultRoutes];
|
||||
const appPath = import.meta.env.VITE_APP_PATH;
|
||||
const router = createRouter({
|
||||
history: createWebHistory(`${appPath}/`),
|
||||
routes: [...constantRoutes, ...dynamicRoutes],
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
} else {
|
||||
return { top: 0 };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const whiteList = ["/login"];
|
||||
let isAddRoute = false;
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
next();
|
||||
});
|
||||
router.afterEach(() => {
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,50 @@
|
||||
import { HttpRequest } from "@qomo-platform/core";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { getToken } from "@/utils/auth";
|
||||
|
||||
export const genHttp = function (baseURL = "") {
|
||||
const httpRequest =
|
||||
new HttpRequest({
|
||||
baseURL: baseURL,
|
||||
//接口回调是否成功
|
||||
isSuccess(resp) {
|
||||
return resp?.code == 0 || resp?.code == 200 || resp?.success;
|
||||
},
|
||||
//获取成功情况下的data
|
||||
getSuccessData(resp) {
|
||||
return resp.result;
|
||||
},
|
||||
//不直接获取api响应的数据,而是获取成功后里面的data
|
||||
getApiResponse: false,
|
||||
loading: false,
|
||||
message: false,
|
||||
needToken: true,
|
||||
processToken(config) {
|
||||
Object.assign(config.headers, {
|
||||
"X-Access-Token": getToken()
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 配置401拦截
|
||||
*/
|
||||
httpRequest.addInterceptor({
|
||||
responseInterceptorCatch(error) {
|
||||
let response = error;
|
||||
if (error) {
|
||||
response = response.response;
|
||||
}
|
||||
//401
|
||||
if (response?.status == 401) {
|
||||
next(`/login`);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
return httpRequest;
|
||||
};
|
||||
//http
|
||||
export const request = genHttp("/newEnergy");
|
||||
|
||||
export default request;
|
||||
@@ -0,0 +1,47 @@
|
||||
export default {
|
||||
/**
|
||||
* 网页标题
|
||||
*/
|
||||
title: import.meta.env.VITE_APP_TITLE,
|
||||
/**
|
||||
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light
|
||||
*/
|
||||
sideTheme: "theme-light",
|
||||
/**
|
||||
* 是否系统布局配置
|
||||
*/
|
||||
showSettings: true,
|
||||
|
||||
/**
|
||||
* 是否显示顶部导航
|
||||
*/
|
||||
topNav: false,
|
||||
|
||||
/**
|
||||
* 是否显示 tagsView
|
||||
*/
|
||||
tagsView: true,
|
||||
|
||||
/**
|
||||
* 是否固定头部
|
||||
*/
|
||||
fixedHeader: false,
|
||||
|
||||
/**
|
||||
* 是否显示logo
|
||||
*/
|
||||
sidebarLogo: true,
|
||||
|
||||
/**
|
||||
* 是否显示动态标题
|
||||
*/
|
||||
dynamicTitle: false,
|
||||
|
||||
/**
|
||||
* @type {string | array} 'production' | ['production', 'development']
|
||||
* @description Need show err logs component.
|
||||
* The default is only used in the production env
|
||||
* If you want to also use it in dev, you can pass ['production', 'development']
|
||||
*/
|
||||
errorLog: "production"
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
import md5 from 'md5'
|
||||
//签名密钥串(前后端要一致,正式发布请自行修改)
|
||||
const signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a";
|
||||
|
||||
export default class signMd5Utils {
|
||||
/**
|
||||
* json参数升序
|
||||
* @param jsonObj 发送参数
|
||||
*/
|
||||
|
||||
static sortAsc(jsonObj) {
|
||||
let arr = new Array();
|
||||
let num = 0;
|
||||
for (let i in jsonObj) {
|
||||
arr[num] = i;
|
||||
num++;
|
||||
}
|
||||
let sortArr = arr.sort();
|
||||
let sortObj = {};
|
||||
for (let i in sortArr) {
|
||||
sortObj[sortArr[i]] = jsonObj[sortArr[i]];
|
||||
}
|
||||
return sortObj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
|
||||
* @param requestParams 请求参数(POST的JSON参数)
|
||||
* @returns {string} 获取签名
|
||||
*/
|
||||
static getSign(url, requestParams) {
|
||||
let urlParams = this.parseQueryString(url);
|
||||
let jsonObj = this.mergeObject(urlParams, requestParams);
|
||||
//console.log("sign jsonObj: ",jsonObj)
|
||||
let requestBody = this.sortAsc(jsonObj);
|
||||
console.log("sign requestBody: ",requestBody)
|
||||
return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url 请求的url
|
||||
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
|
||||
*/
|
||||
static parseQueryString(url) {
|
||||
let urlReg = /^[^\?]+\?([\w\W]+)$/,
|
||||
paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g,
|
||||
urlArray = urlReg.exec(url),
|
||||
result = {};
|
||||
|
||||
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
|
||||
//【这边条件没有encode】带条件参数例子:/sys/dict/getDictItems/sys_user,realname,id,username!='admin'%20order%20by%20create_time
|
||||
let lastpathVariable = url.substring(url.lastIndexOf('/') + 1);
|
||||
if(lastpathVariable.includes(",")){
|
||||
if(lastpathVariable.includes("?")){
|
||||
lastpathVariable = lastpathVariable.substring(0, lastpathVariable.indexOf("?"));
|
||||
}
|
||||
//解决Sign 签名校验失败 #2728
|
||||
result["x-path-variable"] = decodeURIComponent(lastpathVariable);
|
||||
}
|
||||
if (urlArray && urlArray[1]) {
|
||||
let paramString = urlArray[1], paramResult;
|
||||
while ((paramResult = paramReg.exec(paramString)) != null) {
|
||||
//数字值转为string类型,前后端加密规则保持一致
|
||||
if(this.myIsNaN(paramResult[2])){
|
||||
paramResult[2] = paramResult[2].toString()
|
||||
}
|
||||
result[paramResult[1]] = paramResult[2];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {*} 将两个对象合并成一个
|
||||
*/
|
||||
static mergeObject(objectOne, objectTwo) {
|
||||
if (objectTwo && Object.keys(objectTwo).length > 0) {
|
||||
for (let key in objectTwo) {
|
||||
if (objectTwo.hasOwnProperty(key) === true) {
|
||||
//数字值转为string类型,前后端加密规则保持一致
|
||||
if(this.myIsNaN(objectTwo[key])){
|
||||
objectTwo[key] = objectTwo[key].toString()
|
||||
}
|
||||
objectOne[key] = objectTwo[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return objectOne;
|
||||
}
|
||||
|
||||
static urlEncode(param, key, encode) {
|
||||
if (param == null) return '';
|
||||
let paramStr = '';
|
||||
let t = typeof (param);
|
||||
if (t == 'string' || t == 'number' || t == 'boolean') {
|
||||
paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param);
|
||||
} else {
|
||||
for (let i in param) {
|
||||
let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i);
|
||||
paramStr += this.urlEncode(param[i], k, encode);
|
||||
}
|
||||
}
|
||||
return paramStr;
|
||||
};
|
||||
|
||||
/**
|
||||
* 接口签名用 生成header中的时间戳
|
||||
* @returns {number}
|
||||
*/
|
||||
static getTimestamp(){
|
||||
return new Date().getTime()
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 获取客户端时间(签名参数 X_TIMESTAMP)
|
||||
// * @returns {string}
|
||||
// */
|
||||
// static getDateTimeToString() {
|
||||
// const date_ = new Date()
|
||||
// const year = date_.getFullYear()
|
||||
// let month = date_.getMonth() + 1
|
||||
// let day = date_.getDate()
|
||||
// if (month < 10) month = '0' + month
|
||||
// if (day < 10) day = '0' + day
|
||||
// let hours = date_.getHours()
|
||||
// let mins = date_.getMinutes()
|
||||
// let secs = date_.getSeconds()
|
||||
// const msecs = date_.getMilliseconds()
|
||||
// if (hours < 10) hours = '0' + hours
|
||||
// if (mins < 10) mins = '0' + mins
|
||||
// if (secs < 10) secs = '0' + secs
|
||||
// if (msecs < 10) secs = '0' + msecs
|
||||
// return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs
|
||||
// }
|
||||
// true:数值型的,false:非数值型
|
||||
static myIsNaN(value) {
|
||||
return typeof value === 'number' && !isNaN(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
const store = createPinia();
|
||||
store.use(piniaPluginPersistedstate);
|
||||
|
||||
export default store;
|
||||