数据大屏初始化

This commit is contained in:
lzc 2025-03-14 17:52:05 +08:00
parent a564d61c22
commit 3a4554bfb5
134 changed files with 15304 additions and 4 deletions

View File

@ -5,6 +5,7 @@ VITE_APP_TITLE = '数字农业产业管理平台'
VITE_APP_SUB_OS = '//localhost:9526/sub-operation-service/'
VITE_APP_SUB_ADMIN = '//localhost:9527/sub-admin/'
VITE_APP_SUB_GAS = '//localhost:9528/sub-government-affairs-service/'
VITE_APP_SUB_GSS = '//localhost:9529/sub-government-screen-service/'
# 接口
VITE_APP_BASE_API = '/apis'
VITE_APP_BASE_URL = 'http://192.168.18.99:8080'

View File

@ -0,0 +1,86 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script>
import { ref, reactive, watchEffect } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '../../hooks/useEcharts';
export default {
name: 'CustomEchartBarLine',
props: {
chartData: {
type: Array,
default: () => [],
},
option: {
type: Object,
default: () => ({}),
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: 'calc(100vh - 78px)',
},
},
setup(props) {
const chartRef = ref(null);
const { setOptions } = useEcharts(chartRef);
const option = reactive({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true,
backgroundColor: '#333',
},
},
},
xAxis: {
type: 'category',
data: [],
},
yAxis: [],
series: [
{
name: 'bar',
type: 'bar',
data: [],
},
],
});
watchEffect(() => {
props.chartData && initCharts();
});
function initCharts() {
if (props.option) {
Object.assign(option, cloneDeep(props.option));
}
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
let seriesData = [];
typeArr.forEach((type, index) => {
let obj = { name: type };
let chartArr = props.chartData.filter((item) => type === item.type);
obj['data'] = chartArr.map((item) => item.value);
obj['type'] = chartArr[0].seriesType;
obj['barWidth'] = chartArr[0].barWidth;
obj['itemStyle'] = chartArr[0].itemStyle;
seriesData.push(obj);
});
option.series = seriesData;
option.xAxis.data = xAxisData;
option.yAxis = props.option.yAxis;
setOptions(option);
}
return { chartRef };
},
};
</script>

View File

@ -7,6 +7,7 @@ import CustomEchartBar from './custom-echart-bar';
import CustomEchartPie from './custom-echart-pie';
import CustomEchartLine from './custom-echart-line';
import CustomEchartMixin from './custom-echart-mixin';
import CustomEchartBarLine from './custom-echart-bar-line';
export {
SvgIcon,
@ -18,4 +19,5 @@ export {
CustomEchartPie,
CustomEchartLine,
CustomEchartMixin,
CustomEchartBarLine,
};

View File

@ -1,6 +1,6 @@
import actions from './actions';
const { VITE_APP_SUB_OS, VITE_APP_SUB_ADMIN, VITE_APP_SUB_GAS } = import.meta.env;
const { VITE_APP_SUB_OS, VITE_APP_SUB_ADMIN, VITE_APP_SUB_GAS, VITE_APP_SUB_GSS } = import.meta.env;
export const leftApps = [
{
@ -56,9 +56,9 @@ export const rightApps = [
icon: 'images/platform/icon-app.png',
},
{
name: 'sub-government-screen',
entry: VITE_APP_SUB_ADMIN,
activeRule: '/sub-government-screen/',
name: 'sub-government-screen-service',
entry: VITE_APP_SUB_GSS,
activeRule: '/sub-government-screen-service/',
title: '数据大屏',
icon: 'images/platform/icon-screen.png',
},
@ -72,6 +72,13 @@ export const defaultApps = [
title: '政务服务',
icon: 'images/platform/icon-home.png',
},
{
name: 'sub-government-screen-service',
entry: VITE_APP_SUB_GSS,
activeRule: '/sub-government-screen-service/',
title: '数据大屏',
icon: 'images/platform/icon-screen.png',
},
];
// export const microApps = [...defaultApps, ...leftApps, ...rightApps];

View File

@ -8,16 +8,19 @@
"install:main": "cd main && yarn install",
"install:sub-admin": "cd sub-admin && yarn install",
"install:sub-gas": "cd sub-government-affairs-service && yarn install",
"install:sub-gss": "cd sub-government-screen-service && yarn install",
"install:sub-os": "cd sub-operation-service && yarn install",
"dev": "npm-run-all --parallel dev:*",
"dev:main": "cd main && yarn dev",
"dev:sub-admin": "cd sub-app && yarn dev",
"dev:sub-gas": "cd sub-government-affairs-service && yarn dev",
"dev:sub-gss": "cd sub-government-screen-service && yarn dev",
"dev:sub-os": "cd sub-operation-service && yarn dev",
"build": "npm-run-all --serial build:*",
"build:main": "cd main && yarn build",
"build:sub-admin": "cd sub-admin && yarn build",
"build:sub-gas": "cd sub-government-affairs-service && yarn build",
"build:sub-gss": "cd sub-government-screen-service && yarn build",
"build:sub-os": "cd sub-operation-service && yarn build",
"test": "echo \"Error: no test specified\" && exit 1"
},

View File

@ -0,0 +1,9 @@
root = true
[*.{js,jsx,ts,tsx,vue}]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = auto

View File

@ -0,0 +1,10 @@
# 开发环境
VITE_PORT = 9529
VITE_MODE = 'DEV'
VITE_APP_MIAN = 'daimp-front-main'
VITE_APP_MIAN_URL = 'http://localhost:9000'
VITE_APP_NAME = 'sub-government-screen-service'
VITE_APP_BASE_API = '/apis'
VITE_APP_BASE_URL = 'http://192.168.18.99:8080'
VITE_APP_UPLOAD_API = '/uploadApis'
VITE_APP_UPLOAD_URL = 'http://192.168.18.99:9300'

View File

@ -0,0 +1,10 @@
# 生产环境
VITE_MODE = 'PRO'
VITE_APP_MIAN = 'daimp-front-main'
VITE_APP_MIAN_URL = 'http://192.168.18.99:88'
VITE_APP_NAME = 'sub-government-screen-service'
# 接口
VITE_APP_BASE_API = '/apis'
VITE_APP_BASE_URL = ''
VITE_APP_UPLOAD_API = '/uploadApis'
VITE_APP_UPLOAD_URL = ''

View File

@ -0,0 +1,14 @@
*.sh
*.md
*.woff
*.ttf
.vscode
.idea
.husky
.local
dist
src/assets
node_modules
Dockerfile
stats.html
tailwind.config.js

View File

@ -0,0 +1,62 @@
/*
* @Descripttion: .eslintrc.cjs
* 在VSCode中安装ESLint插件编写过程中检测代码质量
* ESLint 代码质量校验相关配置
* 这里使用prettier作为代码格式化工具用ESLint做代码质检
* 相关配置使用下面extends扩展先做默认设置
* .prettierrc.cjs文件中配置好后格式化规则会以.prettierrc.cjs作为最终格式所以不建议在本文件中做代码格式化相关配置
* 相关prettier配置ESLint会默认加载为代码质检 格式化以prettier为主
* 在本配置文件中只做代码质量约束规范配置
* @Author: zenghua.wang
* @Date: 2022-09-22 15:53:58
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-03-22 10:19:39
*/
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint-config-prettier',
'eslint:recommended',
// 'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-essential',
'plugin:prettier/recommended',
],
overrides: [
{
env: {
node: true,
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script',
},
},
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
requireConfigFile: false,
parser: '@babel/eslint-parser',
// parser: '@typescript-eslint/parser',
},
plugins: ['vue', 'prettier'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
},
// 这里时配置规则的,自己看情况配置
rules: {
'prettier/prettier': 'error',
'no-debugger': 'off',
'no-unused-vars': 'off',
'vue/no-unused-vars': 'off',
'vue/multi-word-component-names': 'off',
},
};

116
sub-government-screen-service/.gitignore vendored Normal file
View File

@ -0,0 +1,116 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View File

@ -0,0 +1,20 @@
## OS
.DS_Store
node_modules
.idea
.editorconfig
package-lock.json
.npmrc
# Ignored suffix
*.log
*.md
*.svg
*.png
*ignore
## Local
## Built-files
.cache
dist

View File

@ -0,0 +1,52 @@
/*
* @Descripttion: .prettierrc.cjs
* 在VSCode中安装prettier插件 打开插件配置填写`.prettierrc.js` 将本文件作为其代码格式化规范
* 在本文件中修改格式化规则不会同时触发改变ESLint代码检查所以每次修改本文件需要重启VSCodeESLint检查才能同步代码格式化
* 需要相应的代码格式化规范请自行查阅配置下面为默认项目配置
* @Author: zenghua.wang
* @Date: 2022-09-22 15:53:58
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-24 19:22:25
*/
module.exports = {
// 一行最多多少个字符
printWidth: 150,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: false,
// 在语句末尾是否需要分号
semi: true,
// 是否使用单引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always(x) => x \ avoidx => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
//在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF)
//然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
//对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'auto',
};

View File

