feat:政务服务

This commit is contained in:
wangzenghua 2025-01-23 01:10:59 +00:00
parent 5de65851f7
commit 93c928a191
71 changed files with 12543 additions and 0 deletions

View File

@ -0,0 +1,6 @@
# 开发环境
VITE_PORT = 9528
VITE_MODE = 'DEV'
VITE_APP_NAME = 'sub-government-affairs-service'
VITE_APP_BASE_API = '/apis'
VITE_APP_BASE_URL = 'http://localhost:8080/'

View File

@ -0,0 +1,5 @@
# 生产环境
VITE_MODE = 'PRO'
VITE_APP_NAME = 'sub-government-affairs-service'
VITE_APP_BASE_API = 'https://www.localhost.com/8080/api/'
VITE_APP_BASE_URL = 'https://www.localhost.com/8080/'

View File

@ -0,0 +1,13 @@
<!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",
"build:dev": "vite build --mode dev",
"build:qa": "vite build --mode qa",
"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.5.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"
},
"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: 2024-01-26 22:57:34
-->
<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.scss';
</style>

View File

@ -0,0 +1,43 @@
import request from '@/utils/axios';
/**
* @Title: 登录
*/
export function Login(params, token) {
return request('/store/login', {
method: 'POST',
data: null,
params: params,
headers: {
'fairies-auth-token': token,
},
});
}
/**
* @Title: 登出
*/
export function LogOut() {
return request('/store/logout', {
method: 'POST',
});
}
/**
* @Title: 验证码
*/
export function GetCaptcha() {
return request('/store/login-captcha', {
method: 'GET',
responseType: 'arraybuffer',
});
}
/**
* @Title: 获取菜单
*/
export function GetMenus() {
return request('/store/self/menu', {
method: 'GET',
});
}

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
}
]
}

View File

@ -0,0 +1,10 @@
const { VITE_APP_NAME } = import.meta.env;
export const GenKey = (key, prefix = `${VITE_APP_NAME}_`) => {
return prefix ? prefix + key : key;
};
export const CONSTANTS = {
PREFIX: `${VITE_APP_NAME}_`,
PRIMARY: '#409eff',
};

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,100 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang 1048523306@qq.com
* @LastEditTime: 2025-01-17 10:33:54
-->
<template>
<el-dropdown class="layout-avatar">
<span class="el-dropdown-link">
<el-avatar :size="30" class="avatar" :src="userInfo.avatar || '/images/avatar.gif'" />
<span class="layout-avatar-name">{{ userInfo.name || '游客' }}</span>
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item :command="0"> 当前角色管理员 </el-dropdown-item> -->
<el-dropdown-item :command="3" divided @click="showUserInfo">
<el-icon><Edit /></el-icon>个人信息
</el-dropdown-item>
<el-dropdown-item :command="4" divided @click="showPassword">
<el-icon><Edit /></el-icon>修改密码
</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>
<!-- <user-info ref="userInfoRef" /> -->
<!-- <edit-password ref="passwordRef" /> -->
</template>
<script setup name="layout-avatar">
import { computed, ref } 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 UserInfo from '@/views/system/user/info.vue';
// import EditPassword from '@/views/system/user/password.vue';
import { LogOut } from '@/apis/login';
const router = useRouter();
const UserStore = useUserStore();
const TagsViewStore = useTagsViewStore();
const PermissionStore = usePermissionStore();
//
const userInfo = computed(() => UserStore.getUserInfo());
const userInfoRef = ref(null);
const passwordRef = ref(null);
const showUserInfo = () => {
userInfoRef?.value && userInfoRef.value.show();
};
const showPassword = () => {
passwordRef?.value && passwordRef.value.show();
};
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: '退出登录成功!',
});
}
});
});
};
</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,38 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-26 23:04:14
-->
<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>

View File

@ -0,0 +1,42 @@
<!--
* @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 {
padding: 0px 15px;
height: 100%;
display: flex;
align-items: center;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
.icon {
font-size: 24px;
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,114 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-02-19 21:11:20
-->
<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">
<Size class="layout-header-tool" />
<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 Size from '../Size';
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;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
&.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();
align-items: center;
justify-content: space-between;
height: 50px;
width: 100%;
padding: 0 10px 0 0;
border-bottom: 1px solid #eee;
box-sizing: border-box;
}
&-left {
@include flex-row();
align-items: center;
height: 100%;
}
&-right {
@include flex-row();
align-items: center;
}
&-tool {
padding: 4px;
margin-right: 10px;
cursor: pointer;
:deep(.el-icon) {
font-size: 20px;
color: #333;
}
}
}
</style>

View File

