Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
839703dc1a |
@@ -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?
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link href="//at.alicdn.com/t/c/font_3654542_filhj6brvds.css" />
|
||||||
|
<title>后台管理系统</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
<!-- 百度地图引入 -->
|
||||||
|
<!-- <script type="text/javascript"
|
||||||
|
src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=Ol0d1CqPErteakupIcX4actXo6GiQI7g"></script> -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "vue-manage-system",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"dev": "vite --host",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@braks/vue-flow": "^0.4.38",
|
||||||
|
"@element-plus/icons-vue": "^1.1.4",
|
||||||
|
"@kangc/v-md-editor": "^2.3.15",
|
||||||
|
"@wangeditor/editor": "^5.1.14",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
|
"axios": "^0.27.2",
|
||||||
|
"dingtalk-jsapi": "^2.15.6",
|
||||||
|
"driver.js": "^0.9.8",
|
||||||
|
"echarts": "^5.3.3",
|
||||||
|
"element-plus": "^2.1.11",
|
||||||
|
"js-base64": "^3.7.2",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"screenfull": "^6.0.1",
|
||||||
|
"video.js": "^7.20.3",
|
||||||
|
"vue": "^3.2.25",
|
||||||
|
"vue-router": "^4.0.13",
|
||||||
|
"vue-video-player": "^6.0.0",
|
||||||
|
"vue3-print-nb": "^0.1.4",
|
||||||
|
"vuedraggable": "^4.1.0",
|
||||||
|
"vuex": "^4.0.2",
|
||||||
|
"vuex-persistedstate": "^4.1.0",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^2.3.1",
|
||||||
|
"sass": "^1.51.0",
|
||||||
|
"vite": "^2.9.7",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
|
"vite-plugin-mock": "^2.9.6",
|
||||||
|
"vue-cli-plugin-style-resources-loader": "~0.1.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<router-view v-if="isRouterAlive"></router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, nextTick, provide } from "vue";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
// 局部组件刷新
|
||||||
|
const isRouterAlive = ref(true);
|
||||||
|
const globalStore = useStore();
|
||||||
|
const reload = () => {
|
||||||
|
isRouterAlive.value = false;
|
||||||
|
nextTick(() => {
|
||||||
|
isRouterAlive.value = true;
|
||||||
|
console.log("数据刷新成功");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
provide("reload", reload);
|
||||||
|
onMounted(() => {
|
||||||
|
globalStore.dispatch("user/changeThem", "#4060c7");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import service from "../request.js";
|
||||||
|
|
||||||
|
// 获取组别列表
|
||||||
|
export function getGroupList() {
|
||||||
|
return service({
|
||||||
|
url: "/group/list",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加组别
|
||||||
|
export function addGroup(data) {
|
||||||
|
return service({
|
||||||
|
url: "/group",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改组别名称
|
||||||
|
export function updateGroup(data) {
|
||||||
|
return service({
|
||||||
|
url: "/group",
|
||||||
|
method: "put",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除组别
|
||||||
|
export function deleteGroup(groupId) {
|
||||||
|
return service({
|
||||||
|
url: `/group/${groupId}`,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取未分组人员列表
|
||||||
|
export function getNonGroupUsers() {
|
||||||
|
return service({
|
||||||
|
url: "/user/non/group",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加/修改组内人员角色
|
||||||
|
export function updateGroupUserRole(groupId, userid, role) {
|
||||||
|
return service({
|
||||||
|
url: `/group/${groupId}/user/${userid}/role/${role}`,
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取组内人员列表
|
||||||
|
export function getGroupUsers(groupId) {
|
||||||
|
return service({
|
||||||
|
url: `/user/${groupId}`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除组内人员
|
||||||
|
export function deleteGroupUser(groupId, userid) {
|
||||||
|
return service({
|
||||||
|
url: `/group/user/${groupId}/${userid}`,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import router from "../router/router";
|
||||||
|
|
||||||
|
const errorCode = {
|
||||||
|
401: "登录状态已过期,即将返回首页重新登录。",
|
||||||
|
403: "当前操作没有权限",
|
||||||
|
404: "访问资源不存在",
|
||||||
|
default: "系统未知错误,请反馈给管理员",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 肖星越服务
|
||||||
|
const url = "https://paystubs.vip.cpolar.top";
|
||||||
|
|
||||||
|
// 袁永军测试服务
|
||||||
|
// const url = "https://crm-test.scdswj.cn/prod-api";
|
||||||
|
|
||||||
|
// 正式环境
|
||||||
|
// const url = "https://paystubs.vip.cpolar.top";
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: url,
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request interceptors
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
if (sessionStorage.getItem("token")) {
|
||||||
|
config.headers["Authorization"] =
|
||||||
|
"Bearer " + sessionStorage.getItem("token");
|
||||||
|
}
|
||||||
|
if (sessionStorage.getItem("deptId")) {
|
||||||
|
config.headers["selected-dept-id"] = sessionStorage.getItem("deptId");
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = config.params;
|
||||||
|
let newData = {};
|
||||||
|
for (let key in params) {
|
||||||
|
if (params[key] !== null && params[key] !== "") {
|
||||||
|
newData[key] = params[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.params = newData;
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Response interceptors
|
||||||
|
service.interceptors.response.use(
|
||||||
|
(res) => {
|
||||||
|
// 对响应数据做点什么
|
||||||
|
const response = res.data;
|
||||||
|
const code = res.data.code || 200;
|
||||||
|
const message = errorCode[code] || res.data.msg || errorCode["default"];
|
||||||
|
if (code === 401) {
|
||||||
|
if (res.code === 401 || res.code === 4001) {
|
||||||
|
// 清除浏览器全部临时缓存
|
||||||
|
// sessionStorage.clear();
|
||||||
|
// localStorage.clear();
|
||||||
|
router.push("/login");
|
||||||
|
// store.commit('setMenuData', {});
|
||||||
|
// resetRouter(); // 重置路由
|
||||||
|
ElMessageBox.alert("你已被登出,请重新登录", "提示", {})
|
||||||
|
.then(() => {})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
} else if (code === 500) {
|
||||||
|
// Message({ message: msg, type: 'error' })
|
||||||
|
ElMessage.error(message);
|
||||||
|
return Promise.reject(new Error(msg));
|
||||||
|
} else if (code === 601) {
|
||||||
|
// Message({ message: msg, type: 'warning' })
|
||||||
|
ElMessage.error(message);
|
||||||
|
return Promise.reject("error");
|
||||||
|
} else if (code != 200) {
|
||||||
|
ElMessage.error(message);
|
||||||
|
return Promise.reject("error");
|
||||||
|
} else {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log("走了错误的", error);
|
||||||
|
// 对响应错误做点什么
|
||||||
|
if (error.message.indexOf("timeout") != -1) {
|
||||||
|
ElMessage.error("网络超时");
|
||||||
|
} else if (error.message == "Network Error") {
|
||||||
|
ElMessage.error("网络连接错误");
|
||||||
|
} else if (error.request.status == "401") {
|
||||||
|
console.log("登录过期捕获到");
|
||||||
|
sessionStorage.clear();
|
||||||
|
router.push("/login");
|
||||||
|
ElMessage.error("登录信息过期");
|
||||||
|
} else {
|
||||||
|
ElMessage.error(
|
||||||
|
error.response.data.errmsg ||
|
||||||
|
error.response.data.error ||
|
||||||
|
error.response.data.errMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default service;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
export const showMessage = (status) => {
|
||||||
|
let message = "";
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
message = "请求错误(400)";
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
message = "未授权,请重新登录(401)";
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
message = "拒绝访问(403)";
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
message = "请求出错(404)";
|
||||||
|
break;
|
||||||
|
case 408:
|
||||||
|
message = "请求超时(408)";
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
message = "服务器错误(500)";
|
||||||
|
break;
|
||||||
|
case 501:
|
||||||
|
message = "服务未实现(501)";
|
||||||
|
break;
|
||||||
|
case 502:
|
||||||
|
message = "网络错误(502)";
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
message = "服务不可用(503)";
|
||||||
|
break;
|
||||||
|
case 504:
|
||||||
|
message = "网络超时(504)";
|
||||||
|
break;
|
||||||
|
case 505:
|
||||||
|
message = "HTTP版本不受支持(505)";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
message = `连接出错(${status})!`;
|
||||||
|
}
|
||||||
|
return `${message},请检查网络或联系管理员!`;
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 868 B |
|
After Width: | Height: | Size: 447 B |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="videoPlay">
|
||||||
|
<video
|
||||||
|
ref="m3u8_video"
|
||||||
|
class="video-js vjs-default-skin vjs-big-play-centered"
|
||||||
|
controls
|
||||||
|
>
|
||||||
|
<source :src="videoSrc" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
|
import videojs from 'video.js'
|
||||||
|
import 'video.js/dist/video-js.css'
|
||||||
|
const props = defineProps({
|
||||||
|
videoSrc: String,
|
||||||
|
autoPlay: Boolean,
|
||||||
|
})
|
||||||
|
|
||||||
|
const m3u8_video = ref()
|
||||||
|
let player
|
||||||
|
const initPlay = async () => {
|
||||||
|
videojs.addLanguage('zh-CN', zh)
|
||||||
|
await nextTick()
|
||||||
|
const options = {
|
||||||
|
muted: true,
|
||||||
|
controls: true,
|
||||||
|
autoplay: true,
|
||||||
|
loop: true,
|
||||||
|
language: 'zh-CN',
|
||||||
|
techOrder: ['html5'],
|
||||||
|
}
|
||||||
|
player = videojs(m3u8_video.value, options, () => {
|
||||||
|
videojs.log('播放器已经准备好了!')
|
||||||
|
if (props.autoPlay && props.videoSrc) {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
player.on('ended', () => {
|
||||||
|
videojs.log('播放结束了!')
|
||||||
|
})
|
||||||
|
player.on('error', () => {
|
||||||
|
videojs.log('播放器解析出错!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
initPlay()
|
||||||
|
})
|
||||||
|
//直接改变路径测试
|
||||||
|
watch(
|
||||||
|
() => props.videoSrc,
|
||||||
|
() => {
|
||||||
|
player.pause()
|
||||||
|
player.src(props.videoSrc)
|
||||||
|
player.load()
|
||||||
|
if (props.videoSrc) {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
player?.dispose()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.videoPlay {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.video-js {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: fill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.vjs-tech) {
|
||||||
|
object-fit: fill;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialogVisible" title="添加数据">
|
||||||
|
<el-form v-model="uploadFrom">
|
||||||
|
<el-form-item label="文件上传:">
|
||||||
|
<el-upload
|
||||||
|
style="width: 80%"
|
||||||
|
drag
|
||||||
|
multiple
|
||||||
|
:limit="excelLimit"
|
||||||
|
:on-success="uploadSuccess"
|
||||||
|
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||||
|
<div class="el-upload__text">将文件拖到此处<em>或点击上传</em></div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="数据覆盖:">
|
||||||
|
<el-switch v-model="is_cover" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="dialogVisible = false">确认</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="filesUpload">
|
||||||
|
import { ref, defineEmits } from 'vue'
|
||||||
|
const excelLimit = ref(1)
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const parameter = ref({})
|
||||||
|
const is_cover = ref(false)
|
||||||
|
const emit = defineEmits(['acceptParams'])
|
||||||
|
|
||||||
|
// 接收父组件参数
|
||||||
|
const acceptParams = (params) => {
|
||||||
|
parameter.value = params
|
||||||
|
emit('gatewayData', parameter.value)
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
const uploadExcel = (param) => {
|
||||||
|
console.log(param)
|
||||||
|
}
|
||||||
|
const uploadSuccess = (files) => {
|
||||||
|
console.log('上传成功' + JSON.stringify(files))
|
||||||
|
}
|
||||||
|
// 接受父组件得参数
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import draggable from "./modules/draggable";
|
||||||
|
import copy from "./modules/copy";
|
||||||
|
import debounce from "./modules/debounce";
|
||||||
|
import longPress from "./modules/longPress";
|
||||||
|
import throttle from "./modules/throttle";
|
||||||
|
|
||||||
|
const directivesList = {
|
||||||
|
draggable,
|
||||||
|
copy,
|
||||||
|
debounce,
|
||||||
|
longPress,
|
||||||
|
throttle
|
||||||
|
};
|
||||||
|
|
||||||
|
const directives = {
|
||||||
|
install: function (app) {
|
||||||
|
Object.keys(directivesList).forEach((key) => {
|
||||||
|
// 注册所有自定义指令
|
||||||
|
app.directive(key, directivesList[key]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default directives;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
需求:实现一个复制指定。复制指定内容至剪贴板
|
||||||
|
1.动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域
|
||||||
|
2.将要复制的值赋给 textarea 标签的 value 属性,并插入到 body
|
||||||
|
3.选中值 textarea 并复制
|
||||||
|
4.将 body 中插入的 textarea 移除
|
||||||
|
5.在第一次调用时绑定事件,在解绑时移除事件
|
||||||
|
*/
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
export const copy = {
|
||||||
|
mounted: function (el, { value }) {
|
||||||
|
console.log(arguments);
|
||||||
|
el.$value = value;
|
||||||
|
|
||||||
|
el.onclick = () => {
|
||||||
|
if (!el.$value) {
|
||||||
|
// 值为空的时候,给出提示。可根据项目UI仔细设计
|
||||||
|
ElMessage({
|
||||||
|
type: "error",
|
||||||
|
message: "无复制内容",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 动态创建 textarea 标签
|
||||||
|
const textarea = document.createElement("input");
|
||||||
|
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
|
||||||
|
textarea.readOnly = "readonly";
|
||||||
|
textarea.style.position = "absolute";
|
||||||
|
textarea.style.left = "-9999px";
|
||||||
|
// 将要 copy 的值赋给 textarea 标签的 value 属性
|
||||||
|
textarea.value = el.$value;
|
||||||
|
// 将 textarea 插入到 body 中
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
// 选中值并复制
|
||||||
|
textarea.select();
|
||||||
|
const result = document.execCommand("Copy");
|
||||||
|
if (result) {
|
||||||
|
ElMessage({
|
||||||
|
type: "success",
|
||||||
|
message: "复制成功",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
};
|
||||||
|
// 绑定点击事件,就是所谓的一键 copy 啦
|
||||||
|
el.addEventListener("click", el.handler);
|
||||||
|
},
|
||||||
|
// 当传进来的值更新的时候触发
|
||||||
|
beforeUpdate(el, { value }) {
|
||||||
|
el.$value = value;
|
||||||
|
},
|
||||||
|
// 指令与元素解绑的时候,移除事件绑定
|
||||||
|
unmounted(el) {
|
||||||
|
el.removeEventListener("click", el.handler);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default copy;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* v-debounce
|
||||||
|
* 按钮防抖指令,可自行扩展至input
|
||||||
|
* 接收参数:function类型
|
||||||
|
*/
|
||||||
|
|
||||||
|
const debounce = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
if (typeof binding.value != "function") {
|
||||||
|
console.log("callback must be a function");
|
||||||
|
}
|
||||||
|
let timer = null;
|
||||||
|
el.__handleClick__ = function () {
|
||||||
|
// if (timer) clearInterval(timer);
|
||||||
|
//
|
||||||
|
timer && clearTimeout(timer)
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
binding.value();
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
el.addEventListener("click", el.__handleClick__);
|
||||||
|
},
|
||||||
|
beforeUnmount(el) {
|
||||||
|
el.removeEventListener("click", el.__handleClick__);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default debounce;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1、设置需要拖拽的元素为absolute,其父元素为relative。
|
||||||
|
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
|
||||||
|
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
|
||||||
|
4、鼠标松开(onmouseup)时完成一次拖拽
|
||||||
|
|
||||||
|
使用:在 Dom 上加上 v-draggable 即可
|
||||||
|
<div class="dialog-model" v-draggable></div>
|
||||||
|
*/
|
||||||
|
const draggable = {
|
||||||
|
mounted: function (el) {
|
||||||
|
console.log(el)
|
||||||
|
el.style.cursor = "move";
|
||||||
|
el.style.position = "absolute";
|
||||||
|
el.onmousedown = function (e) {
|
||||||
|
let disX = e.pageX - el.offsetLeft;
|
||||||
|
let disY = e.pageY - el.offsetTop;
|
||||||
|
document.onmousemove = function (e) {
|
||||||
|
let x = e.pageX - disX;
|
||||||
|
let y = e.pageY - disY;
|
||||||
|
console.log(el.parentNode.offsetWidth)
|
||||||
|
let maxX = el.parentNode.offsetWidth - el.offsetWidth;
|
||||||
|
let maxY = el.parentNode.offsetHeight - el.offsetHeight;
|
||||||
|
if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
} else if (x > maxX) {
|
||||||
|
x = maxX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
} else if (y > maxY) {
|
||||||
|
y = maxY;
|
||||||
|
}
|
||||||
|
el.style.left = x + "px";
|
||||||
|
el.style.top = y + "px";
|
||||||
|
};
|
||||||
|
document.onmouseup = function () {
|
||||||
|
document.onmousemove = document.onmouseup = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default draggable;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
const longPress = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
if (typeof binding.value != "function") {
|
||||||
|
console.log("callback must be a function");
|
||||||
|
}
|
||||||
|
let pressTimer = null;
|
||||||
|
let start = (e) => {
|
||||||
|
if (e.type === "click" && e.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pressTimer === null) {
|
||||||
|
pressTimer = setTimeout(() => {
|
||||||
|
// 执行函数
|
||||||
|
handler();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 取消计时器
|
||||||
|
let cancel = () => {
|
||||||
|
// 检查计时器是否有值
|
||||||
|
if (pressTimer !== null) {
|
||||||
|
clearTimeout(pressTimer);
|
||||||
|
pressTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 运行函数
|
||||||
|
const handler = (e) => {
|
||||||
|
// 执行传递给指令的方法
|
||||||
|
binding.value(e);
|
||||||
|
console.log(e);
|
||||||
|
};
|
||||||
|
// 添加事件监听器
|
||||||
|
el.addEventListener("mousedown", start);
|
||||||
|
el.addEventListener("touchstart", start);
|
||||||
|
el.addEventListener("click", start);
|
||||||
|
// 取消计时器
|
||||||
|
el.addEventListener("click", cancel);
|
||||||
|
el.addEventListener("mouseout", cancel);
|
||||||
|
el.addEventListener("touchend", cancel);
|
||||||
|
el.addEventListener("touchcancel", cancel);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default longPress;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 一段时间内控制请求的频率
|
||||||
|
* 上一次时间触发的时间和这次触发的时间相减 传入节流的时间
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* 应用场景:scroll加载的时候 和input框输入的情况
|
||||||
|
*/
|
||||||
|
|
||||||
|
const throttle = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
if (typeof binding.value != "function") {
|
||||||
|
console.log("callback must be a function");
|
||||||
|
}
|
||||||
|
let lastTime = 0
|
||||||
|
|
||||||
|
let newtimer = +new Date(); //拿到当前的时间
|
||||||
|
el.__handleClick__ = function () {
|
||||||
|
if (newtimer - lastTime > 3000) {
|
||||||
|
binding.value()
|
||||||
|
lastTime = newtimer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
el.addEventListener("click", el.__handleClick__);
|
||||||
|
},
|
||||||
|
beforeUnmount(el) {
|
||||||
|
el.removeEventListener("click", el.__handleClick__);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default throttle
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tags">
|
||||||
|
<el-tag
|
||||||
|
ref="tagRef"
|
||||||
|
v-for="tag in tags"
|
||||||
|
:key="tag.title"
|
||||||
|
class="mR10 tabs"
|
||||||
|
:closable="tag.close"
|
||||||
|
:effect="tag.checked ? 'dark' : 'plain'"
|
||||||
|
@click="tagChange(tag)"
|
||||||
|
@close="delectTag(tag, index)"
|
||||||
|
>
|
||||||
|
<el-icon class="tabs-icon">
|
||||||
|
<component :is="tag.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
{{ tag.title }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
|
import store from '../../store/index.js'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
const tabsMenuValue = ref('')
|
||||||
|
const nameScroll = ref(false)
|
||||||
|
const tagRef = ref()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const globalStore = useStore() // 该方法用于返回store 实例
|
||||||
|
// 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue)
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
() => {
|
||||||
|
let params = {
|
||||||
|
title: route.meta.name,
|
||||||
|
url: route.path,
|
||||||
|
icon: 'Menu',
|
||||||
|
close: true,
|
||||||
|
checked: true,
|
||||||
|
}
|
||||||
|
globalStore.dispatch('tabs/addTabs', params)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const tags = store.getters.tabsMenuList
|
||||||
|
|
||||||
|
const tagChange = (item) => {
|
||||||
|
item.checked = true
|
||||||
|
router.push(item.url)
|
||||||
|
}
|
||||||
|
const delectTag = (item, index) => {
|
||||||
|
globalStore.dispatch('tabs/delectTag', item)
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tags {
|
||||||
|
width: 95%;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
overflow: scroll;
|
||||||
|
white-space: nowrap;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tabs {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<el-icon class="icon-style" id="collapseIcon" @click="changeShrinkage">
|
||||||
|
<component :is="!isCollapse ? 'Fold' : 'Expand'"></component>
|
||||||
|
</el-icon>
|
||||||
|
<el-breadcrumb separator="/" id="breadcrumb">
|
||||||
|
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
|
||||||
|
<span class="no-redirect" v-if="index === breadcrumbList.length - 1" :style="{ color: themeConfig.footColor }">{{ item.meta.name }}</span>
|
||||||
|
<span class="redirect" v-else @click="handleRedirect(item.path)" :style="{ color: themeConfig.footColor }">{{ item.meta.name }}</span>
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { watch, ref } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import store from "../../../store";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const breadcrumbList = ref([]);
|
||||||
|
const isCollapse = ref(false);
|
||||||
|
const globalStore = useStore();
|
||||||
|
const themeConfig = store.getters.themeConfig;
|
||||||
|
const initBreadcrumbList = () => {
|
||||||
|
breadcrumbList.value = route.matched;
|
||||||
|
console.log(route.matched);
|
||||||
|
};
|
||||||
|
const handleRedirect = (path) => {
|
||||||
|
router.push(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeShrinkage = () => {
|
||||||
|
isCollapse.value = !store.getters.isCollapse;
|
||||||
|
globalStore.dispatch("user/changeIsCollapse", isCollapse.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
route,
|
||||||
|
() => {
|
||||||
|
initBreadcrumbList();
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.no-redirect {
|
||||||
|
// color: #97a8be;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
.redirect {
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #97a8be;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon-style {
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export const steps = () => [{
|
||||||
|
element: "#guide",
|
||||||
|
popover: {
|
||||||
|
title: "菜单栏",
|
||||||
|
description: "Body of the popover",
|
||||||
|
position: "left",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: "#breadcrumb",
|
||||||
|
popover: {
|
||||||
|
title: "标签页",
|
||||||
|
description: "",
|
||||||
|
position: "bottom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
element: "#screenFul",
|
||||||
|
popover: {
|
||||||
|
title: "全屏",
|
||||||
|
description: "Body of the popover",
|
||||||
|
position: "bottom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<el-icon @click="handleDriver"><Position /></el-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Driver from 'driver.js'
|
||||||
|
import 'driver.js/dist/driver.min.css'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { steps } from './index.js'
|
||||||
|
|
||||||
|
let driver = ref('')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
driver.value = new Driver({
|
||||||
|
className: 'scoped-class', //包裹driver.js弹窗的类名 className to wrap driver.js popover
|
||||||
|
animate: true, // 高亮元素改变时是否显示动画 Animate while changing highlighted element
|
||||||
|
opacity: 0.75, //背景透明度(0 表示只有弹窗并且没有遮罩) Background opacity (0 means only popovers and without overlay)
|
||||||
|
padding: 10, // 元素与边缘的距离 Distance of element from around the edges
|
||||||
|
allowClose: true, // 是否允许点击遮罩时关闭 Whether clicking on overlay should close or not
|
||||||
|
overlayClickNext: false, //是否允许点击遮罩时移到到下一步 Should it move to next step on overlay click
|
||||||
|
doneBtnText: '完成', // 最终按钮上的文本 Text on the final button
|
||||||
|
closeBtnText: '跳过', // 当前步骤关闭按钮上的文本 Text on the close button for this step
|
||||||
|
nextBtnText: '下一步', //当前步骤下一步按钮上的文本 Next button text for this step
|
||||||
|
prevBtnText: '上一步',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const handleDriver = () => {
|
||||||
|
driver.value.defineSteps(steps)
|
||||||
|
driver.value.start()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<el-icon class="icon-style" @click="dialogVisible = true"><Search /></el-icon>
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogVisible" :show-close="false">
|
||||||
|
<template #header>
|
||||||
|
<el-input
|
||||||
|
v-model="searchValue"
|
||||||
|
placeholder="搜索"
|
||||||
|
class="input-with-select"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<el-button :icon="Search" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
<div class="centent col-center">暂无搜索结果🦁🦁🦁🦁</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="footer-tip flx-row">
|
||||||
|
<span>↩</span> 确认 <span class="ml10">⇅</span>切换
|
||||||
|
<span class="ml10" style="font-size: 10px">ESC</span>关闭
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Search, Top, Bottom } from '@element-plus/icons-vue'
|
||||||
|
import { nextTick, onMounted, ref } from 'vue'
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const searchValue = ref('')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
document.addEventListener('keyup', function (e) {
|
||||||
|
if (e.keyCode == 27) {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.input-with-select {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.footer-tip {
|
||||||
|
height: 20px;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
width: 20px;
|
||||||
|
height: 18px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
margin-right: 0.4em;
|
||||||
|
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff,
|
||||||
|
0 1px 2px 1px #1e235a66;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<el-dropdown placement="bottom">
|
||||||
|
<div class="flx-center">
|
||||||
|
<div class="avatar">
|
||||||
|
<img src="../../../assets/images/avart.jpg" alt="avatar" />
|
||||||
|
</div>
|
||||||
|
<span class="username" :style="{ color: themeConfig.footColor }">BIG CUTE</span>
|
||||||
|
</div>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<!-- <el-dropdown-item>个人中心</el-dropdown-item> -->
|
||||||
|
<el-dropdown-item @click="gotoLogin">退出登录</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ArrowDown } from "@element-plus/icons-vue";
|
||||||
|
import { nextTick, onMounted, ref } from "vue";
|
||||||
|
import store from "../../../store/index.js";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
const themeConfig = store.getters.themeConfig;
|
||||||
|
const handleClick = () => {
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
alert("button click");
|
||||||
|
};
|
||||||
|
|
||||||
|
const gotoLogin = () => {
|
||||||
|
sessionStorage.removeItem("token");
|
||||||
|
router.push("/login");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-dropdown {
|
||||||
|
cursor: pointer;
|
||||||
|
.el-tooltip__trigger {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.username {
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
margin: 0 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<el-icon id="fullscreen" class="icon-style" @click="handleFullScreen">
|
||||||
|
<component :is="icon ? 'Rank' : 'FullScreen'"></component>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import screenfull from 'screenfull'
|
||||||
|
import { ref, onMounted, onBeforeMount } from 'vue'
|
||||||
|
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
let icon = ref(screenfull.isFullscreen)
|
||||||
|
const handleFullScreen = () => {
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeIcon = () => {
|
||||||
|
icon.value = screenfull.isFullscreen
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
screenfull.on('change', changeIcon)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
screenfull.off('change')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<el-popover :width="400" placement="bottom" trigger="click">
|
||||||
|
<!-- <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"> -->
|
||||||
|
<el-tabs v-model="activeName" class="demo-tabs">
|
||||||
|
<el-tab-pane label="通知" name="first">
|
||||||
|
<div class="notice-lists">
|
||||||
|
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
|
||||||
|
<span class="type">通知</span> {{ item.text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="公告" name="second">
|
||||||
|
<div class="notice-lists">
|
||||||
|
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
|
||||||
|
<span class="type">公告</span> {{ item.text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="系统消息" name="third">
|
||||||
|
<div class="notice-lists">
|
||||||
|
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
|
||||||
|
<span class="type" style="color: rgb(47, 96, 194); background-color: #e8f3ff"
|
||||||
|
>系统消息</span
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<template #reference>
|
||||||
|
<el-icon class="icon-style" @click="visible = !visible"><bell /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
<script setup name="messages">
|
||||||
|
import { Bell } from "@element-plus/icons-vue";
|
||||||
|
import { nextTick, onMounted, ref } from "vue";
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const activeName = ref("first");
|
||||||
|
const notList = ref("first");
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.icon-style {
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 11px;
|
||||||
|
}
|
||||||
|
.notice-lists {
|
||||||
|
height: 260px;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.type {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 0px;
|
||||||
|
color: #fa4a1e;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fa4a1e14;
|
||||||
|
|
||||||
|
&.error-type {
|
||||||
|
color: #4060c7;
|
||||||
|
background: #e8f3ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<el-icon @click="openDrawer" class="icon-style"><Setting /></el-icon>
|
||||||
|
<el-drawer v-model="drawerVisible" title="布局设置" size="300px">
|
||||||
|
<el-divider>主题颜色</el-divider>
|
||||||
|
<div class="flx-row">
|
||||||
|
<div
|
||||||
|
class="theme-item"
|
||||||
|
v-for="item in colorList"
|
||||||
|
:key="item"
|
||||||
|
:style="{ backgroundColor: item }"
|
||||||
|
@click="changePrimary(item)"
|
||||||
|
>
|
||||||
|
<el-icon v-if="item == themeConfig.primary" class="icon"
|
||||||
|
><Select
|
||||||
|
/></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-divider>导航栏颜色</el-divider>
|
||||||
|
<div class="flx-row">
|
||||||
|
<div
|
||||||
|
class="theme-item"
|
||||||
|
v-for="item in tabColorList"
|
||||||
|
:key="item"
|
||||||
|
:style="{ backgroundColor: item }"
|
||||||
|
@click="changeTabCole(item)"
|
||||||
|
>
|
||||||
|
<el-icon v-if="item == themeConfig.tabColor" class="icon"
|
||||||
|
><Select
|
||||||
|
/></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <el-switch v-model="value2" class="mt-2" style="margin-left: 24px" inline-prompt :active-icon="Check" :inactive-icon="Close" /> -->
|
||||||
|
<div class="flx-row">
|
||||||
|
<div class="flx-tit">侧边栏模式</div>
|
||||||
|
<el-switch
|
||||||
|
v-model="value2"
|
||||||
|
@change="changeGreyOrWeak($event)"
|
||||||
|
class="mt-2"
|
||||||
|
style="margin-left: 24px"
|
||||||
|
inline-prompt
|
||||||
|
:active-icon="Sunny"
|
||||||
|
:inactive-icon="Moon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flx-row">
|
||||||
|
<div class="flx-tit">标签栏是否显示</div>
|
||||||
|
<el-switch
|
||||||
|
v-model="istags"
|
||||||
|
@change="changeTags($event)"
|
||||||
|
class="mt-2"
|
||||||
|
style="margin-left: 24px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="theme-item">
|
||||||
|
<el-color-picker
|
||||||
|
v-model="themeConfig.primary"
|
||||||
|
:predefine="colorList"
|
||||||
|
@change="changePrimary"
|
||||||
|
>
|
||||||
|
</el-color-picker>
|
||||||
|
</div> -->
|
||||||
|
<!-- <div class="theme-item">
|
||||||
|
<span>暗黑模式</span>
|
||||||
|
<SwitchDark></SwitchDark>
|
||||||
|
</div>
|
||||||
|
<div class="theme-item">
|
||||||
|
<span>灰色模式</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="themeConfig.isGrey"
|
||||||
|
@change="changeGreyOrWeak($event, 'grey')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="theme-item">
|
||||||
|
<span>色弱模式</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="themeConfig.isWeak"
|
||||||
|
@change="changeGreyOrWeak($event, 'weak')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<el-divider class="divider" content-position="center">
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
界面设置
|
||||||
|
</el-divider>
|
||||||
|
<div class="theme-item">
|
||||||
|
<span>折叠菜单</span>
|
||||||
|
<el-switch v-model="isCollapse" />
|
||||||
|
</div>
|
||||||
|
<div class="theme-item">
|
||||||
|
<span>面包屑导航</span>
|
||||||
|
<el-switch v-model="themeConfig.breadcrumb" />
|
||||||
|
</div>
|
||||||
|
<div class="theme-item">
|
||||||
|
<span>标签栏</span>
|
||||||
|
<el-switch v-model="themeConfig.tabs" />
|
||||||
|
</div>
|
||||||
|
<div class="theme-item">
|
||||||
|
<span>页脚</span>
|
||||||
|
<el-switch v-model="themeConfig.footer" />
|
||||||
|
</div> -->
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, useAttrs } from 'vue'
|
||||||
|
import { Sunny, Moon } from '@element-plus/icons-vue'
|
||||||
|
import store from '../../../store/index.js'
|
||||||
|
import { mix } from '../../../utils/color.js'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
const value2 = ref(true)
|
||||||
|
const istags = computed(() => {
|
||||||
|
return store.getters.themeConfig.istags
|
||||||
|
})
|
||||||
|
// 预定义主题颜色
|
||||||
|
const colorList = [
|
||||||
|
'#4060c7',
|
||||||
|
'#409EFF',
|
||||||
|
'#009688',
|
||||||
|
'#27ae60',
|
||||||
|
'#e74c3c',
|
||||||
|
'#fd726d',
|
||||||
|
'#f39c12',
|
||||||
|
'#9b59b6',
|
||||||
|
]
|
||||||
|
// 预导航栏颜色
|
||||||
|
const tabColorList = [
|
||||||
|
'#FFFFFF',
|
||||||
|
'#333333',
|
||||||
|
'#009688',
|
||||||
|
'#27ae60',
|
||||||
|
'#e74c3c',
|
||||||
|
'#fd726d',
|
||||||
|
'#f39c12',
|
||||||
|
'#9b59b6',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 主题初始化
|
||||||
|
const globalStore = useStore()
|
||||||
|
const themeConfig = computed(() => {
|
||||||
|
return store.getters.themeConfig
|
||||||
|
})
|
||||||
|
|
||||||
|
const changePrimary = (val) => {
|
||||||
|
globalStore.dispatch('user/changeThem', val)
|
||||||
|
}
|
||||||
|
const changeTabCole = (val) => {
|
||||||
|
globalStore.dispatch('user/changeTabColor', val)
|
||||||
|
}
|
||||||
|
const drawerVisible = ref(false)
|
||||||
|
const openDrawer = () => {
|
||||||
|
drawerVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeGreyOrWeak = (e) => {
|
||||||
|
globalStore.dispatch('user/changeMenuColor', e)
|
||||||
|
}
|
||||||
|
const changeTags = (e) => {
|
||||||
|
globalStore.dispatch('user/changeTags', e)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.icon-style {
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 11px;
|
||||||
|
}
|
||||||
|
.flx-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.flx-tit {
|
||||||
|
color: #303133;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.theme-item {
|
||||||
|
margin: 16px 5px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 2px;
|
||||||
|
position: relative;
|
||||||
|
.icon {
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
right: 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="header"
|
||||||
|
:style="{ backgroundColor: themeConfig.tabColor, color: themeConfig.footColor }"
|
||||||
|
>
|
||||||
|
<div class="header-lf flx-center">
|
||||||
|
<Breadcrumb></Breadcrumb>
|
||||||
|
</div>
|
||||||
|
<div class="header-ri flx-center">
|
||||||
|
<!-- <Search></Search> -->
|
||||||
|
<el-icon class="icon-style" @click="refresh"><Refresh /></el-icon>
|
||||||
|
<!-- <message></message> -->
|
||||||
|
|
||||||
|
<!-- <Driver></Driver> -->
|
||||||
|
<fullScreen></fullScreen>
|
||||||
|
<avatar></avatar>
|
||||||
|
<setting></setting>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup name="header">
|
||||||
|
import { inject, ref } from "vue";
|
||||||
|
import store from "../../store/index.js";
|
||||||
|
// import { Setting, Search } from '@element-plus/icons-vue'
|
||||||
|
import avatar from "./components/avatar.vue";
|
||||||
|
import setting from "./components/setting.vue";
|
||||||
|
import Search from "./components/Search.vue";
|
||||||
|
import fullScreen from "./components/fullScreen.vue";
|
||||||
|
import message from "./components/message.vue";
|
||||||
|
import Breadcrumb from "./components/Breadcrumb.vue";
|
||||||
|
// import Driver from "./components/Driver/index.vue";
|
||||||
|
// 局部数据刷新
|
||||||
|
const refresh = inject("reload");
|
||||||
|
|
||||||
|
const themeConfig = store.getters.themeConfig;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.header {
|
||||||
|
border-bottom: 1px solid #f6f6f6;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 15px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
|
||||||
|
.header-lf {
|
||||||
|
.collapse-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
margin-right: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-ri {
|
||||||
|
// margin-left:10px;
|
||||||
|
|
||||||
|
.message-box {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: 10px;
|
||||||
|
background-color: #d54f50;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bell-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
margin-right: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-style {
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 0 0 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
.el-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.el-aside {
|
||||||
|
width: auto;
|
||||||
|
overflow: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-header,
|
||||||
|
.el-footer {
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-tabs {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-main {
|
||||||
|
background: #f0f2f5;
|
||||||
|
padding: 10px 13px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// 防止切换出现横向滚动条
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
.main-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
// background-color: #f5f5f5;
|
||||||
|
// box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||||
|
// border-radius: 4px;
|
||||||
|
// padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.shake {
|
||||||
|
animation: shake 0.5s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
from {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<el-container class="container">
|
||||||
|
<el-aside :style="{ backgroundColor: themeConfig.backgroundColor }"
|
||||||
|
><Menu></Menu
|
||||||
|
></el-aside>
|
||||||
|
<el-container>
|
||||||
|
<el-header>
|
||||||
|
<Header></Header>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<div class="main-tabs flx-row" v-if="themeConfig.istags">
|
||||||
|
<tabs></tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-box">
|
||||||
|
<router-view :class="{ shake: disabled }"></router-view>
|
||||||
|
</div>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="layout">
|
||||||
|
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import store from '../store/index.js'
|
||||||
|
import Header from './header/index.vue'
|
||||||
|
import Menu from './menu/index.vue'
|
||||||
|
import tabs from './Tabs/index.vue'
|
||||||
|
const themeConfig = store.getters.themeConfig
|
||||||
|
console.log(themeConfig.backgroundColor)
|
||||||
|
|
||||||
|
let router = useRouter()
|
||||||
|
const disabled = ref(false)
|
||||||
|
// onMounted(() => {
|
||||||
|
// disabled.value = true
|
||||||
|
// setTimeout(() => {
|
||||||
|
// disabled.value = false
|
||||||
|
// }, 1500)
|
||||||
|
// })
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => router.currentRoute.value.path,
|
||||||
|
(newValue, oldValue) => {
|
||||||
|
console.log('watch', newValue)
|
||||||
|
console.log('watch', oldValue)
|
||||||
|
if (newValue == oldValue) {
|
||||||
|
} else {
|
||||||
|
disabled.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
disabled.value = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<template v-for="subItem in menuList" :key="subItem.path">
|
||||||
|
<el-sub-menu
|
||||||
|
v-if="subItem.children && subItem.children.length > 0"
|
||||||
|
:index="subItem.path"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="subItem.meta.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ subItem.meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
<Menu-items :menuList="subItem.children"></Menu-items>
|
||||||
|
<!--<template v-for="child in subItem.children" :key="child.path">
|
||||||
|
<template v-if="child.children && child.children.length > 0">
|
||||||
|
<Menu-items :menuList="child.children"></Menu-items>
|
||||||
|
</template>
|
||||||
|
<el-menu-item v-else :index="child.path">
|
||||||
|
<el-icon>
|
||||||
|
<component :is="child.meta.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
<template #title>
|
||||||
|
<span>{{ child.meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</template> -->
|
||||||
|
</el-sub-menu>
|
||||||
|
<el-menu-item v-else :index="subItem.path">
|
||||||
|
<el-icon>
|
||||||
|
<component :is="subItem.meta.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
<template #title>
|
||||||
|
<span>{{ subItem.meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps(["menuList"]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icons {
|
||||||
|
width: 24px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<template v-for="subItem in menuList" :key="subItem.path">
|
||||||
|
<el-menu-item v-if="!subItem.children" :index="subItem.url" :route="subItem.url">
|
||||||
|
<el-icon>
|
||||||
|
<component :is="subItem.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ subItem.title }}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
<!--有多级菜单-->
|
||||||
|
<el-sub-menu v-if="subItem.children" :key="subItem.url" :index="subItem.url">
|
||||||
|
<template #title>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="subItem.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
<span> {{ subItem.title }}</span>
|
||||||
|
</template>
|
||||||
|
<!--递归组件,把遍历的值传回子组件,完成递归调用-->
|
||||||
|
<menu-items :menuList="subItem.children"></menu-items>
|
||||||
|
</el-sub-menu>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps(["menuList"]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icons {
|
||||||
|
width: 24px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
.menu {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #20222a;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
height: 55px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 2px solid #1d1e26;
|
||||||
|
box-shadow: 2px 0 6px rgb(0 21 41 / 35%);
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #dadada;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
height: calc(100% - 55px);
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
// flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
border: none !important;
|
||||||
|
width: 100% !important;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background-color: #20222a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #41444b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: #060708 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active::before {
|
||||||
|
content: "";
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 4px;
|
||||||
|
background: $bg-color;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.href {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #bdbdc0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .el-menu--popup {
|
||||||
|
// .el-menu-item {
|
||||||
|
// background-color: #20222a;
|
||||||
|
// width: 200px;
|
||||||
|
|
||||||
|
// i {
|
||||||
|
// margin-right: 5px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// i,
|
||||||
|
// span {
|
||||||
|
// color: hsla(0, 0%, 100%, 0.7);
|
||||||
|
// }
|
||||||
|
// //
|
||||||
|
// &:hover {
|
||||||
|
|
||||||
|
// i,
|
||||||
|
// span {
|
||||||
|
// color: #fff !important;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &.is-active {
|
||||||
|
// background-color: #060708 !important;
|
||||||
|
|
||||||
|
// &:before {
|
||||||
|
// content: "";
|
||||||
|
// top: 0;
|
||||||
|
// left: 0;
|
||||||
|
// bottom: 0;
|
||||||
|
// width: 4px;
|
||||||
|
// background: $bg-color;
|
||||||
|
// position: absolute;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// i,
|
||||||
|
// span {
|
||||||
|
// color: #fff !important;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
id="guide"
|
||||||
|
class="menu"
|
||||||
|
:style="{
|
||||||
|
width: store.getters.isCollapse == true ? '' : '200px',
|
||||||
|
height: '100%',
|
||||||
|
color: themeConfig.textColor,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="logo" v-if="!store.getters.isCollapse">
|
||||||
|
<img src="../../assets/logo.png" alt="" style="margin-right: 5px" /> BIG CUTE
|
||||||
|
</div>
|
||||||
|
<div class="logo" v-else>
|
||||||
|
<img src="../../assets/logo.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<el-scrollbar style="height: 100%">
|
||||||
|
<el-menu
|
||||||
|
:default-active="activeMenu"
|
||||||
|
:router="true"
|
||||||
|
:collapse="store.getters.isCollapse"
|
||||||
|
:collapse-transition="false"
|
||||||
|
:unique-opened="true"
|
||||||
|
:background-color="themeConfig.backgroundColor"
|
||||||
|
:text-color="themeConfig.textColor"
|
||||||
|
:active-text-color="themeConfig.primary"
|
||||||
|
>
|
||||||
|
<menuItems :menuList="menuList"></menuItems>
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, reactive, ref } from "vue";
|
||||||
|
import store from "../../store/index.js";
|
||||||
|
import menuItems from "./components/menuItems.vue";
|
||||||
|
// 引入本地路由表渲染导航栏
|
||||||
|
import menuTable from "./menuTable.js";
|
||||||
|
import { useRouter, useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const activeMenu = computed(() => {
|
||||||
|
return route.path;
|
||||||
|
});
|
||||||
|
const menuList = ref([]);
|
||||||
|
const handleOpen = (key, keyPath) => {
|
||||||
|
console.log(key, keyPath);
|
||||||
|
};
|
||||||
|
const handleClose = (key, keyPath) => {
|
||||||
|
console.log(key, keyPath);
|
||||||
|
};
|
||||||
|
const themeConfig = store.getters.themeConfig;
|
||||||
|
|
||||||
|
// 菜单表存在后端的操作方式
|
||||||
|
onMounted(() => {
|
||||||
|
// 获取本地菜单表渲染
|
||||||
|
menuList.value = menuTable;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.menu {
|
||||||
|
// width: 200px;
|
||||||
|
.logo {
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
padding: 0 20px;
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
img {
|
||||||
|
width: 25px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 菜单列表改了路由要记得改菜单相互匹配
|
||||||
|
const menuTable = [
|
||||||
|
{
|
||||||
|
title: "首页",
|
||||||
|
url: "/home",
|
||||||
|
icon: "HomeFilled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "组别管理",
|
||||||
|
url: "/groupManagement",
|
||||||
|
icon: "Notebook",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default menuTable;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import router from "./router/index";
|
||||||
|
import store from "../src/store/index";
|
||||||
|
import "./styles/common.scss";
|
||||||
|
import ElementPlus from "element-plus";
|
||||||
|
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
||||||
|
// 引入注册自定义指令
|
||||||
|
import directives from "./instruction/index.js";
|
||||||
|
import "element-plus/dist/index.css";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import * as ELIcons from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
import VMdEditor from "@kangc/v-md-editor";
|
||||||
|
import vuepressTheme from "@kangc/v-md-editor/lib/theme/vuepress.js";
|
||||||
|
import "@kangc/v-md-editor/lib/style/base-editor.css";
|
||||||
|
import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
|
||||||
|
|
||||||
|
VMdEditor.use(vuepressTheme);
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
// 小图标引用
|
||||||
|
for (const iconName in ELIcons) {
|
||||||
|
// 注册组件
|
||||||
|
app.component(iconName, ELIcons[iconName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(router).use(store).use(directives).use(VMdEditor).use(ElementPlus, {
|
||||||
|
locale: zhCn,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import router from "./router.js";
|
||||||
|
import NProgress from "nprogress";
|
||||||
|
import "nprogress/nprogress.css";
|
||||||
|
|
||||||
|
NProgress.configure({
|
||||||
|
ease: "ease",
|
||||||
|
speed: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
const writeNames = ["/login"];
|
||||||
|
// router.beforeEach((to, from, next) => {
|
||||||
|
// NProgress.start();
|
||||||
|
// if (sessionStorage.getItem("token")) {
|
||||||
|
// if (to.path === "/login") {
|
||||||
|
// next("/");
|
||||||
|
// }
|
||||||
|
// return next();
|
||||||
|
// } else {
|
||||||
|
// if (writeNames.includes(to.path)) {
|
||||||
|
// next();
|
||||||
|
// } else {
|
||||||
|
// next("/login");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
router.afterEach((transition) => {
|
||||||
|
NProgress.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// 模板自带的功能路由表
|
||||||
|
import login from "../views/login/index.vue";
|
||||||
|
import Layout from "../layout/index.vue";
|
||||||
|
const routerList = [
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: login,
|
||||||
|
component: login,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
component: Layout,
|
||||||
|
name: "container",
|
||||||
|
redirect: "home",
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true, //有一些页面是否登录才能进去
|
||||||
|
name: "首页",
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/home",
|
||||||
|
name: "home",
|
||||||
|
component: () => import("../views/home/index.vue"),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true, //有一些页面是否登录才能进去
|
||||||
|
name: "首页",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/group",
|
||||||
|
name: "employe",
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true, //有一些页面是否登录才能进去
|
||||||
|
name: "组别管理",
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/groupManagement",
|
||||||
|
name: "groupManagement",
|
||||||
|
component: () => import("../views/groupManagement/index.vue"),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
name: "组别管理",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export default routerList;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
|
import home from "../views/home/index.vue";
|
||||||
|
import login from "../views/login/index.vue";
|
||||||
|
import Layout from "../layout/index.vue";
|
||||||
|
// 原本模板功能路由表
|
||||||
|
import routerList from "./originalRoutingTable.js";
|
||||||
|
|
||||||
|
const routes = routerList;
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const getters = {
|
||||||
|
token: (state) => state.user.token,
|
||||||
|
isCollapse: (state) => state.user.isCollapse,
|
||||||
|
UserInfo: (state) => state.user.UserInfo,
|
||||||
|
themeConfig: (state) => state.user.themeConfig,
|
||||||
|
tabsMenuList: (state) => state.tabs.tabsMenuList
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getters;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
createStore
|
||||||
|
} from "vuex";
|
||||||
|
import user from "./modules/users";
|
||||||
|
import tabs from './modules/tabs'
|
||||||
|
import getters from "./getters";
|
||||||
|
import createPersistedState from "vuex-persistedstate";
|
||||||
|
export default createStore({
|
||||||
|
getters,
|
||||||
|
modules: {
|
||||||
|
user,
|
||||||
|
tabs
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// 默认储存在localstorage
|
||||||
|
createPersistedState({
|
||||||
|
// 本地储存名
|
||||||
|
key: "user",
|
||||||
|
// 指定模块
|
||||||
|
paths: ["user"],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
ElStep
|
||||||
|
} from "element-plus";
|
||||||
|
import router from "../../router/router.js";
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
tabsMenuList: [{
|
||||||
|
title: "首页",
|
||||||
|
url: 'home',
|
||||||
|
icon: "Menu",
|
||||||
|
close: false,
|
||||||
|
checked: true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
addTabsMenu(state, obj) {
|
||||||
|
|
||||||
|
let titles = state.tabsMenuList.map(item => item.title).join(',')
|
||||||
|
if (titles.includes(obj.title)) {
|
||||||
|
for (let index = 0; index < state.tabsMenuList.length; index++) {
|
||||||
|
const element = state.tabsMenuList[index];
|
||||||
|
state.tabsMenuList[index].checked = false
|
||||||
|
if (element.title == obj.title) {
|
||||||
|
state.tabsMenuList[index].checked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.tabsMenuList.forEach(item => {
|
||||||
|
item.checked = false
|
||||||
|
});
|
||||||
|
state.tabsMenuList.push(obj)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
delectTagMenu(state, item) {
|
||||||
|
if (state.tabsMenuList.length == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let i = 0; i < state.tabsMenuList.length; i++) {
|
||||||
|
let ele = state.tabsMenuList[i]
|
||||||
|
console.log(ele)
|
||||||
|
if (ele == item) {
|
||||||
|
state.tabsMenuList[i - 1].checked = true
|
||||||
|
state.tabsMenuList.splice(i, 1)
|
||||||
|
return router.push(state.tabsMenuList[i - 1].url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(state.tabsMenuList)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
addTabs({
|
||||||
|
commit
|
||||||
|
}, str) {
|
||||||
|
console.log(str);
|
||||||
|
commit("addTabsMenu", str);
|
||||||
|
},
|
||||||
|
delectTag({
|
||||||
|
commit
|
||||||
|
}, val) {
|
||||||
|
|
||||||
|
commit('delectTagMenu', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import router from "../../router/router.js";
|
||||||
|
import { mix } from "../../utils/color.js";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
UserInfo: {},
|
||||||
|
token: sessionStorage.getItem("token") || "",
|
||||||
|
isCollapse: true,
|
||||||
|
themeConfig: {
|
||||||
|
primary: "#4060c7",
|
||||||
|
tabColor: "#FFFFFF",
|
||||||
|
footColor: "#606266",
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
textColor: "#00000099",
|
||||||
|
istags: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setToken(state, token) {
|
||||||
|
state.token = token;
|
||||||
|
},
|
||||||
|
setUserInfo(state, userinfo) {
|
||||||
|
state.UserInfo = userinfo;
|
||||||
|
},
|
||||||
|
SetIsCollapse(state, isCollapse) {
|
||||||
|
state.isCollapse = isCollapse;
|
||||||
|
},
|
||||||
|
setThemeConfig(state, primary) {
|
||||||
|
state.themeConfig.primary = primary;
|
||||||
|
},
|
||||||
|
setThemeConfigTbaColor(state, primary) {
|
||||||
|
state.themeConfig.tabColor = primary;
|
||||||
|
if (primary == "#FFFFFF") {
|
||||||
|
state.themeConfig.footColor = "#606266";
|
||||||
|
} else {
|
||||||
|
state.themeConfig.footColor = "#ffffff";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setThemeConfigMenuColor(state, primary) {
|
||||||
|
if (primary) {
|
||||||
|
state.themeConfig.backgroundColor = "#FFFFFF";
|
||||||
|
state.themeConfig.textColor = "#00000099";
|
||||||
|
} else {
|
||||||
|
state.themeConfig.backgroundColor = "#1d2129";
|
||||||
|
state.themeConfig.textColor = "#bdbdc0";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setThemeConfigchangeTags(state, primary) {
|
||||||
|
if (primary) {
|
||||||
|
state.themeConfig.istags = true;
|
||||||
|
} else {
|
||||||
|
state.themeConfig.istags = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
// 登录
|
||||||
|
// login({ commit }, userInfo) {
|
||||||
|
// return new Promise((resolve, reject) => {
|
||||||
|
// LoginInfo(userInfo)
|
||||||
|
// .then((res) => {
|
||||||
|
// console.log(res.data.data);
|
||||||
|
// sessionStorage.setItem("token", res.data.data.token);
|
||||||
|
// sessionStorage.setItem("UserInfo", JSON.stringify(res.data.data));
|
||||||
|
// commit("setToken", res.data.data.token);
|
||||||
|
// commit("setUserInfo", res.data.data);
|
||||||
|
// router.replace("/");
|
||||||
|
// ElMessage({
|
||||||
|
// message: "登录成功",
|
||||||
|
// type: "success",
|
||||||
|
// });
|
||||||
|
// resolve();
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// reject(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
changeIsCollapse({ commit }, str) {
|
||||||
|
console.log(str);
|
||||||
|
commit("SetIsCollapse", str);
|
||||||
|
},
|
||||||
|
|
||||||
|
changeThem({ commit }, str) {
|
||||||
|
commit("setThemeConfig", str);
|
||||||
|
const pre = "--el-color-primary";
|
||||||
|
// 白色混合色
|
||||||
|
const mixWhite = "#ffffff";
|
||||||
|
// 黑色混合色
|
||||||
|
const mixBlack = "#000000";
|
||||||
|
const el = document.documentElement;
|
||||||
|
el.style.setProperty(pre, str);
|
||||||
|
// 这里是覆盖原有颜色的核心代码
|
||||||
|
for (let i = 1; i < 10; i += 1) {
|
||||||
|
el.style.setProperty(`${pre}-light-${i}`, mix(str, mixWhite, i * 0.1));
|
||||||
|
}
|
||||||
|
el.style.setProperty("--el-color-primary-dark", mix(str, mixBlack, 0.1));
|
||||||
|
},
|
||||||
|
changeTabColor({ commit }, val) {
|
||||||
|
commit("setThemeConfigTbaColor", val);
|
||||||
|
},
|
||||||
|
changeMenuColor({ commit }, val) {
|
||||||
|
commit("setThemeConfigMenuColor", val);
|
||||||
|
},
|
||||||
|
changeTags({ commit }, val) {
|
||||||
|
commit("setThemeConfigchangeTags", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
Reset style sheet
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HTML5 display-role reset for older browsers */
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote:before,
|
||||||
|
blockquote:after,
|
||||||
|
q:before,
|
||||||
|
q:after {
|
||||||
|
content: "";
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* flex */
|
||||||
|
.flx-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flx-justify-between {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flx-align-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flx-row {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flx-row-right {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-center {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml10 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p10 {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p20 {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m10 {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m20 {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt10 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt20 {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml20 {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mR10 {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.one-cut-txt {
|
||||||
|
/*强制文字在一行文本框内*/
|
||||||
|
white-space: nowrap;
|
||||||
|
/*溢出部分文字隐藏*/
|
||||||
|
overflow: hidden;
|
||||||
|
/*溢出部分省略号处理*/
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 清除浮动 */
|
||||||
|
.clearfix::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 0px;
|
||||||
|
clear: both;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fade-transform */
|
||||||
|
.fade-transform-leave-active,
|
||||||
|
.fade-transform-enter-active {
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-transform-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Breadcrumb */
|
||||||
|
.breadcrumb-enter-active,
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-enter-from,
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scroll bar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
box-shadow: inset 0 0 0px white;
|
||||||
|
-webkit-box-shadow: inset 0 0 0px white;
|
||||||
|
background-color: #dddee0;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 23px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgb(88, 88, 88);
|
||||||
|
line-height: 100px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export const mix = (c1, c2, ratio) => {
|
||||||
|
ratio = Math.max(Math.min(Number(ratio), 1), 0)
|
||||||
|
let r1 = parseInt(c1.substring(1, 3), 16)
|
||||||
|
let g1 = parseInt(c1.substring(3, 5), 16)
|
||||||
|
let b1 = parseInt(c1.substring(5, 7), 16)
|
||||||
|
let r2 = parseInt(c2.substring(1, 3), 16)
|
||||||
|
let g2 = parseInt(c2.substring(3, 5), 16)
|
||||||
|
let b2 = parseInt(c2.substring(5, 7), 16)
|
||||||
|
let r = Math.round(r1 * (1 - ratio) + r2 * ratio) + ''
|
||||||
|
let g = Math.round(g1 * (1 - ratio) + g2 * ratio) + ''
|
||||||
|
let b = Math.round(b1 * (1 - ratio) + b2 * ratio) + ''
|
||||||
|
r = ('0' + (r || 0).toString(16)).slice(-2)
|
||||||
|
g = ('0' + (g || 0).toString(16)).slice(-2)
|
||||||
|
b = ('0' + (b || 0).toString(16)).slice(-2)
|
||||||
|
return '#' + r + g + b
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
export function getDateTime(type) {
|
||||||
|
var date = new Date();
|
||||||
|
var hengGang = "-";
|
||||||
|
var maoHao = ":";
|
||||||
|
var year = date.getFullYear();
|
||||||
|
var month = date.getMonth() + 1;
|
||||||
|
var curDate = date.getDate();
|
||||||
|
var curHours = date.getHours();
|
||||||
|
var curMinutes = date.getMinutes();
|
||||||
|
var curSeconds = date.getSeconds();
|
||||||
|
|
||||||
|
if (month >= 1 && month <= 9) {
|
||||||
|
month = "0" + month;
|
||||||
|
}
|
||||||
|
if (curDate >= 0 && curDate <= 9) {
|
||||||
|
curDate = "0" + curDate;
|
||||||
|
}
|
||||||
|
if (curHours >= 0 && curHours <= 9) {
|
||||||
|
curHours = "0" + curHours;
|
||||||
|
}
|
||||||
|
if (curMinutes >= 0 && curMinutes <= 9) {
|
||||||
|
curMinutes = "0" + curMinutes;
|
||||||
|
}
|
||||||
|
if (curSeconds >= 0 && curSeconds <= 9) {
|
||||||
|
curSeconds = "0" + curSeconds;
|
||||||
|
}
|
||||||
|
var currentdate = "";
|
||||||
|
if (type == "year") {
|
||||||
|
currentdate = year;
|
||||||
|
return currentdate;
|
||||||
|
} else if (type == "month") {
|
||||||
|
currentdate = year + hengGang + month;
|
||||||
|
return currentdate;
|
||||||
|
} else {
|
||||||
|
currentdate = year + hengGang + month + hengGang + curDate + " "
|
||||||
|
return currentdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// var year = getDateTime('year');
|
||||||
|
// console.log(year); // 2021
|
||||||
|
// var month = getDateTime('month');
|
||||||
|
// console.log(month); // 12
|
||||||
|
// var date = getDateTime('');
|
||||||
|
// console.log(date); // 2021-12-03 09:00:00
|
||||||
@@ -0,0 +1,377 @@
|
|||||||
|
<template>
|
||||||
|
<div class="group-management-container">
|
||||||
|
<!-- 左侧组别管理 -->
|
||||||
|
<div class="left-panel">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>组别管理</span>
|
||||||
|
<el-button type="primary" @click="handleAdd">添加组别</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table :data="groupList" style="width: 100%" @row-click="handleGroupSelect">
|
||||||
|
<el-table-column prop="groupId" label="组别ID" width="180" />
|
||||||
|
<el-table-column prop="groupName" label="组别名称" />
|
||||||
|
<el-table-column prop="createTime" label="创建时间">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.createTime || "暂无" }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="180">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button type="primary" link @click.stop="handleEdit(scope.row)"
|
||||||
|
>编辑</el-button
|
||||||
|
>
|
||||||
|
<el-button type="danger" link @click.stop="handleDelete(scope.row)"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧成员管理 -->
|
||||||
|
<div class="right-panel">
|
||||||
|
<el-card v-if="currentGroup">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ currentGroup.groupName }} - 成员管理</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="user-management">
|
||||||
|
<div class="user-container">
|
||||||
|
<!-- 组内人员列表 -->
|
||||||
|
<div class="user-section">
|
||||||
|
<h3>组内成员</h3>
|
||||||
|
<el-table :data="groupUsers" style="width: 100%" height="500">
|
||||||
|
<el-table-column label="头像" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-avatar :size="40" :src="scope.row.avatar" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="name" label="姓名" />
|
||||||
|
<el-table-column label="角色" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-select
|
||||||
|
v-model="scope.row.role"
|
||||||
|
placeholder="选择角色"
|
||||||
|
@change="(value) => handleRoleChange(scope.row, value)"
|
||||||
|
>
|
||||||
|
<el-option label="普通员工" :value="0" />
|
||||||
|
<el-option label="部门主管" :value="1" />
|
||||||
|
<el-option label="综合部" :value="2" />
|
||||||
|
<el-option label="财务部" :value="3" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button type="danger" link @click="handleRemoveUser(scope.row)"
|
||||||
|
>移除</el-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 未分组人员列表 -->
|
||||||
|
<div class="user-section">
|
||||||
|
<h3>未分组人员</h3>
|
||||||
|
<el-table :data="nonGroupUsers" style="width: 100%" height="500">
|
||||||
|
<el-table-column label="头像" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-avatar :size="40" :src="scope.row.avatar" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="name" label="姓名" />
|
||||||
|
<el-table-column label="操作" width="200">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-select
|
||||||
|
v-model="scope.row.selectedRole"
|
||||||
|
placeholder="选择角色"
|
||||||
|
style="width: 120px"
|
||||||
|
>
|
||||||
|
<el-option label="普通员工" :value="0" />
|
||||||
|
<el-option label="部门主管" :value="1" />
|
||||||
|
<el-option label="综合部" :value="2" />
|
||||||
|
<el-option label="财务部" :value="3" />
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" link @click="handleAddUser(scope.row)"
|
||||||
|
>添加</el-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<el-empty v-else description="请选择左侧组别进行管理" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加/编辑组别对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogType === 'add' ? '添加组别' : '编辑组别'"
|
||||||
|
width="500px"
|
||||||
|
>
|
||||||
|
<el-form :model="groupForm" label-width="80px">
|
||||||
|
<el-form-item label="组别名称" required>
|
||||||
|
<el-input v-model="groupForm.groupName" placeholder="请输入组别名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import {
|
||||||
|
getGroupList,
|
||||||
|
addGroup,
|
||||||
|
updateGroup,
|
||||||
|
deleteGroup,
|
||||||
|
getNonGroupUsers,
|
||||||
|
updateGroupUserRole,
|
||||||
|
getGroupUsers,
|
||||||
|
deleteGroupUser,
|
||||||
|
} from "../../api/modules/index";
|
||||||
|
|
||||||
|
// 组别列表数据
|
||||||
|
const groupList = ref([]);
|
||||||
|
const currentGroup = ref(null);
|
||||||
|
|
||||||
|
// 对话框相关
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const dialogType = ref("add"); // add 或 edit
|
||||||
|
const groupForm = ref({
|
||||||
|
groupId: null,
|
||||||
|
groupName: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 成员管理相关
|
||||||
|
const nonGroupUsers = ref([]);
|
||||||
|
const groupUsers = ref([]);
|
||||||
|
|
||||||
|
// 获取组别列表
|
||||||
|
const fetchGroupList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getGroupList();
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
groupList.value = res.result;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("获取组别列表失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择组别
|
||||||
|
const handleGroupSelect = async (row) => {
|
||||||
|
currentGroup.value = row;
|
||||||
|
await Promise.all([fetchNonGroupUsers(), fetchGroupUsers(row.groupId)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取未分组人员列表
|
||||||
|
const fetchNonGroupUsers = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getNonGroupUsers();
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
nonGroupUsers.value = res.result.map((user) => ({
|
||||||
|
...user,
|
||||||
|
selectedRole: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("获取未分组人员列表失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取组内人员列表
|
||||||
|
const fetchGroupUsers = async (groupId) => {
|
||||||
|
try {
|
||||||
|
const res = await getGroupUsers(groupId);
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
groupUsers.value = res.result;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("获取组内人员列表失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加成员
|
||||||
|
const handleAddUser = async (user) => {
|
||||||
|
try {
|
||||||
|
const res = await updateGroupUserRole(
|
||||||
|
currentGroup.value.groupId,
|
||||||
|
user.userid,
|
||||||
|
user.selectedRole
|
||||||
|
);
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
ElMessage.success("添加成功");
|
||||||
|
await Promise.all([
|
||||||
|
fetchNonGroupUsers(),
|
||||||
|
fetchGroupUsers(currentGroup.value.groupId),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("添加失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改成员角色
|
||||||
|
const handleRoleChange = async (user, role) => {
|
||||||
|
try {
|
||||||
|
const res = await updateGroupUserRole(currentGroup.value.groupId, user.userid, role);
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
ElMessage.success("修改成功");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("修改失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除成员
|
||||||
|
const handleRemoveUser = async (user) => {
|
||||||
|
try {
|
||||||
|
const res = await deleteGroupUser(currentGroup.value.groupId, user.userid);
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
ElMessage.success("移除成功");
|
||||||
|
await Promise.all([
|
||||||
|
fetchNonGroupUsers(),
|
||||||
|
fetchGroupUsers(currentGroup.value.groupId),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("移除失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加组别
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogType.value = "add";
|
||||||
|
groupForm.value = {
|
||||||
|
groupId: null,
|
||||||
|
groupName: "",
|
||||||
|
};
|
||||||
|
dialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑组别
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
dialogType.value = "edit";
|
||||||
|
groupForm.value = {
|
||||||
|
groupId: row.groupId,
|
||||||
|
groupName: row.groupName,
|
||||||
|
};
|
||||||
|
dialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除组别
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm("确认删除该组别吗?", "提示", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteGroup(row.groupId);
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
if (currentGroup.value?.groupId === row.groupId) {
|
||||||
|
currentGroup.value = null;
|
||||||
|
}
|
||||||
|
fetchGroupList();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("删除失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!groupForm.value.groupName) {
|
||||||
|
ElMessage.warning("请输入组别名称");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (dialogType.value === "add") {
|
||||||
|
const res = await addGroup({ groupName: groupForm.value.groupName });
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
ElMessage.success("添加成功");
|
||||||
|
dialogVisible.value = false;
|
||||||
|
fetchGroupList();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const res = await updateGroup({
|
||||||
|
groupId: groupForm.value.groupId,
|
||||||
|
groupName: groupForm.value.groupName,
|
||||||
|
});
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
ElMessage.success("修改成功");
|
||||||
|
dialogVisible.value = false;
|
||||||
|
fetchGroupList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(dialogType.value === "add" ? "添加失败" : "修改失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时获取组别列表
|
||||||
|
onMounted(() => {
|
||||||
|
fetchGroupList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.group-management-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
height: calc(100vh - 120px);
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
width: 40%;
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-management {
|
||||||
|
.user-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-section {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; // 防止flex子项溢出
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
.data-lists {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-col {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-item-one {
|
||||||
|
background: #2969ff;
|
||||||
|
margin-right: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
height: 156px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.item-left {}
|
||||||
|
|
||||||
|
.item-right {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 10px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.item-right-top {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.tit {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: #fff;
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-right-bottom {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.tit {
|
||||||
|
font-size: 14px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-item-two {
|
||||||
|
height: 176px;
|
||||||
|
margin-right: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
height: 50%;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
.item-des {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&:nth-child(2n) {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 22px;
|
||||||
|
|
||||||
|
.num {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(30, 189, 94, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.txt {
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-item-three {
|
||||||
|
padding: 10px;
|
||||||
|
height: 156px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.tit {
|
||||||
|
font-size: 14px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
|
||||||
|
.item-data {
|
||||||
|
margin: 5px;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fafafcff;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 40%;
|
||||||
|
color: rgba(25, 30, 36, 0.5);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tit {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(25, 30, 36, 0.5);
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num {
|
||||||
|
color: #0052d9;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #666;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(25, 30, 36, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataLayer {
|
||||||
|
.datalayer-echarts {
|
||||||
|
flex: 1;
|
||||||
|
height: 260px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-data {
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-right: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
.tit {
|
||||||
|
padding: 10px 10px 0 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-box {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
.el-table {
|
||||||
|
width: 100%;
|
||||||
|
height: 360px;
|
||||||
|
|
||||||
|
.type {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 0px;
|
||||||
|
color: #fa4a1e;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fa4a1e14;
|
||||||
|
|
||||||
|
&.error-type {
|
||||||
|
color: #4060c7;
|
||||||
|
background: #e8f3ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: #4060c7;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
&.error-state {
|
||||||
|
background-color: #fa4a1e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-item-three {
|
||||||
|
padding: 10px;
|
||||||
|
// height: 156px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.tit {
|
||||||
|
font-size: 14px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
|
||||||
|
.item-data {
|
||||||
|
margin: 5px;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fafafcff;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 40%;
|
||||||
|
color: rgba(25, 30, 36, 0.5);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tit {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(25, 30, 36, 0.5);
|
||||||
|
padding-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: rgba(25, 30, 36, 0.5);
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
width: 100%;
|
||||||
|
height: 152px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.notice-news {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.txt {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-lists {
|
||||||
|
height: 260px;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.type {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 0px;
|
||||||
|
color: #fa4a1e;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fa4a1e14;
|
||||||
|
|
||||||
|
&.error-type {
|
||||||
|
color: #4060c7;
|
||||||
|
background: #e8f3ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-right-1px {
|
||||||
|
border-right: 1px solid #eae3e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-edit {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #2f60c2;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dele {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #ff5722;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shake {
|
||||||
|
animation: shake 0.5s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shakeRight {
|
||||||
|
animation: shakeRight 0.5s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
from {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shakeRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<el-card>
|
||||||
|
<div>欢迎使用💐🌸💮🏵️🌹🌺🌻</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup></script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
.login-container {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
// background-color: #6c88d5;
|
||||||
|
background-image: url('../../assets/bg.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
width: 96%;
|
||||||
|
height: 94%;
|
||||||
|
background-color: hsla(0, 0%, 100%, 0.85);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 8% 0 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
.login-left {
|
||||||
|
width: 800px;
|
||||||
|
color: #6E736FFF;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
color: rgba(47, 57, 78, 1);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 735px;
|
||||||
|
height: 465px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
width: 400px;
|
||||||
|
height: 260px;
|
||||||
|
padding: 10px 40px 35px 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
-webkit-box-shadow: 2px 3px 7px rgb(0 0 0 / 20%);
|
||||||
|
box-shadow: 2px 3px 7px rgb(0 0 0 / 20%);
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
// margin-bottom: 45px;
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
width: 60px;
|
||||||
|
// height: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 32px;
|
||||||
|
padding-left: 25px;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #2d51e6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.flx-row {
|
||||||
|
span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #2d51e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
width: 185px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-container flx-center">
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="login-left">
|
||||||
|
<div class="title">人资薪资核算系统<span style="font-size: 14px">v1.7</span></div>
|
||||||
|
<div class="sub">用于自动管理核算人员工资</div>
|
||||||
|
<img src="../../assets/login.png" alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-form">
|
||||||
|
<div class="login-logo">
|
||||||
|
<img class="login-icon" src="../../assets/logo.png" alt="" />
|
||||||
|
<p class="logo-text">人资薪资核算系统</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="login">
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { ref } from "vue";
|
||||||
|
// import { login, obtainUserInformationApi } from "../../api/modules/index";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import dd from "dingtalk-jsapi";
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
const loginIn = async (code) => {
|
||||||
|
let res = await login(code);
|
||||||
|
sessionStorage.setItem("token", res.result.token);
|
||||||
|
ElMessage({
|
||||||
|
message: "授权成功",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
// 拿用户信息
|
||||||
|
obInformation();
|
||||||
|
router.push("/");
|
||||||
|
};
|
||||||
|
// 登录判断
|
||||||
|
const token = sessionStorage.getItem("token");
|
||||||
|
if (token) {
|
||||||
|
// 有then代表已经登录
|
||||||
|
} else {
|
||||||
|
// 钉钉环境去登录,
|
||||||
|
if (dd.env.platform === "notInDingTalk") {
|
||||||
|
// ElMessage({
|
||||||
|
// message: "检测到不是钉钉环境,请进入钉钉环境打开",
|
||||||
|
// type: "error",
|
||||||
|
// });
|
||||||
|
router.push("/");
|
||||||
|
console.log("跳转");
|
||||||
|
} else {
|
||||||
|
dd.ready(() => {
|
||||||
|
dd.runtime.permission.requestAuthCode({
|
||||||
|
corpId: "ding03f8e88dde9b426835c2f4657eb6378f", //企业id
|
||||||
|
onSuccess: function (info) {
|
||||||
|
loginIn(info.code);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 测试修改环境
|
||||||
|
// loginIn("4545");
|
||||||
|
}
|
||||||
|
// 获取个人信息
|
||||||
|
const obInformation = async () => {
|
||||||
|
let res = await obtainUserInformationApi();
|
||||||
|
const userInfo = res.result;
|
||||||
|
ElMessage({
|
||||||
|
message: "获取个人信息成功",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
sessionStorage.setItem("userInfo", JSON.stringify(userInfo));
|
||||||
|
router.push("/");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "./index.scss";
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import { viteMockServe } from "vite-plugin-mock";
|
||||||
|
|
||||||
|
import viteCompression from "vite-plugin-compression";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
viteMockServe: {
|
||||||
|
supportTs: false,
|
||||||
|
logger: false,
|
||||||
|
mockPath: "./mock/index.js",
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
viteCompression({
|
||||||
|
//gzip压缩
|
||||||
|
verbose: true,
|
||||||
|
disable: false,
|
||||||
|
threshold: 10240,
|
||||||
|
algorithm: "gzip",
|
||||||
|
ext: ".gz",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
minify: "terser", // 明确指定使用 Terser 作为压缩工具
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true,
|
||||||
|
drop_debugger: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// chunkSizeWarningLimit: 1500,大文件报警阈值设置,不建议使用
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
//静态资源分类打包
|
||||||
|
chunkFileNames: "static/js/[name]-[hash].js",
|
||||||
|
entryFileNames: "static/js/[name]-[hash].js",
|
||||||
|
assetFileNames: "static/[ext]/[name]-[hash].[ext]",
|
||||||
|
manualChunks(id) {
|
||||||
|
//静态资源分拆打包
|
||||||
|
if (id.includes("node_modules")) {
|
||||||
|
return id
|
||||||
|
.toString()
|
||||||
|
.split("node_modules/")[1]
|
||||||
|
.split("/")[0]
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
/* CSS 预处理器 导入全局样式*/
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
// additionalData: '@import "src/styles/common.scss";',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||