@ -0,0 +1,17 @@
# .stylelintignore
# 旧的不需打包的样式库
*.min.css
# 其他类型文件
*.js
*.jpg
*.png
*.eot
*.ttf
*.woff
*.json
# 测试和打包目录
/dist/*
/node_modules/*
/src/assets/*

View File

@ -0,0 +1,131 @@
/*
* @Descripttion: .stylelintrc.cjs
* @Author: zenghua.wang
* @Date: 2022-09-22 15:53:58
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-24 18:49:26
*/
module.exports = {
root: true,
plugins: ['stylelint-order', 'stylelint-scss'],
extends: [
'stylelint-config-standard',
'stylelint-config-standard-scss',
'stylelint-config-prettier',
'stylelint-config-html/vue',
'stylelint-config-recommended-vue',
'stylelint-config-recommended-scss'
],
overrides: [
{
files: ['**/*.{html,vue}'],
customSyntax: 'postcss-html'
}
],
rules: {
indentation: 2,
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', ':deep']
}
],
'number-leading-zero': 'always',
'no-descending-specificity': null,
'function-url-quotes': 'always',
'string-quotes': 'single',
'unit-case': null,
'color-hex-case': 'lower',
'color-hex-length': 'long',
'rule-empty-line-before': 'never',
'font-family-no-missing-generic-family-keyword': null,
'selector-type-no-unknown': null,
'block-opening-brace-space-before': 'always',
'at-rule-no-unknown': null,
'no-duplicate-selectors': null,
'property-no-unknown': null,
'no-empty-source': null,
'selector-class-pattern': null,
'keyframes-name-pattern': null,
'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['global', 'deep'] }],
'function-no-unknown': null,
'order/properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'justify-content',
'align-items',
'float',
'clear',
'overflow',
'overflow-x',
'overflow-y',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'font-size',
'font-family',
'font-weight',
'border',
'border-style',
'border-width',
'border-color',
'border-top',
'border-top-style',
'border-top-width',
'border-top-color',
'border-right',
'border-right-style',
'border-right-width',
'border-right-color',
'border-bottom',
'border-bottom-style',
'border-bottom-width',
'border-bottom-color',
'border-left',
'border-left-style',
'border-left-width',
'border-left-color',
'border-radius',
'text-align',
'text-justify',
'text-indent',
'text-overflow',
'text-decoration',
'white-space',
'color',
'background',
'background-position',
'background-repeat',
'background-size',
'background-color',
'background-clip',
'opacity',
'filter',
'list-style',
'outline',
'visibility',
'box-shadow',
'text-shadow',
'resize',
'transition'
]
}
};

View File

@ -0,0 +1,75 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useLink: typeof import('vue-router')['useLink']
const useModel: typeof import('vue')['useModel']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

View File

@ -0,0 +1,23 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
BaseBg: typeof import('./src/components/baseBg.vue')['default']
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
CustomCard: typeof import('./src/components/CustomCard.vue')['default']
CustomSelect: typeof import('./src/components/CustomSelect.vue')['default']
GridSelect: typeof import('./src/components/GridSelect.vue')['default']
LandClassificationType: typeof import('./src/components/LandClassificationType.vue')['default']
LandIsTransfer: typeof import('./src/components/LandIsTransfer.vue')['default']
LandType: typeof import('./src/components/LandType.vue')['default']
Pagina: typeof import('./src/components/Pagina.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SubTop: typeof import('./src/components/subTop.vue')['default']
}
}