@ -0,0 +1,45 @@
<!--
* @Description:
* @Author: zenghua.wang
* @Date: 2023-06-20 14:29:45
* @LastEditors: zenghua.wang 1048523306@qq.com
* @LastEditTime: 2025-01-17 11:55:43
-->
<template>
<div class="logo">
<!-- <img src="/images/logo.png" class="logo-picture" /> -->
<h2 v-show="!isCollapse" class="logo-title">管理系统</h2>
</div>
</template>
<script setup name="logo">
defineProps({
isCollapse: {
type: Boolean,
default: false,
},
});
</script>
<style lang="scss" scoped>
.logo {
overflow: hidden;
@include flex-row();
align-items: center;
padding: 7px 5px;
margin-bottom: 2px;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
transition: all 0.28s;
justify-content: center;
&-picture {
width: 70px;
height: 35px;
margin: 0 auto;
}
&-title {
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 {
width: 100%;
padding: 10px;
box-sizing: border-box;
background-color: #f5f5f5;
}
}
</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,91 @@
<!--
* @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 {
overflow: hidden;
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 98;
width: 210px;
height: 100%;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
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,40 @@
<!--
* @Description: layout
* @Author: zenghua.wang
* @Date: 2024-01-26 20:13:37
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-27 15:35:52
-->
<template>
<el-dropdown trigger="hover" @command="setAssemblySize">
<el-icon class="iconfont icon-size"></el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in assemblySizeList" :key="item" :disabled="globalComSize === item" :command="item">
{{ assemblySizeListCh[item] }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup name="layout-size">
import { reactive, computed } from 'vue';
import { useSettingStore } from '@/store/modules/setting';
const SettingStore = useSettingStore();
const globalComSize = computed(() => SettingStore.themeConfig.globalComSize);
const assemblySizeListCh = reactive({
default: '默认',
large: '大型',
small: '小型',
});
const assemblySizeList = reactive(['default', 'large', 'small']);
const setAssemblySize = (item) => {
if (globalComSize.value === item) return;
SettingStore.setThemeConfig({ key: 'globalComSize', val: item });
};
</script>

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,164 @@
<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-left: 10px;
padding-right: 10px;
background: white;
&-inner {
flex: 1;
overflow: hidden;
box-sizing: border-box;
.el-tabs--card :deep(.el-tabs__header) {
box-sizing: border-box;
height: 40px;
padding: 0 10px;
margin: 0;
}
:deep(.el-tabs) {
.el-tabs__nav {
border: none;
}
.el-tabs__header .el-tabs__item {
border: none;
color: #ccc;
}
.el-tabs__header .el-tabs__item.is-active {
color: $color-primary;
border-bottom: 2px solid $color-primary;
}
}
}
&-more {
height: 100%;
flex-shrink: 0;
}
}
</style>

View File

@ -0,0 +1,57 @@
<!--
* @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 {
height: 100%;
width: 100%;
min-width: 1200px;
&.hideSider {
:deep(.layout-sider) {
width: 60px !important;
}
.basic-layout-container {
margin-left: 60px !important;
}
}
&-container {
position: relative;
@include flex-column();
height: 100%;
margin-left: 210px;
box-sizing: border-box;
transition: margin-left 0.28s;
}
}
</style>

View File

@ -0,0 +1,28 @@
/**
* @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);
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 "../../../main/src/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,25 @@
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
export const registerMicroApps = (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 = ({ container }) => {
app.mount(container ? container : '#app');
};
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQiankun() : render({});
};

View File

@ -0,0 +1,62 @@
/*
* @Description: router
* @Author: zenghua.wang
* @Date: 2023-06-20 11:48:41
* @LastEditors: zenghua.wang 1048523306@qq.com
* @LastEditTime: 2025-01-21 14:27:43
*/
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import Layout from '@/layouts/index.vue';
import resourceRouter from './modules/resource';
const { VITE_APP_NAME } = import.meta.env;
export const constantRoutes = [
{
path: '/404',
name: '404',
component: () => import('@/views/error/404.vue'),
hidden: true,
},
{
path: '/403',
name: '403',
component: () => import('@/views/error/403.vue'),
hidden: true,
},
{
path: '/',
name: 'layout',
component: Layout,
redirect: '/home',
meta: { title: '政务服务', icon: 'House' },
children: [
{
path: '/home',
component: () => import('@/views/home/index.vue'),
name: 'home',
meta: { title: '政务服务主页', icon: 'House' },
},
],
},
...resourceRouter,
];
/**
* @Title notFoundRouter(找不到路由)
*/
export const notFoundRouter = {
path: '/:pathMatch(.*)',
name: 'notFound',
redirect: '/404',
};
const router = createRouter({
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? `/${VITE_APP_NAME}/` : '/'), // history
// history: createWebHashHistory(), // hash
routes: constantRoutes,
});
export default router;

View File

@ -0,0 +1,68 @@
import Layout from '@/layouts/index.vue';
import Views from '@/layouts/Views.vue';
export default [
{
path: '/resource',
name: 'resource',
component: Layout,
redirect: '/resource-map',
meta: { title: '土地资源管理管理', icon: 'icon-test' },
children: [
{
path: '/resource-map',
component: () => import('@/views/resource/map/index.vue'),
name: 'resource-map',
meta: { title: '土地资源一张图', icon: 'Document' },
},
{
path: '/resource-info',
name: 'resource-info-manager',
component: Views,
meta: { title: '土地信息管理', icon: 'Document' },
redirect: '/land',
children: [
{
path: '/land',
component: () => import('@/views/resource/land/index.vue'),
name: 'land',
meta: { title: '土地资源', icon: 'Document' },
},
{
path: '/land-fill',
name: 'land-fill',
component: () => import('@/views/resource/land/index.vue'),
meta: { title: '土地信息填报审核', icon: 'Document' },
},
{
path: '/land-fill-review',
name: 'land-fill-review',
component: () => import('@/views/resource/land/index.vue'),
meta: { title: '土地流转信息填报审核', icon: 'Document' },
},
],
},
{
path: '/land-monitor',
name: 'land-monitor',
component: Views,
meta: { title: '土地监管', icon: 'Document' },
redirect: '/land-patrol',
children: [
{
path: '/land-patrol',
component: () => import('@/views/resource/land/index.vue'),
name: 'land-patrol',
meta: { title: '土地使用巡查', icon: 'Document' },
},
{
path: '/land-handle',
name: 'land-handle',
component: () => import('@/views/resource/land/index.vue'),
meta: { title: '土地违法处理', icon: 'Document' },
},
],
},
],
},
];

View File

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

View File

@ -0,0 +1,52 @@
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';
export const usePermissionStore = defineStore({
id: '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,69 @@
import { defineStore } from 'pinia';
import { CONSTANTS } from '@/config';
export const useSettingStore = defineStore({
id: '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,104 @@
import { defineStore } from 'pinia';
import router from '@/router';
export const useTagsViewStore = defineStore({
id: '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('USER_STATE'),
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('USER_STATE'));
},
clear() {
localStorage.removeItem(GenKey('USER_STATE'));
},
},
persist: {
key: GenKey('USER_STATE'),
storage: window.localStorage,
},
});

View File

@ -0,0 +1,283 @@
html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
border: 0;
text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}
a,
abbr,
acronym,
address,
article,
aside,
blockquote,
caption,
code,
del,
dfn,
dialog,
header,
footer,
nav,
object,
section,
body,
dd,
div,
dl,
dt,
em,
img,
fieldset,
figure,
form,
h1,
h2,
h3,
h4,
h5,
h6,
hgroup,
iframe,
legend,
p,
pre,
q,
span,
tbody,
tfoot,
thead,
ul,
ol,
li {
margin: 0;
padding: 0;
border: 0;
list-style: none;
vertical-align: baseline;
}
article,
aside,
details,
dialog,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
width: 100%;
height: 100%;
font-size: 12px;
font-family: 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;
color: #323232;
// background: #000;
}
img {
vertical-align: bottom;
border: 0;
}
// ::input-placeholder {
// color: #999999;
// }
::placeholder {
color: #999999;
}
// :input-placeholder {
// color: #cccccc;
// }
button::-moz-focus-inner,
input::-moz-focus-inner {
padding: 0;
border: 0;
}
textarea {
overflow: auto;
}
button:focus,
input:focus,
select:focus,
textarea:focus {
outline: 0;
}
input::-ms-clear {
display: none;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
vertical-align: baseline;
}
audio:not([controls]) {
display: none;
height: 0;
}
[hidden],
template {
display: none;
}
a {
text-decoration: none;
color: #323232;
background: none;
cursor: pointer;
}
a:active,
a:hover {
outline: 0;
}
a:focus {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
abbr[title] {
border-bottom: 1px dotted;
}
b,
strong {
font-weight: 700;
}
dfn {
font-style: italic;
}
mark {
color: #000000;
background: #ffff00;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
svg:not(:root) {
overflow: hidden;
}
figure {
margin: 1em 40px;
}
hr {
box-sizing: content-box;
height: 0;
}
pre {
overflow: auto;
}
code,
kbd,
pre,
samp {
font-size: 1em;
font-family: monospace;
}
button,
input,
optgroup,
select,
textarea {
margin: 0;
font: inherit;
}
button {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html input[type='button'],
input[type='reset'],
input[type='submit'] {
appearance: button;
cursor: pointer;
}
button[disabled],
html input[disabled] {
cursor: default;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
padding: 0;
border: 0;
}
input {
margin: 0;
padding: 0;
line-height: normal;
}
input[type='checkbox'],
input[type='radio'] {
box-sizing: border-box;
padding: 0;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
height: auto;
}
input[type='search'] {
box-sizing: content-box;
appearance: textfield;
}
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-decoration {
appearance: none;
}
fieldset {
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
border: 1px solid silver;
}
legend {
padding: 0;
border: 0;
}
optgroup {
font-weight: 700;
}
table {
border-spacing: 0;
border-collapse: collapse;
}
td,
th {
padding: 0;
}

View File

@ -0,0 +1,126 @@
.flex {
&-row {
display: flex;
flex-direction: row;
}
&-column {
display: flex;
flex-direction: column;
}
}
.custom-table {
padding: 10px;
background: #fff;
border-radius: 10px;
}
.custom-form {
&__title {
padding: 10px 0;
font-size: 15px;
font-weight: bold;
color: $color-333;
&::before {
@include icon-space();
width: 4px;
background: $color-primary;
height: 1.15em;
vertical-align: -0.25em;
margin-right: 8px;
}
}
&__panel {
padding: 16px 0;
}
&__input {
width: 450px !important;
}
&__rich {
overflow: hidden;
width: 800px;
}
&__picture {
display: inline-block;
padding: 15px;
vertical-align: top;
&-wrap {
margin-bottom: 30px;
}
}
&__avatar {
display: flex;
justify-content: center;
align-items: center;
width: 100px;
height: 100px;
border: 1px solid #eeeeee;
}
&__img {
max-height: 100%;
}
&__uploader {
position: relative;
display: inline-block;
overflow: hidden;
width: 160px;
height: 160px;
border: 1px solid;
border-radius: 4px;
text-align: center;
color: $color-input-border;
background-color: var(--el-fill-color-lighter);
cursor: pointer;
vertical-align: top;
&:hover {
color: $color-primary;
}
&__img,
&__icon {
position: absolute !important;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
&__img {
width: 100%;
}
&__icon {
height: 1em;
font-size: 28px !important;
text-align: center;
line-height: 1;
}
&__remove {
position: absolute;
right: 0;
bottom: 0;
padding: 5px 10px;
font-size: 18px;
}
.el-upload {
display: block;
width: 100%;
height: 100%;
}
&-inline {
display: inline-block;
text-align: center;
& + & {
margin-left: 10px;
}
}
}
&__tips {
padding-left: 3px;
font-size: 12px;
color: #999;
}
&__note {
font-size: 12px;
color: $color-danger;
}
}

View File

@ -0,0 +1,61 @@
// color
$legacy-ie: 10;
$color-primary: #20a0ff;
$color-success: #13ce66;
$color-warning: #f7ba2a;
$color-danger: #ff4949;
$color-info: #50bfff;
$color-secondary: #2e90fe;
$color-white: #ffffff;
$color-black: #1f2d3d;
$color-black-light: #324057;
$color-black-lighter: #475669;
$color-blue-light: #5da9ff;
$color-blue-lighter: #5da9ff;
$color-black: #000000;
$color-silver: #8492a6;
$color-silver-light: #99a9bf;
$color-silver-lighter: #c0ccda;
$color-gray: #d3dce6;
$color-gray-light: #e5e9f2;
$color-gray-lighter: #eff2f7;
$color-333: #333333;
$color-666: #333333;
$color-999: #999999;
$color-border-gray: #d1dbe5;
$color-input-border: #dcdfe6;
$color-border: $color-border-gray;
$color-types: (
primary: (
$color-primary,
#4db3ff,
#1d90e6,
),
info: (
$color-info,
#73ccff,
#48ace6,
),
success: (
$color-success,
#42d885,
#11b95c,
),
warning: (
$color-warning,
#f9c855,
#dea726,
),
danger: (
$color-danger,
#ff6d6d,
#e64242,
),
gray: (
$color-999,
#999999,
#9d9d9d,
),
);
@import 'utils/utils';

View File

@ -0,0 +1,19 @@
@import 'common/base.scss';
@import 'common/define.scss';
@import '@/assets/fonts/iconfont.css';
#app {
position: relative;
width: 100%;
height: 100%;
font-family: Avenir, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5;
}
.el-dialog__body {
overflow: hidden auto;
height: auto;
max-height: calc(100vh - 130px);
}

View File

@ -0,0 +1,31 @@
/// Block Element
/// @access public
/// @param {String} $element - Element's name
@mixin element($element) {
&__#{$element} {
@content;
}
}
/// @alias element
@mixin e($element) {
@include element($element) {
@content;
}
}
/// Block Modifier
/// @access public
/// @param {String} $modifier - Modifier's name
@mixin modifier($modifier) {
&_#{$modifier} {
@content;
}
}
/// @alias modifier
@mixin m($modifier) {
@include modifier($modifier) {
@content;
}
}

View File

@ -0,0 +1,30 @@
@mixin ellipsis($lines: 1, $line-height: 0) {
overflow: hidden;
@if $lines == 1 {
@if $legacy-ie <= 8 {
word-wrap: normal; // for ie
}
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: flexbox;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
@if value-of($line-height) == 0 {
@error 'line-height is required when clamp muti lines';
}
@if unitless($line-height) or unit($line-height) == '%' {
$line-height: value-of($line-height) * 1em;
}
max-height: $line-height * $lines;
// &:after {
// content: " ...";
// }
}
}

View File

@ -0,0 +1,5 @@
@mixin scrollable() {
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}

View File

@ -0,0 +1,34 @@
/// Mixin to customize scrollbars
/// Beware, this does not work in all browsers
/// @author Hugo Giraudel
/// @param {Length} $size - Horizontal scrollbar's height and vertical scrollbar's width
/// @param {Color} $foreground-color - Scrollbar's color
/// @param {Color} $background-color [mix($foreground-color, white, 50%)] - Scrollbar's color
/// @example scss - Scrollbar styling
/// @include scrollbars(.5em, slategray);
@mixin scrollbars($size, $foreground-color, $background-color: mix($foreground-color, white, 50%)) {
// For Google Chrome
::-webkit-scrollbar {
width: $size;
height: $size;
}
::-webkit-scrollbar-thumb {
background: $foreground-color;
}
::-webkit-scrollbar-track {
background: $background-color;
}
// For Internet Explorer
body {
scrollbar-face-color: $foreground-color;
scrollbar-track-color: $background-color;
}
}
/// alias
@mixin scrollbar($size, $foreground-color, $background-color: mix($foreground-color, white, 50%)) {
@include scrollbars($size, $foreground-color, $background-color: mix($foreground-color, white, 50%));
}

View File

@ -0,0 +1,11 @@
@function value-of($value) {
@if type-of($value) == 'number' and not unitless($value) {
@return $value / ($value * 0 + 1);
}
@return $value;
}
// alias
@function strip-unit($value) {
@return value-of($value);
}

View File

@ -0,0 +1,21 @@
@import 'bem';
@import 'ellipsis';
@import 'scrollable';
@import 'scrollbar';
@import 'value-of';
@mixin flex-row() {
display: flex;
flex-direction: row;
}
@mixin flex-column() {
display: flex;
flex-direction: column;
}
@mixin icon-space() {
display: inline-block;
content: '';
background-size: 100%;
}

View File

@ -0,0 +1,106 @@
/*
* @Descripttion:
* @Author: zenghua.wang
* @Date: 2022-02-23 21:12:37
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-06 11:29:36
*/
import axios from 'axios';
import { ElNotification } from 'element-plus';
import router from '@/router';
import { isEmpty } from '@/utils';
import { useUserStore } from '@/store/modules/user';
const { VITE_APP_BASE_API, VITE_APP_UPLOAD_API } = import.meta.env;
/**
* 创建axios实例
*/
const publicAxios = axios.create({
baseURL: VITE_APP_BASE_API, // API请求的默认前缀
timeout: 30000,
});
/**
* 异常拦截处理器
* @param error
* @returns
*/
const errorHandler = async (error) => {
const { response } = error;
const UserStore = useUserStore();
if (response && response.status) {
switch (response.status) {
case 401:
await UserStore.logout();
router.push('/login');
break;
default:
break;
}
}
return Promise.reject(error?.response?.data);
};
/**
* 请求拦截器
*/
publicAxios.interceptors.request.use(async (config) => {
const UserStore = useUserStore();
config.baseURL = config.isUpload ? VITE_APP_UPLOAD_API : VITE_APP_BASE_API;
if (UserStore.hasToken()) {
config.headers['fairies-auth-token'] = config.headers['fairies-auth-token'] ?? UserStore.token;
config.headers['fairies-org-id'] = UserStore.currentOrg;
config.headers['cache-control'] = 'no-cache';
config.headers.Pragma = 'no-cache';
if (config?.isUpload) {
config.headers['Content-Type'] = config.uploadType;
}
}
if (config.method === 'POST' || config.method === 'DELETE') {
config.headers.Accept = 'application/json';
}
return config;
}, errorHandler);
/**
* 返回结果处理
* @param res
* @returns
*/
const formatResult = (res) => {
const code = res.data.code || res.status;
switch (code) {
case 200:
case 0:
// code === 0 或 200 代表没有错误
return res.data || res.data.data || res;
case 500:
case 1:
// code === 1 或 500 代表存在错误
ElNotification.error(res.data.message);
break;
default:
ElNotification.error(res.data.message);
break;
}
};
/**
* 响应拦截器
*/
publicAxios.interceptors.response.use((response) => {
const { config } = response;
if (config?.responseType) {
return response;
}
const token = response?.headers['fairies-auth-token'];
if (!isEmpty(token)) {
const UserStore = useUserStore();
UserStore.setToken(token);
}
const result = formatResult(response);
if (result) {
return result;
}
throw new Error(response.data.message);
}, errorHandler);
export default publicAxios;

View File

@ -0,0 +1,353 @@
/*
* @Descripttion:
* @Author: zenghua.wang
* @Date: 2022-02-23 21:12:37
* @LastEditors: wzh 1048523306@qq.com
* @LastEditTime: 2024-09-09 18:46:56
*/
import lodash from 'lodash';
import moment from 'moment';
import { Base64 } from 'js-base64';
/**
* @Title 防抖指在一定时间内多次触发同一个事件只执行最后一次操作
* @param {*} fn
* @param {*} delay
* @returns
*/
export function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
/**
* @Title 节流指在一定时间内多次触发同一个事件只执行第一次操作
* @param {*} fn
* @param {*} delay
* @returns
*/
export function throttle(fn, delay) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
/**
* @Title 判断是否 empty,返回ture
* @param {*} val:null 'null' undefined 'undefined' 0 '0' "" 返回true
* @returns
*/
export const isEmpty = (val) => {
if (val && parseInt(val) === 0) return false;
if (typeof val === 'undefined' || val === 'null' || val == null || val === 'undefined' || val === undefined || val === '') {
return true;
} else if (typeof val === 'object' && Object.keys(val).length === 0) {
return true;
} else if (val instanceof Array && val.length === 0) {
return true;
} else {
return false;
}
};
/**
* @Title 深度拷贝对象
* @param {*} obj
* @returns
*/
export const deepClone = (obj = {}) => {
return lodash.cloneDeep(obj);
};
/**
* @Title 将number转换为px
* @param {*} val
* @returns
*/
export const setPx = (val) => {
if (isEmpty(val)) return '';
val = val + '';
if (val.indexOf('%') === -1) {
val = val + 'px';
}
return val;
};
/**
* @Tilte 设置属性默认值
* @param {*} options
* @param {*} prop
* @param {*} defaultVal
* @returns
*/
export const setDefaultOption = (options, prop, defaultVal) => {
return options[prop] === undefined ? defaultVal : options.prop;
};
/**
* 设置字典值
* @param {*} columns
* @param {*} key
* @param {*} data
* @returns
*/
export const setDicData = (columns, key, data = []) => {
if (isEmpty(data)) return;
const len = columns.length;
for (let i = 0; i < len; i++) {
if (columns[i]?.prop === key) {
columns[i]['dicData'] = data;
break;
}
}
};
/**
* 求字段lable
* @param {*} tree
* @returns
*/
export const setDicLabel = (dicData, value) => {
let label = value;
if (isEmpty(dicData)) return label;
const len = dicData.length;
for (let i = 0; i < len; i++) {
if (dicData[i]?.value === value) {
label = dicData[i].label;
break;
}
}
return label;
};
/**
* 加密
* @param {*} n
* @returns
*/
export const encode = (n, flag = false) => {
if (flag) {
return (
((e) => {
let t = e.length.toString();
for (let n = 10 - t.length; n > 0; n--) t = '0' + t;
return t;
})(n) +
((e) => {
const t = Base64.encode(e).split('');
for (let n = 0; n < Math.floor(e.length / 100 + 1); n++) t.splice(100 * n + 1, 0, 3);
return t.join('');
})(n)
);
}
return n;
};
/**
* 解密
* @param {*} e
* @returns
*/
export const decode = (e, flag = false) => {
if (flag) {
try {
const t = Number(e.substr(0, 10));
const n = e.substr(10).split('');
for (let i = 0, s = 0; s < Math.floor(t / 100) + 1; s++) {
n.splice(100 * s + 1 - i, 1);
i++;
}
const o = Base64.decode(n.join(''));
return o;
} catch (error) {
return e;
}
}
return e;
};
/**
* @Title 图片转base64
* @param {*} file
* @returns
*/
export const imageToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
});
};
/**
* @Title bufferToBase64
* @param {*} buffer
* @returns
*/
export const bufferToBase64 = (buffer) => {
return 'data:image/jpeg;base64,' + window.btoa(new Uint8Array(buffer).reduce((data, byte) => data + String.fromCharCode(byte), ''));
};
/**
* @Title 将array转化为树
* @param tree
* @returns
*/
export const getTree = (tree = []) => {
tree.forEach((item) => {
delete item.children;
});
const map = {};
tree.forEach((item) => {
map[item.id] = item;
});
const arr = [];
tree.forEach((item) => {
const parent = map[item.parentId];
if (parent) {
(parent.children || (parent.children = [])).push(item);
} else {
arr.push(item);
}
});
return arr;
};
/**
* @Title 获取路由中的参数
* @param name
* @returns
*/
export const getUrlQuery = (name) => {
const url = window.location.href;
const hash = url.substring(url.indexOf('#') + 1);
const searchIndex = hash.indexOf('?');
const search = searchIndex !== -1 ? hash.substring(searchIndex + 1) : '';
const usp = new URLSearchParams(search);
return usp.get(name);
};
/**
* @Title 获取静态资源文件
* @param {*} url
* @returns
*/
export const getAssetsFile = (url) => {
return new URL(`../assets/images/${url}`, import.meta.url);
};
/**
* @Title 替换图片url字段值
* @param {*} url
* @returns
*/
export const setUploadField = (url) => {
if (isEmpty(url) || url.includes('http')) return null;
return url;
};
/**
* 合并列
* @param {*} param0
* @param {*} options
* @param {*} rowList
* @returns
*/
export const setSpan = ({ rowIndex, columnIndex }, options, rowList) => {
const columnList = deepClone(options.columns).filter((item) => !item.hide);
const conditions = [];
const getColspan = (column, conditions) => {
const len = rowList.length;
const arr = [];
for (let i = 0; i < len; i++) {
let colspan = 1;
for (let j = i + 1; j < len; j++) {
const bool = conditions.every((col) => {
return rowList[i][col] === rowList[j][col];
});
if (bool && rowList[i][column] === rowList[j][column]) {
colspan += 1;
arr[i] = colspan;
arr[j] = 0;
if (j === len - 1) i = j;
} else {
colspan = 1;
if (!arr[i]) arr[i] = colspan;
i = j;
arr[j] = colspan;
}
}
}
return arr;
};
if (rowList.length <= 1) {
return {
rowspan: 1,
colspan: 1,
};
} else {
for (let i = 0, columnLen = columnList.length; i < columnLen; i++) {
if (columnList[i].isColspan) {
const arr = getColspan(columnList[i].prop, conditions);
const index = options.selection ? (options.index ? i + 2 : i + 1) : options.index ? i + 1 : i;
conditions.push(columnList[i].prop);
if (columnIndex === index) {
for (let j = 0, rowLen = rowList.length; j < rowLen; j++) {
if (rowIndex === j) {
return {
rowspan: arr[j],
colspan: arr[j] > 0 ? 1 : 0,
};
}
}
}
}
}
}
};
/**
* @Title: a链接方式文件下载
* @param {void} content:
* @param {void} fileName:
* @return {void}
*/
export const dowloadLink = (content, fileName) => {
const blob = new Blob([content]);
if ('download' in document.createElement('a')) {
const elink = document.createElement('a');
elink.download = fileName;
elink.style.display = 'none';
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href);
document.body.removeChild(elink);
} else {
navigator.msSaveBlob(blob, fileName);
}
};
/**
* @Title 模拟休眠
* @param {*} duration
* @returns
*/
export const sleep = (duration = 0) =>
new Promise((resolve) => {
setTimeout(resolve, duration);
});
/**
* 日期格式化
* @param {*} date
* @param {*} format
* @returns
*/
export const dateFormat = (datetime, type = 'yyyy-MM-dd') => {
return moment(datetime).format(type);
};
/**
* 上日///
*/
export const lastDate = (last = 0, date = 'month', type = 'yyyy-MM-dd') => {
if (date === 'day') {
return moment().subtract(last, 'day').endOf('day').format(type);
}
return moment().subtract(last, date).format(type);
};

