init
6
.env.development
Normal file
@@ -0,0 +1,6 @@
|
||||
# 本地环境
|
||||
ENV = development
|
||||
|
||||
# 本地环境接口地址
|
||||
# VITE_API_URL = 'http://admintestapi.sxczgkj.cn/admin/'
|
||||
VITE_API_URL = 'http://admintestapi.sxczgkj.cn/admin/'
|
||||
5
.env.production
Normal file
@@ -0,0 +1,5 @@
|
||||
# 线上环境
|
||||
ENV = production
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = 'http://admintestapi.sxczgkj.cn/admin/'
|
||||
82
.eslintrc-auto-import.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"globals": {
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"ENUMS": true,
|
||||
"EffectScope": true,
|
||||
"ElMessage": true,
|
||||
"InjectionKey": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"VNode": true,
|
||||
"_hook": true,
|
||||
"acceptHMRUpdate": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"createPinia": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"defineStore": true,
|
||||
"effectScope": true,
|
||||
"getActivePinia": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"mapActions": true,
|
||||
"mapGetters": true,
|
||||
"mapState": true,
|
||||
"mapStores": true,
|
||||
"mapWritableState": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeRouteLeave": true,
|
||||
"onBeforeRouteUpdate": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"setActivePinia": true,
|
||||
"setMapStoreSuffix": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"storeToRefs": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useLink": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useSlots": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true
|
||||
}
|
||||
}
|
||||
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
43
README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
## 🌈 介绍
|
||||
|
||||
基于 vue3.x + CompositionAPI setup 语法糖 + vite + element plus + vue-router-next + pinia 技术的后台开源免费模板,希望减少工作量,实现快速开发,此项目为 JS 非 TS 版本。
|
||||
|
||||
### 🏭 环境支持
|
||||
|
||||
| Edge | Firefox | Chrome | Safari |
|
||||
| --------- | ------------ | ----------- | ----------- |
|
||||
| Edge ≥ 88 | Firefox ≥ 78 | Chrome ≥ 87 | Safari ≥ 13 |
|
||||
|
||||
> 由于 Vue3 不再支持 IE11,故而 ElementPlus 也不支持 IE11 及之前版本。
|
||||
|
||||
### 😉 hooks
|
||||
|
||||
> 依赖 ElementPlus,方法提示均使用 ElMessage
|
||||
|
||||
| 说明 | 方法 | 说明 | 使用 |
|
||||
| ---- | ------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 数据 | `useDeepClone` | 深度克隆 | `const result = useDeepClone(target)`<br />`@param {Object} target`:要克隆的目标<br />`@return {Object} result`:结果 |
|
||||
| 存储 | `useLocalStorage` | 会话存储 | `useLocalStorage.set(key, value)`: 设置临时缓存<br />`useLocalStorage.get(key)`: 获取临时缓存<br />`useLocalStorage.remove(key)`: 移除临时缓存<br />`useLocalStorage.clear()`: 移除全部临时缓存<br />`@param {String} key`:设置缓存的名称<br />`@param {Any} value`:设置缓存的值 |
|
||||
| | `useLocalStorage` | 本地存储 | `useLocalStorage.set(key, value)`: 设置临时缓存<br />`useLocalStorage.get(key)`: 获取临时缓存<br />`useLocalStorage.remove(key)`: 移除临时缓存<br />`useLocalStorage.clear()`: 移除全部临时缓存<br />`@param {String} key`:设置缓存的名称<br />`@param {Any} value`:设置缓存的值 |
|
||||
| 函数 | `useDebounce` | 防抖 | `useDebounce(function, time)`<br />`@param {Function} function`:要处理防抖的函数<br />`@param {String,Number} time`:防抖的时间<br />`@return {function}`:防抖的函数 |
|
||||
| 日期 | `useDateFormat` | 日期格式 | `useDateFormat(date, [format])`<br />`@param {Date} date`:任何合法的时间格式、秒或毫秒的时间戳<br />`@param {String} format`:时间格式,可选,默认为 YYYY-mm-dd。<br />`@return {String}`:格式好的时间 |
|
||||
| 验证 | `usePureNum` | 验证是否为纯数字 | `usePureNum(str)`<br />`@param {String,Number} str`:要验证的字符串或数字<br />`@return {Boolean}`:true / false |
|
||||
| | `usePhoneNum` | 验证是手机号 | `usePhoneNum(str)`<br />`@param {String,Number} str`:要验证的手机号<br />`@return {Boolean}`:true / false |
|
||||
| | `useEmailNum` | 验证是邮箱号 | `useEmailNum(str)`<br />`@param {String} str`:要验证的字符串<br />`@return {Boolean}`:true / false |
|
||||
| | `useEmptyObj` | 验证是空对象 | `useEmptyObj(obj)`<br />`@param {Object} obj`:要验证的对象<br />`@return {Boolean}`:true / false |
|
||||
| | `useDataType` | 检查数据类型 | `useDataType(data,[type])`<br />`@param {Any} data`:数据<br />`@param {Type}type`:类型,可选值。<br />如果传了 type 会返回一个布尔值表示 data 是否与 type 类型相等<br />`@return {String,Boolean}`:数据类型或 true / false |
|
||||
| 样式 | `useCssVar` | 设置 css 变量 | `useCssVar(key, value)`<br />`@param {String} key`:要设置变量<br />`@param {String} value`:设置的值 |
|
||||
| | `useRgbToHex` | hex 颜色转 rgb 颜色 | `useRgbToHex(r, g, b)`<br />`@param {String} r`:红色<br />`@param {String} g`:绿色<br />`@param {String} b`:蓝色<br />`@return {String}`:Hex 值 |
|
||||
| | `useHexToRgb` | rgb 颜色转 Hex 颜色 | `useHexToRgb(str)`<br />`@param {String} str`:颜色值字符串<br />`@return {String}`:Rgb 值 |
|
||||
| | `useDarkColor` | 加深颜色值 | `useDarkColor(color, level)`<br />`@param {String} color`:颜色值字符串<br />`@param {String} level`:加深的程度,限 0-1 之间<br />`@return {String}`:加深后的颜色 |
|
||||
| | `useLightColor` | 变浅颜色值 | `useLightColor(color, level)`<br />`@param {String} color`:颜色值字符串<br />`@param {String} level`:变浅的程度,限 0-1 之间<br />`@return {String}`:变浅后的颜色 |
|
||||
| 按键 | `useKeyStroke` | 键盘按下事件 **<br />注: 仅支持如下键 Esc、Tab、<br />BackSpace、Enter、Shift、Ctrl、<br />Alt、Up、Down、Left、Right** | `useKeyStroke(key, fun)`<br />`@param {String} key`:要监听的键<br />`@param {Function} fun`:回调函数 |
|
||||
| 方法 | `useRepairZero` | 补零 ( 当数小于 10 在前补零 ) | `useRepairZero(number)`<br />`@param {Number} number`:当前数<br />`@return {Number}`:补零后的数 |
|
||||
|
||||
### 😊 指令
|
||||
|
||||
| 方法 | 说明 | 使用 |
|
||||
| ------------ | ---------------------- | ----------------------- |
|
||||
| `v-isLogin` | 需要登录后可操作的函数 | `v-isLogin="fun"` |
|
||||
| `v-throttle` | 节流函数指令 | `v-throttle:1000="fun"` |
|
||||
| `v-size-ob` | 监控元素的尺寸变化 | `v-size-ob="fun"` |
|
||||
20
addVersion.js
Normal file
@@ -0,0 +1,20 @@
|
||||
//npm run build打包前执行此段代码
|
||||
import fs from 'fs'
|
||||
|
||||
//返回package的json数据
|
||||
function getPackageJson() {
|
||||
let data = fs.readFileSync('./package.json');//fs读取文件
|
||||
return JSON.parse(data);//转换为json对象
|
||||
}
|
||||
|
||||
let packageData = getPackageJson();//获取package的json
|
||||
let arr = packageData.version.split('.');//切割后的版本号数组
|
||||
arr[2] = parseInt(arr[2]) + 1;
|
||||
packageData.version = arr.join('.');//转换为以"."分割的字符串
|
||||
//用packageData覆盖package.json内容
|
||||
fs.writeFile(
|
||||
'./package.json',
|
||||
JSON.stringify(packageData, null, "\t"
|
||||
),
|
||||
(err) => { }
|
||||
);
|
||||
16
index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favico.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>vue admin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
3770
package-lock.json
generated
Normal file
34
package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "vue-admin",
|
||||
"private": true,
|
||||
"version": "1.2.23",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "node ./addVersion.js && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"axios": "^1.3.4",
|
||||
"echarts": "^5.4.2",
|
||||
"element-china-area-data": "^6.0.2",
|
||||
"element-plus": "^2.3.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"js-cookie": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.33",
|
||||
"vue": "^3.2.47",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-count-to": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"sass": "^1.59.3",
|
||||
"unplugin-auto-import": "^0.15.1",
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
}
|
||||
BIN
public/favico.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/logo.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
81
src/App.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
const storeConfigure = useConfigure();
|
||||
const storeRoutes = useRoutes();
|
||||
const route = useRoute();
|
||||
// 获取枚举值
|
||||
const { layoutModeEnum } = ENUMS;
|
||||
|
||||
watch(
|
||||
route,
|
||||
(newValue) => {
|
||||
const componentName = _hook.useComponentName(newValue);
|
||||
if (newValue?.meta?.isKeepAlive && componentName) {
|
||||
storeRoutes.setCachedRoute(componentName);
|
||||
}
|
||||
storeRoutes.setBreadcrumb(newValue.matched.slice(1));
|
||||
document.title = route?.meta?.title || route.path;
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化配置
|
||||
const configure = _hook.useLocalStorage.get("configure");
|
||||
if (configure) {
|
||||
storeConfigure.initConfigure(configure);
|
||||
}
|
||||
|
||||
// 初始化颜色
|
||||
_hook.useCssVar("--el-color-primary", storeConfigure.configure.themeColor);
|
||||
switch (storeConfigure.configure.menuMode) {
|
||||
case layoutModeEnum.key[0]:
|
||||
_hook.useCssVar("--el-menu-bg-color", storeConfigure.configure.menuBGColor);
|
||||
break;
|
||||
case layoutModeEnum.key[1]:
|
||||
_hook.useCssVar("--el-menu-bg-color", "#ffffff");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
_hook.useCssVar("--admin-column-bg-color", storeConfigure.configure.menuBGColor);
|
||||
_hook.useCssVar("--el-menu-text-color", storeConfigure.configure.textColor);
|
||||
_hook.useCssVar("--el-menu-active-color", storeConfigure.configure.activeTextColor);
|
||||
[3, 5, 7, 8, 9].forEach((i) => {
|
||||
_hook.useCssVar(`--el-color-primary-light-${i}`, _hook.useLightColor(storeConfigure.configure.themeColor, `0.${i}`));
|
||||
});
|
||||
|
||||
// 面包屑导航
|
||||
storeRoutes.setBreadcrumb(route.matched.slice(1));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 自定义进度条颜色
|
||||
#nprogress .bar {
|
||||
height: 5px !important;
|
||||
background: var(--el-color-primary-light-3) !important;
|
||||
}
|
||||
|
||||
.mt15 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.pb15 {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
24
src/api/device.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import request from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 当前用户设备总数
|
||||
* @returns
|
||||
*/
|
||||
export function getSumDeviceStock() {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/getSumDeviceStock"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
* @returns
|
||||
*/
|
||||
export function getDeviceStockInfo(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/getDeviceStockInfo",
|
||||
params
|
||||
});
|
||||
}
|
||||
88
src/api/home.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import request from "@/utils/request.js";
|
||||
import { dayjs } from 'element-plus';
|
||||
/**
|
||||
* 获取首页数据
|
||||
* @returns
|
||||
*/
|
||||
export function getIndexData() {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: "/user/getIndexData"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页图表数据
|
||||
* @returns
|
||||
*/
|
||||
export function getChartData(type, reqUserId) {
|
||||
const m = {
|
||||
1: '/user/getDayOrder', // 获取近七日收单额
|
||||
2: '/user/getDayProfit', // 获取近七日收益
|
||||
3: '/user/getMonthOrder', // 获取近一年的收单额
|
||||
4: '/user/getMonthProfit' // 获取近一年的收益数据
|
||||
}
|
||||
const params = {
|
||||
year: dayjs().format('YYYY'),
|
||||
reqUserId: reqUserId
|
||||
}
|
||||
return request({
|
||||
method: "GET",
|
||||
url: m[type],
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传OSS
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function uploadOSS(data) {
|
||||
// console.log('data', data)
|
||||
let formData = new FormData()
|
||||
formData.append('file', data)
|
||||
console.log('formData', formData)
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/promotion/OSSUpdate',
|
||||
data: formData
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* app菜单list
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function appMenuPage(params) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: '/AppMenu/page',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过code查询
|
||||
* @param {*} code
|
||||
* @returns
|
||||
*/
|
||||
export function getDictGroup(code) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: `/dict/getGroup/${code}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加菜单
|
||||
* @returns
|
||||
*/
|
||||
export function appMenuSave(data) {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: "/AppMenu/save",
|
||||
data
|
||||
});
|
||||
}
|
||||
97
src/api/organization.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// 机构管理接口
|
||||
import request from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 创建机构
|
||||
* @returns
|
||||
*/
|
||||
export function addAgency(data) {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: "/agency/addAgency",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 机构列表
|
||||
* @returns
|
||||
*/
|
||||
export function queryAgency(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/queryAgency",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改下级费率
|
||||
* @returns
|
||||
*/
|
||||
export function modifyFee(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/modifyFee",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收益信息
|
||||
* @returns
|
||||
*/
|
||||
export function queryProfit(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/queryProfit",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收益信息 商家列表
|
||||
* @returns
|
||||
*/
|
||||
export function queryOrder(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/queryOrder",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户总收益和可提收益,冻结收益 ,已提收益
|
||||
* @returns
|
||||
*/
|
||||
export function getUserBalance() {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/user/getUserBalance"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提现
|
||||
* @returns
|
||||
*/
|
||||
export function withdrawalProfit(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/user/withdrawalProfit",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提现流水
|
||||
* @returns
|
||||
*/
|
||||
export function getUserOutFlow(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/user/getUserOutFlow",
|
||||
params
|
||||
});
|
||||
}
|
||||
37
src/api/promotion.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import request from "@/utils/request.js"
|
||||
|
||||
/**
|
||||
* 推广宽图列表
|
||||
* @returns
|
||||
*/
|
||||
export function promotionImageList(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/promotion/promotionImageList",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加图片
|
||||
* @returns
|
||||
*/
|
||||
export function insert(data) {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: "/promotion/insert",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更改推广宽图
|
||||
* @returns
|
||||
*/
|
||||
export function updateById(data) {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: "/promotion/updateById",
|
||||
data
|
||||
});
|
||||
}
|
||||
49
src/api/setting.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import request from "@/utils/request.js"
|
||||
|
||||
/**
|
||||
* 获取appid 信息
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function querySystemApis(params) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: '/systemApi/querySystemApis',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成密钥对
|
||||
* @returns
|
||||
*/
|
||||
export function createKey() {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: '/systemApi/createKey'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增api
|
||||
* @returns
|
||||
*/
|
||||
export function initApi(params) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: '/systemApi/initApi',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改api 配置
|
||||
* @returns
|
||||
*/
|
||||
export function modfityApi(params) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: '/systemApi/modfityApi',
|
||||
params
|
||||
})
|
||||
}
|
||||
162
src/api/shop.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import request from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 获取商户列表
|
||||
* @returns
|
||||
*/
|
||||
export function queryCustormerFlow(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/queryCustormerFlow",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 商户列表数据统计
|
||||
* @returns
|
||||
*/
|
||||
export function queryCustormSum() {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/agency/queryCustormSum"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 实名认证信息
|
||||
* @returns
|
||||
*/
|
||||
export function merchantInfoDetail(userId) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/merchantInfo/detail/audit/${userId}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取创客审核列表
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function getUserMark(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/agency/getUserMark`,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核创客申请审核
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function updateUserMark(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/agency/updateUserMark`,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据城市获取对应的支行
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function getBranchList(params) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: '/merchantInfo/getBranchList',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更改实名认证信息
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function updatePromoterInformation(data) {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/merchantInfo/updatePromoterInformation',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* mcc相关
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function mccPageData(params) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: '/merchantInfo/mccPageData',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 商户基本信息
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function merchBaseInfo(userId) {
|
||||
return request({
|
||||
method: 'GET',
|
||||
url: `/merchantInfo/detail/merchBaseInfo/${userId}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改商户相关信息
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function updateMerchantInformation(data) {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/merchantInfo/updateMerchantInformation',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 实名认证信息页面(实名个数)
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function connectInfo(userId) {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: `/merchantInfo/connectInfo/${userId}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 结算信息
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function merchBaseAccount(userId) {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: `/merchantInfo/detail/merchBaseAccount/${userId}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更改结算信息
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function updateAccount(data) {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/merchantInfo/detail/updateAccount',
|
||||
data
|
||||
})
|
||||
}
|
||||
15
src/api/user.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import request from "@/utils/request.js";
|
||||
export function login(data) {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: "/user/doLogin",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserInfo() {
|
||||
return request({
|
||||
method: "POST",
|
||||
url: "/user/getUserInfoByToken",
|
||||
});
|
||||
}
|
||||
25
src/api/withdraw.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 提现申请查询
|
||||
* @returns
|
||||
*/
|
||||
export function getOutFlow(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/user/getOutFlow",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提现审核
|
||||
* @returns
|
||||
*/
|
||||
export function modifyOutFlow(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/user/modifyOutFlow",
|
||||
params
|
||||
});
|
||||
}
|
||||
BIN
src/assets/home_icon1.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/home_icon2.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/home_icon3.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/home_icon4.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/home_icon5.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/home_icon6.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/home_icon7.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/home_icon8.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/icon_dev1.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/icon_dev2.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/icon_dev3.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/icon_earnings1.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/icon_earnings2.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/icon_earnings3.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/404.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/logo_bg.png
Normal file
|
After Width: | Height: | Size: 309 KiB |
21
src/components/SvgIcon.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<el-icon v-if="name" :size="size" :color="color">
|
||||
<component :is="name"></component>
|
||||
</el-icon>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
size: {
|
||||
type: [String, Number],
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
36
src/components/addressCard.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<el-cascader :placeholder="placeholder" :options="regionData" v-model="selectedOptions" @change="change"></el-cascader>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { regionData, codeToText } from 'element-china-area-data'
|
||||
import { defineEmits, defineExpose } from 'vue'
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const placeholder = ref('')
|
||||
const selectedOptions = ref([])
|
||||
|
||||
// 选择区域
|
||||
function change(e) {
|
||||
emit('change', [{
|
||||
code: selectedOptions.value[0],
|
||||
label: codeToText[selectedOptions.value[0]]
|
||||
}, {
|
||||
code: selectedOptions.value[1],
|
||||
label: codeToText[selectedOptions.value[1]]
|
||||
}, {
|
||||
code: selectedOptions.value[2],
|
||||
label: codeToText[selectedOptions.value[2]]
|
||||
}])
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
function setValue(arr) {
|
||||
selectedOptions.value = arr
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
placeholder,
|
||||
setValue
|
||||
})
|
||||
</script>
|
||||
226
src/components/chartCard.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div class="chart_header">
|
||||
<div class="item" :class="{ active1: chartType == 1 }" @click="chartTypeChange(1)">
|
||||
<div class="icon"></div>
|
||||
<span>收益</span>
|
||||
</div>
|
||||
<div class="item" :class="{ active2: chartType == 2 }" @click="chartTypeChange(2)">
|
||||
<div class="icon"></div>
|
||||
<span>流水</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart_wrap" v-loading="chartLoading">
|
||||
<div ref="lineRef1" class="item" style="height: 600px;"></div>
|
||||
<div ref="lineRef2" class="item" style="height: 600px;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getChartData } from '@/api/home.js'
|
||||
import * as echarts from "echarts"
|
||||
import { dayjs } from 'element-plus'
|
||||
import hooks from '@/hooks'
|
||||
|
||||
// 定义变量内容
|
||||
const lineRef1 = ref();
|
||||
const lineRef2 = ref();
|
||||
const chartsObj = {
|
||||
lineRef1: '',
|
||||
lineRef2: '',
|
||||
myCharts: [],
|
||||
};
|
||||
|
||||
const chartLoading = ref(true)
|
||||
const chartType = ref(1)
|
||||
const chartYear = ref(dayjs().format('YYYY'))
|
||||
const props = defineProps({
|
||||
userId: {
|
||||
type: [String, Number],
|
||||
default: hooks.useLocalStorage.get('userInfo').userId
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化折线图
|
||||
function initDeicount(yearData = [], sevenData = [], sevenDataTime = []) {
|
||||
if (!chartsObj.lineRef1 && !chartsObj.lineRef2) {
|
||||
chartsObj.lineRef1 = echarts.init(lineRef1.value);
|
||||
chartsObj.lineRef2 = echarts.init(lineRef2.value);
|
||||
}
|
||||
const option1 = {
|
||||
title: {
|
||||
text: chartType.value == 1 ? '年度收益' : '年度流水',
|
||||
x: 'center',
|
||||
y: '95%'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二'],
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: "value"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: yearData,
|
||||
barWidth: '20',
|
||||
itemStyle: {
|
||||
color: chartType.value == 1 ? '#5470C6' : '#91CB75'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const option2 = {
|
||||
title: {
|
||||
text: chartType.value == 1 ? '七日收益' : '七日流水',
|
||||
x: 'center',
|
||||
y: '95%'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: sevenDataTime
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: "line",
|
||||
data: sevenData,
|
||||
itemStyle: {
|
||||
color: chartType.value == 1 ? '#5470C6' : '#91CB75'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
chartsObj.lineRef1.setOption(option1)
|
||||
chartsObj.lineRef2.setOption(option2)
|
||||
chartsObj.myCharts.push(chartsObj.lineRef1)
|
||||
chartsObj.myCharts.push(chartsObj.lineRef2)
|
||||
}
|
||||
|
||||
// 改变chart类型
|
||||
function chartTypeChange(t) {
|
||||
chartType.value = t
|
||||
chartLoading.value = true
|
||||
getChartDataMethod()
|
||||
}
|
||||
|
||||
// 获取chart数据
|
||||
async function getChartDataMethod() {
|
||||
try {
|
||||
let yearData = ''
|
||||
let sevenData = ''
|
||||
let sevenDataTime = ''
|
||||
if (chartType.value == 1) {
|
||||
// 收益
|
||||
const res1 = await getChartData(4, props.userId)
|
||||
const res2 = await getChartData(2, props.userId)
|
||||
yearData = res1.map(item => item.price)
|
||||
sevenData = res2.map(item => item.price)
|
||||
sevenDataTime = res2.map(item => dayjs(item.times).format('MM/DD'))
|
||||
} else {
|
||||
// 流水
|
||||
const res1 = await getChartData(3, props.userId)
|
||||
const res2 = await getChartData(1, props.userId)
|
||||
yearData = res1.map(item => item.consumeFee)
|
||||
sevenData = res2.map(item => item.consumeFee)
|
||||
sevenDataTime = res2.map(item => dayjs(item.times).format('MM/DD'))
|
||||
}
|
||||
initDeicount(yearData, sevenData, sevenDataTime)
|
||||
chartLoading.value = false
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// 批量设置 echarts resize
|
||||
function initEChartsResize() {
|
||||
var myEvent = new Event("resize");
|
||||
window.addEventListener("resize", () => {
|
||||
nextTick(() => {
|
||||
for (let i = 0; i < chartsObj.myCharts.length; i++) {
|
||||
setTimeout(() => {
|
||||
chartsObj.myCharts[i].resize();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
window.dispatchEvent(myEvent);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartDataMethod()
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
initEChartsResize();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chart_header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 20px;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:last-child {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.active1 {
|
||||
.icon {
|
||||
background-color: #5470C6;
|
||||
}
|
||||
}
|
||||
|
||||
&.active2 {
|
||||
.icon {
|
||||
background-color: #91CB75;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart_wrap {
|
||||
display: flex;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
src/components/editor.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="editor-container">
|
||||
<Toolbar :editor="editorRef" :mode="mode" />
|
||||
<Editor :mode="mode" :defaultConfig="state.editorConfig" :style="{ height }" v-model="state.editorVal" @onCreated="handleCreated" @onChange="handleChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// https://www.wangeditor.com/v5/for-frame.html#vue3
|
||||
import "@wangeditor/editor/dist/css/style.css";
|
||||
import { Toolbar, Editor } from "@wangeditor/editor-for-vue";
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
// 是否禁用
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
|
||||
// 内容框默认 placeholder
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => "请输入内容...",
|
||||
},
|
||||
|
||||
// 模式,可选 <default|simple>,默认 default
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => "default",
|
||||
},
|
||||
|
||||
// 高度
|
||||
height: {
|
||||
type: String,
|
||||
default: () => "300px",
|
||||
},
|
||||
|
||||
// 双向绑定,用于获取 editor.getHtml()
|
||||
getHtml: String,
|
||||
|
||||
// 双向绑定,用于获取 editor.getText()
|
||||
getText: String,
|
||||
});
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(["update:getHtml", "update:getText"]);
|
||||
|
||||
// 定义变量内容
|
||||
const editorRef = shallowRef();
|
||||
const state = reactive({
|
||||
editorConfig: {
|
||||
placeholder: props.placeholder,
|
||||
},
|
||||
editorVal: props.getHtml,
|
||||
});
|
||||
|
||||
// 编辑器回调函数
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor; // 记录 editor 实例,重要!
|
||||
};
|
||||
|
||||
// 编辑器内容改变时
|
||||
const handleChange = (editor) => {
|
||||
emit("update:getHtml", editor.getHtml());
|
||||
emit("update:getText", editor.getText());
|
||||
};
|
||||
|
||||
nextTick(() => {
|
||||
// 监听是否禁用改变
|
||||
watch(
|
||||
() => props.disable,
|
||||
(bool) => {
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) return;
|
||||
bool ? editor.disable() : editor.enable();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
|
||||
// 监听双向绑定值改变,用于回显
|
||||
watch(
|
||||
() => props.getHtml,
|
||||
(val) => {
|
||||
state.editorVal = val;
|
||||
}
|
||||
);
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value;
|
||||
if (editor == null) return;
|
||||
editor.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
99
src/components/totalEarnings.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<el-dialog v-model="showDialog" title="累计收益" width="80%">
|
||||
<el-space>
|
||||
<el-input placeholder="请输入订单号搜索" v-model="tableOptions.orderNumber" style="width: 200px;" />
|
||||
<el-input placeholder="请输入商户号搜索" v-model="tableOptions.merchantCode" style="width: 200px;" />
|
||||
<el-button type="primary" icon="Search" @click="searchHandle">搜索</el-button>
|
||||
</el-space>
|
||||
<div class="mt15">
|
||||
<el-table :data="tableOptions.list" v-loading="tableOptions.loading">
|
||||
<el-table-column prop="orderNumber" label="订单号"></el-table-column>
|
||||
<el-table-column prop="merchantCode" label="商户号"></el-table-column>
|
||||
<el-table-column prop="price" label="收益额"></el-table-column>
|
||||
<el-table-column prop="current_fee" label="推广费率">
|
||||
<template #default="scope">
|
||||
<el-text type="primary">{{ scope.row.currentFee }}%</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="loginName" label="名称">
|
||||
<template #default="scope">
|
||||
<el-text>{{ scope.row.loginName }}</el-text>
|
||||
<el-tag disable-transitions style="margin-left: 8px;">{{ typeNames[scope.row.typeCode] }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="merchantName" label="商户名称">
|
||||
</el-table-column>
|
||||
<el-table-column prop="createDt" label="时间">
|
||||
<template #default="scope">
|
||||
{{ dayjs(scope.row.createDt).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="mt15">
|
||||
<el-pagination layout="prev, pager, next, total, sizes, jumper" v-model:current-page="tableOptions.pageNum"
|
||||
v-model:page-size="tableOptions.pageSzie" :page-size="tableOptions.pageSzie" :page-sizes="[10, 20, 30, 50]"
|
||||
:total="tableOptions.total" @size-change="paginationChange" @current-change="paginationChange" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { dayjs } from 'element-plus';
|
||||
import { typeNames } from '@/utils/index.js'
|
||||
import { queryProfit } from '@/api/organization.js'
|
||||
|
||||
const showDialog = ref(false)
|
||||
|
||||
// 表格参数
|
||||
const tableOptions = reactive({
|
||||
loading: true,
|
||||
reqUserId: '', // 上级id
|
||||
orderNumber: '', // 订单号
|
||||
merchantCode: '', // 商户号
|
||||
list: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSzie: 10
|
||||
})
|
||||
|
||||
// 搜索
|
||||
function searchHandle() {
|
||||
tableOptions.pageNum = 1;
|
||||
paginationChange()
|
||||
}
|
||||
|
||||
// 分页回调
|
||||
function paginationChange() {
|
||||
tableOptions.loading = true
|
||||
getTableDate()
|
||||
}
|
||||
|
||||
// 获取机构列表
|
||||
async function getTableDate() {
|
||||
try {
|
||||
const res = await queryProfit({
|
||||
reqUserId: tableOptions.reqUserId,
|
||||
orderNumber: tableOptions.orderNumber,
|
||||
merchantCode: tableOptions.merchantCode,
|
||||
pageNum: tableOptions.pageNum,
|
||||
pageSzie: tableOptions.pageSzie
|
||||
})
|
||||
tableOptions.loading = false
|
||||
tableOptions.list = res.list
|
||||
tableOptions.total = res.total
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
const show = (reqUserId) => {
|
||||
tableOptions.reqUserId = reqUserId
|
||||
showDialog.value = true
|
||||
getTableDate()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
125
src/components/verificationCode.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<canvas id="canvas" :width="props.width" :height="props.height" @click="handleCanvas"> </canvas>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
width: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 38,
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
line: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
spot: {
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
});
|
||||
|
||||
let trueCode = ref(""); //保存正确的验证码
|
||||
// 只用于传参,并且数组长度不能「多于」下面验证码遍历的次数,不然最终得到的 trueCode 会有问题
|
||||
let verificationCode = [];
|
||||
|
||||
function draw(showCode) {
|
||||
var canvas_width = document.querySelector("#canvas").clientWidth;
|
||||
var canvas_height = document.querySelector("#canvas").clientHeight;
|
||||
var canvas = document.getElementById("canvas"); // 获取到canvas
|
||||
var context = canvas.getContext("2d"); // 获取到canvas画图
|
||||
canvas.width = canvas_width;
|
||||
canvas.height = canvas_height;
|
||||
var sCode = "a,b,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,W,X,Y,Z,1,2,3,4,5,6,7,8,9,0";
|
||||
var aCode = sCode.split(",");
|
||||
var aLength = aCode.length; // 获取到数组的长度
|
||||
|
||||
// 验证码个数
|
||||
for (var i = 0; i < props.quantity; i++) {
|
||||
var j = Math.floor(Math.random() * aLength); // 获取到随机的索引值
|
||||
var deg = (Math.random() * 30 * Math.PI) / 180; // 产生0~30之间的随机弧度
|
||||
var txt = aCode[j]; // 得到随机的一个内容
|
||||
showCode[i] = txt.toLowerCase(); // 依次把取得的内容放到数组里面
|
||||
var x = 10 + i * 20; // 文字在canvas上的x坐标
|
||||
var y = 20 + Math.random() * 8; // 文字在canvas上的y坐标
|
||||
context.font = "bold 23px 微软雅黑";
|
||||
|
||||
context.translate(x, y);
|
||||
context.rotate(deg);
|
||||
|
||||
context.fillStyle = randomColor();
|
||||
context.fillText(txt, 0, 0);
|
||||
|
||||
context.rotate(-deg);
|
||||
context.translate(-x, -y);
|
||||
}
|
||||
|
||||
// 验证码上显示的线条
|
||||
for (var i = 0; i < props.line; i++) {
|
||||
context.strokeStyle = randomColor();
|
||||
context.beginPath();
|
||||
context.moveTo(Math.random() * canvas_width, Math.random() * canvas_height);
|
||||
context.lineTo(Math.random() * canvas_width, Math.random() * canvas_height);
|
||||
context.stroke();
|
||||
}
|
||||
// 验证码上显示的小点
|
||||
for (var i = 0; i < props.spot; i++) {
|
||||
context.strokeStyle = randomColor();
|
||||
context.beginPath();
|
||||
var x = Math.random() * canvas_width;
|
||||
var y = Math.random() * canvas_height;
|
||||
context.moveTo(x, y);
|
||||
context.lineTo(x + 1, y + 1);
|
||||
context.stroke();
|
||||
}
|
||||
// 最后把取得的验证码数组存起来,方式不唯一
|
||||
var num = showCode.join("");
|
||||
trueCode.value = num;
|
||||
}
|
||||
// 得到随机的颜色值
|
||||
function randomColor() {
|
||||
var r = Math.floor(Math.random() * 256);
|
||||
var g = Math.floor(Math.random() * 256);
|
||||
var b = Math.floor(Math.random() * 256);
|
||||
return "rgb(" + r + "," + g + "," + b + ")";
|
||||
}
|
||||
const emits = defineEmits(["getCode"]);
|
||||
// canvas 点击刷新
|
||||
function handleCanvas() {
|
||||
draw(verificationCode);
|
||||
emits("getCode", trueCode.value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
draw(verificationCode);
|
||||
emits("getCode", trueCode.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#canvas {
|
||||
margin-right: 1%;
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-right: 80px;
|
||||
}
|
||||
|
||||
.input-val {
|
||||
width: 50%;
|
||||
background: #ffffff;
|
||||
height: 2.8rem;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
padding: 0 0 0 12px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
13
src/directive/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import isLogin from "./isLogin.js";
|
||||
import throttle from "./throttle.js";
|
||||
import sizeOb from "./sizeOb.js";
|
||||
import permission from './permission'
|
||||
const initDirective = {
|
||||
install(app) {
|
||||
app.directive("isLogin", isLogin);
|
||||
app.directive("throttle", throttle);
|
||||
app.directive("sizeOb", sizeOb);
|
||||
app.directive("permission", permission);
|
||||
},
|
||||
};
|
||||
export default initDirective;
|
||||
48
src/directive/isLogin.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import _hook from "@/hooks/index.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
if (!_hook.useDataType(binding.value, "function")) {
|
||||
ElMessage({
|
||||
message: "v-isLogin 请绑定函数",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
el.handle = () => {
|
||||
if (!_hook.useLocalStorage.get("token")) {
|
||||
ElMessage({
|
||||
message: "请登录后在操作",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
binding.value();
|
||||
};
|
||||
el.addEventListener("click", el.handle);
|
||||
},
|
||||
updated() {
|
||||
if (!_hook.useDataType(binding.value, "function")) {
|
||||
ElMessage({
|
||||
message: "v-isLogin 请绑定函数",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
el.removeEventListener("click", el.handler);
|
||||
el.handle = () => {
|
||||
if (!_hook.useLocalStorage.get("token")) {
|
||||
ElMessage({
|
||||
message: "请登录后在操作.",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
binding.value();
|
||||
};
|
||||
el.addEventListener("click", el.handle);
|
||||
},
|
||||
unmounted(el) {
|
||||
el.removeEventListener("click", el.handler);
|
||||
},
|
||||
};
|
||||
19
src/directive/permission.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import _hook from "@/hooks/index.js";
|
||||
import { useUser } from "@/store/user.js";
|
||||
const permission = async (el, bindings) => {
|
||||
const storeUser = useUser();
|
||||
// 触发初始化用户信息
|
||||
await storeUser.setUserInfo();
|
||||
const roles = storeUser.userInfo.userType;
|
||||
|
||||
// 匹配权限,无权择删除按钮
|
||||
if (!bindings.value.includes(roles)) {
|
||||
if (el.parentNode) {
|
||||
// console.log('el===', el)
|
||||
// console.log('parentNode===', el.parentNode)
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default permission
|
||||
22
src/directive/sizeOb.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const map = new WeakMap();
|
||||
const ob = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const handler = map.get(entry.target);
|
||||
if (handler) {
|
||||
const box = entry.borderBoxSize[0];
|
||||
handler({
|
||||
width: box.inlineSize,
|
||||
height: box.blockSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
ob.observe(el);
|
||||
map.set(el, binding.value);
|
||||
},
|
||||
unmounted(el) {
|
||||
ob.unobserve(el);
|
||||
},
|
||||
};
|
||||
65
src/directive/throttle.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import _hook from "@/hooks/index.js";
|
||||
import { ElMessage } from "element-plus";
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
if (!_hook.useDataType(binding?.value, "function")) {
|
||||
ElMessage({
|
||||
message: "v-throttle 请绑定函数",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!_hook.usePureNum(binding?.arg)) {
|
||||
ElMessage({
|
||||
message: "v-throttle 指令参数仅支持传入纯数字",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
el.handler = () => {
|
||||
el.classList.add("is-disabled");
|
||||
el.disabled = true;
|
||||
if (el.disabled) {
|
||||
binding.value();
|
||||
setTimeout(() => {
|
||||
el.classList.remove("is-disabled");
|
||||
el.disabled = false;
|
||||
}, binding.arg);
|
||||
}
|
||||
};
|
||||
el.addEventListener("click", el.handler);
|
||||
},
|
||||
|
||||
updated(el, binding) {
|
||||
if (!_hook.useDataType(binding?.value, "function")) {
|
||||
ElMessage({
|
||||
message: "v-throttle 请绑定函数",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!_hook.usePureNum(binding?.arg)) {
|
||||
ElMessage({
|
||||
message: "v-throttle 指令参数仅支持传入纯数字",
|
||||
type: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
el.removeEventListener("click", el.handler);
|
||||
el.handler = () => {
|
||||
el.classList.add("is-disabled");
|
||||
el.disabled = true;
|
||||
if (el.disabled) {
|
||||
binding.value();
|
||||
setTimeout(() => {
|
||||
el.classList.remove("is-disabled");
|
||||
el.disabled = false;
|
||||
}, binding.arg);
|
||||
}
|
||||
};
|
||||
el.addEventListener("click", el.handler);
|
||||
},
|
||||
unmounted(el) {
|
||||
el.removeEventListener("click", el.handler);
|
||||
},
|
||||
};
|
||||
36
src/hooks/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// 样式相关
|
||||
import useCssVar from "./useCssVar.js";
|
||||
import { useHexToRgb, useRgbToHex, useDarkColor, useLightColor } from "./useConvertColor.js";
|
||||
// 组件相关
|
||||
import useComponentName from "./useComponentName.js";
|
||||
// 工具相关
|
||||
import useDeepClone from "./useDeepClone.js";
|
||||
import useDebounce from "./useDebounce.js";
|
||||
import useThrottle from "./useThrottle.js";
|
||||
import useKeyStroke from "./useKeyStroke.js";
|
||||
import useDataType from "./useDataType.js";
|
||||
import { useSessionStorage, useLocalStorage } from "./useStorage.js";
|
||||
import { useRepairZero, useDateFormat } from "./useDateFormat.js";
|
||||
import { usePureNum, usePhoneNum, useEmailNum, useEmptyObj } from "./useVerification.js";
|
||||
|
||||
export default {
|
||||
useCssVar,
|
||||
useHexToRgb,
|
||||
useRgbToHex,
|
||||
useDarkColor,
|
||||
useLightColor,
|
||||
useComponentName,
|
||||
useDeepClone,
|
||||
useDebounce,
|
||||
useThrottle,
|
||||
useKeyStroke,
|
||||
useDataType,
|
||||
useSessionStorage,
|
||||
useLocalStorage,
|
||||
useRepairZero,
|
||||
useDateFormat,
|
||||
usePureNum,
|
||||
usePhoneNum,
|
||||
useEmailNum,
|
||||
useEmptyObj,
|
||||
};
|
||||
14
src/hooks/useComponentName.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @description: 获取组件的名称
|
||||
* @param {Object} route: 路由实例
|
||||
* @return {String} 组件名称
|
||||
*/
|
||||
function useComponentName(route) {
|
||||
let currentMatched = route.matched;
|
||||
let currentComponent = currentMatched[currentMatched.length - 1]?.components?.default;
|
||||
let componentName = currentComponent.name || currentComponent.__name;
|
||||
// 在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,即使是在配合 <KeepAlive> 使用时也无需再手动声明。
|
||||
// 如果组件内的 <script setup> 没有内容,并且没有自定义组件 name,就会返回 undefined
|
||||
return componentName;
|
||||
}
|
||||
export default useComponentName;
|
||||
81
src/hooks/useConvertColor.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
/**
|
||||
* 颜色转换函数
|
||||
* @method useHexToRgb: hex 颜色转 rgb 颜色
|
||||
* @method useRgbToHex: rgb 颜色转 Hex 颜色
|
||||
* @method useDarkColor: 加深颜色值
|
||||
* @method useLightColor: 变浅颜色值
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description: hex 颜色转 rgb 颜色
|
||||
* @param {String} str: str 颜色值字符串
|
||||
* @return {String} hex 值
|
||||
*/
|
||||
function useHexToRgb(str) {
|
||||
let hexs = "";
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(str)) {
|
||||
ElMessage.warning("输入错误的hex");
|
||||
return "";
|
||||
}
|
||||
str = str.replace("#", "");
|
||||
hexs = str.match(/../g);
|
||||
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
|
||||
return hexs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: rgb 颜色转 Hex 颜色
|
||||
* @param {String} r: 代表红色
|
||||
* @param {String} g: 代表绿色
|
||||
* @param {String} b: 代表蓝色
|
||||
* @return {String} Hex 颜色值
|
||||
*/
|
||||
function useRgbToHex(r, g, b) {
|
||||
let reg = /^\d{1,3}$/;
|
||||
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) {
|
||||
ElMessage.warning("输入错误的rgb颜色值");
|
||||
return "";
|
||||
}
|
||||
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
|
||||
for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
|
||||
return `#${hexs.join("")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 加深颜色值
|
||||
* @param {String} color: 颜色值字符串
|
||||
* @param {String} level: level 加深的程度,限0-1之间
|
||||
* @return {String} 加深后的颜色
|
||||
*/
|
||||
function useDarkColor(color, level) {
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(color)) {
|
||||
ElMessage.warning("输入错误的hex颜色值");
|
||||
return "";
|
||||
}
|
||||
let rgb = useHexToRgb(color);
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level));
|
||||
return useRgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 变浅颜色值
|
||||
* @param {String} color: 颜色值字符串
|
||||
* @param {String} level: level 变浅的程度,限 0-1 之间
|
||||
* @return {String} 变浅后的颜色
|
||||
*/
|
||||
function useLightColor(color, level) {
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(color)) {
|
||||
ElMessage.warning("输入错误的hex颜色值");
|
||||
return "";
|
||||
}
|
||||
let rgb = useHexToRgb(color);
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
|
||||
return useRgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
export { useHexToRgb, useRgbToHex, useDarkColor, useLightColor };
|
||||
8
src/hooks/useCssVar.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @description: 设置 css 变量
|
||||
* @param {String} key: 要设置变量
|
||||
* @param {String} value: 设置的值
|
||||
*/
|
||||
export default function useCssVar(key, value) {
|
||||
document.documentElement.style.setProperty(key, value);
|
||||
}
|
||||
60
src/hooks/useDataType.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @description: 检测数据类型 ( 当只传了 data 时判断 data 的类型, 返回 data 的类型. 当传了 data 和 type 时, 判断 data 和 type 是否是同一类型, 返回布尔值 )
|
||||
* @param {Any} data: 数据
|
||||
* @param {Type} type: 类型: 可选择, 如果传了 type 会返回一个布尔值表示 data 是否与 type 类型相等
|
||||
* @return {String} 数据类型 / 布尔值
|
||||
*/
|
||||
function useDataType(data, type = "") {
|
||||
if (type === "") {
|
||||
switch (Object.prototype.toString.call(data)) {
|
||||
case "[object String]":
|
||||
return "string";
|
||||
case "[object Number]":
|
||||
return "number";
|
||||
case "[object Boolean]":
|
||||
return "boolean";
|
||||
case "[object Null]":
|
||||
return "null";
|
||||
case "[object Undefined]":
|
||||
return "undefined";
|
||||
case "[object Array]":
|
||||
return "array";
|
||||
case "[object Function]":
|
||||
return "function";
|
||||
case "[object AsyncFunction]":
|
||||
return "asyncFunction";
|
||||
case "[object Object]":
|
||||
return "object";
|
||||
case "[object Symbol]":
|
||||
return "symbol";
|
||||
default:
|
||||
return "未知类型";
|
||||
}
|
||||
} else {
|
||||
switch (Object.prototype.toString.call(data)) {
|
||||
case "[object String]":
|
||||
return type === "string";
|
||||
case "[object Number]":
|
||||
return type === "number";
|
||||
case "[object Boolean]":
|
||||
return type === "boolean";
|
||||
case "[object Null]":
|
||||
return type === "null";
|
||||
case "[object Undefined]":
|
||||
return type === "undefined";
|
||||
case "[object Array]":
|
||||
return type === "array";
|
||||
case "[object Function]":
|
||||
return type === "function";
|
||||
case "[object AsyncFunction]":
|
||||
return type === "asyncFunction";
|
||||
case "[object Object]":
|
||||
return type === "object";
|
||||
case "[object Symbol]":
|
||||
return type === "symbol";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
export default useDataType;
|
||||
57
src/hooks/useDateFormat.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ElMessage } from "element-plus";
|
||||
import useDataType from "./useDataType.js";
|
||||
import { usePureNum } from "./useVerification";
|
||||
|
||||
/**
|
||||
* @description: 获得时间格式的枚举
|
||||
* @return {*}
|
||||
*/
|
||||
function getFormatEnum(newDate) {
|
||||
return {
|
||||
key: ["YYYY", "YY", "mm", "dd", "HH", "MM", "ss", "m", "d", "H", "M", "s"],
|
||||
value: {
|
||||
YYYY: newDate.getFullYear(),
|
||||
YY: newDate.getYear(),
|
||||
mm: useRepairZero(newDate.getMonth() - 1),
|
||||
m: newDate.getMonth() - 1,
|
||||
dd: useRepairZero(newDate.getDate()),
|
||||
d: newDate.getDate(),
|
||||
HH: useRepairZero(newDate.getHours()),
|
||||
H: newDate.getHours(),
|
||||
MM: useRepairZero(newDate.getMinutes()),
|
||||
M: newDate.getMinutes(),
|
||||
ss: useRepairZero(newDate.getSeconds()),
|
||||
s: newDate.getSeconds(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 补零
|
||||
* @param {Number} number: 当前数
|
||||
* @return {Number} 补零后的数
|
||||
*/
|
||||
export function useRepairZero(number) {
|
||||
return number >= 10 ? number : `0${number}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 该函数必须传入第一个参数,第二个参数是可选的,函数返回一个格式化好的时间。
|
||||
* @param {String} date: 任何合法的时间格式、秒或毫秒的时间戳
|
||||
* @param {String} format: 时间格式,可选。默认为YYYY-mm-dd,年为"YYYY",月为"mm",日为"dd",时为"hh",分为"MM",秒为"ss",格式可以自由搭配,如: YYYY:mm:dd,YYYY-mm-dd,YYYY年mm月dd日,YYYY年mm月dd日 hh时MM分ss秒,YYYY/mm/dd/,MM:ss等组合
|
||||
* @return {String} 格式好的日期
|
||||
*/
|
||||
export function useDateFormat(date, format) {
|
||||
let newDate = "",
|
||||
time = format || "YYYY-mm-dd";
|
||||
if (["string", "number"].includes(useDataType(date)) && usePureNum(date) && date.toString().length !== 13) {
|
||||
newDate = new Date(date * 1000);
|
||||
} else {
|
||||
newDate = new Date(date);
|
||||
}
|
||||
const formatEnum = getFormatEnum(newDate);
|
||||
formatEnum.key.forEach((i) => {
|
||||
time = time.replace(i, formatEnum.value[i]);
|
||||
});
|
||||
return time;
|
||||
}
|
||||
16
src/hooks/useDebounce.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @description: 函数防抖
|
||||
* @param {Function} fn: 函数
|
||||
* @param {Number} time: 时间
|
||||
* @return {Function} 处理后的函数
|
||||
*/
|
||||
function useDebounce(fn, time = 1000) {
|
||||
let timeLock = null;
|
||||
return function (...args) {
|
||||
clearTimeout(timeLock);
|
||||
timeLock = setTimeout(() => {
|
||||
fn(...args);
|
||||
}, +time);
|
||||
};
|
||||
}
|
||||
export default useDebounce;
|
||||
31
src/hooks/useDeepClone.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @description: 判断 arr 是否为一个数组,返回一个 bool 值
|
||||
* @param {null} arr: 要判断的值
|
||||
* @return {Boolean} 是否为数组的 Boolean 值
|
||||
*/
|
||||
function isArray(arr) {
|
||||
return Object.prototype.toString.call(arr) === "[object Array]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 深度克隆
|
||||
* @param {Object} obj: 要克隆的对象
|
||||
* @return {Object} 克隆好的对象
|
||||
*/
|
||||
function deepClone(obj) {
|
||||
// 对常见的 “非” 值,直接返回原来值
|
||||
if ([null, undefined, NaN, false].includes(obj)) return obj;
|
||||
if (typeof obj !== "object" && typeof obj !== "function") {
|
||||
// 原始类型直接返回
|
||||
return obj;
|
||||
}
|
||||
var o = isArray(obj) ? [] : {};
|
||||
for (let i in obj) {
|
||||
if (obj.hasOwnProperty(i)) {
|
||||
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
export default deepClone;
|
||||
46
src/hooks/useKeyStroke.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { onMounted, onBeforeUnmount } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import useDataType from "./useDataType.js";
|
||||
|
||||
const keys = ["Esc", "Tab", "BackSpace", "Enter", "Shift", "Ctrl", "Alt", "Up", "Down", "Left", "Right"];
|
||||
const keysCode = [27, 9, 8, 13, 16, 17, 18, 38, 40, 37, 39];
|
||||
let callback = "";
|
||||
let keyVal = "";
|
||||
function keydownFun(e) {
|
||||
if (e.keyCode == keysCode[keys.indexOf(keyVal)]) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @description: 监听键盘某些键被按下
|
||||
* @param {String} key: 要监听的键
|
||||
* @param {Function} fun: 回调函数
|
||||
*/
|
||||
function useKeyStroke(key, fun) {
|
||||
onMounted(() => {
|
||||
if (!keys.includes(key)) {
|
||||
ElMessage({
|
||||
message: `请输入如下支持的键 Esc、Tab、BackSpace、Enter、Shift、Ctrl、Alt、Up、Down、Left、Right`,
|
||||
type: "warning",
|
||||
});
|
||||
} else if (!fun) {
|
||||
ElMessage({
|
||||
message: "回调函数不可为空",
|
||||
type: "warning",
|
||||
});
|
||||
} else if (!["asyncFunction", "function"].includes(useDataType(fun))) {
|
||||
ElMessage({
|
||||
message: "请传入正确的回调函数",
|
||||
type: "warning",
|
||||
});
|
||||
} else {
|
||||
keyVal = key;
|
||||
callback = fun;
|
||||
window.addEventListener("keydown", keydownFun);
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("keydown", keydownFun);
|
||||
});
|
||||
}
|
||||
export default useKeyStroke;
|
||||
50
src/hooks/useStorage.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import Cookies from "js-cookie";
|
||||
/**
|
||||
* window.sessionStorage 浏览器临时缓存
|
||||
* @method set 设置临时缓存
|
||||
* @method get 获取临时缓存
|
||||
* @method remove 移除临时缓存
|
||||
* @method clear 移除全部临时缓存
|
||||
*/
|
||||
export const useSessionStorage = {
|
||||
set(key, value) {
|
||||
if (key === "token") return Cookies.set(key, value);
|
||||
window.sessionStorage.setItem(key, JSON.stringify(value));
|
||||
},
|
||||
get(key) {
|
||||
if (key === "token") return Cookies.get(key);
|
||||
let json = window.sessionStorage.getItem(key);
|
||||
return JSON.parse(json);
|
||||
},
|
||||
remove(key) {
|
||||
if (key === "token") return Cookies.remove(key);
|
||||
window.sessionStorage.removeItem(key);
|
||||
},
|
||||
clear() {
|
||||
Cookies.remove("token");
|
||||
window.sessionStorage.clear();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* window.localStorage 浏览器永久缓存
|
||||
* @method set 设置永久缓存
|
||||
* @method get 获取永久缓存
|
||||
* @method remove 移除永久缓存
|
||||
* @method clear 移除全部永久缓存
|
||||
*/
|
||||
export const useLocalStorage = {
|
||||
set(key, value) {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
},
|
||||
get(key) {
|
||||
let json = window.localStorage.getItem(key);
|
||||
return JSON.parse(json);
|
||||
},
|
||||
remove(key) {
|
||||
window.localStorage.removeItem(key);
|
||||
},
|
||||
clear() {
|
||||
window.localStorage.clear();
|
||||
},
|
||||
};
|
||||
19
src/hooks/useThrottle.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @description: 函数节流
|
||||
* @param {Function} fn: 函数
|
||||
* @param {Number} time: 节流时间
|
||||
* @return {Function} 节流的函数
|
||||
*/
|
||||
function useThrottle(fn, time = 1000) {
|
||||
let flag = true;
|
||||
return function (...args) {
|
||||
if (flag) {
|
||||
flag = false;
|
||||
fn(...args);
|
||||
setTimeout(() => {
|
||||
flag = true;
|
||||
}, time);
|
||||
}
|
||||
};
|
||||
}
|
||||
export default useThrottle;
|
||||
69
src/hooks/useVerification.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ElMessage } from "element-plus";
|
||||
import useDataType from "./useDataType.js";
|
||||
|
||||
/**
|
||||
* @description:是否是纯数字
|
||||
* @param {String|Number} str: 要验证的字符串或数字
|
||||
* @return {Boolean} 是或否
|
||||
*/
|
||||
export function usePureNum(str) {
|
||||
if (["string", "number"].includes(useDataType(str))) {
|
||||
var reg = /^\d+$/;
|
||||
return reg.test(str);
|
||||
} else {
|
||||
ElMessage({
|
||||
message: "仅支持字符串与数字",
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description:是否是手机号
|
||||
* @param {String|Number} str: 要验证的字符串或数字
|
||||
* @return {Boolean} 是或否
|
||||
*/
|
||||
export function usePhoneNum(str) {
|
||||
if (["string", "number"].includes(useDataType(str))) {
|
||||
var reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
|
||||
return reg.test(str);
|
||||
} else {
|
||||
ElMessage({
|
||||
message: "仅支持字符串与数字",
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否是邮箱
|
||||
* @param {String} str: 要验证的字符串
|
||||
* @return {Boolean} 是或否
|
||||
*/
|
||||
export function useEmailNum(str) {
|
||||
if (["string"].includes(useDataType(str))) {
|
||||
var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
|
||||
return reg.test(str);
|
||||
} else {
|
||||
ElMessage({
|
||||
message: "仅支持字符串",
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否是空对象
|
||||
* @param {Object} obj: 要验证的对象
|
||||
* @return {Boolean} 是或否
|
||||
*/
|
||||
export function useEmptyObj(obj) {
|
||||
if (["object"].includes(useDataType(obj))) {
|
||||
return Reflect.ownKeys(obj).length === 0;
|
||||
} else {
|
||||
ElMessage({
|
||||
message: "仅支持对象",
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
}
|
||||
34
src/main.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createApp } from "vue";
|
||||
|
||||
import initComponents from "@/utils/components.js";
|
||||
|
||||
import initDirective from "@/directive/index.js";
|
||||
|
||||
import "./style/normalize.css";
|
||||
import "./style/public.css";
|
||||
|
||||
import router from "./router/index.js";
|
||||
|
||||
import pinia from "./store/store";
|
||||
|
||||
import ElementPlus from "element-plus";
|
||||
import "element-plus/dist/index.css";
|
||||
import locale from 'element-plus/lib/locale/lang/zh-cn'
|
||||
|
||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
||||
|
||||
import countTo from 'vue3-count-to';
|
||||
|
||||
import App from "./App.vue";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(ElementPlus, { locale });
|
||||
app.use(initComponents);
|
||||
app.use(initDirective);
|
||||
app.use(countTo);
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
app.mount("#app");
|
||||
83
src/router/frontEnd.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import router from "./index.js";
|
||||
import { staticRoutes, asyncRoutes } from "@/router/routes.js";
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
import { useUser } from "@/store/user.js";
|
||||
|
||||
/**
|
||||
* @description: 初始化路由
|
||||
* @param {Array} roles: 用户权限
|
||||
*/
|
||||
export async function initFrontEndRoutes() {
|
||||
const storeUser = useUser();
|
||||
|
||||
// 触发初始化用户信息
|
||||
await storeUser.setUserInfo();
|
||||
const roles = [storeUser.userInfo.userType]; // 权限
|
||||
// if (roles?.length <= 0) {}; 无权限时的处理
|
||||
|
||||
let addRoutes;
|
||||
if (roles.includes("MG")) {
|
||||
// 管理员权限包含所有的路由权限
|
||||
addRoutes = asyncRoutes || [];
|
||||
} else {
|
||||
addRoutes = filterAsyncRoutes(asyncRoutes, roles);
|
||||
}
|
||||
setAddRoute(addRoutes);
|
||||
const storeRoutes = useRoutes();
|
||||
storeRoutes.setRoutesList([...staticRoutes[0].children, ...addRoutes]);
|
||||
storeRoutes.setAllRoutes(router.getRoutes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 通过递归过滤异步路由表
|
||||
* @param {Array} routes: 异步路由
|
||||
* @param {Array} roles: 用户的所有权限
|
||||
* @return {Array} 符合权限的异步路由
|
||||
*/
|
||||
function filterAsyncRoutes(routes, roles) {
|
||||
const res = [];
|
||||
routes.forEach((route) => {
|
||||
if (hasPermission(route, roles)) {
|
||||
if (route.children) {
|
||||
route.children = filterAsyncRoutes(route.children, roles);
|
||||
}
|
||||
res.push(route);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* @description: 判断路由 `meta.roles` 中是否包含当前登录用户权限字段
|
||||
* @param {Array} route: 路由
|
||||
* @param {Array} roles: 用户的所有权限
|
||||
* @return {Boolean} 是否有权限
|
||||
*/
|
||||
function hasPermission(route, roles) {
|
||||
if (route.meta && route.meta.roles) {
|
||||
return roles.some((role) => route.meta.roles.includes(role));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 添加动态路由
|
||||
* @param {Array} addRoutes: 要添加的路由列表
|
||||
*/
|
||||
function setAddRoute(addRoutes) {
|
||||
addRoutes.forEach((route) => {
|
||||
// 添加嵌套路由
|
||||
router.addRoute("/", route);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 重置路由
|
||||
* @param {Array} roles: 用户权限
|
||||
*/
|
||||
export function resetFrontEndRoute(roles) {
|
||||
// 获取用户可以访问的权限路由后
|
||||
const routes = filterAsyncRoutes(asyncRoutes, roles);
|
||||
// 通过重新添加路由来覆盖
|
||||
setAddRoute(routes);
|
||||
}
|
||||
48
src/router/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import { notFoundAndNoPower, fullScreenRouting, staticRoutes } from "./routes.js";
|
||||
import { initFrontEndRoutes } from "./frontEnd.js";
|
||||
import _hook from "@/hooks/index.js";
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [...notFoundAndNoPower, ...fullScreenRouting, ...staticRoutes],
|
||||
});
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
const token = _hook.useLocalStorage.get("token");
|
||||
if (to.path === "/login" && !token) {
|
||||
next();
|
||||
NProgress.done();
|
||||
} else {
|
||||
if (!token) {
|
||||
next("/login");
|
||||
_hook.useLocalStorage.clear();
|
||||
} else if (token && to.path === "/login") {
|
||||
next("/home");
|
||||
NProgress.done();
|
||||
} else {
|
||||
const storeRoutes = useRoutes();
|
||||
if (storeRoutes.routesList.length === 0) {
|
||||
await initFrontEndRoutes(); // 初始化前端路由
|
||||
storeRoutes.setNavbar("/home"); // 每次初始化时都添加首页
|
||||
next({ path: to.path, query: to.query });
|
||||
NProgress.done();
|
||||
} else {
|
||||
storeRoutes.setNavbar(to.path);
|
||||
next();
|
||||
NProgress.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach((to, from, next) => {
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
export default router;
|
||||
524
src/router/routes.js
Normal file
@@ -0,0 +1,524 @@
|
||||
import bridge from "@/views/layout/bridge.vue"; // 多级路由
|
||||
import layout from "@/views/layout/layout.vue" // 只需要展示一页
|
||||
|
||||
/**
|
||||
* 路由参数说明
|
||||
*
|
||||
* path: !!! 路由 path 路径需完整,因为菜单中所有路由跳转均使用 path 跳转。
|
||||
* 如:
|
||||
* 父路由 path:'/home'
|
||||
* 子路由 path:'/home/page'
|
||||
* 孙路由 path:'/home/page/index'
|
||||
* 首页 path 必须为 '/home'
|
||||
* path 路径作为菜单唯一标识不可重复, 且在没有配置 meta.title 时使用 path 作为标题.
|
||||
* meta: {
|
||||
* title: 菜单的标题 ( 缺少 title 时会使用路由的 path 作为标题)
|
||||
* isHide: 是否隐藏此路由
|
||||
* isKeepAlive: 是否缓存路由
|
||||
* roles: 当前路由权限标识,取角色管理。控制路由显示、隐藏。
|
||||
* icon: 图标 elementUI
|
||||
* activeMenu: 需要保持高亮的菜单
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* 定义404、401界面
|
||||
*/
|
||||
export const notFoundAndNoPower = [
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
name: "notFound",
|
||||
component: () => import("@/views/error/404.vue"),
|
||||
meta: {
|
||||
title: "404",
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/401",
|
||||
name: "noPower",
|
||||
component: () => import("@/views/error/401.vue"),
|
||||
meta: {
|
||||
title: "401",
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 全屏路由页面
|
||||
* 此处的页面会占满浏览器的整个屏幕
|
||||
*/
|
||||
export const fullScreenRouting = [
|
||||
{
|
||||
path: "/login",
|
||||
name: "login",
|
||||
component: () => import("@/views/login/login.vue"),
|
||||
meta: {
|
||||
title: "登录",
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 静态路由
|
||||
* 没有权限要求的基本页面
|
||||
* 所有角色都可以访问
|
||||
*/
|
||||
export const staticRoutes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "/",
|
||||
component: layout,
|
||||
redirect: "/home",
|
||||
meta: {
|
||||
isHide: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/home",
|
||||
component: () => import("@/views/home.vue"),
|
||||
meta: {
|
||||
title: "首页",
|
||||
icon: "House",
|
||||
// isKeepAlive: true,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// path: "/makes",
|
||||
// component: bridge,
|
||||
// redirect: "/makes/svgIcon",
|
||||
// meta: {
|
||||
// title: "组件封装",
|
||||
// icon: "Edit",
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: "/makes/svgIcon",
|
||||
// component: () => import("@/views/makes/svgIcon.vue"),
|
||||
// meta: {
|
||||
// title: "SvgIcon",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: "/tools",
|
||||
// component: bridge,
|
||||
// redirect: "/tools/editor",
|
||||
// meta: {
|
||||
// title: "工具",
|
||||
// icon: "Flag",
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: "/tools/editor",
|
||||
// component: () => import("@/views/tools/editor.vue"),
|
||||
// meta: {
|
||||
// title: "编辑器",
|
||||
// icon: "Edit",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: "/other",
|
||||
// component: bridge,
|
||||
// redirect: "/other/watermark",
|
||||
// meta: {
|
||||
// title: "其他",
|
||||
// icon: "Box",
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: "/other/watermark",
|
||||
// component: () => import("@/views/other/watermark.vue"),
|
||||
// meta: {
|
||||
// title: "水印",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// path: "/other/resize",
|
||||
// component: () => import("@/views/other/resize.vue"),
|
||||
// meta: {
|
||||
// title: "监控元素尺寸变化",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 异步路由
|
||||
* 需要根据用户角色动态加载的路由
|
||||
*/
|
||||
export const asyncRoutes = [
|
||||
// {
|
||||
// path: "/demo",
|
||||
// component: bridge,
|
||||
// redirect: "/demo/demo1/demo11",
|
||||
// meta: {
|
||||
// title: "权限路由",
|
||||
// icon: "Edit",
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: "demo1",
|
||||
// component: bridge,
|
||||
// meta: {
|
||||
// title: "用户1路由-1",
|
||||
// icon: "Edit",
|
||||
// roles: ["yonghu1"],
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: "/demo/demo1/demo11",
|
||||
// component: () => import("@/views/demo/demo11.vue"),
|
||||
// name: "demo11",
|
||||
// meta: {
|
||||
// title: "用户1路由-11",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// path: "/demo/demo1/demo12",
|
||||
// component: () => import("@/views/demo/demo12.vue"),
|
||||
// name: "demo12",
|
||||
// meta: {
|
||||
// title: "用户1路由-12",
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: "/demo/demo1/demo121",
|
||||
// component: () => import("@/views/demo/demo121.vue"),
|
||||
// name: "demo121",
|
||||
// meta: {
|
||||
// title: "用户1路由-121",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// path: "/demo/demo1/demo122",
|
||||
// component: () => import("@/views/demo/demo122.vue"),
|
||||
// name: "demo122",
|
||||
// meta: {
|
||||
// title: "用户1路由-122",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: "/demo/demo1/demo13",
|
||||
// component: () => import("@/views/demo/demo13.vue"),
|
||||
// name: "demo13",
|
||||
// meta: {
|
||||
// title: "用户1路由-13",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: "/demo/demo2",
|
||||
// component: bridge,
|
||||
// meta: {
|
||||
// title: "用户2路由-2",
|
||||
// icon: "Edit",
|
||||
// roles: ["yonghu2"],
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: "/demo/demo2/demo21",
|
||||
// component: () => import("@/views/demo/demo21.vue"),
|
||||
// meta: {
|
||||
// title: "用户2路由-21",
|
||||
// icon: "Edit",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// path: "/demo/demo2/demo22",
|
||||
// component: () => import("@/views/demo/demo22.vue"),
|
||||
// meta: {
|
||||
// title: "用户2路由-22",
|
||||
// icon: "Edit",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// path: "/demo/demo2/demo23",
|
||||
// component: () => import("@/views/demo/demo23.vue"),
|
||||
// meta: {
|
||||
// title: "用户2路由-23",
|
||||
// icon: "Edit",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: '/system',
|
||||
// component: bridge,
|
||||
// redirect: '/system/menu_manage',
|
||||
// meta: {
|
||||
// title: '系统管理',
|
||||
// icon: 'Setting',
|
||||
// roles: ['FO']
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/system/menu_manage',
|
||||
// component: () => import('@/views/system/menuMange.vue'),
|
||||
// meta: {
|
||||
// title: '菜单管理',
|
||||
// icon: 'Stopwatch'
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
path: '/organization',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '大机构管理',
|
||||
roles: ['MG'],
|
||||
isHide: true
|
||||
},
|
||||
redirect: '/organization/big_organization',
|
||||
children: [
|
||||
{
|
||||
path: '/organization/big_organization',
|
||||
component: () => import('@/views/organization/big_organization.vue'),
|
||||
meta: {
|
||||
title: '大机构',
|
||||
icon: 'Tickets'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/mini_organization_manage',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '小机构管理',
|
||||
roles: ['FO'],
|
||||
isHide: true
|
||||
},
|
||||
redirect: '/mini_organization_manage/mini_organization',
|
||||
children: [
|
||||
{
|
||||
path: '/mini_organization/mini_organization',
|
||||
component: () => import('@/views/organization/mini_organization.vue'),
|
||||
meta: {
|
||||
title: '小机构',
|
||||
icon: 'SetUp'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/agent_manage',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '大代理管理',
|
||||
roles: ['FO', 'SO'],
|
||||
isHide: true
|
||||
},
|
||||
redirect: '/agent_manage/agent_list',
|
||||
children: [
|
||||
{
|
||||
path: '/agent_manage/agent_list',
|
||||
component: () => import('@/views/organization/agent_list.vue'),
|
||||
meta: {
|
||||
title: '大代理',
|
||||
icon: 'Discount'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/promotion_manage',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '代理',
|
||||
icon: 'Connection',
|
||||
},
|
||||
redirect: '/promotion_manage/one_promotion_list',
|
||||
children: [
|
||||
{
|
||||
path: '/promotion_manage/one_promotion_list',
|
||||
component: () => import('@/views/organization/one_promotion_list.vue'),
|
||||
meta: {
|
||||
title: '一级代理',
|
||||
icon: 'User',
|
||||
roles: ['FO', 'SO', 'AG']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/promotion_manage/two_promotion_list',
|
||||
component: () => import('@/views/organization/two_promotion_list.vue'),
|
||||
meta: {
|
||||
title: '二级代理',
|
||||
icon: 'User',
|
||||
roles: ['FO', 'SO', 'AG', 'FB']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/shop_manage',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '商家管理',
|
||||
roles: ['FO', 'SO', 'AG', 'FB', 'SB'],
|
||||
icon: 'Handbag'
|
||||
},
|
||||
redirect: '/shop_manage/shop_list',
|
||||
children: [
|
||||
{
|
||||
path: '/shop_manage/shop_list',
|
||||
name: 'shop_list',
|
||||
component: () => import('@/views/organization/shop_list.vue'),
|
||||
meta: {
|
||||
title: '商家列表',
|
||||
icon: 'Tickets'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/shop_manage/shop_detail',
|
||||
name: 'shop_detail',
|
||||
component: () => import('@/views/organization/shop_detail.vue'),
|
||||
meta: {
|
||||
title: '详情',
|
||||
isHide: true,
|
||||
activeMenu: '/shop_manage/shop_list'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/shop_manage/maker_apply',
|
||||
component: () => import('@/views/organization/maker_apply.vue'),
|
||||
meta: {
|
||||
title: '创客申请',
|
||||
icon: 'User'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/withdraw_manage',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '提现管理',
|
||||
roles: ['FO', 'SO'],
|
||||
isHide: true
|
||||
},
|
||||
redirect: '/withdraw_manage/withdraw_list',
|
||||
children: [
|
||||
{
|
||||
path: '/withdraw_manage/withdraw_list',
|
||||
component: () => import('@/views/withdraw/withdraw_list.vue'),
|
||||
meta: {
|
||||
title: '提现申请',
|
||||
icon: 'CreditCard'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/device_manage',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '设备管理',
|
||||
isHide: true
|
||||
},
|
||||
redirect: '/device_manage/device_list',
|
||||
children: [
|
||||
{
|
||||
path: '/device_manage/device_list',
|
||||
component: () => import('@/views/device/device_list.vue'),
|
||||
meta: {
|
||||
title: '设备列表',
|
||||
icon: 'TakeawayBox'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/total_earnings',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '累计收益管理',
|
||||
isHide: true
|
||||
},
|
||||
redirect: '/total_earnings/total_earnings_list',
|
||||
children: [
|
||||
{
|
||||
path: '/total_earnings/total_earnings_list',
|
||||
component: () => import('@/views/total_earnings/total_earnings_list.vue'),
|
||||
meta: {
|
||||
title: '累计收益',
|
||||
icon: 'Coin'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/promotion',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '推广图片管理',
|
||||
isHide: true,
|
||||
roles: ['MG']
|
||||
},
|
||||
redirect: '/promotion/promotion_list',
|
||||
children: [
|
||||
{
|
||||
path: '/promotion/promotion_list',
|
||||
component: () => import('@/views/promotion/promotion_list.vue'),
|
||||
meta: {
|
||||
title: '推广图片',
|
||||
icon: 'PictureRounded'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/app_manage',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: 'APP管理',
|
||||
icon: 'Iphone',
|
||||
roles: ['MG']
|
||||
},
|
||||
redirect: '/app_manage/menu_list',
|
||||
children: [
|
||||
{
|
||||
path: '/app_manage/menu_list',
|
||||
component: () => import('@/views/app_manage/menu_list.vue'),
|
||||
meta: {
|
||||
title: '菜单管理',
|
||||
icon: 'Tickets'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/setting',
|
||||
component: layout,
|
||||
meta: {
|
||||
title: '系统设置',
|
||||
icon: 'Setting',
|
||||
roles: ['MG']
|
||||
},
|
||||
redirect: '/setting/appid_manage',
|
||||
children: [
|
||||
{
|
||||
path: '/setting/appid_manage',
|
||||
component: () => import('@/views/setting/appid_manage.vue'),
|
||||
meta: {
|
||||
title: 'Appid管理',
|
||||
icon: 'Tickets'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
50
src/store/configure.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ENUMS } from "@/utils/enums.js";
|
||||
|
||||
export const useConfigure = defineStore("useConfigure", {
|
||||
state: () => {
|
||||
return {
|
||||
projectName: '银收客机构管理端',
|
||||
defaultActive: "/home", // 默认选中的菜单
|
||||
configure: {
|
||||
// ---------- 主题 ----------
|
||||
themeColor: "#3186FD", // 主题色
|
||||
// ---------- 菜单 ----------
|
||||
collapse: false, // 是否收起侧边栏
|
||||
menuMode: ENUMS.layoutModeEnum.key[0], // 菜单的布局模式
|
||||
menuBGColor: "#ffffff", // 菜单的背景色
|
||||
textColor: "#333333", // 菜单的文字颜色
|
||||
activeTextColor: "#ffffff", // 菜单的激活文字颜色
|
||||
showMenuLogo: true, // 菜单的 logo
|
||||
// ---------- 分栏 ----------
|
||||
columnBgColor: "#3186FD",
|
||||
// ---------- 导航栏 ----------
|
||||
navbarMode: ENUMS.navbarModeEnum.key[1],
|
||||
navbarIcon: false,
|
||||
// 过度
|
||||
componentTransition: true,
|
||||
componentTransitionMode: ENUMS.componentTransitionEnum.key[1],
|
||||
},
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
/**
|
||||
* @description: 初始化配置
|
||||
* @param {Object} configure: 配置
|
||||
*/
|
||||
initConfigure(configure) {
|
||||
const keys = Object.keys(configure);
|
||||
keys.forEach((key) => {
|
||||
this.configure[key] = configure[key];
|
||||
});
|
||||
},
|
||||
// 设置默认激活的路由
|
||||
setDefaultActive(value) {
|
||||
this.defaultActive = value;
|
||||
},
|
||||
change(key, value) {
|
||||
this.configure[key] = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
128
src/store/routes.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { useConfigure } from "./configure.js";
|
||||
|
||||
export const useRoutes = defineStore("useRoutes", {
|
||||
state: () => {
|
||||
return {
|
||||
routesList: [],
|
||||
allRoutes: {},
|
||||
breadcrumb: [],
|
||||
breadcrumbKeys: [],
|
||||
navList: new Map(),
|
||||
activeRoute: "",
|
||||
cachedRoute: [],
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* @description: 设置路由列表
|
||||
* @param {Array} routes: 路由列表
|
||||
*/
|
||||
setRoutesList(routes) {
|
||||
this.routesList = routes;
|
||||
},
|
||||
/**
|
||||
* @description: 设置当前的所有路由
|
||||
* @param {Array} routes: 所有路由
|
||||
*/
|
||||
setAllRoutes(routes) {
|
||||
routes.forEach((i) => {
|
||||
this.allRoutes[i.path] = i;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description: 设置需要缓存的路由
|
||||
* @param {String} name: 要缓存的路由组件名称
|
||||
*/
|
||||
setCachedRoute(name) {
|
||||
if (!this.cachedRoute.includes(name)) {
|
||||
this.cachedRoute.push(name);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description: 设置面包屑
|
||||
* @param {Array} matched: 相配的路由
|
||||
*/
|
||||
setBreadcrumb(matched) {
|
||||
this.breadcrumb = matched;
|
||||
this.breadcrumbKeys = [];
|
||||
matched.forEach((i) => {
|
||||
this.breadcrumbKeys.push(i.path);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description: 设置导航栏
|
||||
* @param {String} route: 路由
|
||||
*/
|
||||
setNavbar(route) {
|
||||
if (this.allRoutes[route]) {
|
||||
this.navList.set(route, this.allRoutes[route]);
|
||||
this.activeRoute = route;
|
||||
useConfigure().setDefaultActive(route);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description: 设置 navList 的列表
|
||||
* @param {String} type: 操作类型
|
||||
*/
|
||||
setNavList(type) {
|
||||
return new Promise((resolve) => {
|
||||
const navListKeys = [...this.navList].slice(1);
|
||||
switch (type) {
|
||||
case "else":
|
||||
for (let i = 0; i < navListKeys.length; i++) {
|
||||
if (navListKeys[i][0] !== this.activeRoute) {
|
||||
this.navList.delete(navListKeys[i][0]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "left":
|
||||
for (let i = 0; i < navListKeys.length; i++) {
|
||||
if (navListKeys[i][0] == this.activeRoute) return;
|
||||
this.navList.delete(navListKeys[i][0]);
|
||||
}
|
||||
break;
|
||||
case "right":
|
||||
for (let i = navListKeys.length - 1; i >= 0; i--) {
|
||||
if (navListKeys[i][0] == this.activeRoute) return;
|
||||
this.navList.delete(navListKeys[i][0]);
|
||||
}
|
||||
break;
|
||||
case "all":
|
||||
for (let i = 0; i < navListKeys.length; i++) {
|
||||
this.navList.delete(navListKeys[i][0]);
|
||||
}
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description: 移除 navbar 中的某一项
|
||||
* @param {String} route: 要移除的路由项
|
||||
*/
|
||||
deleteNavItem(route) {
|
||||
return new Promise((resolve) => {
|
||||
if (route == this.activeRoute) {
|
||||
let navList = [...this.navList],
|
||||
length = navList.length,
|
||||
r = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (navList[i][0] == route) {
|
||||
if (i == length - 1) {
|
||||
r = navList[i - 1];
|
||||
} else if (i < length - 1) {
|
||||
r = navList[i + 1];
|
||||
}
|
||||
this.navList.delete(route);
|
||||
resolve(r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.navList.delete(route);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
3
src/store/store.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createPinia } from "pinia";
|
||||
const pinia = createPinia();
|
||||
export default pinia;
|
||||
44
src/store/user.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { defineStore } from "pinia";
|
||||
import _hook from "@/hooks/index.js";
|
||||
import { login, getUserInfo } from "@/api/user.js";
|
||||
|
||||
export const useUser = defineStore("useUser", {
|
||||
state: () => {
|
||||
return {
|
||||
userInfo: {},
|
||||
token: _hook.useLocalStorage.get("token") || "",
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
// 设置用户信息
|
||||
async setUserInfo() {
|
||||
if (_hook.useLocalStorage.get("userInfo")) {
|
||||
this.userInfo = _hook.useLocalStorage.get("userInfo");
|
||||
} else {
|
||||
this.userInfo = await this.getUserInfo();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description: 用户登录
|
||||
* @param {*} param: 登录的参数
|
||||
*/
|
||||
userlogin(param) {
|
||||
return login(param).then((res) => {
|
||||
// console.log(res);
|
||||
this.userInfo = res;
|
||||
_hook.useLocalStorage.set("token", this.userInfo.token);
|
||||
_hook.useLocalStorage.set("userInfo", this.userInfo.user);
|
||||
return this.userInfo;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description: 获取用户信息
|
||||
*/
|
||||
getUserInfo() {
|
||||
return getUserInfo().then((res) => {
|
||||
return res.userInfo;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
379
src/style/normalize.css
vendored
Normal file
@@ -0,0 +1,379 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
/* Document
|
||||
========================================================================== */
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
html {
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
body,
|
||||
html,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
form,
|
||||
fieldset,
|
||||
legend,
|
||||
input,
|
||||
textarea,
|
||||
select,
|
||||
button,
|
||||
th,
|
||||
td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
/* 1 */
|
||||
height: 0;
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover,
|
||||
a:active {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
/* 1 */
|
||||
text-decoration: underline;
|
||||
/* 2 */
|
||||
text-decoration: underline dotted;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
button,
|
||||
input {
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
button,
|
||||
select {
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
legend {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
display: table;
|
||||
/* 1 */
|
||||
max-width: 100%;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
white-space: normal;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
/* 1 */
|
||||
outline-offset: -2px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
font: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div,
|
||||
p {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
198
src/style/public.css
Normal file
@@ -0,0 +1,198 @@
|
||||
/* 定位 */
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/* 层深 */
|
||||
.z-1 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-2 {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.z-3 {
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.z-4 {
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.z-5 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.z-6 {
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
.z-7 {
|
||||
z-index: 70;
|
||||
}
|
||||
|
||||
.z-8 {
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
.z-9 {
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 字体加粗 */
|
||||
.font-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 文本对齐 */
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* 文本溢出省略 */
|
||||
.text-ellipsis-1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-ellipsis-2 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.text-ellipsis-3 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
/* flex 布局 */
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-lr {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-rl {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.flex-tb {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-bt {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flex-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.justify-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.align-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.align-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.flex-initial {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.flex-none {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.flex-grow-0 {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* hover 样式 */
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
27
src/style/root.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
:root {
|
||||
--admin-column-bg-color: #282c34;
|
||||
}
|
||||
|
||||
.scrollbar-y {
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #dfdfdf;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-x {
|
||||
&::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #dfdfdf;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
8
src/utils/components.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
|
||||
const initComponents = {
|
||||
install(app) {
|
||||
app.component("SvgIcon", SvgIcon);
|
||||
},
|
||||
};
|
||||
export default initComponents;
|
||||
25
src/utils/enums.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export const ENUMS = {
|
||||
// 布局模式
|
||||
layoutModeEnum: {
|
||||
key: ["modeA", "modeB"],
|
||||
value: ["纵向", "分栏"],
|
||||
},
|
||||
// 布局模式
|
||||
navbarModeEnum: {
|
||||
key: ["modeA", "modeB", "modeC"],
|
||||
value: ["圆滑", "卡片", "灵动"],
|
||||
},
|
||||
// 设置配置
|
||||
setColorEnum: {
|
||||
themeColor: "--el-color-primary",
|
||||
menuBGColor: "--el-menu-bg-color",
|
||||
textColor: "--el-menu-text-color",
|
||||
activeTextColor: "--el-menu-active-color",
|
||||
columnBgColor: "--admin-column-bg-color",
|
||||
},
|
||||
// 组件切换
|
||||
componentTransitionEnum: {
|
||||
key: ["mainA", "mainB"],
|
||||
value: ["下至上", "右至左"],
|
||||
},
|
||||
};
|
||||
95
src/utils/index.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 校验手机号码
|
||||
* @param {*} tel
|
||||
*/
|
||||
export function validPhone(tel) {
|
||||
const reg = /^1[3-9]\d{9}$/;
|
||||
return reg.test(tel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 机构名称
|
||||
*/
|
||||
export const typeNames = {
|
||||
"AG": '代理',
|
||||
"FB": '一级业务员',
|
||||
"FO": '大机构',
|
||||
"MC": '商家',
|
||||
"SB": '二级业务员',
|
||||
"SO": '小机构',
|
||||
"XW": '小微商户',
|
||||
"MG": '平台'
|
||||
}
|
||||
|
||||
/**
|
||||
* 机构列表
|
||||
*/
|
||||
export const organizationList = [
|
||||
{
|
||||
type_code: 'MG',
|
||||
type_name: '平台'
|
||||
},
|
||||
{
|
||||
type_code: 'AG',
|
||||
type_name: '代理'
|
||||
},
|
||||
{
|
||||
type_code: 'FB',
|
||||
type_name: '一级业务员'
|
||||
},
|
||||
{
|
||||
type_code: 'FO',
|
||||
type_name: '大机构'
|
||||
},
|
||||
{
|
||||
type_code: 'MC',
|
||||
type_name: '商家'
|
||||
},
|
||||
{
|
||||
type_code: 'SB',
|
||||
type_name: '二级业务员'
|
||||
},
|
||||
{
|
||||
type_code: 'SO',
|
||||
type_name: '小机构'
|
||||
},
|
||||
{
|
||||
type_code: 'XW',
|
||||
type_name: '小微商户'
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 添加事对象下一级类型
|
||||
*/
|
||||
export const addOrganizations = {
|
||||
'MG': 'FO',
|
||||
'FO': 'SO',
|
||||
'SO': 'AG'
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除字符串中除了数字和点以外的其他字符
|
||||
* @param {Object} obj
|
||||
*/
|
||||
export function clearNoNum(obj) {
|
||||
//如果用户第一位输入的是小数点,则重置输入框内容
|
||||
if (obj.value != '' && obj.value.substr(0, 1) == '.') {
|
||||
obj.value = '';
|
||||
}
|
||||
obj.value = obj.value.replace(/^0*(0\.|[1-9])/, '$1'); //粘贴不生效
|
||||
obj.value = obj.value.replace(/[^\d.]/g, ''); //清除“数字”和“.”以外的字符
|
||||
obj.value = obj.value.replace(/\.{2,}/g, '.'); //只保留第一个. 清除多余的
|
||||
obj.value = obj.value
|
||||
.replace('.', '$#$')
|
||||
.replace(/\./g, '')
|
||||
.replace('$#$', '.');
|
||||
obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3'); //只能输入两个小数
|
||||
if (obj.value.indexOf('.') < 0 && obj.value != '') {
|
||||
//以上已经过滤,此处控制的是如果没有小数点,首位不能为类似于 01、02的金额
|
||||
if (obj.value.substr(0, 1) == '0' && obj.value.length == 2) {
|
||||
obj.value = obj.value.substr(1, obj.value.length);
|
||||
}
|
||||
}
|
||||
return obj.value;
|
||||
}
|
||||
72
src/utils/request.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import axios from "axios";
|
||||
import { ElMessage } from "element-plus";
|
||||
import _hook from "@/hooks/index.js";
|
||||
import NProgress from "nprogress";
|
||||
import router from '@/router'
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.MODE == 'development' ? '/api/' : '/api/admin',
|
||||
// withCredentials: true, // 跨域请求时发送 cookies
|
||||
timeout: 5000, // 请求超时
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
NProgress.start();
|
||||
// 在发送请求之前做些什么 token
|
||||
if (_hook.useLocalStorage.get("token")) {
|
||||
// 让每个请求携带 token
|
||||
// ['X-Token'] 是自定义标题键
|
||||
// 请根据实际情况修改
|
||||
config.headers["token"] = _hook.useLocalStorage.get("token");
|
||||
config.headers["loginName"] = _hook.useLocalStorage.get("userInfo").loginName;
|
||||
config.headers["userId"] = _hook.useLocalStorage.get("userInfo").userId;
|
||||
// config.headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
NProgress.done();
|
||||
// 处理请求错误
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
NProgress.done();
|
||||
// 对响应数据做点什么
|
||||
if (+response.status === 200) {
|
||||
if (+response.data.code == '000000') {
|
||||
return response.data.data;
|
||||
} else if (+response.data.code == '999999') {
|
||||
ElMessage.error('登录已过期,请重新登录')
|
||||
_hook.useLocalStorage.clear()
|
||||
router.replace("/login")
|
||||
return Promise.reject('登录已过期,请重新登录')
|
||||
} else {
|
||||
// 响应错误
|
||||
ElMessage.error(response.data.message)
|
||||
return Promise.reject(response.data.message)
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
NProgress.done();
|
||||
// 对响应错误做点什么
|
||||
if (error.message.indexOf("timeout") != -1) {
|
||||
ElMessage.error("网络超时");
|
||||
} else if (error.message == "Network Error") {
|
||||
ElMessage.error("网络连接错误");
|
||||
} else {
|
||||
console.log(error);
|
||||
if (error.response.data) ElMessage.error(error.response.statusText);
|
||||
else ElMessage.error("接口路径找不到");
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default service;
|
||||
69
src/utils/watermark.js
Normal file
@@ -0,0 +1,69 @@
|
||||
let id = "uniqueIdProhibitsDuplication",
|
||||
watermarkText = "";
|
||||
|
||||
function createWatermark() {
|
||||
if (document.getElementById(id) !== null) {
|
||||
document.body.removeChild(document.getElementById(id));
|
||||
}
|
||||
// 创建一个画布
|
||||
const can = document.createElement("canvas");
|
||||
// 设置画布的长宽
|
||||
can.width = 250;
|
||||
can.height = 200;
|
||||
const cans = can.getContext("2d");
|
||||
// 旋转角度
|
||||
cans.rotate((-20 * Math.PI) / 180);
|
||||
cans.font = "16px Vedana";
|
||||
// 设置填充绘画的颜色、渐变或者模式
|
||||
cans.fillStyle = "rgba(200, 200, 200, 0.35)";
|
||||
// 设置文本内容的当前对齐方式
|
||||
cans.textAlign = "left";
|
||||
// 设置在绘制文本时使用的当前文本基线
|
||||
cans.textBaseline = "Middle";
|
||||
// 在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
|
||||
cans.fillText(watermarkText, can.width / 8, can.height / 2);
|
||||
const div = document.createElement("div");
|
||||
div.id = id;
|
||||
div.style.pointerEvents = "none"; //禁用鼠标事件
|
||||
div.style.top = "30px";
|
||||
div.style.left = "0px";
|
||||
div.style.position = "fixed";
|
||||
div.style.zIndex = "999999";
|
||||
div.style.width = document.documentElement.clientWidth - 20 + "px";
|
||||
div.style.height = document.documentElement.clientHeight - 20 + "px";
|
||||
div.style.background = "url(" + can.toDataURL("image/png") + ") left top repeat";
|
||||
document.body.appendChild(div);
|
||||
mutationFun();
|
||||
}
|
||||
|
||||
// 添加水印
|
||||
function set(text = "Vue3 ElePlus Admin") {
|
||||
watermarkText = text;
|
||||
createWatermark();
|
||||
window.addEventListener("resize", createWatermark);
|
||||
}
|
||||
|
||||
// 移除水印
|
||||
function remove() {
|
||||
document.body.removeChild(document.getElementById(id));
|
||||
window.removeEventListener("resize", createWatermark);
|
||||
}
|
||||
|
||||
// 监听元素的变化
|
||||
function mutationFun() {
|
||||
const mutation = new MutationObserver((el) => {
|
||||
mutation.disconnect();
|
||||
createWatermark();
|
||||
});
|
||||
const config = {
|
||||
attributes: true,
|
||||
subtree: true,
|
||||
childList: true,
|
||||
};
|
||||
mutation.observe(document.getElementById(id), config);
|
||||
}
|
||||
|
||||
export default {
|
||||
set,
|
||||
remove,
|
||||
};
|
||||
347
src/views/app_manage/menu_list.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<el-space>
|
||||
<el-button type="primary" icon="Plus" @click="addHandle">添加菜单</el-button>
|
||||
</el-space>
|
||||
<div class="mt15">
|
||||
<el-space>
|
||||
<el-select placeholder="请选择分组" v-model="tableOptions.navcode">
|
||||
<el-option :value="item.id" :label="item.name" v-for="item in menuGroups" :key="item.id"></el-option>
|
||||
</el-select>
|
||||
<el-select placeholder="请选择导航" v-model="tableOptions.navName">
|
||||
<el-option :value="item.id" :label="item.name" v-for="item in navcodes" :key="item.id"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" icon="Search" @click="searchHandle">搜索</el-button>
|
||||
<el-button icon="RefreshRight" @click="resizeTable">重置</el-button>
|
||||
</el-space>
|
||||
</div>
|
||||
<div class="table mt15">
|
||||
<el-table ref="table" :data="tableOptions.list" border height="100%" v-loading="tableOptions.loading">
|
||||
<el-table-column prop="id" label="ID" width="50"></el-table-column>
|
||||
<el-table-column label="图标" width="100">
|
||||
<template #default="scope">
|
||||
<el-image style="width: 50px; height: 50px" :src="scope.row.icon" preview-teleported
|
||||
hide-on-click-modal :preview-src-list="[scope.row.icon]" fit="cover">
|
||||
</el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="menuGroup" label="分组">
|
||||
<template #default="scope">
|
||||
<el-text>{{ menuGroups.find(item => item.id == scope.row.menuGroup).name }}</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称"></el-table-column>
|
||||
<el-table-column prop="navName" label="导航"></el-table-column>
|
||||
<el-table-column prop="url" label="链接"></el-table-column>
|
||||
<el-table-column label="是否显示" width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="scope.row.visible == 0" type="info">不显示</el-text>
|
||||
<el-text v-if="scope.row.visible == 1" type="primary">显示</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否小程序" width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="scope.row.isapplets == 0" type="info">否</el-text>
|
||||
<el-text v-if="scope.row.isapplets == 1" type="primary">是</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否uniapp" width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="scope.row.isuniapp == 0" type="info">否</el-text>
|
||||
<el-text v-if="scope.row.isuniapp == 1" type="primary">是</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="显示(安卓/iso)" width="100">
|
||||
<template #default="scope">
|
||||
<el-text v-if="scope.row.isandroidenabled == 0" type="info">否</el-text>
|
||||
<el-text v-if="scope.row.isandroidenabled == 1" type="primary">是</el-text>
|
||||
<el-text type="info">/</el-text>
|
||||
<el-text v-if="scope.row.isiphoneenabled == 0" type="info">否</el-text>
|
||||
<el-text v-if="scope.row.isiphoneenabled == 1" type="primary">是</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="小程序ID"></el-table-column>
|
||||
<el-table-column prop="path" label="跳转路径/uniApp路径"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updateTime" label="更新时间">
|
||||
<template #default="scope">
|
||||
{{ dayjs(scope.row.updateTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" icon="EditPen">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="mt15">
|
||||
<el-pagination layout="prev, pager, next, total, sizes, jumper" background
|
||||
v-model:current-page="tableOptions.pageNum" v-model:page-size="tableOptions.pageSzie"
|
||||
:page-size="tableOptions.pageSzie" :page-sizes="[10, 20, 30, 50]" :total="tableOptions.total"
|
||||
@size-change="paginationChange" @current-change="paginationChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog title="增加菜单" v-model="showDialog" @closed="dialogClosed">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100">
|
||||
<el-form-item prop="name" label="菜单名称">
|
||||
<el-input placeholder="请输入菜单名称" v-model="form.name" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" label="菜单code">
|
||||
<el-input placeholder="请输入菜单code" v-model="form.code" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="sort" label="排序">
|
||||
<el-input placeholder="请输入菜单排序" v-model="form.sort" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="url" label="图片url">
|
||||
<el-upload ref="uploadRef" v-model:file-list="fileList" :limit="1" :on-exceed="handleExceed"
|
||||
list-type="picture" :auto-upload="false" @change="selectFile" @remove="removeFile">
|
||||
<template #trigger>
|
||||
<el-button type="primary" icon="Picture">选择图片</el-button>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item prop="navcode" label="导航">
|
||||
<el-select v-model="form.navcode">
|
||||
<el-option :label="item.name" :value="item.id" v-for="item in navcodes" :key="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="menuGroup" label="分组">
|
||||
<el-select v-model="form.menuGroup">
|
||||
<el-option :label="item.name" :value="item.id" v-for="item in menuGroups" :key="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" label="是否显示">
|
||||
<el-radio-group v-model="form.visible">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" label="安卓是否显示">
|
||||
<el-radio-group v-model="form.isAndroidEnabled">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" label="ios是否显示">
|
||||
<el-radio-group v-model="form.isIphoneEnabled">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-space>
|
||||
<el-button @click="showDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="submitHandle">提交</el-button>
|
||||
</el-space>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { dayjs } from 'element-plus';
|
||||
import { appMenuPage, getDictGroup, uploadOSS, appMenuSave } from '@/api/home.js'
|
||||
import { onMounted, reactive } from 'vue';
|
||||
|
||||
const table = ref(null)
|
||||
const navcodes = ref([])
|
||||
const menuGroups = ref([])
|
||||
// 表格参数
|
||||
const tableOptions = reactive({
|
||||
loading: true,
|
||||
navcode: '',
|
||||
navName: '',
|
||||
list: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSzie: 10
|
||||
})
|
||||
|
||||
// 添加菜单
|
||||
function addHandle() {
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
// 重置表格
|
||||
function resizeTable() {
|
||||
tableOptions.navcode = ''
|
||||
tableOptions.navName = ''
|
||||
searchHandle()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function searchHandle() {
|
||||
tableOptions.pageNum = 1;
|
||||
paginationChange()
|
||||
}
|
||||
// 分页回调
|
||||
function paginationChange() {
|
||||
tableOptions.loading = true
|
||||
appMenuPageAjax()
|
||||
}
|
||||
|
||||
// app菜单list
|
||||
async function appMenuPageAjax() {
|
||||
try {
|
||||
const res = await appMenuPage({
|
||||
page: tableOptions.pageNum,
|
||||
size: tableOptions.pageSzie
|
||||
})
|
||||
tableOptions.loading = false
|
||||
tableOptions.list = res.list
|
||||
tableOptions.total = res.total
|
||||
table.value.setScrollTop(0)
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// 获取分组字典值
|
||||
async function getMenuGrounp() {
|
||||
try {
|
||||
const res = await getDictGroup('APP_MENU_GROUP')
|
||||
menuGroups.value = res
|
||||
} catch (error) { }
|
||||
}
|
||||
// 获取导航字典值
|
||||
async function getNavCodes() {
|
||||
try {
|
||||
const res = await getDictGroup('APP_MENU_NAV')
|
||||
navcodes.value = res
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// 显示添加表单
|
||||
const showDialog = ref(false)
|
||||
const formLoading = ref(false)
|
||||
const formRef = ref(null)
|
||||
const fileList = ref([])
|
||||
|
||||
// 创建表单
|
||||
const form = reactive({
|
||||
name: '', // 菜单名称
|
||||
code: '', // 菜单code
|
||||
sort: '', // 排序
|
||||
url: '', // 图标
|
||||
navcode: '', // 导航选择
|
||||
menuGroup: '', // 分组
|
||||
visible: 1, // 是否显示0否1是
|
||||
isAndroidEnabled: 1, // 安卓是否显示
|
||||
isIphoneEnabled: 1, // ios是否显示
|
||||
})
|
||||
|
||||
|
||||
// 校验是否选择图片
|
||||
const imgValidate = (rule, value, callback) => {
|
||||
if (fileList.value.length <= 0) {
|
||||
return callback(new Error('请选择图片'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// 表单校验规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sort: [
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
url: [
|
||||
{
|
||||
required: true,
|
||||
validator: imgValidate,
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
navcode: [
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
menuGroup: [
|
||||
{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 只能选择一个文件,替换之前的文件
|
||||
const handleExceed = (files) => {
|
||||
uploadRef.value.clearFiles()
|
||||
const file = files[0]
|
||||
file.uid = genFileId()
|
||||
uploadRef.value.handleStart(file)
|
||||
form.url = ''
|
||||
}
|
||||
|
||||
// 选择图片
|
||||
const selectFile = async (file) => {
|
||||
fileList.value = [file]
|
||||
}
|
||||
|
||||
// 移除图片
|
||||
const removeFile = async () => {
|
||||
fileList.value = []
|
||||
form.url = ''
|
||||
}
|
||||
|
||||
// 表单关闭
|
||||
function dialogClosed() {
|
||||
formRef.value.resetFields()
|
||||
fileList.value = []
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitHandle = async () => {
|
||||
await formRef.value.validate(async (vaild) => {
|
||||
if (vaild) {
|
||||
try {
|
||||
formLoading.value = true
|
||||
form.url = await uploadOSS(fileList.value[0].raw)
|
||||
await appMenuSave(form)
|
||||
formLoading.value = false
|
||||
showDialog.value = false
|
||||
ElMessage.success('添加成功')
|
||||
paginationChange()
|
||||
} catch (error) {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getMenuGrounp()
|
||||
await getNavCodes()
|
||||
await appMenuPageAjax()
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.table {
|
||||
height: calc(100vh - 356px);
|
||||
}
|
||||
</style>
|
||||
12
src/views/demo/demo11.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo11</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let aa = ref('')
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
12
src/views/demo/demo12.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo12</h2>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let aa = ref("");
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
11
src/views/demo/demo121.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo121</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let aa = ref("");
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
9
src/views/demo/demo122.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo122</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
12
src/views/demo/demo13.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo13</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let aa = ref('')
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
11
src/views/demo/demo21.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo21</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let aa = ref('')
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
11
src/views/demo/demo22.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo22</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let aa = ref('')
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
11
src/views/demo/demo23.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="demo">
|
||||
<h2>demo23</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
let aa = ref('')
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
200
src/views/device/device_list.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="data_row">
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<img class="img" src="@/assets/icon_dev1.png">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">总设备数</div>
|
||||
<div class="num">
|
||||
<count-to :start-val="0" :end-val="deviceData.sumCount" :duration="1000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<img class="img" src="@/assets/icon_dev2.png">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">已激活数</div>
|
||||
<div class="num">
|
||||
<count-to :start-val="0" :end-val="deviceData.activityCount" :duration="1000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<img class="img" src="@/assets/icon_dev3.png">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">未激活数</div>
|
||||
<div class="num">
|
||||
<count-to :start-val="0" :end-val="deviceData.sellCount" :duration="1000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt15">
|
||||
<el-space class="pb15">
|
||||
<el-input placeholder="请输入商户编号搜索" v-model="tableOptions.merchantCode" style="width: 200px;" />
|
||||
<el-button type="primary" icon="Search" @click="searchHandle">搜索</el-button>
|
||||
</el-space>
|
||||
<div class="table">
|
||||
<el-table :data="tableOptions.list" size="large" stripe border height="100%" v-loading="tableOptions.loading">
|
||||
<el-table-column prop="merchantCode" label="商户号"></el-table-column>
|
||||
<el-table-column prop="snNo" label="设备号"></el-table-column>
|
||||
<el-table-column prop="actMercName" label="商户名"></el-table-column>
|
||||
<el-table-column prop="phone" label="联系电话"></el-table-column>
|
||||
<el-table-column prop="status" label="激活状态">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" disable-transitions v-if="scope.row.status == 3">已激活</el-tag>
|
||||
<el-tag type="warning" disable-transitions v-else>未激活</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="countSum" label="收单笔数"></el-table-column>
|
||||
<el-table-column prop="consumeFee" label="收单总额"></el-table-column>
|
||||
<el-table-column prop="loginName" label="上级名称"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="mt15">
|
||||
<el-pagination layout="prev, pager, next, total, sizes, jumper" background
|
||||
v-model:current-page="tableOptions.pageNum" v-model:page-size="tableOptions.pageSzie"
|
||||
:page-size="tableOptions.pageSzie" :page-sizes="[10, 20, 30, 50]" :total="tableOptions.total"
|
||||
@size-change="paginationChange" @current-change="paginationChange" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { getSumDeviceStock, getDeviceStockInfo } from '@/api/device.js'
|
||||
|
||||
const deviceData = reactive({
|
||||
sumCount: 0,
|
||||
activityCount: 0,
|
||||
sellCount: 0
|
||||
})
|
||||
|
||||
// 表格参数
|
||||
const tableOptions = reactive({
|
||||
loading: true,
|
||||
merchantCode: '',
|
||||
list: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSzie: 10
|
||||
})
|
||||
|
||||
// 搜索
|
||||
function searchHandle() {
|
||||
tableOptions.pageNum = 1;
|
||||
paginationChange()
|
||||
}
|
||||
|
||||
// 分页回调
|
||||
function paginationChange() {
|
||||
tableOptions.loading = true
|
||||
getDeviceStockInfoHandle()
|
||||
}
|
||||
|
||||
// 获取当前用户设备总数
|
||||
async function getSumDeviceStockHandle() {
|
||||
try {
|
||||
const { sumCount, activityCount, sellCount } = await getSumDeviceStock()
|
||||
deviceData.sumCount = sumCount
|
||||
deviceData.activityCount = activityCount
|
||||
deviceData.sellCount = sellCount
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// 获取设备列表
|
||||
async function getDeviceStockInfoHandle() {
|
||||
try {
|
||||
const { total, list } = await getDeviceStockInfo({
|
||||
merchantCode: tableOptions.merchantCode,
|
||||
pageNum: tableOptions.pageNum,
|
||||
pageSize: tableOptions.pageSzie
|
||||
})
|
||||
tableOptions.loading = false
|
||||
tableOptions.total = total
|
||||
tableOptions.list = list
|
||||
} catch (error) {
|
||||
tableOptions.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSumDeviceStockHandle()
|
||||
getDeviceStockInfoHandle()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.table {
|
||||
height: calc(100vh - 456px);
|
||||
}
|
||||
|
||||
.data_row {
|
||||
display: flex;
|
||||
padding: 24px 0;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding-left: 50px;
|
||||
|
||||
&:not(:last-child) {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
height: 100%;
|
||||
border-right: 1px solid #ececec;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
$size: 50px;
|
||||
width: $size;
|
||||
height: $size;
|
||||
|
||||
.img {
|
||||
width: $size;
|
||||
height: $size;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding-top: 10px;
|
||||
|
||||
.i {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
right: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
10
src/views/error/401.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div class="401">
|
||||
<h1>401</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
44
src/views/error/404.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="empty-404">
|
||||
<img src="../../assets/images/404.png">
|
||||
<p class="tips">您访问的页面不存在</p>
|
||||
<el-button @click="goRoute">{{ countDown }}s 返回</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
function goRoute() {
|
||||
router.push('/home');
|
||||
}
|
||||
let countDown = ref(5)
|
||||
let timer = setInterval(() => {
|
||||
countDown.value--
|
||||
if (countDown.value == 0) {
|
||||
goRoute()
|
||||
}
|
||||
}, 1000)
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty-404 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.tips {
|
||||
font-size: 40px;
|
||||
line-height: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
215
src/views/home.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header_title">
|
||||
欢迎回来!{{ storeUser.userInfo.loginName }}
|
||||
</div>
|
||||
<div class="data_row">
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<img class="img" src="../assets/home_icon1.png">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">团队总流水</div>
|
||||
<div class="num">
|
||||
<count-to :start-val="0" :decimals="2" :end-val="homeData.sumConsumeFee" :duration="1000" />
|
||||
<!-- {{ homeData.sumConsumeFee }} -->
|
||||
<span class="i">元</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<img class="img" src="../assets/home_icon2.png">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">总收益</div>
|
||||
<div class="num">
|
||||
<count-to :start-val="0" :decimals="2" :end-val="homeData.sumfansShareMoney" :duration="1000" />
|
||||
<span class="i">元</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<img class="img" src="../assets/home_icon3.png">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">今日收益</div>
|
||||
<div class="num">
|
||||
<count-to :start-val="0" :decimals="2" :end-val="homeData.yestedayShareMoney" :duration="1000" />
|
||||
<span class="i">元</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="icon">
|
||||
<img class="img" src="../assets/home_icon4.png">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">推广费率</div>
|
||||
<div class="num">
|
||||
{{ homeData.currentFee }}
|
||||
<span class="i">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt15">
|
||||
<chart-card :user-id="storeUser.userInfo.userId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getIndexData } from '@/api/home.js'
|
||||
import { useUser } from "@/store/user.js";
|
||||
import chartCard from '@/components/chartCard.vue';
|
||||
|
||||
const storeUser = useUser();
|
||||
|
||||
let homeData = reactive({
|
||||
sumConsumeFee: 0,
|
||||
sumfansShareMoney: 0,
|
||||
yestedayShareMoney: 0,
|
||||
currentFee: 0
|
||||
})
|
||||
|
||||
// 获取首页数据
|
||||
async function getIndexDataHandle() {
|
||||
try {
|
||||
const res = await getIndexData()
|
||||
homeData.sumConsumeFee = res.sumConsumeFee
|
||||
homeData.sumfansShareMoney = res.sumfansShareMoney
|
||||
homeData.yestedayShareMoney = res.yestedayShareMoney
|
||||
homeData.currentFee = res.currentFee
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getIndexDataHandle()
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header_title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding: 15px 0 30px 0;
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
|
||||
.data_row {
|
||||
display: flex;
|
||||
padding: 50px 0;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding-left: 50px;
|
||||
|
||||
&:not(:last-child) {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
height: 100%;
|
||||
border-right: 1px solid #ececec;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
$size: 55px;
|
||||
width: $size;
|
||||
height: $size;
|
||||
border-radius: 50%;
|
||||
background-color: #F2F3F5;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.img {
|
||||
$size: 30px;
|
||||
width: $size;
|
||||
height: $size;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding-top: 10px;
|
||||
|
||||
.i {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
right: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.userInfo {
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0 20px;
|
||||
border: 1px solid #eee;
|
||||
background-color: #fff;
|
||||
|
||||
.name {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.charts-case {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
grid-template-areas:
|
||||
"a b"
|
||||
"c c";
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.charts-chunk {
|
||||
border: 1px solid #eee;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.charts-chunk:nth-child(0) {
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.charts-chunk:nth-child(1) {
|
||||
grid-area: b;
|
||||
}
|
||||
|
||||
.charts-chunk:last-child {
|
||||
grid-area: c;
|
||||
}
|
||||
</style>
|
||||
3
src/views/layout/bridge.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
219
src/views/layout/components/configure.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<el-drawer class="vab-drawer" size="400px" :model-value="props.modelValue" :before-close="close">
|
||||
<template #header>
|
||||
<h4>配置</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-divider>
|
||||
<span>布局切换</span>
|
||||
</el-divider>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title"> 布局 </el-col>
|
||||
<el-col :span="15">
|
||||
<el-select v-model="getConfigure.menuMode" placeholder="请选择布局" @change="changeMenuMode(getConfigure.menuMode)">
|
||||
<el-option v-for="(item, index) in layoutModeEnum.key" :key="item" :label="layoutModeEnum.value[index]" :value="item" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider>
|
||||
<span>主题配置</span>
|
||||
</el-divider>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title"> 主题色 </el-col>
|
||||
<el-col :span="15">
|
||||
<el-color-picker v-model="getConfigure.themeColor" @change="setConfigure('themeColor')" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider>
|
||||
<span>标签</span>
|
||||
</el-divider>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title"> 标签风格 </el-col>
|
||||
<el-col :span="15">
|
||||
<el-select v-model="getConfigure.navbarMode" placeholder="请选择布局">
|
||||
<el-option v-for="(item, index) in navbarModeEnum.key" :key="item" :label="navbarModeEnum.value[index]" :value="item" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title"> 标签图标 </el-col>
|
||||
<el-col :span="15">
|
||||
<el-switch v-model="getConfigure.navbarIcon" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider>
|
||||
<span>菜单</span>
|
||||
</el-divider>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title"> 菜单背景 </el-col>
|
||||
<el-col :span="15">
|
||||
<el-color-picker v-if="getConfigure.menuMode == layoutModeEnum.key[0]" v-model="getConfigure.menuBGColor" @change="setConfigure('menuBGColor')" />
|
||||
<el-color-picker v-else-if="getConfigure.menuMode == layoutModeEnum.key[1]" v-model="getConfigure.columnBgColor" @change="setConfigure('columnBgColor')" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title"> 菜单文字颜色 </el-col>
|
||||
<el-col :span="15">
|
||||
<el-color-picker v-model="getConfigure.textColor" @change="setConfigure('textColor')" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title"> 菜单活跃文字颜色 </el-col>
|
||||
<el-col :span="15">
|
||||
<el-color-picker v-model="getConfigure.activeTextColor" @change="setConfigure('activeTextColor')" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title">菜单的 Logo</el-col>
|
||||
<el-col :span="15">
|
||||
<el-switch v-model="getConfigure.showMenuLogo" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider>
|
||||
<span>过度</span>
|
||||
</el-divider>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title">
|
||||
组件切换过度
|
||||
<el-tooltip class="box-item" effect="dark" content="开启组件切换过渡时,组件只能有一个根元素。" placement="top">
|
||||
<SvgIcon name="QuestionFilled" color="#909399" style="margin-left: 5px"></SvgIcon>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="15">
|
||||
<el-switch v-model="getConfigure.componentTransition" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="vab-row">
|
||||
<el-col :span="9" class="list-title">组件过渡</el-col>
|
||||
<el-col :span="15">
|
||||
<el-select v-model="getConfigure.componentTransitionMode" placeholder="请选择过度模式">
|
||||
<el-option v-for="(item, index) in componentTransitionEnum.key" :key="item" :label="componentTransitionEnum.value[index]" :value="item" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div style="flex: auto">
|
||||
<el-button type="primary" @click="confirm">保存</el-button>
|
||||
<el-button @click="recovery">恢复默认</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
|
||||
// 控制组件的显示
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
function close() {
|
||||
emits("update:modelValue", !props.modelValue);
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
const storeConfigure = useConfigure();
|
||||
const { configure } = storeToRefs(storeConfigure);
|
||||
const getConfigure = computed(() => {
|
||||
return configure.value;
|
||||
});
|
||||
|
||||
// 获取枚举值
|
||||
const { layoutModeEnum, navbarModeEnum, setColorEnum, componentTransitionEnum } = ENUMS;
|
||||
|
||||
// 切换侧边导航的布局
|
||||
function changeMenuMode(mode) {
|
||||
switch (mode) {
|
||||
case layoutModeEnum.key[0]:
|
||||
_hook.useCssVar("--el-menu-bg-color", storeConfigure.configure.menuBGColor);
|
||||
break;
|
||||
case layoutModeEnum.key[1]:
|
||||
_hook.useCssVar("--el-menu-bg-color", "#ffffff");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// 设置配置
|
||||
function setConfigure(type) {
|
||||
_hook.useCssVar(setColorEnum[type], getConfigure.value[type]);
|
||||
switch (type) {
|
||||
case "themeColor":
|
||||
_hook.useCssVar("--el-menu-hover-bg-color", storeConfigure.configure.themeColor);
|
||||
[3, 5, 7, 8, 9].forEach((i) => {
|
||||
_hook.useCssVar(`--el-color-primary-light-${i}`, _hook.useLightColor(getConfigure.value[type], `0.${i}`));
|
||||
});
|
||||
break;
|
||||
case "menuBGColor":
|
||||
storeConfigure.change("columnBgColor", storeConfigure.configure.menuBGColor);
|
||||
_hook.useCssVar("--admin-column-bg-color", storeConfigure.configure.menuBGColor);
|
||||
break;
|
||||
case "columnBgColor":
|
||||
storeConfigure.change("menuBGColor", storeConfigure.configure.columnBgColor);
|
||||
break;
|
||||
case "activeTextColor":
|
||||
_hook.useCssVar("--el-menu-hover-text-color", storeConfigure.configure.activeTextColor);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
function confirm() {
|
||||
_hook.useLocalStorage.set("configure", storeConfigure.configure);
|
||||
ElMessage({
|
||||
message: "配置保存成功",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
|
||||
// 恢复默认
|
||||
function recovery() {
|
||||
_hook.useLocalStorage.remove("configure");
|
||||
storeConfigure.$reset();
|
||||
|
||||
// 初始化颜色
|
||||
_hook.useCssVar("--el-color-primary", storeConfigure.configure.themeColor);
|
||||
switch (storeConfigure.configure.menuMode) {
|
||||
case layoutModeEnum.key[0]:
|
||||
_hook.useCssVar("--el-menu-bg-color", storeConfigure.configure.menuBGColor);
|
||||
break;
|
||||
case layoutModeEnum.key[1]:
|
||||
_hook.useCssVar("--el-menu-bg-color", "#ffffff");
|
||||
break;
|
||||
default:
|
||||
}
|
||||
_hook.useCssVar("--admin-column-bg-color", storeConfigure.configure.menuBGColor);
|
||||
_hook.useCssVar("--el-menu-text-color", storeConfigure.configure.textColor);
|
||||
_hook.useCssVar("--el-menu-active-color", storeConfigure.configure.activeTextColor);
|
||||
[3, 5, 7, 8, 9].forEach((i) => {
|
||||
_hook.useCssVar(`--el-color-primary-light-${i}`, _hook.useLightColor(storeConfigure.configure.themeColor, `0.${i}`));
|
||||
});
|
||||
|
||||
ElMessage({
|
||||
message: "配置已恢复默认",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
color: --el-text-color-secondary;
|
||||
}
|
||||
</style>
|
||||
121
src/views/layout/components/navBar.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="navbar-case">
|
||||
<Transition name="navbar" mode="out-in">
|
||||
<navbar-mode-a v-if="configure.navbarMode == navbarModeEnum.key[0]"></navbar-mode-a>
|
||||
<navbar-mode-b v-else-if="configure.navbarMode == navbarModeEnum.key[1]"></navbar-mode-b>
|
||||
<navbar-mode-c v-else-if="configure.navbarMode == navbarModeEnum.key[2]"></navbar-mode-c>
|
||||
</Transition>
|
||||
<div class="menu">
|
||||
<el-dropdown @visible-change="changeDropdownVisible">
|
||||
<div class="flex align-center" style="gap: 0 10px">
|
||||
<SvgIcon class="menu-icon" :style="menuIconStyle" name="Menu"></SvgIcon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="item in menuItem" :key="item.title" @click="setNavList(item.type)">
|
||||
<SvgIcon :name="item.icon"></SvgIcon>
|
||||
<span>{{ item.title }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import navbarModeA from "./navbar-modeA.vue";
|
||||
import navbarModeB from "./navbar-modeB.vue";
|
||||
import navbarModeC from "./navbar-modeC.vue";
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
const storeRoutes = useRoutes();
|
||||
|
||||
// 获取枚举值
|
||||
const { navbarModeEnum } = ENUMS;
|
||||
|
||||
// 获取配置
|
||||
const storeConfigure = useConfigure();
|
||||
const { configure } = storeToRefs(storeConfigure);
|
||||
|
||||
// 下拉菜单
|
||||
let menuItem = [
|
||||
{
|
||||
title: "关闭其他",
|
||||
icon: "Close",
|
||||
type: "else",
|
||||
},
|
||||
{
|
||||
title: "关闭左侧",
|
||||
icon: "Back",
|
||||
type: "left",
|
||||
},
|
||||
{
|
||||
title: "关闭右侧",
|
||||
icon: "Right",
|
||||
type: "right",
|
||||
},
|
||||
{
|
||||
title: "关闭全部",
|
||||
icon: "Close",
|
||||
type: "all",
|
||||
},
|
||||
];
|
||||
let menuIconStyle = reactive({
|
||||
transform: `rotate(0deg)`,
|
||||
color: "",
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 下拉菜单的显示与隐藏
|
||||
* @param {Bool} bool: true显示 false隐藏
|
||||
*/
|
||||
function changeDropdownVisible(bool) {
|
||||
menuIconStyle.transform = bool ? "rotate(45deg)" : "rotate(0deg)";
|
||||
menuIconStyle.color = bool ? storeConfigure.configure.themeColor : "";
|
||||
}
|
||||
|
||||
// 设置 nav 的列表
|
||||
const router = useRouter();
|
||||
function setNavList(type) {
|
||||
storeRoutes.setNavList(type).then(() => {
|
||||
router.push("/");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 导航栏动画
|
||||
.navbar-enter-active,
|
||||
.navbar-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-enter-from,
|
||||
.navbar-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.navbar-case {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--el-color-info-light-9);
|
||||
}
|
||||
|
||||
.menu {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// background-color: var(--el-bg-color);
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
.menu-icon {
|
||||
font-size: 20px;
|
||||
color: var(--el-text-color-secondary);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
169
src/views/layout/components/navbar-modeA.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="navBar-list-mode-a">
|
||||
<div :class="[storeRoutes.activeRoute == item[0] ? 'list-active' : 'list']" @click="goRoute(item[0])"
|
||||
v-for="item in storeRoutes.navList">
|
||||
<SvgIcon class="list-icon" v-if="storeConfigure.configure.navbarIcon" :name="item[1]?.meta?.icon"></SvgIcon>
|
||||
<span class="list-title">{{ item[1]?.meta?.title || item[0] }}</span>
|
||||
<SvgIcon class="list-close" name="Close" v-if="item[0] !== '/home'" @click.stop="closeNavbarItem(item[0])">
|
||||
</SvgIcon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoutes } from '@/store/routes.js'
|
||||
import { useConfigure } from '@/store/configure.js'
|
||||
const storeRoutes = useRoutes()
|
||||
const storeConfigure = useConfigure()
|
||||
const router = useRouter()
|
||||
|
||||
/**
|
||||
* @description: 路由跳转
|
||||
* @param {String} route: 要跳转的路由
|
||||
*/
|
||||
function goRoute(route) {
|
||||
router.push(route)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 关闭导航的某一项
|
||||
* @param {String} route: 要关闭的当前项
|
||||
*/
|
||||
function closeNavbarItem(route) {
|
||||
storeRoutes.deleteNavItem(route).then(res => {
|
||||
goRoute(res[0])
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navBar-list-mode-a {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 0 30px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
overflow: overlay;
|
||||
overflow-y: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-icon {
|
||||
font-size: 16px;
|
||||
bottom: -0.2em;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.list-title {
|
||||
padding: 0 5px;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.list-close {
|
||||
font-size: 14px;
|
||||
transform: scale(0, 0);
|
||||
transition: all 0.3s;
|
||||
bottom: -0.18em;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.list {
|
||||
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
overflow: visible;
|
||||
line-height: 16px;
|
||||
font-size: 16px;
|
||||
padding: 0 10px;
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
transition: all .3s cubic-bezier(.645, .045, .355, 1) !important;
|
||||
|
||||
&:hover {
|
||||
padding: 0 20px;
|
||||
|
||||
&::after {
|
||||
z-index: -1;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
right: -10px;
|
||||
height: 100%;
|
||||
background-color: var(--el-color-info-light-7);
|
||||
-webkit-mask: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANoAAAAkBAMAAAAdqzmBAAAAMFBMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlTPQ5AAAAD3RSTlMAr3DvEM8wgCBA379gj5//tJBPAAAAnUlEQVRIx2NgAAM27fj/tAO/xBsYkIHyf9qCT8iWMf6nNQhAsk2f5rYheY7Dnua2/U+A28ZEe8v+F9Ax2v7/F4DbxkUH2wzgtvHTwbYPo7aN2jZq26hto7aN2jZq25Cy7Qvctnw62PYNbls9HWz7S8/G6//PsI6H4396gAUQy1je08W2jxDbpv6nD4gB2uWp+J9eYPsEhv/0BPS1DQBvoBLVZ3BppgAAAABJRU5ErkJggg==);
|
||||
mask: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANoAAAAkBAMAAAAdqzmBAAAAMFBMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlTPQ5AAAAD3RSTlMAr3DvEM8wgCBA379gj5//tJBPAAAAnUlEQVRIx2NgAAM27fj/tAO/xBsYkIHyf9qCT8iWMf6nNQhAsk2f5rYheY7Dnua2/U+A28ZEe8v+F9Ax2v7/F4DbxkUH2wzgtvHTwbYPo7aN2jZq26hto7aN2jZq25Cy7Qvctnw62PYNbls9HWz7S8/G6//PsI6H4396gAUQy1je08W2jxDbpv6nD4gB2uWp+J9eYPsEhv/0BPS1DQBvoBLVZ3BppgAAAABJRU5ErkJggg==);
|
||||
-webkit-mask-size: 100% 100%;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
.list-close {
|
||||
transform: scale(1, 1);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-text-color-primary);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-active {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
overflow: visible;
|
||||
color: var(--el-color-primary);
|
||||
padding: 0 20px;
|
||||
display: inline-block;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
|
||||
&::before {
|
||||
z-index: -1;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
right: -10px;
|
||||
height: 100%;
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
-webkit-mask: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANoAAAAkBAMAAAAdqzmBAAAAMFBMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlTPQ5AAAAD3RSTlMAr3DvEM8wgCBA379gj5//tJBPAAAAnUlEQVRIx2NgAAM27fj/tAO/xBsYkIHyf9qCT8iWMf6nNQhAsk2f5rYheY7Dnua2/U+A28ZEe8v+F9Ax2v7/F4DbxkUH2wzgtvHTwbYPo7aN2jZq26hto7aN2jZq25Cy7Qvctnw62PYNbls9HWz7S8/G6//PsI6H4396gAUQy1je08W2jxDbpv6nD4gB2uWp+J9eYPsEhv/0BPS1DQBvoBLVZ3BppgAAAABJRU5ErkJggg==);
|
||||
mask: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANoAAAAkBAMAAAAdqzmBAAAAMFBMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlTPQ5AAAAD3RSTlMAr3DvEM8wgCBA379gj5//tJBPAAAAnUlEQVRIx2NgAAM27fj/tAO/xBsYkIHyf9qCT8iWMf6nNQhAsk2f5rYheY7Dnua2/U+A28ZEe8v+F9Ax2v7/F4DbxkUH2wzgtvHTwbYPo7aN2jZq26hto7aN2jZq25Cy7Qvctnw62PYNbls9HWz7S8/G6//PsI6H4396gAUQy1je08W2jxDbpv6nD4gB2uWp+J9eYPsEhv/0BPS1DQBvoBLVZ3BppgAAAABJRU5ErkJggg==);
|
||||
-webkit-mask-size: 100% 100%;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
.list-icon,
|
||||
.list-title,
|
||||
.list-close {
|
||||
color: inherit;
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
.list-close {
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
138
src/views/layout/components/navbar-modeB.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="navBar-list-mode-b">
|
||||
<div class="list" :class="[storeRoutes.activeRoute == item[0] ? 'list-active' : '']" @click="goRoute(item[0])"
|
||||
v-for="item in storeRoutes.navList">
|
||||
<SvgIcon class="list-icon" v-if="storeConfigure.configure.navbarIcon" :name="item[1]?.meta?.icon"></SvgIcon>
|
||||
<span class="list-title">{{ item[1]?.meta?.title || item[0] }}</span>
|
||||
<SvgIcon class="list-close" name="Close" v-if="item[0] !== '/home'" @click.stop="closeNavbarItem(item[0])">
|
||||
</SvgIcon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
|
||||
import { useRoutes } from '@/store/routes.js'
|
||||
import { useConfigure } from '@/store/configure.js'
|
||||
|
||||
const storeRoutes = useRoutes()
|
||||
const storeConfigure = useConfigure()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
/**
|
||||
* @description: 路由跳转
|
||||
* @param {String} route: 要跳转的路由
|
||||
*/
|
||||
function goRoute(route) {
|
||||
router.push(route)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 关闭导航的某一项
|
||||
* @param {String} route: 要关闭的当前项
|
||||
*/
|
||||
function closeNavbarItem(route) {
|
||||
storeRoutes.deleteNavItem(route).then(res => {
|
||||
goRoute(res[0])
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navBar-list-mode-b {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0 5px;
|
||||
padding: 0 15px;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
overflow: overlay;
|
||||
overflow-y: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-icon {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
bottom: 0.05em;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
padding: 0 5px;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.list-close {
|
||||
font-size: 0px;
|
||||
transform: scale(0, 0);
|
||||
transition: all 0.3s;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.list {
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--el-color-info-light-7);
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
|
||||
&.list-active {
|
||||
color: var(--el-color-primary);
|
||||
border: 1px solid var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-info-light-7);
|
||||
|
||||
.list-icon,
|
||||
.list-title,
|
||||
.list-close {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.list-close {
|
||||
font-size: 14px;
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.list-close {
|
||||
&:hover {
|
||||
background-color: var(--el-text-color-primary);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
176
src/views/layout/components/navbar-modeC.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="navBar-list-mode-c">
|
||||
<div :class="[storeRoutes.activeRoute == item[0] ? 'list-active' : 'list']" @click="goRoute(item[0])" v-for="item in storeRoutes.navList">
|
||||
<SvgIcon class="list-icon" v-if="storeConfigure.configure.navbarIcon" :name="item[1]?.meta?.icon"></SvgIcon>
|
||||
<span class="list-title">{{ item[1]?.meta?.title || item[0] }}</span>
|
||||
<SvgIcon class="list-close" name="Close" v-if="item[0] !== '/home'" @click.stop="closeNavbarItem(item[0])"> </SvgIcon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
|
||||
const storeRoutes = useRoutes();
|
||||
const storeConfigure = useConfigure();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* @description: 路由跳转
|
||||
* @param {String} route: 要跳转的路由
|
||||
*/
|
||||
function goRoute(route) {
|
||||
router.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 关闭导航的某一项
|
||||
* @param {String} route: 要关闭的当前项
|
||||
*/
|
||||
function closeNavbarItem(route) {
|
||||
storeRoutes.deleteNavItem(route).then((res) => {
|
||||
goRoute(res[0]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navBar-list-mode-c {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0 5px;
|
||||
padding: 0 30px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
overflow: overlay;
|
||||
overflow-y: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-icon {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
bottom: 0.05em;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
padding: 0 5px;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.list-close {
|
||||
font-size: 0px;
|
||||
transform: scale(0, 0);
|
||||
transition: all 0.3s;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.list {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
height: 35px;
|
||||
padding: 0 10px;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 0;
|
||||
background-color: var(--el-color-primary);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
.list-icon,
|
||||
.list-title,
|
||||
.list-close {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.list-close {
|
||||
font-size: 14px;
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.list-close {
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-active {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--el-color-primary);
|
||||
height: 35px;
|
||||
padding: 0 10px;
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.list-icon,
|
||||
.list-title,
|
||||
.list-close {
|
||||
color: inherit;
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
.list-close {
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
139
src/views/layout/components/pageHeader.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="pageHeader">
|
||||
<div class="routes-case">
|
||||
<SvgIcon class="sidebar-icon" :name="storeConfigure.configure.collapse ? 'Expand' : 'Fold'" size="20" color="#555"
|
||||
@click="changeSidebar"></SvgIcon>
|
||||
<el-breadcrumb separator="/">
|
||||
<!-- <transition-group name="breadcrumb"> -->
|
||||
<el-breadcrumb-item v-for="item in storeRoutes.breadcrumb" :key="item.path">
|
||||
<span style="color: #333;">{{ item?.meta?.title || item.path
|
||||
}}</span></el-breadcrumb-item>
|
||||
<!-- </transition-group> -->
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
<div class="info-case">
|
||||
<!-- <SvgIcon class="info-icon" name="Operation" @click="operationFunction('operation')"></SvgIcon> -->
|
||||
<SvgIcon class="info-icon" name="Refresh" @click="operationFunction('refresh')"></SvgIcon>
|
||||
<el-dropdown style="cursor: pointer" @visible-change="changeDropdownVisible">
|
||||
<div class="flex align-center" style="gap: 0 10px">
|
||||
<!-- <img class="info-avatar" :src="storeUser.userInfo.avatar" alt="" srcset="" /> -->
|
||||
<p class="info-name">{{ storeUser.userInfo.loginName }}</p>
|
||||
<SvgIcon color="#333" name="arrow-down"></SvgIcon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<!-- <el-dropdown-item>个人中心</el-dropdown-item> -->
|
||||
<el-dropdown-item @click="logOut">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
import { useUser } from "@/store/user.js";
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
const storeUser = useUser();
|
||||
const storeRoutes = useRoutes();
|
||||
|
||||
// 获取配置
|
||||
const storeConfigure = useConfigure();
|
||||
|
||||
// 侧边栏导航的收起与展开
|
||||
function changeSidebar() {
|
||||
storeConfigure.change("collapse", !storeConfigure.configure.collapse);
|
||||
}
|
||||
|
||||
// 下拉菜单
|
||||
let showDropdown = ref(false);
|
||||
let rotateUserIcon = ref("0deg");
|
||||
function changeDropdownVisible() {
|
||||
showDropdown.value = !showDropdown.value;
|
||||
rotateUserIcon.value = showDropdown.value ? "180deg" : "0deg";
|
||||
}
|
||||
|
||||
// info 中的按钮操作
|
||||
const emits = defineEmits(["operation"]);
|
||||
function operationFunction(type) {
|
||||
emits("operation", type);
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
function logOut() {
|
||||
// 清除缓存 / token 等
|
||||
_hook.useLocalStorage.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
// 且刷新页面时 pinia 数据会重置
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 面包屑的动画
|
||||
.breadcrumb-enter-active,
|
||||
.breadcrumb-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.breadcrumb-enter-from,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
transition: all 0s ease;
|
||||
}
|
||||
|
||||
.pageHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
height: 60px;
|
||||
// border-bottom: 1px solid #efefef;
|
||||
}
|
||||
|
||||
.routes-case {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.sidebar-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-case {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0 16px;
|
||||
|
||||
.info-icon {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.info-avatar {
|
||||
border-radius: 2px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.info-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
124
src/views/layout/components/sidebar-modeA-sub.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<template v-if="props.item.children">
|
||||
<el-sub-menu
|
||||
:class="{ 'menu-sub-modeA': configure.menuMode == layoutModeEnum.key[0], 'menu-sub-modeB': configure.menuMode == layoutModeEnum.key[1] }"
|
||||
v-if="!props.item.meta?.isHide" :index="props.item?.path">
|
||||
<template #title>
|
||||
<SvgIcon class="route-icon" :name="props.item?.meta?.icon"></SvgIcon>
|
||||
<span class="route-title">{{ props.item?.meta?.title || props.item?.path }}</span>
|
||||
</template>
|
||||
<sidebar-modeA-sub v-for="route in props.item?.children" :item="route" :key="route.path"></sidebar-modeA-sub>
|
||||
</el-sub-menu>
|
||||
<template v-else>
|
||||
<sidebar-modeA-sub v-for="route in props.item?.children" :item="route" :key="route.path"></sidebar-modeA-sub>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-menu-item
|
||||
:class="{ 'active-menu': activeMenuCount(), 'is-active': activeMenuCount(), 'menu-modeA': configure.menuMode == layoutModeEnum.key[0], 'menu-modeB': configure.menuMode == layoutModeEnum.key[1] }"
|
||||
v-if="!props.item?.meta?.isHide" :index="props.item?.path">
|
||||
<SvgIcon class="route-icon" :name="props.item?.meta?.icon"></SvgIcon>
|
||||
<span class="route-title">{{ props.item?.meta?.title || props.item?.path }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const storeRoutes = useRoutes();
|
||||
const route = useRoute();
|
||||
|
||||
// 获取配置
|
||||
const storeConfigure = useConfigure();
|
||||
const { configure } = storeToRefs(storeConfigure);
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 获取枚举值
|
||||
const { layoutModeEnum } = ENUMS;
|
||||
|
||||
function activeMenuCount() {
|
||||
if (route.meta.activeMenu) {
|
||||
return props.item?.path == route.meta.activeMenu
|
||||
} else {
|
||||
return storeRoutes.activeRoute == props.item?.path
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.menu-sub-modeA {
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-sub-modeB {
|
||||
.el-sub-menu__title {
|
||||
margin: 0 5px;
|
||||
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-modeA {
|
||||
&:hover {
|
||||
// color: var(--el-menu-active-color);
|
||||
// background-color: var(--el-color-primary-light-3);
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-modeA.active-menu {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.menu-modeB {
|
||||
margin: 5px;
|
||||
border-radius: 5px;
|
||||
|
||||
.route-title,
|
||||
.route-icon {
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
.route-title,
|
||||
.route-icon {
|
||||
color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-modeB.active-menu {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
.route-title,
|
||||
.route-icon {
|
||||
color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
77
src/views/layout/components/sidebar-modeA.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="sidebar-mode-a">
|
||||
<div class="logo-case" v-if="configure.showMenuLogo">
|
||||
<img class="logo" src="/logo.png" alt="logo" srcset="" />
|
||||
<transition name="el-fade-in">
|
||||
<span v-show="!storeConfigure.configure.collapse" style="margin-left: 10px;">{{ storeConfigure.projectName
|
||||
}}</span>
|
||||
</transition>
|
||||
</div>
|
||||
<el-menu @select="goRoute" :collapse="configure.collapse" :default-active="defaultActive" style="border-right: none"
|
||||
class="el-menu-vertical-demo">
|
||||
<sidebar-modea-sub v-for="route in props.list" :key="route.path" :item="route" />
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
import sidebarModeaSub from "./sidebar-modeA-sub.vue";
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
let width = ref(props.width);
|
||||
|
||||
// 获取配置
|
||||
const storeConfigure = useConfigure();
|
||||
const { configure, defaultActive } = storeToRefs(storeConfigure);
|
||||
|
||||
/**
|
||||
* @description: 路由跳转
|
||||
* @param {String} index: 路由
|
||||
*/
|
||||
const router = useRouter();
|
||||
function goRoute(index) {
|
||||
router.push(index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: v-bind(width);
|
||||
}
|
||||
|
||||
.sidebar-mode-a {
|
||||
overflow-y: scroll;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-case {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
|
||||
.logo {
|
||||
height: 30px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
150
src/views/layout/components/sidebar-modeB.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="sidebar-mode-b">
|
||||
<el-scrollbar class="scrollbar">
|
||||
<div class="route-list" :class="{ 'active-route-list': storeRoutes.breadcrumbKeys.includes(item.path) }" v-for="item in data.routesList" @click="goRoute(item.path)">
|
||||
<SvgIcon class="route-icon" size="16" :name="item?.meta?.icon"></SvgIcon>
|
||||
<span class="route-title">{{ item?.meta?.title || item.path }}</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div :class="['sidebar-mode-b-menu', configure.collapse ? 'sidebar-switch' : 'sidebar-open']">
|
||||
<sidebar-mode-a menu-trigger="hover" :list="data.activeRouteChildren" width="190px"></sidebar-mode-a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import sidebarModeA from "./sidebar-modeA.vue";
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
|
||||
// 获取配置
|
||||
const storeConfigure = useConfigure();
|
||||
const { configure } = storeToRefs(storeConfigure);
|
||||
const route = useRoute();
|
||||
|
||||
// 获取路由 store
|
||||
const storeRoutes = useRoutes();
|
||||
|
||||
const data = reactive({
|
||||
routesList: [],
|
||||
activeRouteChildren: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 过滤侧栏可显示的一级路由
|
||||
* @param {Array} routes: 路由列表
|
||||
* @return {Array} 过滤好的路由
|
||||
*/
|
||||
function filterRoute(routes) {
|
||||
let arr = [];
|
||||
routes.forEach((i) => {
|
||||
if (i?.meta?.isHide == false || i?.meta?.isHide == undefined) {
|
||||
arr.push(i);
|
||||
} else {
|
||||
if (i?.children?.length > 0) {
|
||||
arr = filterRoute(i.children);
|
||||
}
|
||||
}
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 路由跳转
|
||||
* @param {String} index: 路由
|
||||
*/
|
||||
const router = useRouter();
|
||||
function goRoute(index) {
|
||||
router.push(index);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
data.routesList = filterRoute(storeRoutes.routesList);
|
||||
data.activeRouteChildren = storeRoutes.allRoutes[route.matched[1].path].children;
|
||||
if (data.activeRouteChildren.length == 0) {
|
||||
storeConfigure.change("collapse", true);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => storeRoutes.breadcrumbKeys,
|
||||
() => {
|
||||
data.activeRouteChildren = storeRoutes.allRoutes[route.matched[1].path]?.children;
|
||||
if (data?.activeRouteChildren?.length == 0) {
|
||||
storeConfigure.change("collapse", true);
|
||||
} else {
|
||||
storeConfigure.change("collapse", false);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebar-mode-b {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
border-right: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.route-list {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
border-radius: 5px;
|
||||
margin: 5px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary-light-3);
|
||||
|
||||
.route-icon,
|
||||
.route-title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
.route-icon,
|
||||
.route-title {
|
||||
text-align: center;
|
||||
color: var(--el-menu-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.active-route-list {
|
||||
background-color: var(--el-color-primary-light-3);
|
||||
|
||||
.route-icon,
|
||||
.route-title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-switch {
|
||||
width: 0px;
|
||||
overflow: hidden;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.sidebar-open {
|
||||
width: 190px;
|
||||
overflow: hidden;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.sidebar-mode-b-menu {
|
||||
overflow-y: scroll;
|
||||
background-color: var(--el-color-white);
|
||||
height: 100%;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
202
src/views/layout/layout.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="layout-container">
|
||||
<div class="layout-case">
|
||||
<div class="sidebar">
|
||||
<!-- 侧边栏的布局模式 -->
|
||||
<sidebar-mode-a v-if="getConfigure.menuMode == layoutModeEnum.key[0]" :list="storeRoutes.routesList"
|
||||
width="260px"></sidebar-mode-a>
|
||||
<sidebar-mode-b v-else-if="getConfigure.menuMode == layoutModeEnum.key[1]"></sidebar-mode-b>
|
||||
</div>
|
||||
<div class="layout-case" style="flex-direction: column">
|
||||
<header class="header">
|
||||
<page-header @operation="operation"></page-header>
|
||||
<navbar></navbar>
|
||||
</header>
|
||||
<main class="main">
|
||||
<!-- <router-view v-slot="{ Component }" v-if="isRefreshRoute">
|
||||
<transition :name="getConfigure.componentTransitionMode" mode="out-in"
|
||||
v-if="getConfigure.componentTransition">
|
||||
<keep-alive :include="storeRoutes.cachedRoute">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
<keep-alive v-else :include="storeRoutes.cachedRoute">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view> -->
|
||||
<router-view v-slot="{ Component }">
|
||||
<!-- <transition :name="getConfigure.componentTransitionMode" mode="out-in"> -->
|
||||
<!-- <keep-alive :include="storeRoutes.cachedRoute"> -->
|
||||
<component :is="Component" />
|
||||
<!-- </keep-alive> -->
|
||||
<!-- </transition> -->
|
||||
<!-- <keep-alive v-else :include="storeRoutes.cachedRoute"> -->
|
||||
<!-- <component v-else :is="Component" /> -->
|
||||
<!-- </keep-alive> -->
|
||||
</router-view>
|
||||
</main>
|
||||
<div class="version">©银收客 v{{ packageData.version }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<c-configure v-model="bools.showConfigure"></c-configure>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sidebarModeA from "./components/sidebar-modeA.vue";
|
||||
import sidebarModeB from "./components/sidebar-modeB.vue";
|
||||
import pageHeader from "./components/pageHeader.vue";
|
||||
import navbar from "./components/navBar.vue";
|
||||
import cConfigure from "./components/configure.vue";
|
||||
import { useConfigure } from "@/store/configure.js";
|
||||
import { useRoutes } from "@/store/routes.js";
|
||||
import NProgress from "nprogress";
|
||||
import "nprogress/nprogress.css";
|
||||
const storeRoutes = useRoutes();
|
||||
import packageData from '../../../package.json'
|
||||
|
||||
// 获取枚举值
|
||||
const { layoutModeEnum } = ENUMS;
|
||||
|
||||
// 获取配置
|
||||
const storeConfigure = useConfigure();
|
||||
const { configure } = storeToRefs(storeConfigure);
|
||||
const getConfigure = computed(() => {
|
||||
return configure.value;
|
||||
});
|
||||
|
||||
// 控制操作的布尔值
|
||||
const bools = reactive({
|
||||
showConfigure: false,
|
||||
});
|
||||
|
||||
// 页头的操作
|
||||
function operation(type) {
|
||||
switch (type) {
|
||||
case "operation":
|
||||
bools.showConfigure = true;
|
||||
break;
|
||||
case "refresh":
|
||||
reload();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 局部组件刷新
|
||||
const isRefreshRoute = ref(true);
|
||||
function reload() {
|
||||
NProgress.start();
|
||||
isRefreshRoute.value = false;
|
||||
nextTick(() => {
|
||||
isRefreshRoute.value = true;
|
||||
NProgress.done();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vab-drawer .el-drawer__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-icon .el-sub-menu__icon-arrow {
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-container {
|
||||
--versionH: 50px;
|
||||
}
|
||||
|
||||
.version {
|
||||
height: var(--versionH);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 页面切换的动画 模式 A
|
||||
.mainA-enter-active,
|
||||
.mainA-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.mainA-enter-from,
|
||||
.mainA-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateY(100px);
|
||||
}
|
||||
|
||||
.mainA-leave-active {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
transition: all 0s ease;
|
||||
}
|
||||
|
||||
// 页面切换的动画 模式 B
|
||||
.mainB-enter-active,
|
||||
.mainB-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.mainB-enter-from,
|
||||
.mainB-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(100px);
|
||||
}
|
||||
|
||||
.mainB-leave-active {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
transition: all 0s ease;
|
||||
}
|
||||
|
||||
.layout-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.layout-case {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
flex-basis: auto;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: var(--admin-column-bg-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: calc(100vh - 60px * 2 - var(--versionH));
|
||||
display: block;
|
||||
flex: 1;
|
||||
flex-basis: auto;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 15px 15px;
|
||||
background: var(--el-color-info-light-9);
|
||||
@extend .scrollbar-y;
|
||||
}
|
||||
</style>
|
||||