View File

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数据大屏</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,76 @@
{
"name": "government-affairs-service",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite --mode development",
"build": "vite build --mode production",
"test": "vite build --mode test",
"preview": "vite preview",
"format": "prettier --write 'src/**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}'",
"eslint": "npx eslint --init",
"lint": "npm run lint:script && npm run lint:style",
"lint:style": "stylelint 'src/**/*.{vue,scss,css,sass,less}' --fix",
"lint:script": "eslint --ext .js,.ts,.tsx,.vue --fix --quiet ./src"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@smallwei/avue": "^3.6.2",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.5",
"echarts": "^5.6.0",
"element-plus": "^2.7.2",
"js-base64": "^3.7.6",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"screenfull": "^6.0.2",
"vue": "^3.3.11",
"vue-router": "^4.2.5",
"vue3-scroll-seamless": "^1.0.6"
},
"devDependencies": {
"@babel/core": "^7.23.7",
"@babel/eslint-parser": "^7.23.3",
"@types/path-browserify": "^1.0.2",
"@vitejs/plugin-vue": "^4.5.2",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.20.1",
"mockjs": "^1.1.0",
"postcss": "^8.4.33",
"postcss-html": "^1.6.0",
"postcss-import": "^16.0.0",
"prettier": "^3.2.4",
"sass": "^1.70.0",
"stylelint": "^16.2.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-recommended": "^14.0.0",
"stylelint-config-recommended-scss": "^14.0.0",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^36.0.0",
"stylelint-config-standard-scss": "^13.0.0",
"stylelint-order": "^6.0.4",
"stylelint-scss": "^6.1.0",
"terser": "^5.27.0",
"unplugin-auto-import": "^0.17.3",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.0.8",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-mock": "^3.0.1",
"vite-plugin-progress": "^0.0.7",
"vite-plugin-qiankun": "^1.0.15",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-setup-extend": "^0.4.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,27 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2024-01-24 18:54:01
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-02-28 11:31:12
-->
<template>
<el-config-provider :size="size" :locale="zhCn">
<router-view />
</el-config-provider>
</template>
<script setup name="app">
import { computed } from 'vue';
import { useSettingStore } from '@/store/modules/setting';
// element
import zhCn from 'element-plus/es/locale/lang/zh-cn';
const SettingStore = useSettingStore();
//
const size = computed(() => SettingStore.themeConfig.globalComSize);
</script>
<style lang="scss">
@import './styles/style';
</style>

View File

@ -0,0 +1,109 @@
import request from '@/utils/axios';
//基础信息维护——字典项管理
//种植产物相关
// #region
export function getPlanCrop(params = {}) {
return request('land-resource/baseInfo/planTypePage', {
method: 'GET',
params,
});
}
export function savePlanCrop(data) {
return request('land-resource/baseInfo/planTypeSave', {
method: 'POST',
data,
});
}
export function upPlanCrop(data = {}) {
return request('land-resource/baseInfo/planTypeEdit', {
method: 'PUT',
data,
});
}
export function exportPlanCrop(params = {}) {
return request('/land-resource/baseInfo/planTypeExport', {
method: 'GET',
params,
responseType: 'blob',
});
}
export function delPlanCrop(params) {
return request('land-resource/baseInfo/planTypeDelete/' + params.id, {
method: 'DELETE',
});
}
// #endregion
//种植产物对应的种植阶段相关
// #region
// export function getPlanStage(params = {}) {
// return request('land-resource/baseInfo/planTypePage', {
// method: 'GET',
// params,
// });
// }
// export function upPlanStage(data = {}) {
// return request('land-resource/baseInfo/planTypeEdit', {
// method: 'PUT',
// data,
// });
// }
// export function exportPlanStage(params = {}) {
// return request('/land-resource/baseInfo/planTypeExport', {
// method: 'GET',
// params,
// responseType: 'blob',
// });
// }
// export function delPlanStage(params) {
// return request('land-resource/baseInfo/planTypeDelete/' + params.id, {
// method: 'DELETE',
// });
// }
// #endregion
// #region
export function savePlanStage(data) {
return request('land-resource/baseInfo/stageTypeSave', {
method: 'POST',
data,
});
}
/* ------ ------ */
/* 获取土壤类型列表 */
export function getSoilType(params) {
return request('land-resource/baseInfo/soilTypePage', {
method: 'GET',
params,
});
}
/* 创建土壤类型 */
export function saveSoilType(data) {
return request('land-resource/baseInfo/soilTypeSave', {
method: 'POST',
data,
});
}
/* 编辑土壤类型 */
export function updateSoilType(data) {
return request('land-resource/baseInfo/soilTypeSave', {
method: 'PUT',
data,
});
}
// #endregion

View File

@ -0,0 +1 @@
import request from '@/utils/request';

View File

@ -0,0 +1,81 @@
import request from '@/utils/axios';
/**
* @Title: 列表
*/
export function GetEntityList(params = {}) {
return request('/trace/code/farmMange/page', {
method: 'GET',
params,
});
}
/**
* @Title: 新增
*/
export function AddEntity(data = {}) {
return request('/trace/code/farmMange/save', {
method: 'POST',
data,
});
}
/**
* @Title: 修改
*/
export function UpdateEntity(data = {}) {
return request('/trace/code/farmMange/edit', {
method: 'PUT',
data,
});
}
/**
* @Title: 删除
*/
export function DeleteEntity(params = {}) {
return request('/trace/code/farmMange/delete', {
method: 'DELETE',
params,
});
}
/**
* @Title: 导入
*/
export function ImportEntity(data = {}) {
return request('/trace/code/farmMange/import', {
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' },
data,
});
}
/**
* @Title: 导出
*/
export function ExportEntity(params = {}) {
return request('/trace/code/farmMange/export', {
method: 'GET',
params,
responseType: 'blob',
});
}
/**
* @Title: 采收详情
*/
export function GetEntity(params = {}) {
return request(`/trace/code/farmMange/qualityCheck/${params?.id}`, {
method: 'GET',
});
}
/**
* @Title: 溯源详情
*/
export function GetTraceDetail(params = {}) {
return request(`/trace/code/farmMange/originalCodeInfo/${params?.id}`, {
method: 'GET',
});
}

View File

@ -0,0 +1,51 @@
import request from '@/utils/axios';
/**
* @Title: 列表
*/
export function GetEntityList(params = {}) {
return request('/land-resource/gridManage/page', {
method: 'GET',
params,
});
}
/**
* @Title: 新增
*/
export function AddEntity(data = {}) {
return request('/land-resource/gridManage/save', {
method: 'POST',
data,
});
}
/**
* @Title: 修改
*/
export function UpdateEntity(data = {}) {
return request('/land-resource/gridManage/edit', {
method: 'PUT',
data,
});
}
/**
* @Title: 删除
*/
export function DeleteEntity(params = {}) {
return request(`/land-resource/gridManage/delete/${params.id}`, {
method: 'DELETE',
});
}
/**
* @Title: 导出
*/
export function ExportEntity(params = {}) {
return request('/land-resource/gridManage/export', {
method: 'GET',
params,
responseType: 'blob',
});
}

View File

@ -0,0 +1,35 @@
import request from '@/utils/axios';
import { isEmpty } from '@/utils';
/**
* @Title: 获取字典
*/
export function CommonDicData(params = { pageNum: 1, pageSize: 20, dictType: null }) {
if (isEmpty(params?.dictType)) return;
return request(`/system/dict/data/list`, {
method: 'GET',
apisType: 'dicData',
params,
});
}
/**
* @Title: 上传图片
*/
export function CommonUpload(data, params) {
return request(`/upload`, {
method: 'POST',
apisType: 'upload',
uploadType: 'multipart/form-data',
data,
params,
});
}
//云南省所有区域信息
export function getRegion(code) {
let codeVal = code ? code : '530000';
return request('/system/area/region?areaCode=' + codeVal, {
method: 'GET',
});
}

View File

@ -0,0 +1,3 @@
import * as redBlack from './redAndBlank';
export { redBlack };

View File

@ -0,0 +1 @@
import request from '@/utils/request';

View File

@ -0,0 +1,258 @@
import request from '@/utils/axios';
/* 土地列表 */
export function getLandsList(params = {}) {
return request('land-resource/landManage/page', {
method: 'GET',
params,
});
}
export function saveLand(data = {}) {
return request('land-resource/landManage/save', {
method: 'POST',
data,
});
}
export function exportLands(params = {}) {
return request('/land-resource/landManage/export', {
method: 'GET',
params,
responseType: 'blob',
});
}
//年度计划相关
export function getAnnualList(params = {}) {
return request('land-resource/annualManage/page', {
method: 'GET',
params,
});
}
export function saveAnnual(data) {
return request('land-resource/annualManage/save', {
method: 'POST',
data,
});
}
export function editAnnual(data = {}) {
return request('land-resource/annualManage/edit', {
method: 'PUT',
data,
});
}
export function examineAnnual(data = {}) {
return request('land-resource/annualManage/exam', {
method: 'PUT',
data,
});
}
export function exportAnnua(params = {}) {
return request('/land-resource/annualManage/export', {
method: 'GET',
params,
responseType: 'blob',
});
}
export function delAnnual(params) {
return request('land-resource/annualManage/delete/' + params.id, {
method: 'DELETE',
});
}
//种植规划相关
export function getPlanList(params = {}) {
return request('land-resource/planManage/page', {
method: 'GET',
params,
});
}
export function savePlan(data) {
return request('land-resource/planManage/save', {
method: 'POST',
data,
});
}
export function editAlan(data = {}) {
return request('land-resource/planManage/edit', {
method: 'PUT',
data,
});
}
export function exportPlan(params = {}) {
return request('/land-resource/planManage/export', {
method: 'GET',
params,
responseType: 'blob',
});
}
export function delPlan(params) {
return request('land-resource/planManage/delete/' + params.id, {
method: 'DELETE',
});
}
//种植阶段相关
export function getPlantingStage(params = {}) {
return request('land-resource/planManage/pageStage', {
method: 'GET',
params,
});
}
export function savePlantingStage(data) {
return request('land-resource/planManage/stageSave', {
method: 'POST',
data,
});
}
export function editPlantingStage(data = {}) {
return request('land-resource/planManage/editStage', {
method: 'PUT',
data,
});
}
export function delPlantingStage(params) {
return request('land-resource/planManage/deleteStage/' + params.id, {
method: 'DELETE',
});
}
/* 删除土地 */
export function delLand(id) {
return request('land-resource/landManage/delete/' + id, {
method: 'DELETE',
});
}
//作业记录相关
export function getOperationRecord(params = {}) {
return request('land-resource/operationRecord/page', {
method: 'GET',
params,
});
}
export function saveOperationRecord(data) {
return request('land-resource/operationRecord/save', {
method: 'POST',
data,
});
}
export function editOperationRecord(data = {}) {
return request('land-resource/operationRecord/update', {
method: 'PUT',
data,
});
}
export function delOperationRecord(id) {
return request('land-resource/operationRecord/delete/' + id, {
method: 'DELETE',
});
}
export function exportOperationRecord(params = {}) {
return request('/land-resource/operationRecord/export', {
method: 'GET',
params,
responseType: 'blob',
});
}
export function importOperationRecord(data = {}) {
return request('/land-resource/operationRecord/import', {
method: 'POST',
data,
headers: { 'Content-Type': 'multipart/form-data' },
});
}
//通过土地获取产物和位置
export function getAddrCropByLand(landId) {
return request('land-resource/operationRecord/workArea/' + landId, {
method: 'GET',
});
}
/* 导入土地 */
export function importLands(data) {
return request('land-resource/landManage/import', {
method: 'POST',
data,
Headers: {
'Content-Type': 'multipart/form-data',
},
});
}
//土地巡查相关
export function getlandInspection(params = {}) {
return request('land-resource/landInspection/page', {
method: 'GET',
params,
});
}
export function savelandInspection(data) {
return request('land-resource/landInspection/save', {
method: 'POST',
data,
});
}
export function enrolllandInspection(data = {}) {
return request('land-resource/landInspection/registrationResult', {
method: 'PUT',
data,
});
}
export function exportlandInspection(params = {}) {
return request('/land-resource/landInspection/export', {
method: 'GET',
params,
responseType: 'blob',
});
}
/* ------ 土地违法处理 ------ */
// #region
/* 查询土地违法处理 */
export function getLandIllegal(params = {}) {
return request('land-resource/landViolation/page', {
method: 'GET',
params,
});
}
/* 新增土地违法处理 */
export function createLandIllegal(data = {}) {
return request('land-resource/landViolation/save', {
method: 'POST',
data,
});
}
/* 登记处理 */
export function registerLandIllegal(data = {}) {
return request('land-resource/landViolation/registResult', {
method: 'PUT',
data,
});
}
/* 违法详情 */
export function illegalInfo(id = '') {
return request('land-resource/landViolation/detail/' + id, {
method: 'GET',
});
}
// #endregion

View File

@ -0,0 +1,61 @@
import request from '@/utils/axios';
/**
* @Title: 列表
*/
export function GetEntityList(params = {}) {
return request('/land-resource/annualManage/page', {
method: 'GET',
params,
});
}
/**
* @Title: 新增
*/
export function AddEntity(data = {}) {
return request('/land-resource/annualManage/save', {
method: 'POST',
data,
});
}
/**
* @Title: 修改
*/
export function UpdateEntity(data = {}) {
return request('/land-resource/annualManage/edit', {
method: 'PUT',
data,
});
}
/**
* @Title: 审核
*/
export function AuditEntity(data = {}) {
return request('/land-resource/annualManage/exam', {
method: 'PUT',
data,
});
}
/**
* @Title: 删除
*/
export function DeleteEntity(params = {}) {
return request(`/land-resource/annualManage/delete/${params.id}`, {
method: 'DELETE',
});
}
/**
* @Title: 导出
*/
export function ExportEntity(params = {}) {
return request('/land-resource/annualManage/export', {
method: 'GET',
params,
responseType: 'blob',
});
}

View File

@ -0,0 +1,41 @@
import request from '@/utils/axios';
/**
* @Title: 列表
*/
export function GetEntityList(params) {
return request('/trace/code/qualityManage/page', {
method: 'GET',
params,
});
}
/**
* @Title: 新增
*/
export function AddEntity(data) {
return request('/trace/code/qualityManage/save', {
method: 'POST',
data,
});
}
/**
* @Title: 修改
*/
export function UpdateEntity(data) {
return request('/trace/code/qualityManage/edit', {
method: 'PUT',
data,
});
}
/**
* @Title: 删除
*/
export function DeleteEntity(params) {
return request('/trace/code/qualityManage/delete', {
method: 'DELETE',
params,
});
}

View File

@ -0,0 +1,53 @@
import request from '@/utils/axios';
/**
* @Title: 列表
*/
export function GetEntityList(data, params) {
return request('/store/user/query-user', {
method: 'POST',
data: data,
params: params,
});
}
/**
* @Title: 新增
*/
export function AddEntity(data, params = {}) {
return request('/store/user/add-user', {
method: 'POST',
data: data,
params: params,
});
}
/**
* @Title: 修改
*/
export function UpdateEntity(data, params = {}) {
return request('/store/user/update-user', {
method: 'PUT',
data: data,
params: params,
});
}
/**
* @Title: 删除
*/
export function DeleteEntity(data, params) {
return request('/store/user/del-user', {
method: 'DELETE',
data: data,
params: params,
});
}
export function UploadAvatar(data, params) {
return request('/store/self/generate-avatar-pic-upload-url', {
method: 'POST',
data: data,
params: params,
});
}

View File

@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,195 @@
@font-face {
font-family: "iconfont"; /* Project id 4425172 */
src: url('iconfont.woff2?t=1725879404188') format('woff2'),
url('iconfont.woff?t=1725879404188') format('woff'),
url('iconfont.ttf?t=1725879404188') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-data4:before {
content: "\e60c";
}
.icon-data5:before {
content: "\e6be";
}
.icon-recharge-record:before {
content: "\e614";
}
.icon-recharge-rule:before {
content: "\e628";
}
.icon-user-profile:before {
content: "\e783";
}
.icon-achieve:before {
content: "\e616";
}
.icon-activity-level:before {
content: "\e61a";
}
.icon-skins:before {
content: "\e790";
}
.icon-data1:before {
content: "\e996";
}
.icon-data2:before {
content: "\e661";
}
.icon-data3:before {
content: "\e632";
}
.icon-data:before {
content: "\e64e";
}
.icon-game:before {
content: "\e6d0";
}
.icon-banner:before {
content: "\e613";
}
.icon-verification:before {
content: "\e601";
}
.icon-balance:before {
content: "\e6b9";
}
.icon-refund:before {
content: "\e7af";
}
.icon-wechat:before {
content: "\e681";
}
.icon-alipay:before {
content: "\e61e";
}
.icon-user:before {
content: "\e67f";
}
.icon-coupon:before {
content: "\e65a";
}
.icon-level:before {
content: "\e7d8";
}
.icon-activity:before {
content: "\e67b";
}
.icon-shop:before {
content: "\e60a";
}
.icon-member:before {
content: "\e640";
}
.icon-recharge:before {
content: "\e799";
}
.icon-marketing:before {
content: "\e765";
}
.icon-goods-sku:before {
content: "\e6d7";
}
.icon-store:before {
content: "\e62b";
}
.icon-goods-store:before {
content: "\e6c6";
}
.icon-storer:before {
content: "\e64a";
}
.icon-order:before {
content: "\e737";
}
.icon-permission:before {
content: "\e612";
}
.icon-goods:before {
content: "\e889";
}
.icon-menu:before {
content: "\e60e";
}
.icon-dict-type:before {
content: "\e652";
}
.icon-dictionary:before {
content: "\e600";
}
.icon-role:before {
content: "\e604";
}
.icon-fullscreen:before {
content: "\e8fa";
}
.icon-exit-fullscreen:before {
content: "\e8fb";
}
.icon-table:before {
content: "\e615";
}
.icon-test:before {
content: "\e610";
}
.icon-lang:before {
content: "\e649";
}
.icon-demo:before {
content: "\e6ee";
}
.icon-size:before {
content: "\e660";
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,324 @@
{
"id": "4425172",
"name": "sub-vue",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "react/vue项目后台管理平台",
"glyphs": [
{
"icon_id": "1218184",
"name": "销售明细",
"font_class": "data4",
"unicode": "e60c",
"unicode_decimal": 58892
},
{
"icon_id": "2230090",
"name": "销售明细",
"font_class": "data5",
"unicode": "e6be",
"unicode_decimal": 59070
},
{
"icon_id": "6882983",
"name": "充值记录",
"font_class": "recharge-record",
"unicode": "e614",
"unicode_decimal": 58900
},
{
"icon_id": "34611004",
"name": "充值规则",
"font_class": "recharge-rule",
"unicode": "e628",
"unicode_decimal": 58920
},
{
"icon_id": "15562252",
"name": "用户画像",
"font_class": "user-profile",
"unicode": "e783",
"unicode_decimal": 59267
},
{
"icon_id": "18747445",
"name": "成就",
"font_class": "achieve",
"unicode": "e616",
"unicode_decimal": 58902
},
{
"icon_id": "33848542",
"name": "我的-段位",
"font_class": "activity-level",
"unicode": "e61a",
"unicode_decimal": 58906
},
{
"icon_id": "20406821",
"name": "皮肤",
"font_class": "skins",
"unicode": "e790",
"unicode_decimal": 59280
},
{
"icon_id": "2214847",
"name": "个人中心-积分商城",
"font_class": "data1",
"unicode": "e996",
"unicode_decimal": 59798
},
{
"icon_id": "14233304",
"name": "价值投资",
"font_class": "data2",
"unicode": "e661",
"unicode_decimal": 58977
},
{
"icon_id": "23059951",
"name": "费用统计",
"font_class": "data3",
"unicode": "e632",
"unicode_decimal": 58930
},
{
"icon_id": "2199049",
"name": "数据报表",
"font_class": "data",
"unicode": "e64e",
"unicode_decimal": 58958
},
{
"icon_id": "36257316",
"name": "游戏管理",
"font_class": "game",
"unicode": "e6d0",
"unicode_decimal": 59088
},
{
"icon_id": "11913396",
"name": "banner",
"font_class": "banner",
"unicode": "e613",
"unicode_decimal": 58899
},
{
"icon_id": "35264323",
"name": "核销码核销",
"font_class": "verification",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "6514128",
"name": "结算管理",
"font_class": "balance",
"unicode": "e6b9",
"unicode_decimal": 59065
},
{
"icon_id": "12025983",
"name": "退货退款",
"font_class": "refund",
"unicode": "e7af",
"unicode_decimal": 59311
},
{
"icon_id": "1207908",
"name": "wechat",
"font_class": "wechat",
"unicode": "e681",
"unicode_decimal": 59009
},
{
"icon_id": "27188513",
"name": "alipay",
"font_class": "alipay",
"unicode": "e61e",
"unicode_decimal": 58910
},
{
"icon_id": "11111017",
"name": "会员",
"font_class": "user",
"unicode": "e67f",
"unicode_decimal": 59007
},
{
"icon_id": "630079",
"name": "我的优惠券",
"font_class": "coupon",
"unicode": "e65a",
"unicode_decimal": 58970
},
{
"icon_id": "2046370",
"name": "会员等级",
"font_class": "level",
"unicode": "e7d8",
"unicode_decimal": 59352
},
{
"icon_id": "2569868",
"name": "活动",
"font_class": "activity",
"unicode": "e67b",
"unicode_decimal": 59003
},
{
"icon_id": "2681698",
"name": "门店",
"font_class": "shop",
"unicode": "e60a",
"unicode_decimal": 58890
},
{
"icon_id": "2811147",
"name": "会员",
"font_class": "member",
"unicode": "e640",
"unicode_decimal": 58944
},
{
"icon_id": "4560182",
"name": "会员充值",
"font_class": "recharge",
"unicode": "e799",
"unicode_decimal": 59289
},
{
"icon_id": "5880283",
"name": "营销",
"font_class": "marketing",
"unicode": "e765",
"unicode_decimal": 59237
},
{
"icon_id": "6982618",
"name": "商品规格",
"font_class": "goods-sku",
"unicode": "e6d7",
"unicode_decimal": 59095
},
{
"icon_id": "7307041",
"name": "商家入驻",
"font_class": "store",
"unicode": "e62b",
"unicode_decimal": 58923
},
{
"icon_id": "11639867",
"name": "小店商品库",
"font_class": "goods-store",
"unicode": "e6c6",
"unicode_decimal": 59078
},
{
"icon_id": "13872198",
"name": "商家列表",
"font_class": "storer",
"unicode": "e64a",
"unicode_decimal": 58954
},
{
"icon_id": "577335",
"name": "订单",
"font_class": "order",
"unicode": "e737",
"unicode_decimal": 59191
},
{
"icon_id": "736503",
"name": "权限",
"font_class": "permission",
"unicode": "e612",
"unicode_decimal": 58898
},
{
"icon_id": "1727271",
"name": "06商品-线性",
"font_class": "goods",
"unicode": "e889",
"unicode_decimal": 59529
},
{
"icon_id": "7587933",
"name": "菜单",
"font_class": "menu",
"unicode": "e60e",
"unicode_decimal": 58894
},
{
"icon_id": "12758820",
"name": "dictionary",
"font_class": "dict-type",
"unicode": "e652",
"unicode_decimal": 58962
},
{
"icon_id": "13768112",
"name": "dictionary",
"font_class": "dictionary",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "37734141",
"name": "new-role",
"font_class": "role",
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "1727563",
"name": "327全屏",
"font_class": "fullscreen",
"unicode": "e8fa",
"unicode_decimal": 59642
},
{
"icon_id": "1727566",
"name": "328退出全屏",
"font_class": "exit-fullscreen",
"unicode": "e8fb",
"unicode_decimal": 59643
},
{
"icon_id": "11641852",
"name": "表格",
"font_class": "table",
"unicode": "e615",
"unicode_decimal": 58901
},
{
"icon_id": "20104468",
"name": "测试",
"font_class": "test",
"unicode": "e610",
"unicode_decimal": 58896
},
{
"icon_id": "26686335",
"name": "中英文",
"font_class": "lang",
"unicode": "e649",
"unicode_decimal": 58953
},
{
"icon_id": "30012547",
"name": "方案列表-默认",
"font_class": "demo",
"unicode": "e6ee",
"unicode_decimal": 59118
},
{
"icon_id": "37702310",
"name": "文字大小",
"font_class": "size",
"unicode": "e660",
"unicode_decimal": 58976
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,21 @@
<template>
<el-card body-class="custom_content_card_" :shadow="props.shadow">
<slot></slot>
</el-card>
</template>
<script setup>
const props = defineProps({
shadow: {
type: String,
default: 'never',
},
});
</script>
<style lang="scss">
.el-card:has(.custom_content_card_) {
border: none !important;
border-radius: 10px !important;
}
</style>

View File

@ -0,0 +1,94 @@
<template>
<el-select-v2
v-model="val"
:options="options"
:placeholder="props.set.placeholder"
:props="props.set.props"
:multiple="props.set.multiple"
@change="handleSelect"
/>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import request from '@/utils/axios';
const emit = defineEmits(['update:value']);
const props = defineProps({
set: {
type: Object,
default: () => {
return {
url: '',
options: [
{
value: '1',
label: 'label 1',
},
{
value: '2',
label: 'label 2',
},
{
value: '3',
label: 'label 3',
},
],
props: {
value: 'value',
label: 'label',
},
multiple: false,
placeholder: '请选择',
};
},
},
value: {
type: String || Array || null,
default: null,
},
});
onMounted(async () => {
if (props.set.multiple) val.value = [];
if (props.set.url) {
let res = await request(props.set.url, {
method: 'get',
data: { current: 1, size: 9999 },
});
if (res.code == 200) {
options.value = res.data.records;
}
} else {
options.value = props.set.options;
}
});
/* --------------- data --------------- */
// #region
const val = ref(null);
watch(
() => props.value,
() => {
val.value = props.value;
},
{
deep: true,
immediate: true,
}
);
const options = ref([]);
// #endregion
/* --------------- methods --------------- */
// #region
function handleSelect(val_) {
emit('update:value', val_);
}
// #endregion
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,44 @@
<template>
<el-select-v2 v-model="val" :options="options" placeholder="请选择网格" :props="_props" @change="handleSelect" />
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { GetEntityList } from '@/apis/grid.js';
const emit = defineEmits(['update:value']);
const props = defineProps({
//
value: {
type: String || null,
default: null,
},
});
onMounted(async () => {
let res = await GetEntityList();
if (res.code == 200) {
options.value = res.data.records;
}
});
/* --------------- data --------------- */
// #region
const val = ref(null);
const options = ref([]);
const _props = {
value: 'id',
label: 'productName',
};
// #endregion
/* --------------- methods --------------- */
// #region
function handleSelect(val_) {
emit('update:value', val_);
}
// #endregion
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,60 @@
<template>
<el-select v-model="val" @change="change">
<el-option v-for="item in options" :key="`land_type_${item.value}`" :value="item.value" :label="item.label" :placeholder="props.placeholder">
{{ item.label }}
</el-option>
</el-select>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
//
value: {
type: String || Number || null,
default: null,
},
placeholder: {
type: String,
default: '请选择',
},
});
const emit = defineEmits(['update:value']);
/* --------------- data --------------- */
// #region
const val = ref(null);
watch(
() => props.value,
() => {
val.value = props.value || props.value == 0 ? String(props.value) : null;
},
{
deep: true,
immediate: true,
}
);
const options = ref([
{ label: '耕地', value: '0' },
{ label: '果园', value: '1' },
{ label: '茶园', value: '2' },
{ label: '其他园地', value: '3' },
{ label: '林地', value: '4' },
{ label: '草地', value: '5' },
{ label: '其他农用地', value: '6' },
{ label: '农村宅基地', value: '7' },
]);
// #endregion
/* --------------- methods --------------- */
// #region
function change(val_) {
val.value = val_;
emit('update:value', val_);
}
// #endregion
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,54 @@
<template>
<el-select v-model="val" @change="change">
<el-option v-for="item in options" :key="`land_type_${item.value}`" :value="item.value" :label="item.label" :placeholder="props.placeholder">
{{ item.label }}
</el-option>
</el-select>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
//
value: {
type: String || Number || null,
default: null,
},
placeholder: {
type: String,
default: '请选择',
},
});
const emit = defineEmits(['update:value']);
/* --------------- data --------------- */
// #region
const val = ref(null);
watch(
() => props.value,
() => {
val.value = props.value || props.value == 0 ? String(props.value) : null;
},
{
deep: true,
immediate: true,
}
);
const options = ref([
{ label: '是', value: '0' },
{ label: '否', value: '1' },
]);
// #endregion
/* --------------- methods --------------- */
// #region
function change(val_) {
val.value = val_;
emit('update:value', val_);
}
// #endregion
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,55 @@
<template>
<el-select v-model="val" @change="change">
<el-option v-for="item in options" :key="`land_type_${item.value}`" :value="item.value" :label="item.label" :placeholder="props.placeholder">
{{ item.label }}
</el-option>
</el-select>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
//
value: {
type: String || Number || null,
default: null,
},
placeholder: {
type: String,
default: '请选择',
},
});
const emit = defineEmits(['update:value']);
/* --------------- data --------------- */
// #region
const val = ref(null);
watch(
() => props.value,
() => {
val.value = props.value || props.value == 0 ? String(props.value) : null;
},
{
deep: true,
immediate: true,
}
);
const options = ref([
{ label: '农用地', value: '0' },
{ label: '住宅用地', value: '1' },
{ label: '园林', value: '2' },
]);
// #endregion
/* --------------- methods --------------- */
// #region
function change(val_) {
val.value = val_;
emit('update:value', val_);
}
// #endregion
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,72 @@
<template>
<section :style="{ '--right': props.right ? 'flex-end' : 'unset' }">
<el-pagination
v-model:current-page="curPage"
v-model:page-size="curSize"
:page-sizes="[10, 20, 30, 40, 50, 100]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="curTotal"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</section>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
pageData: {
type: Object,
default: () => {
return {
page: 1,
size: 10,
total: 0,
};
},
},
pageSizes: {
type: Array,
default: () => {
return [10, 20, 30, 40, 50, 100];
},
},
right: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(['pageChange', 'sizeChange']);
const curPage = ref(1);
const curSize = ref(10);
const curTotal = ref(0);
watch(
() => props.pageData,
() => {
curPage.value = props.pageData.page || 1;
curSize.value = props.pageData.size || 10;
curTotal.value = props.pageData.total || 0;
},
{
deep: true,
immediate: true,
}
);
function handleCurrentChange(val) {
emit('pageChange', val);
}
function handleSizeChange(val) {
emit('sizeChange', val);
}
</script>
<style lang="scss" scoped>
section {
display: flex;
justify-content: var(--right);
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<div class="data-warp" :style="{ 'background-image': 'url(' + getAssetsFile('images/screenbg.png') + ')' }">
<div class="chart-content">
<div class="top">头部</div>
<div class="content">
<slot name="center"></slot>
</div>
<div class="bottom" :style="{ 'background-image': 'url(' + getAssetsFile('images/bottombj.jpg') + ')' }">
<div class="b-nav">
<div
v-for="n in navlist"
:key="n.name"
class="b-nav-item"
:class="currentName == n.name ? 'nav-act' : 'nav-normal'"
@click="itemAct(n.name)"
>
<!-- <router-link :to="n.name"> -->
<span>{{ n.title }}</span>
<!-- </router-link> -->
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { isEmpty, getAssetsFile } from '@/utils';
import { ref, reactive, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useApp } from '@/hooks';
const router = useRouter();
const props = defineProps({
nameVal: {
type: String,
default: 'home',
},
});
const navlist = ref([
{ title: '首页', name: 'home' },
{ title: '土地资源', name: 'land' },
{ title: '投入品', name: 'inputs' },
{ title: '生产经营主体', name: 'entities' },
{ title: '智慧种植检测', name: 'plant' },
{ title: '智慧养殖检测', name: 'breed' },
{ title: '全流程溯源', name: 'trace' },
{ title: '产业预警决策', name: 'early' },
]);
let currentName = ref('home');
watch(
() => props.nameVal,
() => {
currentName.value = props.nameVal;
},
{
deep: true,
immediate: true,
}
);
const itemAct = (name) => {
currentName.value = name;
router.push({ name: name });
};
</script>
<style lang="scss" scoped>
div {
box-sizing: border-box;
}
.data-warp {
width: 100%;
height: 100vh;
background-size: 100% 100%;
.chart-content {
width: 100%;
height: 100vh;
z-index: 1;
position: absolute;
left: 0;
top: 0;
.top,
.content,
.bottom {
width: 100%;
padding: 0 16px;
background-size: cover;
display: inline-flex;
flex-direction: column;
justify-content: center;
}
.top {
height: 55px;
}
.bottom {
height: 98px;
text-align: center;
.b-nav {
margin: auto;
display: inline-flex;
gap: 20px;
.b-nav-item {
display: inline-block;
cursor: pointer;
span {
font-size: 16px;
font-weight: bold;
display: flex;
transform: skewX(-13deg);
background: linear-gradient(to bottom, '#ff7e5f', '#548fff');
-webkit-background-clip: text;
letter-spacing: 4px;
text-shadow: -2px 0 0 1px #add8f1;
}
&.nav-act {
color: rgba(255, 255, 255, 1);
}
&.nav-normal {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
.content {
height: calc(100% - 153px);
}
}
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<el-dialog
v-model="state.visible"
draggable
title="溯源码"
width="40%"
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="onClose"
>
<div class="code-panel">
<div class="code-panel-picture">
<el-image style="width: 200px; height: 200px" :src="row.orCodeUrl" fit="cover" lazy />
</div>
<el-button type="primary" @click="downloadFile(row.orCodeUrl, `${row.productName}-溯源码.png`, 'image')"> 下载溯源码</el-button>
</div>
</el-dialog>
</template>
<script setup name="code-dialog">
import { reactive } from 'vue';
import { downloadFile } from '@/utils';
const props = defineProps({
row: {
type: Object,
default: () => {},
},
});
const emit = defineEmits(['on-close']);
const state = reactive({
visible: false,
});
const onClose = () => {
state.visible = false;
};
defineExpose({
show: () => {
state.visible = true;
},
hide: () => {
onClose();
},
});
</script>
<style lang="scss" scoped>
.code {
&-panel {
padding-bottom: 40px;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<div :style="{ 'text-align': pos }" class="title-top-warp">{{ topTitle || '--' }}</div>
</template>
<script setup>
import { isEmpty, getAssetsFile } from '@/utils';
import { ref, reactive, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useApp } from '@/hooks';
const router = useRouter();
const props = defineProps({
title: {
type: String,
default: '统计分析',
},
postion: {
type: String,
default: 'left',
},
});
let topTitle = ref('');
let pos = ref('');
watch(
() => (props.title, props.postion),
() => {
topTitle.value = props.title;
pos.value = props.postion;
},
{
deep: true,
immediate: true,
}
);
</script>
<style lang="scss" scoped>
.title-top-warp {
font-size: 14px;
font-weight: bold;
display: inline-block;
transform: skewX(-13deg);
background: linear-gradient(to bottom, '#ff7e5f', '#548fff');
-webkit-background-clip: text;
color: #fff;
letter-spacing: 4px;
text-shadow: -2px 0 0 1px #add8f1;
width: 100%;
}
</style>

View File

@ -0,0 +1,42 @@
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
const { VITE_APP_MIAN, VITE_APP_NAME } = import.meta.env;
export const GenKey = (key, prefix = VITE_APP_NAME) => {
prefix = qiankunWindow.__POWERED_BY_QIANKUN__ ? VITE_APP_MIAN : VITE_APP_MIAN;
return prefix ? `${prefix}_` + key : key;
};
export const CONSTANTS = {
PREFIX: `${VITE_APP_NAME}_`,
PRIMARY: '#409eff',
};
export const CRUD_OPTIONS = {
index: true,
indexLabel: '序号',
indexWidth: 80,
selection: true,
align: 'center',
headerAlign: 'center',
gridBtn: false,
columnBtn: false,
addBtn: true,
viewBtn: false,
editBtn: false,
delBtn: false,
gutter: 20,
labelWidth: 150,
column: [],
menuWidth: 100,
actions: [],
dialogDrag: true,
};
export const CRUD_VIEW_OPTIONS = {
...CRUD_OPTIONS,
index: true,
addBtn: false,
refreshBtn: false,
selection: false,
menu: false,
};

View File

@ -0,0 +1,32 @@
/**
* @Description: 按钮权限
* @Author: zenghua.wang
* @Date: 2022-08-30 09:42:47
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-30 13:59:50
*/
// import { CACHE_KEY, useCache } from '@/hooks/web/useCache';
export function useAuth(app) {
app.directive('auth', (el, binding) => {
// const { wsCache } = useCache();
const { value } = binding;
const all_permission = '*:*:*';
const permissions = []; //wsCache.get(CACHE_KEY.USER).permissions;
if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value;
const hasAuth = permissions.some((permission) => {
return all_permission === permission || permissionFlag.includes(permission);
});
if (!hasAuth) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
// throw new Error('no auth to access it.');
}
});
}

View File

@ -0,0 +1,9 @@
import { useAuth } from './auth';
/**
* 指令v-xxx
* @methods auth 按钮权限用法: v-auth
*/
export const registerDirective = (app) => {
useAuth(app);
};

View File

@ -0,0 +1,5 @@
import { getCurrentInstance } from 'vue';
export const useApp = () => {
return getCurrentInstance().appContext?.config?.globalProperties;
};

View File

@ -0,0 +1,22 @@
import { h } from 'vue';
const wrapperMap = new Map();
export const useWrapComponents = (Component, route) => {
let wrapper;
if (Component) {
const wrapperName = route.name;
if (wrapperMap.has(wrapperName)) {
wrapper = wrapperMap.get(wrapperName);
} else {
wrapper = {
name: wrapperName,
render() {
return h('div', { className: 'layout-main-inner' }, Component);
},
};
wrapperMap.set(wrapperName, wrapper);
}
return h(wrapper);
}
};

View File

@ -0,0 +1,4 @@
<template>
<router-view />
</template>
<script setup name="layout-views"></script>

View File

@ -0,0 +1,82 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-02-18 09:48:18
-->
<template>
<el-dropdown class="layout-avatar">
<span class="el-dropdown-link">
<el-avatar :size="30" class="avatar" :src="userInfo.avatar || getAssetsFile('images/avatar.gif')" />
<span class="layout-avatar-name">{{ userInfo.userName || '游客' }}</span>
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="0"> 当前角色{{ userInfo.nickName }} </el-dropdown-item>
<el-dropdown-item :command="5" divided @click="logOut">
<el-icon><SwitchButton /></el-icon>退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup name="layout-avatar">
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useUserStore } from '@/store/modules/user';
import { useTagsViewStore } from '@/store/modules/tagsView';
import { usePermissionStore } from '@/store/modules/permission';
import { getAssetsFile } from '@/utils';
import { Logout } from '#/apis/login';
const router = useRouter();
const UserStore = useUserStore();
const TagsViewStore = useTagsViewStore();
const PermissionStore = usePermissionStore();
//
const userInfo = computed(() => UserStore.getUserInfo());
const logOut = async () => {
ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
Logout().then((res) => {
if (res.code === 200) {
UserStore.logout();
TagsViewStore.clearVisitedView();
PermissionStore.clearRoutes();
router.push({ path: '/login' });
ElMessage({
type: 'success',
message: '退出登录成功!',
});
localStorage.removeItem('daimp-front-main_user_store');
}
});
});
};
</script>
<style lang="scss" scoped>
.layout-avatar {
&-name {
margin-left: 8px;
@include ellipsis;
}
.el-dropdown-link {
@include flex-row;
align-items: center;
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,46 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-02-13 16:02:18
-->
<template>
<el-breadcrumb class="layout-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-if="matched[0].meta.title !== '首页'" key="home" :to="{ path: '/' }">
<div class="layout-breadcrumb-item">
<span class="layout-breadcrumb-title">首页</span>
</div>
</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in matched" :key="item.name">
<span v-if="item.redirect === 'noRedirect' || index == matched.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 name="layout-breadcrumb">
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const handleLink = (item) => {
router.push({
path: item.path,
});
};
const matched = computed(() => route.matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false));
</script>
<style lang="scss" scoped>
.layout-breadcrumb {
@include flex-row;
align-items: center;
white-space: nowrap;
}
</style>

View File

@ -0,0 +1,40 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-26 23:04:29
-->
<template>
<div class="layout-hamburger" @click="handleCollapse">
<el-icon v-if="isCollapse" class="icon"><expand /></el-icon>
<el-icon v-else class="icon"><fold /></el-icon>
</div>
</template>
<script setup name="layout-hamburger">
import { computed } from 'vue';
import { useSettingStore } from '@/store/modules/setting';
const SettingStore = useSettingStore();
const isCollapse = computed(() => !SettingStore.isCollapse);
const handleCollapse = () => {
SettingStore.setCollapse(isCollapse.value);
};
</script>
<style lang="scss" scoped>
.layout-hamburger {
display: flex;
align-items: center;
padding: 0 15px;
height: 100%;
&:hover {
background: rgb(0 0 0 / 2.5%);
}
.icon {
font-size: 24px;
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,108 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-01-24 15:11:22
-->
<template>
<div class="layout-header-placeholder" :style="stylePlaceholder()"></div>
<div
class="layout-header"
:class="{
'fixed-header': themeConfig.fixedHeader,
collapse: themeConfig.fixedHeader && isCollapse,
'no-collapse': themeConfig.fixedHeader && !isCollapse,
}"
>
<div class="layout-header-inner">
<div class="layout-header-left">
<Hamburger />
<Breadcrumb />
</div>
<div class="layout-header-right">
<ScreenFull class="layout-header-tool" />
<Avatar class="layout-header-tool" />
</div>
</div>
<TagsView v-if="themeConfig.showTag" />
</div>
</template>
<script setup name="layout-header">
import { computed } from 'vue';
import { useSettingStore } from '@/store/modules/setting';
import { setPx } from '@/utils';
import Hamburger from '../Hamburger';
import Breadcrumb from '../Breadcrumb';
import ScreenFull from '../ScreenFull';
import Avatar from '../Avatar';
import TagsView from '../TagsView';
const SettingStore = useSettingStore();
//
const themeConfig = computed(() => SettingStore.themeConfig);
const isCollapse = computed(() => !SettingStore.isCollapse);
const stylePlaceholder = () => {
return { height: themeConfig.value.showTag ? setPx(90) : setPx(50) };
};
</script>
<style lang="scss" scoped>
.layout-header {
width: 100%;
background: white;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
&.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
}
&.collapse {
width: calc(100% - 60px);
}
&.no-collapse {
width: calc(100% - 210px);
}
&-placeholder {
height: 50px;
}
&-inner {
@include flex-row;
justify-content: space-between;
align-items: center;
padding: 0 10px 0 0;
width: 100%;
height: 50px;
border-bottom: 1px solid #eeeeee;
box-sizing: border-box;
}
&-left {
@include flex-row;
align-items: center;
height: 100%;
}
&-right {
@include flex-row;
align-items: center;
}
&-tool {
margin-right: 10px;
padding: 4px;
cursor: pointer;
:deep(.el-icon) {
font-size: 20px;
color: #333333;
}
}
}
</style>

View File

@ -0,0 +1,47 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-02-13 16:04:43
-->
<template>
<div class="logo">
<!-- <img src="/images/logo.png" class="logo-picture" /> -->
<h2 v-show="!isCollapse" class="logo-title">{{ VITE_APP_TITLE }}</h2>
</div>
</template>
<script setup name="logo">
defineProps({
isCollapse: {
type: Boolean,
default: false,
},
});
const { VITE_APP_TITLE } = import.meta.env;
</script>
<style lang="scss" scoped>
.logo {
justify-content: center;
align-items: center;
overflow: hidden;
margin-bottom: 2px;
padding: 7px 5px;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
transition: all 0.28s;
@include flex-row;
&-picture {
margin: 0 auto;
width: 70px;
height: 35px;
}
&-title {
height: 35px;
line-height: 35px;
color: $color-primary;
}
}
</style>

View File

@ -0,0 +1,48 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-02-05 15:47:56
-->
<template>
<div class="layout-main">
<router-view v-slot="{ Component, route }">
<transition name="fade-slide" mode="out-in" appear>
<keep-alive v-if="isReload" :include="cacheRoutes">
<component :is="useWrapComponents(Component, route)" :key="route.path" />
</keep-alive>
</transition>
</router-view>
</div>
</template>
<script setup name="layout-main">
import { computed } from 'vue';
import { useSettingStore } from '@/store/modules/setting';
import { usePermissionStore } from '@/store/modules/permission';
import { useWrapComponents } from '@/hooks/useWrapComponents';
const SettingStore = useSettingStore();
const PermissionStore = usePermissionStore();
const cacheRoutes = computed(() => PermissionStore.keepAliveRoutes);
const isReload = computed(() => SettingStore.isReload);
</script>
<style lang="scss" scoped>
.layout-main {
flex: 1;
display: flex;
width: 100%;
height: 100%;
@include scrollable;
box-sizing: border-box;
&-inner {
padding: 10px;
width: 100%;
background-color: #f5f5f5;
box-sizing: border-box;
}
}
</style>

View File

@ -0,0 +1,23 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2024-01-27 16:02:43
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-04-12 21:12:01
-->
<template>
<el-icon v-if="icon.includes('icon')" :class="`iconfont ${icon}`" :size="size" />
<el-icon v-else :size="size"> <component :is="icon" /></el-icon>
</template>
<script setup name="layout-icon">
defineProps({
icon: {
type: String,
required: true,
},
size: {
type: Number,
default: 20,
},
});
</script>

View File

@ -0,0 +1,44 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-27 09:29:36
-->
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script setup name="layout-link">
import { computed } from 'vue';
import { isExternal } from '@/utils/validate';
const props = defineProps({
to: {
type: String,
required: true,
},
});
const type = computed(() => {
if (isExternal(props.to)) {
return 'a';
}
return 'router-link';
});
const linkProps = (to) => {
if (isExternal(to)) {
return {
href: to,
target: '_blank',
rel: 'noopener',
};
}
return {
to: to,
};
};
</script>

View File

@ -0,0 +1,81 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-27 16:07:37
-->
<template>
<template v-if="!item.hidden">
<template v-if="!item.alwaysShow && hasOneShowingChild(item.children, item)">
<layout-link v-if="onlyOneChild.meta" :to="onlyOneChild.path">
<el-menu-item :index="onlyOneChild.path">
<layout-icon :size="20" :icon="onlyOneChild?.meta?.icon" />
<template #title>{{ onlyOneChild.meta && onlyOneChild.meta?.title }}</template>
</el-menu-item>
</layout-link>
</template>
<el-sub-menu v-else :index="item.path" teleported>
<template #title>
<layout-icon :size="20" :icon="item?.meta?.icon" />
<span>{{ item.meta && item.meta?.title }}</span>
</template>
<sub-item v-for="child in item.children" :key="child.path" :item="child" />
</el-sub-menu>
</template>
</template>
<script setup name="sub-item">
import { ref } from 'vue';
// import { isExternal } from '@/utils/validate.js';
import LayoutLink from './Link';
import LayoutIcon from './Icon';
// import path from 'path-browserify';
defineProps({
item: {
type: Object,
required: true,
},
basePath: {
type: String,
default: '',
},
});
const onlyOneChild = ref(null);
const hasOneShowingChild = (children = [], parent) => {
const showingChildren = children.filter((item) => {
//
if (item.hidden) {
return false;
} else {
// 使
onlyOneChild.value = item;
return true;
}
});
//
if (showingChildren.length === 1) {
return true;
}
//
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, noShowingChildren: true };
return true;
}
return false;
};
// const resolvePath = (routePath) => {
// if (isExternal(routePath)) {
// return routePath;
// }
// if (isExternal(props.basePath)) {
// return props.basePath;
// }
// return path.resolve(props.basePath, routePath);
// };
</script>

View File

@ -0,0 +1,44 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-27 15:54:43
-->
<template>
<div class="layout-screenful">
<el-tooltip effect="dark" :content="isFullscreen ? '取消全屏' : '全屏'" placement="bottom">
<span @click="handleFullScreen">
<el-icon :class="['iconfont', isFullscreen ? 'icon-exit-fullscreen' : 'icon-fullscreen']"></el-icon>
</span>
</el-tooltip>
</div>
</template>
<script setup name="layout-screenful">
import { onMounted, onUnmounted, ref } from 'vue';
import screenfull from 'screenfull';
import { ElMessage } from 'element-plus';
const isFullscreen = ref(false);
const handleChange = () => {
isFullscreen.value = screenfull.isFullscreen;
};
const handleFullScreen = () => {
if (!screenfull.isEnabled) {
ElMessage.warning('you browser can not work');
return false;
}
screenfull.toggle();
};
onMounted(() => {
screenfull.isEnabled && screenfull.on('change', handleChange);
});
onUnmounted(() => {
screenfull.isEnabled && screenfull.off('change', handleChange);
});
</script>

View File

@ -0,0 +1,87 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2024-01-27 20:01:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-03-30 14:32:07
-->
<template>
<div class="layout-sider" :class="{ 'has-logo': themeConfig.showLogo }">
<Logo v-if="themeConfig.showLogo" :is-collapse="isCollapse" />
<el-scrollbar wrap-class="layout-sider-scrollbar">
<el-menu
class="layout-sider-menu"
background-color="#fff"
text-color="#333"
active-text-color=""
:default-active="activeMenu"
:unique-opened="SettingStore.themeConfig.uniqueOpened"
:collapse-transition="false"
:collapse="isCollapse"
>
<SubItem v-for="item in permissionRoutes" :key="item.path" :item="item" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup name="layout-sider">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import Logo from '../Logo';
import SubItem from '../Menu/SubItem';
import { useSettingStore } from '@/store/modules/setting';
import { usePermissionStore } from '@/store/modules/permission';
const route = useRoute();
const PermissionStore = usePermissionStore();
const SettingStore = useSettingStore();
//
const isCollapse = computed(() => !SettingStore.isCollapse);
//
const themeConfig = computed(() => SettingStore.themeConfig);
//
const permissionRoutes = computed(() => PermissionStore.permissionRoutes);
const activeMenu = computed(() => {
const { meta, path } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
});
</script>
<style lang="scss" scoped>
.layout-sider {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 98;
overflow: hidden;
width: 210px;
height: 100%;
background-color: #ffffff;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
transition: width 0.28s;
&-scrollbar {
height: calc(100vh - 60px) !important;
}
:deep(.el-scrollbar__wrap) {
height: calc(100vh - 60px) !important;
}
&-menu {
border-right: 0;
:deep(.el-menu-item.is-active) {
background-color: #f5f5f5;
}
&:not(.el-menu--collapse) {
height: 100%;
}
}
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<div>
<el-dropdown trigger="hover">
<el-button size="small" type="primary">
<span>更多</span>
<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="refresh"
><el-icon :size="14"><Refresh /></el-icon> 刷新当页</el-dropdown-item
>
<el-dropdown-item @click="closeCurrentTab"
><el-icon :size="14"><FolderRemove /></el-icon> 关闭当前</el-dropdown-item
>
<el-dropdown-item @click="closeOtherTab"
><el-icon :size="14"><Close /></el-icon>关闭其他</el-dropdown-item
>
<el-dropdown-item @click="closeAllTab"
><el-icon :size="14"><FolderDelete /></el-icon>关闭所有</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup name="layout-tags-view-more">
import { useRoute } from 'vue-router';
import { useSettingStore } from '@/store/modules/setting';
import { useTagsViewStore } from '@/store/modules/tagsView';
const route = useRoute();
const SettingStore = useSettingStore();
const TagsViewStore = useTagsViewStore();
const refresh = () => {
SettingStore.setReload();
};
//
const closeCurrentTab = () => {
TagsViewStore.toLastView(route.path);
TagsViewStore.delView(route.path);
};
//
const closeOtherTab = async () => {
TagsViewStore.delOtherViews(route.path);
};
//
const closeAllTab = async () => {
await TagsViewStore.delAllViews();
TagsViewStore.goHome();
};
</script>

View File

@ -0,0 +1,161 @@
<template>
<div class="layout-tags-view">
<div class="layout-tags-view-inner">
<el-tabs v-model="activeTabsValue" type="card" @tab-click="tabClick" @tab-remove="removeTab">
<el-tab-pane
v-for="item in visitedViews"
:key="item.path"
:path="item.path"
:label="item.title"
:name="item.path"
:closable="!(item.meta && item.meta.affix)"
>
<template #label>
<el-icon v-if="item.icon" class="tabs-icon">
<component :is="item.icon"></component>
</el-icon>
{{ item.title }}
</template>
</el-tab-pane>
</el-tabs>
</div>
<div class="layout-tags-view-more">
<MoreButton />
</div>
</div>
</template>
<script setup name="layout-tags-view">
import { computed, watch, ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import path from 'path-browserify';
import { useTagsViewStore } from '@/store/modules/tagsView';
import { usePermissionStore } from '@/store/modules/permission';
import MoreButton from './More';
const route = useRoute();
const router = useRouter();
const TagsViewStore = useTagsViewStore();
const PermissionStore = usePermissionStore();
const visitedViews = computed(() => TagsViewStore.visitedViews);
const routes = computed(() => PermissionStore.routes);
const addTags = () => {
const { name } = route;
if (name === 'Login') {
return;
}
if (name) {
TagsViewStore.addView(route);
}
return false;
};
let affixTags = ref([]);
function filterAffixTags(routes, basePath = '/') {
let tags = [];
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(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;
}
const initTags = () => {
let routesNew = routes.value;
let affixTag = (affixTags.value = filterAffixTags(routesNew));
for (const tag of affixTag) {
if (tag.name) {
TagsViewStore.addVisitedView(tag);
}
}
};
onMounted(() => {
initTags();
addTags();
});
watch(route, () => {
addTags();
});
const activeTabsValue = computed({
get: () => {
return TagsViewStore.activeTabsValue;
},
set: (val) => {
TagsViewStore.setTabsMenuValue(val);
},
});
function toLastView(activeTabPath) {
let index = visitedViews.value.findIndex((item) => item.path === activeTabPath);
const nextTab = visitedViews.value[index + 1] || visitedViews.value[index - 1];
if (!nextTab) return;
router.push(nextTab.path);
TagsViewStore.addVisitedView(nextTab);
}
const tabClick = (tabItem) => {
let path = tabItem.props.name;
router.push(path);
};
const isActive = (path) => {
return path === route.path;
};
const removeTab = async (activeTabPath) => {
if (isActive(activeTabPath)) {
toLastView(activeTabPath);
}
await TagsViewStore.delView(activeTabPath);
};
</script>
<style lang="scss" scoped>
.layout-tags-view {
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 10px;
padding-left: 10px;
background: white;
&-inner {
flex: 1;
overflow: hidden;
box-sizing: border-box;
.el-tabs--card :deep(.el-tabs__header) {
margin: 0;
padding: 0 10px;
height: 40px;
box-sizing: border-box;
}
:deep(.el-tabs) {
.el-tabs__nav {
border: none;
}
.el-tabs__header .el-tabs__item {
border: none;
color: #cccccc;
}
.el-tabs__header .el-tabs__item.is-active {
border-bottom: 2px solid $color-primary;
color: $color-primary;
}
}
}
&-more {
height: 100%;
flex-shrink: 0;
}
}
</style>

View File

@ -0,0 +1,54 @@
<!--
* @Description: layout
* @Author: zenghua.wang
* @Date: 2024-01-26 20:13:37
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-02-05 16:03:31
-->
<template>
<div
class="basic-layout"
:class="{
hideSider: !SettingStore.isCollapse,
}"
>
<Sider />
<div class="basic-layout-container">
<Header />
<Main />
</div>
</div>
</template>
<script setup name="layout">
import { useSettingStore } from '@/store/modules/setting';
import Sider from './component/Sider';
import Header from './component/Header';
import Main from './component/Main';
const SettingStore = useSettingStore();
</script>
<style lang="scss" scoped>
.basic-layout {
width: 100%;
min-width: 1200px;
height: 100%;
&.hideSider {
:deep(.layout-sider) {
width: 60px !important;
}
.basic-layout-container {
margin-left: 60px !important;
}
}
&-container {
position: relative;
margin-left: 210px;
height: 100%;
transition: margin-left 0.28s;
box-sizing: border-box;
@include flex-column;
}
}
</style>

View File

@ -0,0 +1,30 @@
/**
* @Description:
* @Author: zenghua.wang
* @Date: 2024-01-24 17:14:41
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-03-22 10:11:34
*/
import 'virtual:svg-icons-register';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import pinia from './store';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import Avue from '@smallwei/avue';
import '@smallwei/avue/lib/index.css';
import './utils/permission';
import { registerDirective } from './directives';
import { registerGlobalComponents } from './plugins/globalComponents';
import { registerElIcons } from './plugins/icon';
import { registerMicroApps } from './plugins/micro';
const app = createApp(App);
// 注册 Vue3 Scroll Seamless 组件
// app.component('Vue3ScrollSeamless', vue3ScrollSeamless);
app.use(pinia).use(router).use(ElementPlus).use(Avue);
registerGlobalComponents(app);
registerElIcons(app);
registerDirective(app);
registerMicroApps(app);

View File

@ -0,0 +1,8 @@
import * as components from '#/components';
// 全局注册组件
export const registerGlobalComponents = (app) => {
Object.keys(components).forEach((key) => {
app.component(key, components[key]);
});
};

View File

@ -0,0 +1,8 @@
import * as ElIconsModules from '@element-plus/icons-vue';
// 全局注册element-plus icon图标组件
export const registerElIcons = (app) => {
Object.keys(ElIconsModules).forEach((key) => {
app.component(key, ElIconsModules[key]);
});
};

View File

@ -0,0 +1,26 @@
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
export const registerMicroApps = async (app) => {
const initQiankun = () => {
renderWithQiankun({
bootstrap() {
console.log('bootstrap');
},
mount(props) {
console.log('mount', props);
render(props);
},
update(props) {
console.log('update', props);
},
unmount(props) {
console.log('unmount', props);
},
});
};
const render = async ({ container }) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
app.mount(container ? container.querySelector('#app') : '#app');
};
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQiankun() : render({});
};

View File

@ -0,0 +1,87 @@
/*
* @Description: router
* @Author: zenghua.wang
* @Date: 2023-06-20 11:48:41
* @LastEditors: zenghua.wang
* @LastEditTime: 2025-03-11 16:51:49
*/
import { createRouter, createWebHistory } from 'vue-router';
import Layout from '@/layouts/index.vue';
export const constantRoutes = [
{
path: '/sub-government-screen-service/404',
name: '404',
component: () => import('@/views/error/404.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/403',
name: '403',
component: () => import('@/views/error/403.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/home',
name: 'home',
component: () => import('@/views/home/index.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/land',
name: 'land',
component: () => import('@/views/land/index.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/inputs',
name: 'inputs',
component: () => import('@/views/inputs/index.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/entities',
name: 'entities',
component: () => import('@/views/entities/index.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/breed',
name: 'breed',
component: () => import('@/views/breed/index.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/plant',
name: 'plant',
component: () => import('@/views/plant/index.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/trace',
name: 'trace',
component: () => import('@/views/trace/index.vue'),
hidden: true,
},
{
path: '/sub-government-screen-service/early',
name: 'early',
component: () => import('@/views/early/index.vue'),
hidden: true,
},
];
/**
* @Title notFoundRouter(找不到路由)
*/
export const notFoundRouter = {
path: '/sub-government-screen-service/:pathMatch(.*)',
name: 'notFound',
redirect: '/sub-government-screen-service/404',
};
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes,
});
export default router;

View File

@ -0,0 +1,20 @@
import { defineStore, createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
const { VITE_APP_NAME } = import.meta.env;
export const Store = defineStore({
id: VITE_APP_NAME,
state: () => ({}),
getters: {},
actions: {},
persist: {
key: VITE_APP_NAME,
storage: window.sessionStorage,
},
});
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;

View File

@ -0,0 +1,53 @@
import { defineStore } from 'pinia';
import { constantRoutes, notFoundRouter } from '@/router';
import { createAsyncRoutes, filterAsyncRoutes, filterKeepAlive } from '@/utils/router';
import { useUserStore } from '@/store/modules/user';
import { getTree } from '@/utils';
import { GenKey } from '@/config';
export const usePermissionStore = defineStore({
id: GenKey('permissionStore'),
state: () => ({
// 路由
routes: [],
// 动态路由
asyncRoutes: [],
// 缓存路由
cacheRoutes: {},
}),
getters: {
permissionRoutes: (state) => {
return state.routes;
},
keepAliveRoutes: (state) => {
return filterKeepAlive(state.asyncRoutes);
},
},
actions: {
generateRoutes(roles) {
return new Promise((resolve) => {
// 在这判断是否有权限,哪些角色拥有哪些权限
const UserStore = useUserStore();
this.asyncRoutes = createAsyncRoutes(getTree(UserStore.getMenus()));
let accessedRoutes;
if (roles && roles.length && !roles.includes('admin')) {
accessedRoutes = filterAsyncRoutes(this.asyncRoutes, roles);
} else {
accessedRoutes = this.asyncRoutes || [];
}
accessedRoutes = accessedRoutes.concat(notFoundRouter);
this.routes = constantRoutes.concat(accessedRoutes);
resolve(accessedRoutes);
});
},
clearRoutes() {
this.routes = [];
this.asyncRoutes = [];
this.cacheRoutes = [];
},
getCacheRoutes() {
this.cacheRoutes = filterKeepAlive(this.asyncRoutes);
return this.cacheRoutes;
},
},
});

View File

@ -0,0 +1,70 @@
import { defineStore } from 'pinia';
import { CONSTANTS } from '@/config';
import { GenKey } from '@/config';
export const useSettingStore = defineStore({
id: GenKey('settingStore'),
state: () => ({
// menu 是否收缩
isCollapse: true,
//
withoutAnimation: false,
device: 'desktop',
// 刷新当前页
isReload: true,
// 主题设置
themeConfig: {
// 显示设置
showSetting: false,
// 菜单展示模式 默认 vertical horizontal / vertical /columns
mode: 'vertical',
// tagsView 是否展示 默认展示
showTag: true,
// 页脚
footer: true,
// 深色模式 切换暗黑模式
isDark: false,
// 显示侧边栏Logo
showLogo: true,
// 主题颜色
primary: CONSTANTS.PRIMARY,
// element组件大小
globalComSize: 'default',
// 是否只保持一个子菜单的展开
uniqueOpened: true,
// 固定header
fixedHeader: true,
// 灰色模式
gray: false,
// 色弱模式
weak: false,
},
}),
getters: {},
actions: {
// 设置主题
setThemeConfig({ key, val }) {
this.themeConfig[key] = val;
},
// 切换 Collapse
setCollapse(value) {
this.isCollapse = value;
this.withoutAnimation = false;
},
// 关闭侧边栏
closeSideBar({ withoutAnimation }) {
this.isCollapse = false;
this.withoutAnimation = withoutAnimation;
},
toggleDevice(device) {
this.device = device;
},
// 刷新
setReload() {
this.isReload = false;
setTimeout(() => {
this.isReload = true;
}, 50);
},
},
});

View File

@ -0,0 +1,105 @@
import { defineStore } from 'pinia';
import { GenKey } from '@/config';
import router from '@/router';
export const useTagsViewStore = defineStore({
id: GenKey('tagsViewStore'),
state: () => ({
activeTabsValue: '/home',
visitedViews: [],
cachedViews: [],
}),
getters: {},
actions: {
setTabsMenuValue(val) {
this.activeTabsValue = val;
},
addView(view) {
this.addVisitedView(view);
},
removeView(routes) {
return new Promise((resolve) => {
this.visitedViews = this.visitedViews.filter((item) => !routes.includes(item.path));
resolve(null);
});
},
addVisitedView(view) {
this.setTabsMenuValue(view.path);
if (this.visitedViews.some((v) => v.path === view.path)) return;
this.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name',
})
);
if (view.meta.keepAlive) {
this.cachedViews.push(view.name);
}
},
delView(activeTabPath) {
return new Promise((resolve) => {
this.delVisitedView(activeTabPath);
this.delCachedView(activeTabPath);
resolve({
visitedViews: [...this.visitedViews],
cachedViews: [...this.cachedViews],
});
});
},
toLastView(activeTabPath) {
const index = this.visitedViews.findIndex((item) => item.path === activeTabPath);
const nextTab = this.visitedViews[index + 1] || this.visitedViews[index - 1];
if (!nextTab) return;
router.push(nextTab.path);
this.addVisitedView(nextTab);
},
delVisitedView(path) {
return new Promise((resolve) => {
this.visitedViews = this.visitedViews.filter((v) => {
return v.path !== path || v.meta.affix;
});
this.cachedViews = this.cachedViews.filter((v) => {
return v.path !== path || v.meta.affix;
});
resolve([...this.visitedViews]);
});
},
delCachedView(view) {
return new Promise((resolve) => {
const index = this.cachedViews.indexOf(view.name);
index > -1 && this.cachedViews.splice(index, 1);
resolve([...this.cachedViews]);
});
},
clearVisitedView() {
this.delAllViews();
},
delAllViews() {
return new Promise((resolve) => {
this.visitedViews = this.visitedViews.filter((v) => v.meta.affix);
this.cachedViews = this.visitedViews.filter((v) => v.meta.affix);
resolve([...this.visitedViews]);
});
},
delOtherViews(path) {
this.visitedViews = this.visitedViews.filter((item) => {
return item.path === path || item.meta.affix;
});
this.cachedViews = this.visitedViews.filter((item) => {
return item.path === path || item.meta.affix;
});
},
goHome() {
this.activeTabsValue = '/home';
router.push({ path: '/home' });
},
updateVisitedView(view) {
for (let v of this.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view);
break;
}
}
},
},
});

View File

@ -0,0 +1,65 @@
import { defineStore } from 'pinia';
import { GenKey } from '@/config';
import { isEmpty, encode, decode } from '@/utils';
export const useUserStore = defineStore({
id: GenKey('userStore'),
state: () => ({
token: null,
userInfo: {},
currentOrg: null,
orgList: [],
menus: [],
}),
getters: {},
actions: {
setToken(token) {
this.token = token;
},
hasToken() {
return !isEmpty(this.token);
},
setUserInfo(userInfo) {
this.userInfo = encode(JSON.stringify(userInfo), true);
},
getUserInfo() {
return !isEmpty(this.userInfo) ? JSON.parse(decode(this.userInfo, true)) : {};
},
setOrgList(orgList) {
this.orgList = encode(JSON.stringify(orgList), true);
},
getOrgList() {
return !isEmpty(this.orgList) ? JSON.parse(decode(this.orgList, true)) : [];
},
setCurrentOrg(org) {
this.currentOrg = org;
},
getCurrentOrg() {
const list = this.getOrgList().filter((item) => {
return item.id === this.currentOrg;
});
return !isEmpty(list) ? list[0] : {};
},
setMenus(menus) {
this.menus = encode(JSON.stringify(menus), true);
},
getMenus() {
return !isEmpty(this.menus) ? JSON.parse(decode(this.menus, true)) : [];
},
logout() {
this.token = null;
this.userInfo = {};
this.currentOrg = null;
this.orgList = [];
this.menus = [];
localStorage.removeItem(GenKey('userStore'));
},
clear() {
localStorage.removeItem(GenKey('userStore'));
},
},
persist: {
key: GenKey('userStore'),
storage: window.localStorage,
},
});

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