View File

@ -0,0 +1,58 @@
/**
* @Description: 路由权限
* @Author: zenghua.wang
* @Date: 2022-01-26 22:04:31
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-02-26 13:54:43
*/
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
import { usePermissionStore } from '@/store/modules/permission';
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/auth-redirect']; // 设置白名单
router.beforeEach(async (to, from, next) => {
NProgress.start();
if (typeof to.meta.title === 'string') {
document.title = '政务服务 | ' + to.meta.title;
}
const userStore = useUserStore();
const hasToken = true;
if (hasToken) {
if (to.path === '/login') {
// 如果已登录,请重定向到主页
next({ path: '/' });
} else {
try {
const PermissionStore = usePermissionStore();
// 路由添加进去了没有及时更新 需要重新进去一次拦截
if (!PermissionStore.routes.length) {
// 获取权限列表进行接口访问 因为这里页面要切换权限
const accessRoutes = await PermissionStore.generateRoutes(userStore.roles);
accessRoutes.forEach((item) => router.addRoute(item)); // 动态添加访问路由表
next({ ...to, replace: true }); // 这里相当于push到一个页面 不在进入路由拦截
} else {
next(); // 如果不传参数就会重新执行路由拦截,重新进到这里
}
} catch (error) {
next(`/login?redirect=${to.path}`);
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
}
}
});
router.afterEach(() => {
NProgress.done();
});

View File

@ -0,0 +1,111 @@
/**
* @Description: 路由方法
* @Author: zenghua.wang
* @Date: 2022-01-26 21:55:58
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-04-13 21:51:35
*/
import path from 'path-browserify';
import Layout from '@/layouts/index.vue';
import Views from '@/layouts/Views.vue';
import { isEmpty } from './index';
const modules = import.meta.glob('../views/**/**.vue');
/**
* 创建路由菜单
* @param {*} menus
*/
export function createAsyncRoutes(menus = [], isLayout = true) {
if (isEmpty(menus)) return menus;
const res = [];
menus.forEach((menu) => {
const tmp = {
id: menu.id,
parentId: menu.parentId,
path: menu.path,
component: isEmpty(menu.component)
? isLayout
? Layout
: Views
: modules[/* @vite-ignore */ `../views/${menu.component.replace('/views/', '')}`],
redirect: menu.redirect,
name: menu.name,
meta: {
title: menu.title,
icon: menu?.icon || 'icon-demo',
keepAlive: menu.keepAlive,
},
children: menu.children,
};
if (tmp.children) {
tmp.children = createAsyncRoutes(tmp.children, false);
}
res.push(tmp);
});
return res;
}
/**
* 通过递归过滤异步路由表
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = [];
routes.forEach((route) => {
const tmp = { ...route };
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles);
}
res.push(tmp);
}
});
return res;
}
/**
* 使用 meta.role 来确定当前用户是否具有权限
* @param roles
* @param route
*/
export function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some((role) => route.meta.roles.includes(role));
} else {
return false;
}
}
/**
* 使用递归过滤需要缓存的路由
* @param {*} routers
* @returns
*/
export function filterKeepAlive(routers) {
const cacheRouter = [];
const loop = (routers) => {
routers.forEach((item) => {
if (item.meta?.keepAlive && item.name) {
cacheRouter.push(item.name);
}
if (item.children && item.children.length) {
loop(item.children);
}
});
};
loop(routers);
return cacheRouter;
}
/**
*
* @param {*} routers
* @param {*} pathUrl
*/
export function handleRoutes(routers, pathUrl = '') {
routers.forEach((item) => {
item.path = path.resolve(pathUrl, item.path);
});
}

View File

@ -0,0 +1,184 @@
/**
* @Description: 验证
* @Author: zenghua.wang
* @Date: 2022-01-25 21:08:52
* @LastEditors: zenghua.wang
* @LastEditTime: 2024-01-26 22:22:58
*/
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor'];
return valid_map.indexOf(str.trim()) >= 0;
}
/**
* @param {string} url
* @returns {Boolean}
*/
export function validURL(url) {
const reg =
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
return reg.test(url);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validLowerCase(str) {
const reg = /^[a-z]+$/;
return reg.test(str);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUpperCase(str) {
const reg = /^[A-Z]+$/;
return reg.test(str);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/;
return reg.test(str);
}
/**
* @param {string} email
* @returns {Boolean}
*/
export function validEmail(email) {
const reg =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return reg.test(email);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function isString(str) {
if (typeof str === 'string' || str instanceof String) {
return true;
}
return false;
}
/**
* @param {Array} arg
* @returns {Boolean}
*/
export function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]';
}
return Array.isArray(arg);
}
/**
* 手机号码
* @param val 当前值字符串
* @returns 返回 true: 手机号码正确
*/
export function verifyPhone(val) {
// false: 手机号码不正确
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0|1,5-9]))\d{8}$/.test(val)) return false;
// true: 手机号码正确
else return true;
}
/**
* 匹配文字变色搜索时
* @param val 当前值字符串
* @param text 要处理的字符串值
* @param color 搜索到时字体高亮颜色
* @returns 返回处理后的字符串
*/
export function verifyTextColor(val, text = '', color = 'red') {
// 返回内容,添加颜色
const v = text.replace(new RegExp(val, 'gi'), `<span style='color: ${color}'>${val}</span>`);
// 返回结果
return v;
}
/**
* 身份证号, 支持1/2(15/18位数字)
* @param val 当前值字符串
* @returns 返回 true: 身份证正确
*/
export function verifyIdCard(val) {
const regx = /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/;
return regx.test(val);
}
/**
* 网址
* @param val 当前值字符串
* @returns 返回 true: 网址正确
*/
export function verifyWebsite(val) {
const regx = /^((https?|ftp):\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\/\w\.-]*)*\/?/;
return regx.test(val);
}
/**
* 是否html标签
* @param val 当前值字符串
* @returns 返回 true: 是否html标签
*/
export function verifyHtml(val) {
const regx = /<(.*)>.*<\/\1>|<(.*) \/>/;
return regx.test(val);
}
/**
* 日期
* @param val 当前值字符串
* @returns 返回 true: 是否日期
*/
export function verifyDate(val) {
const regx =
/^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;
return regx.test(val);
}
/**
* 邮箱
* @param val 当前值字符串
* @returns 返回 true: 邮箱是否正确
*/
export function verifyEmail(val) {
const regx = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return regx.test(val);
}
/**
* 验证校验器函数封装
* @param verifyPhone 验证函数
* @param message 提示
*/
export function validatorMethod(verifyPhone, message) {
return (rule, value, callback) => {
if (!verifyPhone(value)) {
callback(new Error(message));
} else {
callback();
}
};
}

View File

@ -0,0 +1,12 @@
<template>
<el-result icon="error" title="403" sub-title="Sorry, request error">
<template #extra>
<el-button type="primary" @click="push('/')">返回</el-button>
</template>
</el-result>
</template>
<script setup name="error">
import { useRouter } from 'vue-router';
const { push } = useRouter();
</script>

View File

@ -0,0 +1,12 @@
<template>
<el-result icon="error" title="404" sub-title="Sorry, request error">
<template #extra>
<el-button type="primary" @click="push('/')">返回</el-button>
</template>
</el-result>
</template>
<script setup name="error">
import { useRouter } from 'vue-router';
const { push } = useRouter();
</script>

View File

@ -0,0 +1,6 @@
<template>
<div style="width: 100%; height: 100%">
<div>政务服务</div>
</div>
</template>
<script setup name="home"></script>

View File

@ -0,0 +1,195 @@
<template>
<div class="custom-table">
<avue-crud
v-model="state.form"
v-model:search="state.query"
v-model:page="state.page"
:table-loading="state.loading"
:data="state.data"
:option="state.options"
@refresh-change="refreshChange"
@search-reset="searchChange"
@search-change="searchChange"
@row-click="rowClick"
@row-save="rowSave"
@row-update="rowUpdate"
@row-del="rowDel"
></avue-crud>
</div>
</template>
<script setup>
import { reactive } from 'vue';
import { useApp } from '@/hooks';
import { sleep } from '@/utils';
import Mock from 'mockjs';
const res = Mock.mock({
'data|20': [
{
id: '@increment(100000)',
name: '@ctitle(5,10)',
'area|100-1000': 100,
type: '耕地',
tag: '龙津河周边',
address: '耿马镇白塔社区',
createTime: '@datetime("yyyy-MM-dd HH:mm:ss")',
updateTime: '@datetime("yyyy-MM-dd HH:mm:ss")',
},
],
});
const app = useApp();
const state = reactive({
loading: false,
query: {},
form: {},
options: {
selection: true,
align: 'center',
headerAlign: 'center',
gridBtn: false,
addBtn: true,
viewBtn: true,
editBtn: true,
delBtn: false,
gutter: 20,
labelWidth: 150,
column: [
{
label: '土地编号',
prop: 'id',
fixed: true,
display: false,
},
{
label: '土地名称',
prop: 'name',
fixed: true,
search: true,
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '土地面积',
prop: 'area',
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '土地类型',
prop: 'type',
},
{
label: '坐落位置',
prop: 'tag',
type: 'textarea',
width: 200,
span: 24,
rows: 4,
overHidden: true,
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '区域',
prop: 'address',
type: 'textarea',
width: 200,
span: 24,
rows: 4,
overHidden: true,
rules: {
required: true,
message: '请输入',
trigger: 'blur',
},
},
{
label: '信息录入时间',
prop: 'createTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
width: 200,
display: false,
},
{
label: '信息更新时间',
prop: 'updateTime',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
width: 200,
display: false,
},
],
},
page: {
total: 0,
currentPage: 1,
pageSize: 10,
},
data: [],
});
const loadData = async () => {
state.loading = true;
await sleep(500);
state.data = res.data;
state.page = {
total: 20,
currentPage: 1,
pageSize: 10,
};
state.loading = false;
};
loadData();
//
const searchChange = (params, done) => {
console.log('search==', params);
if (done) done();
state.query = params;
state.page.currentPage = 1;
loadData();
app.$message.success('搜索成功');
};
//
const refreshChange = () => {
loadData();
app.$message.success('刷新成功');
};
// row
const rowClick = (row, event, column) => {
app.$message.success(JSON.stringify(row));
};
//
const rowSave = (row, done, loading) => {
console.log('add=', row);
};
//
const rowUpdate = (row, index, done, loading) => {
console.log('update=', row);
done(row);
};
//
const rowDel = (row, index, done) => {
console.log('del=', row);
done(row);
};
</script>

View File

@ -0,0 +1,6 @@
<template>
<div style="width: 100%; height: 100%">
<div>土地资源一张图</div>
</div>
</template>
<script setup name="home"></script>

View File

@ -0,0 +1,116 @@
/*
* @Descripttion:
* @Author: zenghua.wang
* @Date: 2022-09-18 21:24:29
* @LastEditors: zenghua.wang 1048523306@qq.com
* @LastEditTime: 2025-01-21 14:11:58
*/
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun';
import eslintPlugin from 'vite-plugin-eslint';
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import compression from 'vite-plugin-compression';
import { viteMockServe } from 'vite-plugin-mock';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import postcssImport from 'postcss-import';
import autoprefixer from 'autoprefixer';
import { resolve } from 'path';
const useDevMode = true;
export default defineConfig(({ command, mode }) => {
console.log('vite.config.js', command, mode, loadEnv(mode, process.cwd()));
const { VITE_PORT, VITE_APP_NAME, VITE_APP_BASE_API, VITE_APP_BASE_URL, VITE_APP_UPLOAD_API, VITE_APP_UPLOAD_URL } = loadEnv(mode, process.cwd());
const config = {
base: './',
build: {
target: 'ESNext',
outDir: 'dist',
minify: 'terser',
},
server: {
host: '0.0.0.0',
port: VITE_PORT,
open: true,
https: false,
headers: {
'Access-Control-Allow-Origin': '*',
},
proxy: {
[VITE_APP_BASE_API]: {
target: VITE_APP_BASE_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/apis/, ''),
},
[VITE_APP_UPLOAD_API]: {
target: VITE_APP_UPLOAD_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/uploadApis/, ''),
},
},
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
extensions: ['.js', '.vue', '.json', '.ts'],
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/global.scss";',
api: 'modern-compiler',
},
},
postcss: {
plugins: [
postcssImport,
autoprefixer({
overrideBrowserslist: ['> 1%', 'last 2 versions'],
}),
],
},
},
plugins: [
vue(),
qiankun(VITE_APP_NAME, { useDevMode }),
vueSetupExtend(),
eslintPlugin({
include: ['src/**/*.ts', 'src/**/*.vue', 'src/*.ts', 'src/*.vue'],
}),
Components({
dirs: ['src/components'],
extensions: ['vue', 'js', 'jsx', 'ts', 'tsx'],
resolvers: [],
}),
compression(),
AutoImport({
include: [/\.[tj]s?$/, /\.vue$/],
imports: ['vue', 'vue-router'],
}),
createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/svgs')],
symbolId: 'icon-[name]',
}),
viteMockServe({
mockPath: 'src/mock',
watchFiles: true,
localEnabled: command === 'dev',
prodEnabled: false,
}),
],
};
if (mode === 'production') {
config.build.terserOptions = {
compress: {
drop_console: true,
drop_debugger: true,
},
};
}
return config;
});

File diff suppressed because it is too large Load Diff