zory преди 3 седмици
родител
ревизия
85c2abf12e

+ 21 - 0
src/api/model/auth.js

@@ -8,5 +8,26 @@ export default {
 		post: async function(data={}){
 			return await http.post(this.url, data);
 		}
+	},
+	mobile: {
+		url: `${config.API_URL}/login/mobile`,
+		name: "-",
+		post: async function(data={}){
+			return await http.post(this.url, data);
+		}
+	},
+	sms: {
+		url: `${config.API_URL}/login/sms`,
+		name: "-",
+		post: async function(data={}){
+			return await http.post(this.url, data);
+		}
+	},
+	user: {
+		url: `${config.API_URL}/login/profile`,
+		name: "-",
+		get: async function(data={}){
+			return await http.get(this.url, data);
+		}
 	}
 }

+ 0 - 55
src/api/model/demo.js

@@ -1,55 +0,0 @@
-import config from "@/config"
-import http from "@/utils/request"
-
-export default {
-	ver: {
-		url: `${config.API_URL}/demo/ver`,
-		name: "获取最新版本号",
-		get: async function(params){
-			return await http.get(this.url, params);
-		}
-	},
-	post: {
-		url: `${config.API_URL}/demo/post`,
-		name: "分页列表",
-		post: async function(data){
-			return await http.post(this.url, data, {
-				headers: {
-					//'response-status': 401
-				}
-			});
-		}
-	},
-	page: {
-		url: `${config.API_URL}/demo/page`,
-		name: "分页列表",
-		get: async function(params){
-			return await http.get(this.url, params);
-		}
-	},
-	list: {
-		url: `${config.API_URL}/demo/list`,
-		name: "数据列表",
-		get: async function(params){
-			return await http.get(this.url, params);
-		}
-	},
-	menu: {
-		url: `${config.API_URL}/demo/menu`,
-		name: "普通用户菜单",
-		get: async function(){
-			return await http.get(this.url);
-		}
-	},
-	status: {
-		url: `${config.API_URL}/demo/status`,
-		name: "模拟无权限",
-		get: async function(code){
-			return await http.get(this.url, {}, {
-				headers: {
-					"response-status": code
-				}
-			});
-		}
-	}
-}

+ 19 - 0
src/api/model/menu.js

@@ -0,0 +1,19 @@
+import config from "@/config"
+import http from "@/utils/request"
+
+export default {
+    list: {
+        url: `${config.API_URL}/menu/list`,
+        name: "-",
+        get: async function(data={}){
+            return await http.get(this.url, data);
+        }
+    },
+    save: {
+        url: `${config.API_URL}/menu/save`,
+        name: "-",
+        post: async function(data={}){
+            return await http.post(this.url, data);
+        }
+    }
+}

+ 26 - 0
src/api/model/setting.js

@@ -0,0 +1,26 @@
+import config from "@/config"
+import http from "@/utils/request"
+
+export default {
+    config: {
+        url: `${config.API_URL}/config/list`,
+        name: "-",
+        get: async function (data = {}) {
+            return await http.get(this.url, data);
+        }
+    },
+    configSave: {
+        url: `${config.API_URL}/config/save`,
+        name: "-",
+        post: async function (data = {}) {
+            return await http.post(this.url, data);
+        }
+    },
+    regin: {
+        url: `${config.API_URL}/config/regin`,
+        name: "-",
+        get: async function (data = {}) {
+            return await http.get(this.url, data);
+        }
+    },
+}

+ 114 - 114
src/api/model/system.js

@@ -1,114 +1,114 @@
-import config from "@/config"
-import http from "@/utils/request"
-
-export default {
-	menu: {
-		myMenus: {
-			url: `${config.API_URL}/system/menu/my/1.6.1`,
-			name: "获取我的菜单",
-			get: async function(){
-				return await http.get(this.url);
-			}
-		},
-		list: {
-			url: `${config.API_URL}/system/menu/list`,
-			name: "获取菜单",
-			get: async function(){
-				return await http.get(this.url);
-			}
-		}
-	},
-	dic: {
-		tree: {
-			url: `${config.API_URL}/system/dic/tree`,
-			name: "获取字典树",
-			get: async function(){
-				return await http.get(this.url);
-			}
-		},
-		list: {
-			url: `${config.API_URL}/system/dic/list`,
-			name: "字典明细",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		},
-		get: {
-			url: `${config.API_URL}/system/dic/get`,
-			name: "获取字典数据",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		}
-	},
-	role: {
-		list: {
-			url: `${config.API_URL}/system/role/list2`,
-			name: "获取角色列表",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		}
-	},
-	dept: {
-		list: {
-			url: `${config.API_URL}/system/dept/list`,
-			name: "获取部门列表",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		}
-	},
-	user: {
-		list: {
-			url: `${config.API_URL}/system/user/list`,
-			name: "获取用户列表",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		}
-	},
-	app: {
-		list: {
-			url: `${config.API_URL}/system/app/list`,
-			name: "应用列表",
-			get: async function(){
-				return await http.get(this.url);
-			}
-		}
-	},
-	log: {
-		list: {
-			url: `${config.API_URL}/system/log/list`,
-			name: "日志列表",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		}
-	},
-	table: {
-		list: {
-			url: `${config.API_URL}/system/table/list`,
-			name: "表格列管理列表",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		},
-		info: {
-			url: `${config.API_URL}/system/table/info`,
-			name: "表格列管理详情",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		}
-	},
-	tasks: {
-		list: {
-			url: `${config.API_URL}/system/tasks/list`,
-			name: "系统任务管理",
-			get: async function(params){
-				return await http.get(this.url, params);
-			}
-		}
-	}
-}
+import config from "@/config"
+import http from "@/utils/request"
+
+export default {
+	menu: {
+		myMenus: {
+			url: `${config.API_URL}/system/menu/my/1.6.1`,
+			name: "获取我的菜单",
+			get: async function(){
+				return await http.get(this.url);
+			}
+		},
+		list: {
+			url: `${config.API_URL}/system/menu/list`,
+			name: "获取菜单",
+			get: async function(){
+				return await http.get(this.url);
+			}
+		}
+	},
+	dic: {
+		tree: {
+			url: `${config.API_URL}/system/dic/tree`,
+			name: "获取字典树",
+			get: async function(){
+				return await http.get(this.url);
+			}
+		},
+		list: {
+			url: `${config.API_URL}/system/dic/list`,
+			name: "字典明细",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		},
+		get: {
+			url: `${config.API_URL}/system/dic/get`,
+			name: "获取字典数据",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		}
+	},
+	role: {
+		list: {
+			url: `${config.API_URL}/system/role/list2`,
+			name: "获取角色列表",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		}
+	},
+	dept: {
+		list: {
+			url: `${config.API_URL}/system/dept/list`,
+			name: "获取部门列表",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		}
+	},
+	user: {
+		list: {
+			url: `${config.API_URL}/system/user/list`,
+			name: "获取用户列表",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		}
+	},
+	app: {
+		list: {
+			url: `${config.API_URL}/system/app/list`,
+			name: "应用列表",
+			get: async function(){
+				return await http.get(this.url);
+			}
+		}
+	},
+	log: {
+		list: {
+			url: `${config.API_URL}/system/log/list`,
+			name: "日志列表",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		}
+	},
+	table: {
+		list: {
+			url: `${config.API_URL}/system/table/list`,
+			name: "表格列管理列表",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		},
+		info: {
+			url: `${config.API_URL}/system/table/info`,
+			name: "表格列管理详情",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		}
+	},
+	tasks: {
+		list: {
+			url: `${config.API_URL}/system/tasks/list`,
+			name: "系统任务管理",
+			get: async function(params){
+				return await http.get(this.url, params);
+			}
+		}
+	}
+}

+ 2 - 2
src/config/index.js

@@ -31,7 +31,7 @@ const DEFAULT_CONFIG = {
 
 	//布局 默认:default | 通栏:header | 经典:menu | 功能坞:dock
 	//dock将关闭标签和面包屑栏
-	LAYOUT: 'default',
+	LAYOUT: 'menu',
 
 	//菜单是否折叠
 	MENU_IS_COLLAPSE: false,
@@ -40,7 +40,7 @@ const DEFAULT_CONFIG = {
 	MENU_UNIQUE_OPENED: false,
 
 	//是否开启多标签
-	LAYOUT_TAGS: true,
+	LAYOUT_TAGS: false,
 
 	//语言
 	LANG: 'zh-cn',

+ 208 - 171
src/layout/components/userbar.vue

@@ -1,49 +1,16 @@
 <template>
 	<div class="user-bar">
-		<div class="panel-item hidden-sm-and-down" @click="search">
-			<el-icon><el-icon-search /></el-icon>
+		<div class="panel-item hidden-sm-and-down">
+			<el-icon><el-icon-phone /></el-icon> <span>咨询热线:{{ baseData ? baseData.tel : '17712883297' }}</span>
 		</div>
-		<div class="screen panel-item hidden-sm-and-down" @click="screen">
-			<el-icon><el-icon-full-screen /></el-icon>
+		<div class="panel-item hidden-sm-and-down">
+			<el-icon><el-icon-service /></el-icon> <span>在线客服</span>
 		</div>
-		<div class="tasks panel-item" @click="tasks">
-			<el-icon><el-icon-sort /></el-icon>
+		<div class="panel-item hidden-sm-and-down">
+			<el-icon><el-icon-monitor /></el-icon> <span>放到桌面</span>
 		</div>
-		<div class="msg panel-item" @click="showMsg">
-			<el-badge :hidden="msgList.length==0" :value="msgList.length" class="badge" type="danger">
-				<el-icon><el-icon-chat-dot-round /></el-icon>
-			</el-badge>
-			<el-drawer title="新消息" v-model="msg" :size="400" append-to-body destroy-on-close>
-				<el-container>
-					<el-main class="nopadding">
-						<el-scrollbar>
-							<ul class="msg-list">
-								<li v-for="item in msgList" v-bind:key="item.id">
-									<a :href="item.link" target="_blank">
-										<div class="msg-list__icon">
-											<el-badge is-dot type="danger">
-												<el-avatar :size="40" :src="item.avatar"></el-avatar>
-											</el-badge>
-										</div>
-										<div class="msg-list__main">
-											<h2>{{item.title}}</h2>
-											<p>{{item.describe}}</p>
-										</div>
-										<div class="msg-list__time">
-											<p>{{item.time}}</p>
-										</div>
-									</a>
-								</li>
-								<el-empty v-if="msgList.length==0" description="暂无新消息" :image-size="100"></el-empty>
-							</ul>
-						</el-scrollbar>
-					</el-main>
-					<el-footer>
-						<el-button type="primary">消息中心</el-button>
-						<el-button @click="markRead">全部设为已读</el-button>
-					</el-footer>
-				</el-container>
-			</el-drawer>
+		<div class="screen panel-item hidden-sm-and-down" @click="screen">
+			<el-icon><el-icon-full-screen /></el-icon> <span>全屏</span>
 		</div>
 		<el-dropdown class="user panel-item" trigger="click" @command="handleUser">
 			<div class="user-avatar">
@@ -53,155 +20,225 @@
 			</div>
 			<template #dropdown>
 				<el-dropdown-menu>
-					<el-dropdown-item command="uc">帐号信息</el-dropdown-item>
-					<el-dropdown-item command="clearCache">清除缓存</el-dropdown-item>
+					<!-- <el-dropdown-item command="uc">帐号信息</el-dropdown-item> -->
+					<el-dropdown-item command="clearmenu">更新菜单</el-dropdown-item>
+					<el-dropdown-item divided command="passwd">修改密码</el-dropdown-item>
 					<el-dropdown-item divided command="outLogin">退出登录</el-dropdown-item>
 				</el-dropdown-menu>
 			</template>
 		</el-dropdown>
 	</div>
-
-	<el-dialog v-model="searchVisible" :width="700"  title="搜索" center destroy-on-close>
-		<search @success="searchVisible=false"></search>
-	</el-dialog>
-
-	<el-drawer v-model="tasksVisible" :size="450"  title="任务中心" destroy-on-close>
+	
+	<el-drawer v-model="tasksVisible" :size="450" title="任务中心" destroy-on-close>
 		<tasks></tasks>
 	</el-drawer>
 
+	<passwd ref="passwd"></passwd>
 </template>
 
 <script>
-	import search from './search.vue'
-	import tasks from './tasks.vue'
+import search from './search.vue'
+import tasks from './tasks.vue'
+import passwd from "@/views/manage/components/password"
 
-	export default {
-		components: {
-			search,
-			tasks
-		},
-		data(){
-			return {
-				userName: "",
-				userNameF: "",
-				searchVisible: false,
-				tasksVisible: false,
-				msg: false,
-				msgList: [
-					{
-						id: 1,
-						type: 'user',
-						avatar: "img/avatar.jpg",
-						title: "Skuya",
-						describe: "如果喜欢就点个星星支持一下哦",
-						link: "https://gitee.com/lolicode/scui",
-						time: "5分钟前"
-					},
-					{
-						id: 2,
-						type: 'user',
-						avatar: "img/avatar2.gif",
-						title: "Lolowan",
-						describe: "点进去Gitee获取最新开源版本",
-						link: "https://gitee.com/lolicode/scui",
-						time: "14分钟前"
-					},
-					{
-						id: 3,
-						type: 'system',
-						avatar: "img/logo.png",
-						title: "感谢登录SCUI Admin",
-						describe: "Vue 3.0 + Vue-Router 4.0 + ElementPlus + Axios 后台管理系统。",
-						link: "https://gitee.com/lolicode/scui",
-						time: "2020年7月24日"
+export default {
+	components: {
+		search,
+		tasks,
+		passwd
+	},
+	data() {
+		return {
+			userName: "",
+			userNameF: "",
+			searchVisible: false,
+			setVisible: false,
+			tasksVisible: false,
+			msg: false,
+			msgList: [],
+			baseData: {}
+		}
+	},
+	created() {
+		var userInfo = this.$TOOL.data.get("USER_INFO");
+		this.userName = userInfo.truename;
+		this.userNameF = this.userName.substring(0, 1);
+		var baseData = this.$TOOL.data.get("SERVICE");
+		this.baseData = baseData;
+	},
+	methods: {
+		//个人信息
+		handleUser(command) {
+			if (command == "uc") {
+				this.$router.push({ path: '/usercenter' });
+			}
+			if (command == "passwd") {
+				var userInfo = this.$TOOL.data.get("USER_INFO");
+				this.$nextTick(() => {
+					this.$refs.passwd.open("edit").setData(userInfo);
+				})
+			}
+			if (command == "cmd") {
+				this.$router.push({ path: '/cmd' });
+			}
+			if (command == "clearmenu") {
+				this.$confirm('确定要更新栏目数据吗', '提示', {
+					type: 'info',
+				}).then(async () => {
+					const loading = this.$loading()
+					//获取菜单
+					var menu = await this.$API.menu.list.get()
+					loading.close()
+					if (menu.code == 1) {
+						if (menu.data.menu.length == 0) {
+							this.islogin = false
+							this.$alert("当前用户无任何菜单权限,请联系系统管理员", "无权限访问", {
+								type: 'error',
+								center: true
+							})
+							return false
+						}
+						this.$TOOL.data.set("MENU", menu.data.menu)
+						this.$TOOL.data.set("PERMISSIONS", menu.data.permissions)
+						this.$TOOL.data.set("DASHBOARDGRID", menu.data.dashboardGrid)
+						location.reload()
+					} else {
+						this.islogin = false
+						this.$message.warning(menu.msg)
+						return false
 					}
-				]
+				}).catch(() => {
+					//取消
+				})
+			}
+			if (command == "outLogin") {
+				this.$confirm('确认是否退出当前用户?', '提示', {
+					type: 'warning',
+					confirmButtonText: '退出',
+					confirmButtonClass: 'el-button--danger'
+				}).then(() => {
+					this.$router.replace({ path: '/login' });
+				}).catch(() => {
+					//取消退出
+				})
 			}
 		},
-		created() {
-			var userInfo = this.$TOOL.data.get("USER_INFO");
-			this.userName = userInfo.userName;
-			this.userNameF = this.userName.substring(0,1);
+		//全屏
+		screen() {
+			var element = document.documentElement;
+			this.$TOOL.screen(element)
 		},
-		methods: {
-			//个人信息
-			handleUser(command) {
-				if(command == "uc"){
-					this.$router.push({path: '/usercenter'});
-				}
-				if(command == "cmd"){
-					this.$router.push({path: '/cmd'});
-				}
-				if(command == "clearCache"){
-					this.$confirm('清除缓存会将系统初始化,包括登录状态、主题、语言设置等,是否继续?','提示', {
-						type: 'info',
-					}).then(() => {
-						const loading = this.$loading()
-						this.$TOOL.data.clear()
-						this.$router.replace({path: '/login'})
-						setTimeout(()=>{
-							loading.close()
-							location.reload()
-						},1000)
-					}).catch(() => {
-						//取消
-					})
-				}
-				if(command == "outLogin"){
-					this.$confirm('确认是否退出当前用户?','提示', {
-						type: 'warning',
-						confirmButtonText: '退出',
-						confirmButtonClass: 'el-button--danger'
-					}).then(() => {
-						this.$router.replace({path: '/login'});
-					}).catch(() => {
-						//取消退出
-					})
-				}
-			},
-			//全屏
-			screen(){
-				var element = document.documentElement;
-				this.$TOOL.screen(element)
-			},
-			//显示短消息
-			showMsg(){
-				this.msg = true
-			},
-			//标记已读
-			markRead(){
-				this.msgList = []
-			},
-			//搜索
-			search(){
-				this.searchVisible = true
-			},
-			//任务
-			tasks(){
-				this.tasksVisible = true
-			}
+		//显示短消息
+		showMsg() {
+			this.msg = true
+		},
+		//标记已读
+		markRead() {
+			this.msgList = []
+		},
+		//搜索
+		search() {
+			this.searchVisible = true
+		},
+		//任务
+		tasks() {
+			this.tasksVisible = true
 		}
 	}
+}
 </script>
 
 <style scoped>
-	.user-bar {display: flex;align-items: center;height: 100%;}
-	.user-bar .panel-item {padding: 0 10px;cursor: pointer;height: 100%;display: flex;align-items: center;}
-	.user-bar .panel-item i {font-size: 16px;}
-	.user-bar .panel-item:hover {background: rgba(0, 0, 0, 0.1);}
-	.user-bar .user-avatar {height:49px;display: flex;align-items: center;}
-	.user-bar .user-avatar label {display: inline-block;margin-left:5px;font-size: 12px;cursor:pointer;}
-
-	.msg-list li {border-top:1px solid #eee;}
-	.msg-list li a {display: flex;padding:20px;}
-	.msg-list li a:hover {background: #ecf5ff;}
-	.msg-list__icon {width: 40px;margin-right: 15px;}
-	.msg-list__main {flex: 1;}
-	.msg-list__main h2 {font-size: 15px;font-weight: normal;color: #333;}
-	.msg-list__main p {font-size: 12px;color: #999;line-height: 1.8;margin-top: 5px;}
-	.msg-list__time {width: 100px;text-align: right;color: #999;}
-
-	.dark .msg-list__main h2 {color: #d0d0d0;}
-	.dark .msg-list li {border-top:1px solid #363636;}
-	.dark .msg-list li a:hover {background: #383838;}
+.user-bar {
+	display: flex;
+	align-items: center;
+	height: 100%;
+}
+
+.user-bar .panel-item {
+	padding: 0 10px;
+	cursor: pointer;
+	height: 100%;
+	display: flex;
+	align-items: center;
+}
+
+.user-bar .panel-item i {
+	font-size: 16px;
+}
+
+.user-bar .panel-item:hover {
+	background: rgba(0, 0, 0, 0.1);
+}
+
+.user-bar .panel-item span {
+	margin-left: 10px;
+	font-size: 14px;
+}
+
+.user-bar .user-avatar {
+	height: 49px;
+	display: flex;
+	align-items: center;
+}
+
+.user-bar .user-avatar label {
+	display: inline-block;
+	margin-left: 5px;
+	font-size: 12px;
+	cursor: pointer;
+}
+
+.msg-list li {
+	border-top: 1px solid #eee;
+}
+
+.msg-list li a {
+	display: flex;
+	padding: 20px;
+}
+
+.msg-list li a:hover {
+	background: #ecf5ff;
+}
+
+.msg-list__icon {
+	width: 40px;
+	margin-right: 15px;
+}
+
+.msg-list__main {
+	flex: 1;
+}
+
+.msg-list__main h2 {
+	font-size: 15px;
+	font-weight: normal;
+	color: #333;
+}
+
+.msg-list__main p {
+	font-size: 12px;
+	color: #999;
+	line-height: 1.8;
+	margin-top: 5px;
+}
+
+.msg-list__time {
+	width: 100px;
+	text-align: right;
+	color: #999;
+}
+
+.dark .msg-list__main h2 {
+	color: #d0d0d0;
+}
+
+.dark .msg-list li {
+	border-top: 1px solid #363636;
+}
+
+.dark .msg-list li a:hover {
+	background: #383838;
+}
 </style>

+ 203 - 162
src/layout/index.vue

@@ -1,15 +1,18 @@
 <template>
 	<!-- 通栏布局 -->
-	<template v-if="layout=='header'">
+	<template v-if="layout == 'header'">
 		<header class="adminui-header">
 			<div class="adminui-header-left">
-				<div class="logo-bar">
+				<div class="logo-bar" v-if="!ismobile">
 					<img class="logo" src="img/logo.png">
-					<span>{{ $CONFIG.APP_NAME }}</span>
+					<span>{{ baseData.title ? baseData.title : $CONFIG.APP_NAME }}</span>
 				</div>
-				<ul v-if="!ismobile" class="nav">
-					<li v-for="item in menu" :key="item" :class="pmenu.path==item.path?'active':''" @click="showMenu(item)">
-						<el-icon><component :is="item.meta.icon || 'el-icon-menu'" /></el-icon>
+				<ul class="nav">
+					<li v-for="item in menu" :key="item" :class="pmenu.path == item.path ? 'active' : ''"
+						@click="showMenu(item)">
+						<el-icon>
+							<component :is="item.meta.icon || 'el-icon-menu'" />
+						</el-icon>
 						<span>{{ item.meta.title }}</span>
 					</li>
 				</ul>
@@ -19,44 +22,57 @@
 			</div>
 		</header>
 		<section class="aminui-wrapper">
-			<div v-if="!ismobile && nextMenu.length>0 || !pmenu.component" :class="menuIsCollapse?'aminui-side isCollapse':'aminui-side'">
-				<div v-if="!menuIsCollapse" class="adminui-side-top">
-					<h2>{{ pmenu.meta.title }}</h2>
-				</div>
-				<div class="adminui-side-scroll">
-					<el-scrollbar>
-						<el-menu :default-active="active" router :collapse="menuIsCollapse" :unique-opened="$CONFIG.MENU_UNIQUE_OPENED">
-							<NavMenu :navMenus="nextMenu"></NavMenu>
-						</el-menu>
-					</el-scrollbar>
+			<sc-water-mark ref="wm" :text="userName" :subtext="dateTime" color="rgba(228, 231, 237,0.5)">
+				<div v-if="nextMenu.length > 0 || !pmenu.component"
+					:class="menuIsCollapse ? 'aminui-side isCollapse' : 'aminui-side'">
+					<div v-if="!menuIsCollapse" class="adminui-side-top">
+						<h2>{{ pmenu.meta.title }}</h2>
+					</div>
+					<div class="adminui-side-scroll">
+						<el-scrollbar>
+							<el-menu :default-active="active" router :collapse="menuIsCollapse"
+								:unique-opened="$CONFIG.MENU_UNIQUE_OPENED">
+								<NavMenu :navMenus="nextMenu"></NavMenu>
+							</el-menu>
+						</el-scrollbar>
+					</div>
+					<div class="adminui-side-bottom" @click="$store.commit('TOGGLE_menuIsCollapse')">
+						<el-icon><el-icon-expand v-if="menuIsCollapse" /><el-icon-fold v-else /></el-icon>
+					</div>
 				</div>
-				<div class="adminui-side-bottom" @click="$store.commit('TOGGLE_menuIsCollapse')">
-					<el-icon><el-icon-expand v-if="menuIsCollapse"/><el-icon-fold v-else /></el-icon>
+				<!-- <Side-m v-if="ismobile"></Side-m> -->
+				<div class="aminui-body el-container">
+					<!-- <Topbar v-if="!ismobile"></Topbar> -->
+					<!-- <Tags v-if="!ismobile && layoutTags"></Tags> -->
+					<div class="adminui-main" id="adminui-main">
+						<router-view v-slot="{ Component }">
+							<keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
+								<component :is="Component" :key="$route.fullPath"
+									v-if="$store.state.keepAlive.routeShow" />
+							</keep-alive>
+						</router-view>
+						<iframe-view></iframe-view>
+					</div>
 				</div>
-			</div>
-			<Side-m v-if="ismobile"></Side-m>
-			<div class="aminui-body el-container">
-				<Topbar v-if="!ismobile"></Topbar>
-				<Tags v-if="!ismobile && layoutTags"></Tags>
-				<div class="adminui-main" id="adminui-main">
-					<router-view v-slot="{ Component }">
-					    <keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
-					        <component :is="Component" :key="$route.fullPath" v-if="$store.state.keepAlive.routeShow"/>
-					    </keep-alive>
-					</router-view>
-					<iframe-view></iframe-view>
-				</div>
-			</div>
+			</sc-water-mark>
 		</section>
 	</template>
 
 	<!-- 经典布局 -->
-	<template v-else-if="layout=='menu'">
+	<template v-else-if="layout == 'menu'">
 		<header class="adminui-header">
 			<div class="adminui-header-left">
 				<div class="logo-bar">
-					<img class="logo" src="img/logo.png">
-					<span>{{ $CONFIG.APP_NAME }}</span>
+					<img class="logo" :src="baseData.logo ? baseData.logo : 'img/logo.png'">
+					<span>{{ baseData.title ? baseData.title : $CONFIG.APP_NAME }}</span>
+					<div class="ml10">
+						<el-tag v-if="userInfo.type==1">管理后台</el-tag>
+						<el-tooltip class="box-item" effect="dark" :content="userInfo.vip_end==0?'到期时间:永久':'到期时间:'+$TOOL.getTime(userInfo.vip_end,'YYYY年MM月DD日')" placement="right" v-else>
+							<el-tag v-if="userInfo.type==2">代理-旗舰版</el-tag>
+							<el-tag v-if="userInfo.type==3">门店-旗舰版</el-tag>
+							<el-tag v-if="userInfo.type==4">工厂-旗舰版</el-tag>
+						</el-tooltip>
+					</div>
 				</div>
 			</div>
 			<div class="adminui-header-right">
@@ -64,27 +80,27 @@
 			</div>
 		</header>
 		<section class="aminui-wrapper">
-			<div v-if="!ismobile" :class="menuIsCollapse?'aminui-side isCollapse':'aminui-side'">
+			<div :class="menuIsCollapse ? 'aminui-side isCollapse' : 'aminui-side'">
 				<div class="adminui-side-scroll">
 					<el-scrollbar>
-						<el-menu :default-active="active" router :collapse="menuIsCollapse" :unique-opened="$CONFIG.MENU_UNIQUE_OPENED">
+						<el-menu :default-active="active" router :collapse="menuIsCollapse"
+							:unique-opened="$CONFIG.MENU_UNIQUE_OPENED">
 							<NavMenu :navMenus="menu"></NavMenu>
 						</el-menu>
 					</el-scrollbar>
 				</div>
 				<div class="adminui-side-bottom" @click="$store.commit('TOGGLE_menuIsCollapse')">
-					<el-icon><el-icon-expand v-if="menuIsCollapse"/><el-icon-fold v-else /></el-icon>
+					<el-icon><el-icon-expand v-if="menuIsCollapse" /><el-icon-fold v-else /></el-icon>
 				</div>
 			</div>
-			<Side-m v-if="ismobile"></Side-m>
 			<div class="aminui-body el-container">
-				<Topbar v-if="!ismobile"></Topbar>
-				<Tags v-if="!ismobile && layoutTags"></Tags>
+				<Tags v-if="layoutTags"></Tags>
+				<!-- <Topbar></Topbar> -->
 				<div class="adminui-main" id="adminui-main">
 					<router-view v-slot="{ Component }">
-					    <keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
-					        <component :is="Component" :key="$route.fullPath" v-if="$store.state.keepAlive.routeShow"/>
-					    </keep-alive>
+						<keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
+							<component :is="Component" :key="$route.fullPath" v-if="$store.state.keepAlive.routeShow" />
+						</keep-alive>
 					</router-view>
 					<iframe-view></iframe-view>
 				</div>
@@ -93,7 +109,7 @@
 	</template>
 
 	<!-- 功能坞布局 -->
-	<template v-else-if="layout=='dock'">
+	<template v-else-if="layout == 'dock'">
 		<header class="adminui-header">
 			<div class="adminui-header-left">
 				<div class="logo-bar">
@@ -103,7 +119,8 @@
 			</div>
 			<div class="adminui-header-right">
 				<div v-if="!ismobile" class="adminui-header-menu">
-					<el-menu mode="horizontal" :default-active="active" router background-color="#222b45" text-color="#fff" active-text-color="var(--el-color-primary)">
+					<el-menu mode="horizontal" :default-active="active" router background-color="#222b45"
+						text-color="#fff" active-text-color="var(--el-color-primary)">
 						<NavMenu :navMenus="menu"></NavMenu>
 					</el-menu>
 				</div>
@@ -116,9 +133,9 @@
 				<Tags v-if="!ismobile && layoutTags"></Tags>
 				<div class="adminui-main" id="adminui-main">
 					<router-view v-slot="{ Component }">
-					    <keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
-					        <component :is="Component" :key="$route.fullPath" v-if="$store.state.keepAlive.routeShow"/>
-					    </keep-alive>
+						<keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
+							<component :is="Component" :key="$route.fullPath" v-if="$store.state.keepAlive.routeShow" />
+						</keep-alive>
 					</router-view>
 					<iframe-view></iframe-view>
 				</div>
@@ -129,40 +146,44 @@
 	<!-- 默认布局 -->
 	<template v-else>
 		<section class="aminui-wrapper">
-			<div v-if="!ismobile" class="aminui-side-split">
+			<div class="aminui-side-split">
 				<div class="aminui-side-split-top">
 					<router-link :to="$CONFIG.DASHBOARD_URL">
-						<img class="logo" :title="$CONFIG.APP_NAME" src="img/logo-r.png">
+						<img class="logo" :title="$CONFIG.APP_NAME" src="img/logo.png">
 					</router-link>
 				</div>
 				<div class="adminui-side-split-scroll">
 					<el-scrollbar>
 						<ul>
-							<li v-for="item in menu" :key="item" :class="pmenu.path==item.path?'active':''"
+							<li v-for="item in menu" :key="item" :class="pmenu.path == item.path ? 'active' : ''"
 								@click="showMenu(item)">
-								<el-icon><component :is="item.meta.icon || el-icon-menu" /></el-icon>
+								<el-icon>
+									<component :is="item.meta.icon || el - icon - menu" />
+								</el-icon>
 								<p>{{ item.meta.title }}</p>
 							</li>
 						</ul>
 					</el-scrollbar>
 				</div>
 			</div>
-			<div v-if="!ismobile && nextMenu.length>0 || !pmenu.component" :class="menuIsCollapse?'aminui-side isCollapse':'aminui-side'">
+			<div v-if="nextMenu.length > 0 || !pmenu.component"
+				:class="menuIsCollapse ? 'aminui-side isCollapse' : 'aminui-side'">
 				<div v-if="!menuIsCollapse" class="adminui-side-top">
 					<h2>{{ pmenu.meta.title }}</h2>
 				</div>
 				<div class="adminui-side-scroll">
 					<el-scrollbar>
-						<el-menu :default-active="active" router :collapse="menuIsCollapse" :unique-opened="$CONFIG.MENU_UNIQUE_OPENED">
+						<el-menu :default-active="active" router :collapse="menuIsCollapse"
+							:unique-opened="$CONFIG.MENU_UNIQUE_OPENED">
 							<NavMenu :navMenus="nextMenu"></NavMenu>
 						</el-menu>
 					</el-scrollbar>
 				</div>
 				<div class="adminui-side-bottom" @click="$store.commit('TOGGLE_menuIsCollapse')">
-					<el-icon><el-icon-expand v-if="menuIsCollapse"/><el-icon-fold v-else /></el-icon>
+					<el-icon><el-icon-expand v-if="menuIsCollapse" /><el-icon-fold v-else /></el-icon>
 				</div>
 			</div>
-			<Side-m v-if="ismobile"></Side-m>
+			<!-- <Side-m v-if="ismobile"></Side-m> -->
 			<div class="aminui-body el-container">
 				<Topbar>
 					<userbar></userbar>
@@ -170,9 +191,9 @@
 				<Tags v-if="!ismobile && layoutTags"></Tags>
 				<div class="adminui-main" id="adminui-main">
 					<router-view v-slot="{ Component }">
-					    <keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
-					        <component :is="Component" :key="$route.fullPath" v-if="$store.state.keepAlive.routeShow"/>
-					    </keep-alive>
+						<keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
+							<component :is="Component" :key="$route.fullPath" v-if="$store.state.keepAlive.routeShow" />
+						</keep-alive>
 					</router-view>
 					<iframe-view></iframe-view>
 				</div>
@@ -182,126 +203,146 @@
 
 	<div class="main-maximize-exit" @click="exitMaximize"><el-icon><el-icon-close /></el-icon></div>
 
-	<div class="layout-setting" @click="openSetting"><el-icon><el-icon-brush-filled /></el-icon></div>
+	<!-- <div class="layout-setting" @click="openSetting"><el-icon><el-icon-brush-filled /></el-icon></div>
 
 	<el-drawer title="布局实时演示" v-model="settingDialog" :size="400" append-to-body destroy-on-close>
 		<setting></setting>
-	</el-drawer>
+	</el-drawer> -->
 
 	<auto-exit></auto-exit>
 </template>
 
 <script>
-	import SideM from './components/sideM.vue';
-	import Topbar from './components/topbar.vue';
-	import Tags from './components/tags.vue';
-	import NavMenu from './components/NavMenu.vue';
-	import userbar from './components/userbar.vue';
-	import setting from './components/setting.vue';
-	import iframeView from './components/iframeView.vue';
-	import autoExit from './other/autoExit.js';
+import SideM from './components/sideM.vue';
+import Topbar from './components/topbar.vue';
+import Tags from './components/tags.vue';
+import NavMenu from './components/NavMenu.vue';
+import userbar from './components/userbar.vue';
+import setting from './components/setting.vue';
+import iframeView from './components/iframeView.vue';
+import autoExit from './other/autoExit.js';
 
-	export default {
-		name: 'index',
-		components: {
-			SideM,
-			Topbar,
-			Tags,
-			NavMenu,
-			userbar,
-			setting,
-			iframeView,
-			autoExit
+export default {
+	name: 'index',
+	components: {
+		SideM,
+		Topbar,
+		Tags,
+		NavMenu,
+		userbar,
+		setting,
+		iframeView,
+		autoExit
+	},
+	data() {
+		return {
+			settingDialog: false,
+			menu: [],
+			nextMenu: [],
+			pmenu: {},
+			userName: "",
+			active: '',
+			baseData: {},
+			userInfo:{}
+		}
+	},
+	computed: {
+		ismobile() {
+			return this.$store.state.global.ismobile
 		},
-		data() {
-			return {
-				settingDialog: false,
-				menu: [],
-				nextMenu: [],
-				pmenu: {},
-				active: ''
-			}
+		layout() {
+			return this.$store.state.global.layout
 		},
-		computed:{
-			ismobile(){
-				return this.$store.state.global.ismobile
-			},
-			layout(){
-				return this.$store.state.global.layout
-			},
-			layoutTags(){
-				return this.$store.state.global.layoutTags
-			},
-			menuIsCollapse(){
-				return this.$store.state.global.menuIsCollapse
-			}
+		layoutTags() {
+			return this.$store.state.global.layoutTags
 		},
-		created() {
-			this.onLayoutResize();
-			window.addEventListener('resize', this.onLayoutResize);
-			var menu = this.$router.sc_getMenu();
-			this.menu = this.filterUrl(menu);
+		menuIsCollapse() {
+			return this.$store.state.global.menuIsCollapse
+		}
+	},
+	created() {
+		this.onLayoutResize();
+		window.addEventListener('resize', this.onLayoutResize);
+		var menu = this.$router.sc_getMenu();
+		this.menu = this.filterUrl(menu);
+		var userInfo = this.$TOOL.data.get("USER_INFO");
+		this.userInfo = userInfo;
+
+		this.userName = userInfo.truename;
+		var baseData = this.$TOOL.data.get("SERVICE");
+		this.baseData = baseData;
+		this.showThis()
+	},
+	watch: {
+		$route() {
 			this.showThis()
 		},
-		watch: {
-			$route() {
-				this.showThis()
+		layout: {
+			handler(val) {
+				document.body.setAttribute('data-layout', val)
 			},
-			layout: {
-				handler(val){
-					document.body.setAttribute('data-layout', val)
-				},
-				immediate: true,
+			immediate: true,
+		}
+	},
+	methods: {
+		openSetting() {
+			this.settingDialog = true;
+		},
+		onLayoutResize() {
+			this.$store.commit("SET_ismobile", document.body.clientWidth < 992)
+		},
+		//路由监听高亮
+		showThis() {
+			this.pmenu = this.$route.meta.breadcrumb ? this.$route.meta.breadcrumb[0] : {}
+			this.nextMenu = this.filterUrl(this.pmenu.children);
+			this.$nextTick(() => {
+				this.active = this.$route.meta.active || this.$route.fullPath;
+			})
+		},
+		//点击显示
+		showMenu(route) {
+			this.pmenu = route;
+			this.nextMenu = this.filterUrl(route.children);
+			if (route.meta.type == 'link') {
+				window.open(route.path);
+			} else {
+				this.$router.push({ path: route.path })
 			}
+			// if((!route.children || route.children.length == 0) && route.component){
+			// 	this.$router.push({path: route.path})
+			// }
 		},
-		methods: {
-			openSetting(){
-				this.settingDialog = true;
-			},
-			onLayoutResize(){
-				this.$store.commit("SET_ismobile", document.body.clientWidth < 992)
-			},
-			//路由监听高亮
-			showThis(){
-				this.pmenu = this.$route.meta.breadcrumb ? this.$route.meta.breadcrumb[0] : {}
-				this.nextMenu = this.filterUrl(this.pmenu.children);
-				this.$nextTick(()=>{
-					this.active = this.$route.meta.active || this.$route.fullPath;
-				})
-			},
-			//点击显示
-			showMenu(route) {
-				this.pmenu = route;
-				this.nextMenu = this.filterUrl(route.children);
-				if((!route.children || route.children.length == 0) && route.component){
-					this.$router.push({path: route.path})
+		//转换外部链接的路由
+		filterUrl(map) {
+			var newMap = []
+			map && map.forEach(item => {
+				item.meta = item.meta ? item.meta : {};
+				//处理隐藏
+				if (item.meta.hidden || item.meta.type == "button") {
+					return false
 				}
-			},
-			//转换外部链接的路由
-			filterUrl(map){
-				var newMap = []
-				map && map.forEach(item => {
-					item.meta = item.meta?item.meta:{};
-					//处理隐藏
-					if(item.meta.hidden || item.meta.type=="button"){
-						return false
-					}
-					//处理http
-					if(item.meta.type=='iframe'){
-						item.path = `/i/${item.name}`;
-					}
-					//递归循环
-					if(item.children&&item.children.length > 0){
-						item.children = this.filterUrl(item.children)
-					}
-					newMap.push(item)
-				})
-				return newMap;
-			},
-			//退出最大化
-			exitMaximize(){
-				document.getElementById('app').classList.remove('main-maximize')
-			}
+				//处理http
+				if (item.meta.type == 'iframe') {
+					item.path = `/i/${item.name}`;
+				}
+				//递归循环
+				if (item.children && item.children.length > 0) {
+					item.children = this.filterUrl(item.children)
+				}
+				newMap.push(item)
+			})
+			return newMap;
+		},
+		//退出最大化
+		exitMaximize() {
+			document.getElementById('app').classList.remove('main-maximize')
 		}
 	}
+}
 </script>
+
+<style>
+.ml10 {
+	cursor: pointer;
+}
+</style>

+ 1 - 0
src/locales/lang/zh-cn.js

@@ -23,6 +23,7 @@ export default {
 		PWName: "登录密码",
 		code: "验证码",
 		codeErr: "请输入验证码",
+		codeTips: "点击验证码图片可切换",
 		userPlaceholder: "请输入用户名",
 		userError: "请输入用户名",
 		PWPlaceholder: "请输入密码",

+ 856 - 80
src/style/app.scss

@@ -1,103 +1,879 @@
 /* 全局 */
-#app, body, html {width: 100%;height: 100%;background-color: #f6f8f9;font-size: 12px;}
-a {color: #333;text-decoration: none;}
-a:hover, a:focus {color: #000;text-decoration: none;}
-a:link {text-decoration: none;}
-a:-webkit-any-link {text-decoration: none;}
-a,button,input,textarea{-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing: border-box;outline:none !important; -webkit-appearance: none;}
-* {margin: 0;padding: 0;box-sizing: border-box;outline: none;}
+#app,
+body,
+html {
+	width: 100%;
+	height: 100%;
+	// background-color: #f6f8f9;
+	font-size: 12px;
+	font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
+	// font-family: var(--font-family), Helvetica, sans-serif !important;
+	// text-shadow: 0px 1px 1px rgba(0, 0, 0, .6);
+	// font-variation-settings: "wght" 400, "opsz" 8;
+}
+
+a {
+	color: #333;
+	text-decoration: none;
+}
+
+a:hover,
+a:focus {
+	color: #000;
+	text-decoration: none;
+}
+
+a:link {
+	text-decoration: none;
+}
+
+a:-webkit-any-link {
+	text-decoration: none;
+}
+
+a,
+button,
+input,
+textarea {
+	-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+	box-sizing: border-box;
+	outline: none !important;
+	-webkit-appearance: none;	
+	// font-family: var(--font-family);
+}
+
+* {
+	margin: 0;
+	padding: 0;
+	box-sizing: border-box;
+	outline: none;
+}
 
 /* 大布局样式 */
-.aminui {display: flex;flex-flow: column;}
-.aminui-wrapper {display: flex;flex:1;overflow: auto;}
+.aminui {
+	display: flex;
+	flex-flow: column;
+}
+
+.aminui-wrapper {
+	display: flex;
+	flex: 1;
+	overflow: auto;
+}
 
 /* 全局滚动条样式 */
-.scrollable {-webkit-overflow-scrolling: touch;}
-::-webkit-scrollbar {width: 5px;height: 5px;}
-::-webkit-scrollbar-thumb {background-color: rgba(50, 50, 50, 0.3);}
-::-webkit-scrollbar-thumb:hover {background-color: rgba(50, 50, 50, 0.6);}
-::-webkit-scrollbar-track {background-color: rgba(50, 50, 50, 0.1);}
-::-webkit-scrollbar-track:hover {background-color: rgba(50, 50, 50, 0.2);}
+.scrollable {
+	-webkit-overflow-scrolling: touch;
+}
+
+::-webkit-scrollbar {
+	width: 5px;
+	height: 5px;
+}
+
+::-webkit-scrollbar-thumb {
+	background-color: rgba(50, 50, 50, 0.3);
+}
+
+::-webkit-scrollbar-thumb:hover {
+	background-color: rgba(50, 50, 50, 0.6);
+}
+
+::-webkit-scrollbar-track {
+	background-color: rgba(50, 50, 50, 0.1);
+}
+
+::-webkit-scrollbar-track:hover {
+	background-color: rgba(50, 50, 50, 0.2);
+}
 
 /*布局设置*/
-.layout-setting {position: fixed;width: 40px;height: 40px;border-radius: 3px 0 0 3px;bottom: 100px;right: 0px;z-index: 100;background: #409EFF;display: flex;flex-direction: column;align-items: center;justify-content: center;cursor: pointer;}
-.layout-setting i {font-size: 18px;color: #fff;}
+.layout-setting {
+	position: fixed;
+	width: 40px;
+	height: 40px;
+	border-radius: 3px 0 0 3px;
+	bottom: 100px;
+	right: 0px;
+	z-index: 100;
+	background: #409EFF;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	cursor: pointer;
+}
+
+.layout-setting i {
+	font-size: 18px;
+	color: #fff;
+}
 
 /* 头部 */
-.adminui-header {height: 58px;background: #222b45;color: #fff;display: flex;justify-content:space-between;}
-.adminui-header-left {display: flex;align-items: center;padding-left:20px;}
-.adminui-header-right {display: flex;align-items: center;}
-.adminui-header .logo-bar {font-size: 20px;font-weight: bold;display: flex;align-items: center;}
-.adminui-header .logo-bar .logo {margin-right: 10px;width: 35px;height: 35px;}
-.adminui-header .nav {display: flex;height: 100%;margin-left: 40px;}
-.adminui-header .nav li {padding:0 10px;margin: 0 10px 0 0;font-size: 14px;color: rgba(255, 255, 255, 0.6);list-style: none;height: 100%;display: flex;align-items: center;cursor: pointer;}
-.adminui-header .nav li i {margin-right: 5px;}
-.adminui-header .nav li:hover {color: #fff;}
-.adminui-header .nav li.active {background: rgba(255, 255, 255, 0.1);color: #fff;}
-.adminui-header .user-bar .panel-item:hover {background: rgba(255, 255, 255, 0.1)!important;}
-.adminui-header .user-bar .user label{color: #fff;}
+.adminui-header {
+	height: 58px;
+	// background: rgba(255, 255, 255);
+	color: #333;
+	display: flex;
+	justify-content: space-between;
+	border-bottom: 1px solid #f0f2f5;
+	box-shadow: 0 1px 10px #272b3c1a;
+}
+
+.adminui-header-left {
+	display: flex;
+	align-items: center;
+	padding-left: 20px;
+}
+
+.adminui-header-right {
+	display: flex;
+	align-items: center;
+}
+
+.adminui-header .logo-bar {
+	font-size: 18px;
+	font-weight: bold;
+	display: flex;
+	align-items: center;
+}
+
+.adminui-header .logo-bar .logo {
+	margin-right: 10px;
+	height: 25px;
+}
+
+.adminui-header .nav {
+	display: flex;
+	height: 100%;
+	margin-left: 40px;
+}
+
+.adminui-header .nav li {
+	padding: 0 15px;
+	margin: 0 10px 0 0;
+	font-size: 14px;
+	color: #333;
+	list-style: none;
+	height: 100%;
+	display: flex;
+	align-items: center;
+	cursor: pointer;
+	font-weight: 600;
+	position: relative;
+}
+
+.adminui-header .nav li::after {
+	position: absolute;
+	content: "";
+	width: 80%;
+	left: 10%;
+	height: 2px;
+	// background-color: #fff;
+	bottom: 0;
+}
+
+.adminui-header .nav li i {
+	margin-right: 5px;
+}
+
+.adminui-header .nav li:hover {
+	color: var(--el-color-primary);
+}
+
+.adminui-header .nav li.active {
+	background: rgba(255, 255, 255, 0.1);
+	color: var(--el-color-primary);
+	font-weight: 700;
+}
+
+.adminui-header .nav li.active::after {
+	background-color: var(--el-color-primary);
+}
+
+.adminui-header .user-bar .panel-item:hover {
+	background: rgba(255, 255, 255, 0.1) !important;
+}
+
+.adminui-header .user-bar .user label {
+	color: #333;
+}
 
 /* 左侧菜单 */
-.aminui-side-split {width:65px;flex-shrink:0;background: #222b45;display: flex;flex-flow: column;}
-.aminui-side-split-top {height: 49px;}
-.aminui-side-split-top a {display: inline-block;width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;}
-.aminui-side-split-top .logo {height:30px;vertical-align: bottom;}
-.adminui-side-split-scroll {overflow: auto;overflow-x:hidden;height: 100%;flex: 1;}
-.aminui-side-split li {cursor: pointer;width: 65px;height: 65px;color: #fff;text-align: center;display: flex;flex-direction: column;align-items: center;justify-content: center;}
-.aminui-side-split li i {font-size: 18px;}
-.aminui-side-split li p {margin-top:5px;}
-.aminui-side-split li:hover {background: rgba(255, 255, 255, 0.1);}
-.aminui-side-split li.active {background: #409EFF;}
-
-.adminui-side-split-scroll::-webkit-scrollbar-thumb {background-color: rgba(255, 255, 255, 0.4);border-radius:5px;}
-.adminui-side-split-scroll::-webkit-scrollbar-thumb:hover {background-color: rgba(255, 255, 255, 0.5);}
-.adminui-side-split-scroll::-webkit-scrollbar-track {background-color: rgba(255, 255, 255, 0);}
-.adminui-side-split-scroll::-webkit-scrollbar-track:hover {background-color: rgba(255, 255, 255, 0);}
-
-.aminui-side {display: flex;flex-flow: column;flex-shrink:0;width:210px;background: #fff;box-shadow: 2px 0 8px 0 rgba(29,35,41,.05);border-right: 1px solid #e6e6e6;transition:width 0.3s;}
-.adminui-side-top {border-bottom: 1px solid #ebeef5;height:50px;line-height: 50px;}
-.adminui-side-top h2 {padding:0 20px;font-size: 17px;color: #3c4a54;}
-.adminui-side-scroll {overflow: auto;overflow-x:hidden;flex: 1;}
-.adminui-side-bottom {border-top: 1px solid #ebeef5;height:51px;cursor: pointer;display: flex;align-items: center;justify-content: center;}
-.adminui-side-bottom i {font-size: 16px;}
-.adminui-side-bottom:hover {color: var(--el-color-primary);}
-.aminui-side.isCollapse {width: 65px;}
-.el-menu .menu-tag {position: absolute;height: 18px;line-height: 18px;background: var(--el-color-danger);font-size: 12px;color: #fff;right: 20px;border-radius:18px;padding:0 6px;}
-.el-menu .el-sub-menu__title .menu-tag {right: 40px;}
-.el-menu--horizontal > li .menu-tag {display: none;}
+.aminui-side-split {
+	width: 65px;
+	flex-shrink: 0;
+	background: #000;
+	display: flex;
+	flex-flow: column;
+}
+
+.aminui-side-split-top {
+	height: 49px;
+}
+
+.aminui-side-split-top a {
+	display: inline-block;
+	width: 100%;
+	height: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.aminui-side-split-top .logo {
+	height: 30px;
+	vertical-align: bottom;
+}
+
+.adminui-side-split-scroll {
+	overflow: auto;
+	overflow-x: hidden;
+	height: 100%;
+	flex: 1;
+}
+
+.aminui-side-split li {
+	cursor: pointer;
+	width: 65px;
+	height: 65px;
+	color: #fff;
+	text-align: center;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+}
+
+.aminui-side-split li i {
+	font-size: 18px;
+}
+
+.aminui-side-split li p {
+	margin-top: 5px;
+}
+
+.aminui-side-split li:hover {
+	background: rgba(255, 255, 255, 0.1);
+}
+
+.aminui-side-split li.active {
+	background: #409EFF;
+}
+
+.adminui-side-split-scroll::-webkit-scrollbar-thumb {
+	background-color: rgba(255, 255, 255, 0.4);
+	border-radius: 5px;
+}
+
+.adminui-side-split-scroll::-webkit-scrollbar-thumb:hover {
+	background-color: rgba(255, 255, 255, 0.5);
+}
+
+.adminui-side-split-scroll::-webkit-scrollbar-track {
+	background-color: rgba(255, 255, 255, 0);
+}
+
+.adminui-side-split-scroll::-webkit-scrollbar-track:hover {
+	background-color: rgba(255, 255, 255, 0);
+}
+
+.aminui-side {
+	display: flex;
+	flex-flow: column;
+	flex-shrink: 0;
+	width: 210px;
+	background: #f2f3f5;
+	// background: #f8f8fa;
+	// box-shadow: 2px 0 8px 0 rgba(29, 35, 41, .05);
+	transition: width 0.3s;
+	border-right: 1px solid var(--el-border-color-light);
+}
+
+.adminui-side-top {
+	border-bottom: 1px solid #ebeef5;
+	height: 50px;
+	line-height: 50px;
+}
+
+.adminui-side-top h2 {
+	padding: 0 20px;
+	font-size: 17px;
+	color: #3c4a54;
+}
+
+.adminui-side-scroll {
+	overflow: auto;
+	overflow-x: hidden;
+	flex: 1;
+}
+
+.adminui-side-bottom {
+	border-top: 1px solid #ebeef5;
+	height: 51px;
+	cursor: pointer;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.adminui-side-bottom i {
+	font-size: 16px;
+}
+
+.adminui-side-bottom:hover {
+	color: var(--el-color-primary);
+}
+
+.aminui-side.isCollapse {
+	width: 65px;
+}
+
+.el-menu {
+	background: none;
+}
+
+.el-menu .menu-tag {
+	position: absolute;
+	height: 18px;
+	line-height: 18px;
+	background: var(--el-color-danger);
+	font-size: 12px;
+	color: #fff;
+	right: 20px;
+	border-radius: 18px;
+	padding: 0 6px;
+}
+
+.el-menu .el-sub-menu__title .menu-tag {
+	right: 40px;
+}
+
+.el-menu--horizontal>li .menu-tag {
+	display: none;
+}
 
 /* 右侧内容 */
-.aminui-body {flex: 1;display: flex;flex-flow: column;}
+.aminui-body {
+	flex: 1;
+	display: flex;
+	flex-flow: column;
+}
+
+.adminui-topbar {
+	height: 50px;
+	border-bottom: 1px solid #ebeef5;
+	// background: #fff;
+	// box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
+	display: flex;
+	justify-content: space-between;
+}
+
+.adminui-topbar .left-panel {
+	display: flex;
+	align-items: center;
+}
+
+.adminui-topbar .right-panel {
+	display: flex;
+	align-items: center;
+}
+
+.right-panel-search {
+	display: flex;
+	align-items: center;
+}
+
+.right-panel-search>*+* {
+	margin-left: 10px;
+}
 
-.adminui-topbar {height: 50px;border-bottom: 1px solid #ebeef5;background: #fff;box-shadow: 0 1px 4px rgba(0,21,41,.08);display: flex;justify-content:space-between;}
-.adminui-topbar .left-panel {display: flex;align-items: center;}
-.adminui-topbar .right-panel {display: flex;align-items: center;}
+.adminui-tags {
+	height: 35px;
+	background: #fff;
+	border-bottom: 1px solid #e6e6e6;
+}
+
+.adminui-tags ul {
+	display: flex;
+	overflow: hidden;
+}
+
+.adminui-tags li {
+	cursor: pointer;
+	display: inline-block;
+	float: left;
+	height: 34px;
+	line-height: 34px;
+	position: relative;
+	flex-shrink: 0;
+}
+
+.adminui-tags li::after {
+	content: " ";
+	width: 1px;
+	height: 100%;
+	position: absolute;
+	right: 0px;
+	background-image: linear-gradient(#fff, #e6e6e6);
+}
+
+.adminui-tags li a {
+	display: inline-block;
+	padding: 0 10px;
+	width: 100%;
+	height: 100%;
+	color: #999;
+	text-decoration: none;
+	display: flex;
+	align-items: center;
+}
+
+.adminui-tags li i {
+	margin-left: 10px;
+	border-radius: 3px;
+	width: 18px;
+	height: 18px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.adminui-tags li i:hover {
+	background: rgba(0, 0, 0, .2);
+	color: #fff;
+}
+
+.adminui-tags li.active {
+	background: #fff;
+}
+
+.adminui-tags li.active a {
+	// color: var(--el-color-primary);
+	color:#fff;
+	font-weight: bold;
+}
 
-.right-panel-search {display: flex;align-items: center;}
-.right-panel-search > * + * {margin-left:10px;}
+.adminui-tags li.active i {
+	// background: var(--el-color-primary);
+	color: #fff;
+}
 
-.adminui-tags {height:35px;background: #fff;border-bottom: 1px solid #e6e6e6;}
-.adminui-tags ul {display: flex;overflow: hidden;}
-.adminui-tags li {cursor: pointer;display: inline-block;float: left;height:34px;line-height: 34px;position: relative;flex-shrink: 0;}
-.adminui-tags li::after {content: " ";width:1px;height:100%;position: absolute;right:0px;background-image: linear-gradient(#fff, #e6e6e6);}
-.adminui-tags li a {display: inline-block;padding:0 10px;width:100%;height:100%;color: #999;text-decoration:none;display: flex;align-items: center;}
-.adminui-tags li i {margin-left:10px;border-radius: 3px;width:18px;height:18px;display: flex;align-items: center;justify-content: center;}
-.adminui-tags li i:hover {background: rgba(0,0,0,.2);color: #fff;}
-.adminui-tags li:hover {background: #ecf5ff;}
-.adminui-tags li.active {background: #409EFF;}
-.adminui-tags li.active a {color: #fff;}
-.adminui-tags li.sortable-ghost {opacity: 0;}
+.adminui-tags li.sortable-ghost {
+	opacity: 0;
+}
 
-.adminui-main {overflow: auto;background-color: #f6f8f9;flex: 1;}
+.adminui-main {
+	overflow: auto;
+	// background-color: #f0f2f5;
+	background-color: #f8f8f8;
+	flex: 1;
+}
 
 /*页面最大化*/
 .aminui.main-maximize {
-	.main-maximize-exit {display: block;}
-	.aminui-side-split, .aminui-side, .adminui-header, .adminui-topbar, .adminui-tags {display: none;}
+	.main-maximize-exit {
+		display: block;
+	}
+
+	.aminui-side-split,
+	.aminui-side,
+	.adminui-header,
+	.adminui-topbar,
+	.adminui-tags {
+		display: none;
+	}
+}
+
+.main-maximize-exit {
+	display: none;
+	position: fixed;
+	z-index: 3000;
+	top: -20px;
+	left: 50%;
+	margin-left: -20px;
+	border-radius: 50%;
+	width: 40px;
+	height: 40px;
+	cursor: pointer;
+	background: rgba(0, 0, 0, 0.2);
+	text-align: center;
+}
+
+.main-maximize-exit i {
+	font-size: 14px;
+	margin-top: 22px;
+	color: #fff;
+}
+
+.main-maximize-exit:hover {
+	background: rgba(0, 0, 0, 0.4);
 }
-.main-maximize-exit {display: none;position: fixed;z-index: 3000;top:-20px;left:50%;margin-left: -20px;border-radius: 50%;width: 40px;height: 40px;cursor: pointer;background: rgba(0,0,0,0.2);text-align: center;}
-.main-maximize-exit i {font-size: 14px;margin-top: 22px;color: #fff;}
-.main-maximize-exit:hover {background: rgba(0,0,0,0.4);}
 
 /*定宽页面*/
-.sc-page {width: 1230px;margin: 0 auto;}
+.sc-page {
+	width: 1230px;
+	margin: 0 auto;
+}
+
+.el-menu-item.is-active {
+	background-color: #efeff3;
+	// background-color: #e6eeff;
+	// border-right: 4px solid var(--el-color-primary);
+	color: var(--el-color-primary);
+}
+
+.flex-column {
+	flex-direction: column;
+}
+
+.mt10 {
+	margin-top: 10px;
+}
+
+.search-box {
+	background-color: #fff;
+	padding: 15px 15px 10px 15px;
+	margin-bottom: 10px;
+}
+
+.op-header {
+	background-color: #fff;
+	padding: 10px 10px 0 10px;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.search-btn {
+	width: 100%;
+	display: flex;
+	align-items: center;
+}
+
+fieldset {
+	margin: 0 0 10px 0;
+	border: 1px solid #EEE;
+	padding: 10px 20px;
+	background: #fff;
+	border-radius: 3px;
+}
+
+fieldset legend {
+	color: #666;
+	padding: 0 10px;
+	font-size: 12px;
+	letter-spacing: 1px;
+}
+
+.search-form {
+	display: flex;
+	justify-content: flex-start;
+}
+
+.search-form .form-left {
+	flex: 1 1 0%;
+}
+
+.search-form .form-left .el-input {
+	margin-bottom: 10px;
+}
+
+.search-form.mt0 .form-left .el-input {
+	margin-bottom: 0;
+}
+
+.search-form .form-line {
+	display: flex;
+	min-width: 1px;
+	max-width: 1px;
+	min-height: 30px;
+	margin: 0 12px;
+	justify-content: center;
+	align-items: center;
+	vertical-align: middle;
+	border-left: 1px solid var(--el-color-info-light-9);
+}
+
+.search-form .form-right {
+	flex: 0 0 86px;
+	text-align: right;
+}
+
+.search-form .form-right-inline {
+	flex: 0 0 186px;
+	text-align: right;
+}
+
+.search-form .form-right .el-button {
+	margin-bottom: 10px;
+}
+
+.el-table th.el-table__cell {
+	background-color: var(--el-fill-color-light) !important;
+}
+
+.hide-text {
+	white-space: nowrap;
+	text-overflow: ellipsis;
+	overflow: hidden;
+}
+
+.nopadding .el-button-group+.el-button-group {
+	margin-left: 0;
+}
+
+.channel-tabs {
+	background-color: #fff;
+	padding: 0 10px;
+}
+
+.channel_menu {
+	border-bottom: 1px solid var(--el-color-info-light-9) !important;
+}
+
+.channel-tabs .el-tabs__header {
+	margin: 0;
+}
+
+.channel-tabs .el-menu-item {
+	line-height: 46px;
+}
+
+.channel-tabs .el-menu-item.is-active {
+	border-right: 0;
+	background-color: #fff;
+	font-weight: bold;
+}
+
+.el-sub-menu.is-active .el-sub-menu__title {
+	// font-weight: bolder;
+}
+
+.el-sub-menu.is-active .el-sub-menu__title,
+.el-sub-menu.is-active .el-sub-menu__title span {
+	// color: var(--el-menu-active-color);
+}
+.el-sub-menu.is-active .el-sub-menu__title {
+	background-color: #e6eeff;
+	border-right: 4px solid var(--el-color-primary);
+	color: var(--el-menu-active-color);
+}
+.el-sub-menu.is-active .el-menu .el-sub-menu__title{
+	border-right:0;
+	color: var(--el-menu-text-color);;
+	background-color: #f2f3f5;
+}
+.dark .el-sub-menu.is-active .el-sub-menu__title {
+	background-color: var(--el-fill-color-light);
+}
+.dark .adminui-header .logo-bar{
+	color: #fff;
+}
+
+.tip-container {
+	margin: 20px 0;
+}
+
+.el-table__header-wrapper {
+	border-top: 1px solid var(--el-table-border-color);
+}
+
+.log-detail .el-table__header-wrapper {
+	border-top: 0;
+}
+
+.el-table th.el-table__cell {
+	padding: 13px 0;
+}
+
+.w-150px {
+	width: 150px;
+}
+
+.el-text {
+	display: inline-block;
+	max-width: 100%;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+	cursor: pointer;
+	font-size: 12px;
+	color: #666;
+}
+
+.status-danger {
+	color: var(--el-color-danger);
+	cursor: pointer;
+}
+
+.status-info {
+	color: var(--el-color-primary);
+	cursor: pointer;
+}
+
+.status-success {
+	color: var(--el-color-success);
+	cursor: pointer;
+}
+
+.bl_tags {
+	cursor: pointer;
+}
+
+.icon-right {
+	margin-left: 10px;
+}
+
+.el-dialog {
+	border-radius: 5px;
+}
+
+.el-dialog__header {
+	margin-right: 0;
+	border-radius: 5px 5px 0 0;
+	padding-bottom: 20px;
+	border-bottom: 1px solid #f8f8f8;
+}
+
+.el-dialog__footer {
+	border-top: 1px solid #f1f1fa;
+	padding: 10px 15px;
+}
+
+.el-dialog__title {
+	font-weight: 500 !important;
+}
+
+.left-panel .el-dropdown,
+.right-panel .el-dropdown {
+	margin-left: 10px;
+}
+
+.flex {
+	display: flex;
+	align-items: center;
+}
+
+.el-select.diy-select .el-input__wrapper {
+	padding: 1px 11px;
+	padding-left: 1px;
+}
+.el-select.diy-select .el-input__prefix{
+	margin-right: 10px;
+}
+.el-select.diy-select .el-input__prefix-inner {
+	background-color: var(--el-fill-color-light);
+	color: var(--el-color-info);
+	border-right: 1px solid var(--el-input-border-color);
+	padding-left: 11px;
+}
+.left-panel .el-button.el-button--default.is-disabled {
+	// background-color: #f5f5f5;
+	// border-color: #d9d9d9;
+}
+
+.popover-form-right {
+	text-align: right;
+	margin-top: 10px;
+}
+
+.popover-form-line {
+	border-top: 1px solid var(--el-color-info-light-9);
+}
+
+.table-container {
+	display: flex;
+	flex-direction: column;
+	height: 100%;
+	flex: 1;
+	padding: 10px;
+	background-color: #fff;
+	border-radius: 10px;
+}
+
+.table-container .table-data {
+	flex: 1;
+}
+
+.flex-footer {
+	margin-top: 5px;
+}
+
+.ml10 {
+	margin-left: 10px;
+}
+
+.op-header.none {
+	display: block;
+}
+
+.right-panel.none {
+	margin-top: 10px;
+}
+
+.table-search {
+	background-color: #fff;
+	padding: 15px 15px 0 15px;
+	border-bottom: 1px solid var(--el-color-info-light-9);
+}
+
+.table-search-menu {
+	background-color: #fff;
+	padding: 15px;
+	border-bottom: 1px solid var(--el-color-info-light-9);
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+
+.table-search-menu .menu-left {
+	font-size: 16px;
+	color: #333;
+}
+
+.drawer-detail-main {
+	background-color: var(--el-fill-color-light);
+	border-bottom: 1px solid #ebeef5;
+}
+
+.drawer-detail-header {
+	padding: 12px 12px;
+
+	.drawer-detail-header-subtitle {
+		margin-bottom: 2px;
+		font-size: 14px;
+		color: #6b778c;
+	}
+
+	.drawer-detail-header-body {
+		display: flex;
+		align-items: center;
+		width: 100%;
+		text-align: left;
+		justify-content: space-between;
+
+		.drawer-detail-header-left {
+			display: flex;
+			align-items: center;
+			overflow: hidden;
+			font-size: 18px;
+			text-overflow: ellipsis;
+			-webkit-line-clamp: 1;
+			-webkit-box-orient: vertical;
+
+			.tips {
+				color: #666;
+				font-size: 13px;
+				margin-left: 10px;
+			}
+		}
+
+		.drawer-detail-header-right {
+			.el-form-item--default {
+				margin-right: 0;
+				margin-bottom: 0
+			}
+		}
+	}
+}

+ 6 - 0
src/utils/request.js

@@ -19,6 +19,12 @@ axios.interceptors.request.use(
 			config.params = config.params || {};
 			config.params['_'] = new Date().getTime();
 		}
+		var typeName = "web";
+		let flag = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
+		if(flag){
+			typeName = "wap";
+		}
+		config.headers["api-type"] = typeName
 		Object.assign(config.headers, sysConfig.HEADERS)
 		return config;
 	},

+ 27 - 0
src/utils/tool.js

@@ -168,6 +168,33 @@ tool.dateFormat = function (date, fmt='yyyy-MM-dd hh:mm:ss') {
 	return fmt;
 }
 
+tool.getTime = (data,formate = "YYYY-MM-DD HH:mm:ss") => {
+	let value = Number(data.toString().split(".")[0]);
+	let dt = new Date(value * 1000);
+	let year = dt.getFullYear();
+	let month = (dt.getMonth() + 1 + "").padStart(2, "0");
+	let day = (dt.getDate() + "").padStart(2, "0");
+  
+	let hour = (dt.getHours() + "").padStart(2, "0");
+	let min = (dt.getMinutes() + "").padStart(2, "0");
+	let second = (dt.getSeconds() + "").padStart(2, "0");
+	if (formate == 'YYYY') {
+	  return year;
+	} else if (formate == 'YYYY-MM') {
+	  return `${year}-${month}`;
+	} else if (formate == 'YYYY年MM月DD日') {
+	  return `${year}年${month}月${day}日`;
+	} else if (formate == 'YYYY-MM-DD') {
+	  return `${year}-${month}-${day}`;
+	} else if (formate == 'YYYY-MM-DD HH') {
+	  return `${year}-${month}-${day} ${hour}:00`;
+	} else if (formate == 'YYYY-MM-DD HH:mm') {
+	  return `${year}-${month}-${day} ${hour}:${min}`;
+	} else if (formate == 'YYYY-MM-DD HH:mm:ss') {
+	  return `${year}-${month}-${day} ${hour}:${min}:${second}`;
+	}
+}
+
 /* 千分符 */
 tool.groupSeparator = function (num) {
 	num = num + '';

+ 23 - 25
src/views/login/components/mobile.vue

@@ -14,7 +14,9 @@
         <el-form-item prop="code" :label="$t('login.code')">
             <el-input v-model="form.code" :placeholder="$t('login.codeErr')">
 				<template #suffix>
-					<div class="captcha" @click="updateCaptcha"><img :src="captcha.base64" /></div>
+					<el-tooltip placement="top-start" :content="$t('login.codeTips')">
+						<div class="captcha" @click="updateCaptcha"><img :src="captcha.base64" /></div>
+					</el-tooltip>
 				</template>
 			</el-input>
         </el-form-item>
@@ -106,32 +108,28 @@ export default {
 		async onSuccess(){
 			this.isShow = false;
 		},
-		async getYzm(){
+		getYzm(){
 			var field = ["phone","code"];
-			var flag = true;
-			field.forEach((item,key) => {
-				this.$refs.loginForm.validateField(item, valid => {
-					if(valid.length){
-						return flag = false
+			this.$refs.loginForm.validateField(field, async (valid) => {
+				if(!valid){
+					return false
+				}
+				var resp = await this.$API.auth.sms.post({"mobile":this.form.phone,"code":this.form.code,"key":this.captcha.key,"scene":"login"});
+				if (resp.code == 0) {
+					return this.$message.error(resp.msg);
+				}
+				this.$message.success(resp.msg)
+				this.disabled = true
+				this.time = 60
+				var t = setInterval(() => {
+					this.time -= 1
+					if(this.time < 1){
+						clearInterval(t)
+						this.disabled = false
+						this.time = 0
 					}
-				})
+				},1000)
 			})
-			if(!flag){ return false }
-			var resp = await this.$API.login.sms.post({"mobile":this.form.phone,"code":this.form.codem,"key":this.captcha.key,"scene":"login"});
-			if (resp.code == 0) {
-				return this.$message.error(resp.msg);
-			}
-			this.$message.success(resp.msg)
-			this.disabled = true
-			this.time = 60
-			var t = setInterval(() => {
-				this.time -= 1
-				if(this.time < 1){
-					clearInterval(t)
-					this.disabled = false
-					this.time = 0
-				}
-			},1000)
 		},
 		async login(){
 			var validate = await this.$refs.loginForm.validate().catch(()=>{})
@@ -155,7 +153,7 @@ export default {
 				return false
 			}
 			//获取菜单
-			var menu = await this.$API.auth.menu.get()
+			var menu = await this.$API.menu.list.get()
 			if (menu.code == 1) {
 				if (menu.data.menu.length == 0) {
 					this.islogin = false

+ 4 - 2
src/views/login/components/passwd.vue

@@ -9,7 +9,9 @@
         <el-form-item prop="code" :label="$t('login.code')">
             <el-input v-model="form.code" :placeholder="$t('login.codeErr')">
 				<template #suffix>
-					<div class="captcha" @click="updateCaptcha"><img :src="captcha.base64" /></div>
+					<el-tooltip placement="top-start" :content="$t('login.codeTips')">
+						<div class="captcha" @click="updateCaptcha"><img :src="captcha.base64" /></div>
+					</el-tooltip>
 				</template>
 			</el-input>
         </el-form-item>
@@ -103,7 +105,7 @@ export default {
 				return false
 			}
 			//获取菜单
-			var menu = await this.$API.auth.menu.get()
+			var menu = await this.$API.menu.list.get()
 			if (menu.code == 1) {
 				if (menu.data.menu.length == 0) {
 					this.islogin = false

+ 7 - 7
src/views/login/index.vue

@@ -42,12 +42,12 @@ export default {
 		}
 	},
 	created: function () {
-		this.$TOOL.cookie.remove("AGENT_TOKEN")
-		this.$TOOL.data.remove("AGENT_USER_INFO")
-		this.$TOOL.data.remove("AGENT_MENU")
-		this.$TOOL.data.remove("AGENT_PERMISSIONS")
-		this.$TOOL.data.remove("AGENT_DASHBOARDGRID")
-		this.$TOOL.data.remove("AGENT_grid")
+		this.$TOOL.cookie.remove("TOKEN")
+		this.$TOOL.data.remove("USER_INFO")
+		this.$TOOL.data.remove("MENU")
+		this.$TOOL.data.remove("PERMISSIONS")
+		this.$TOOL.data.remove("DASHBOARDGRID")
+		this.$TOOL.data.remove("grid")
 		this.$store.commit("clearViewTags")
 		this.$store.commit("clearKeepLive")
 		this.$store.commit("clearIframeList")
@@ -73,7 +73,7 @@ export default {
 			if (resp.code == 0) {
 				return this.$message.warning(resp.msg)
 			}
-			this.$TOOL.data.set("AGENT_SERVICE", resp.data)
+			this.$TOOL.data.set("SERVICE", resp.data.service)
 			this.baseData = resp.data.service;
 			this.captcha = resp.data.captcha;
 		},

+ 96 - 0
src/views/manage/components/password.vue

@@ -0,0 +1,96 @@
+<template>
+    <el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
+        <el-form :model="formData" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-position="top" label-width="120px">
+            <el-form-item label="登录账号">
+				<el-input v-model="formData.username" disabled></el-input>
+			</el-form-item>
+            <el-form-item label="账号昵称">
+				<el-input v-model="formData.truename" disabled></el-input>
+			</el-form-item>
+            <el-form-item label="新密码" prop="password">
+                <el-input type="password" v-model="formData.password" clearable show-password></el-input>
+            </el-form-item>
+            <el-form-item label="确认新密码" prop="password2">
+                <el-input type="password" v-model="formData.password2" clearable show-password></el-input>
+            </el-form-item>
+        </el-form>
+		<template #footer>
+			<el-button @click="visible=false" >取 消</el-button>
+			<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
+		</template>
+    </el-dialog>
+</template>
+
+<script>
+import scPasswordStrength from '@/components/scPasswordStrength'
+export default{
+    components: {
+        scPasswordStrength
+    },
+    data(){
+        return {
+            loading: false,
+            mode:"edit",
+            titleMap:{
+                edit:"修改密码"
+            },
+            visible: false,
+            isSaveing: false,
+            formData:{},
+            rules:{
+                password: [
+                    {required: true, message: '请输入登录密码'}
+                ],
+                password2: [
+                    {required: true, message: '请再次输入密码'},
+                    {validator: (rule, value, callback) => {
+                        if (value !== this.formData.password) {
+                            callback(new Error('两次输入密码不一致!'));
+                        }else{
+                            callback();
+                        }
+                    }}
+                ],
+            }
+        }
+    },
+    methods:{
+        open(mode = 'edit'){
+            this.mode = mode;
+            this.visible = true;
+            return this
+        },
+        //表单注入数据
+        setData(data){
+            data.password = "";
+            data.password2 = "";
+            this.formData = data;
+        },
+        submit(){
+            this.isSaveing = true;
+            this.$refs.dialogForm.validate(async (valid) => {
+                if (valid) {
+                    let password = this.$TOOL.crypto.MD5(this.formData.password);
+                    var result = await this.$API.user.pwd.post({password:password,id:this.formData.id});
+                    if(result.code !== 1){
+                        this.$message.warning(result.msg)
+                        this.isSaveing = false;
+                        return false;
+                    }
+                    this.$message.success(result.msg)
+                    this.isSaveing = false;
+                    this.visible = false;
+                    this.formData = {};
+                }else{
+                    this.isSaveing = false;
+                    return false;
+                }
+            });
+        }
+    }
+}
+</script>
+
+<style lang="less" scoped>
+
+</style>

+ 209 - 0
src/views/manage/setting/basic/index.vue

@@ -0,0 +1,209 @@
+<template>
+    <el-container>
+        <el-main>
+                <el-card shadow="never" header="系统设置" class="borderNone mt10">
+                    <el-tabs v-model="activeName" class="demo-tabs" @tab-change="handleClick">
+                        <el-tab-pane label="基础设置" name="service" v-loading="isLoading">
+                            <el-form :model="sys" :rules="rules" ref="form" label-position="top" @keyup.enter="saveForm">
+                                <el-form-item label="系统名称" prop="title" class="label-item">
+                                    <el-input v-model="sys.title" placeholder="请输入" clearable />
+                                    <div class="el-form-item-msg"></div>
+                                </el-form-item>
+                                <el-form-item label="客服联系电话" prop="tel" class="label-item">
+                                    <el-input v-model="sys.tel" placeholder="请输入" clearable />
+                                    <div class="el-form-item-msg"></div>
+                                </el-form-item>
+                                <el-form-item label="系统Logo" prop="logo">
+                                    <sc-upload v-model="sys.logo"></sc-upload>
+                                </el-form-item>
+                                <el-form-item label="备案号" prop="miitbeian" class="label-item">
+                                    <el-input v-model="sys.miitbeian" placeholder="请输入" clearable />
+                                    <div class="el-form-item-msg">支持Html格式</div>
+                                </el-form-item>
+                                <el-form-item label="公安安备号" prop="beian" class="label-item">
+                                    <el-input v-model="sys.beian" placeholder="请输入" clearable />
+                                    <div class="el-form-item-msg">网站备案号和公安备案号可以在<a href="https://beian.miit.gov.cn/" target="_blank">备案管理中心</a>查询并获取,网站上线时必需配置备案号,备案号会链接到信息备案管理系统 ~</div>
+                                </el-form-item>
+                                <el-form-item label="版权信息" prop="copyright" class="label-item">
+                                    <el-input v-model="sys.copyright" placeholder="请输入" clearable />
+                                    <div class="el-form-item-msg"></div>
+                                </el-form-item>
+                                <el-form-item label="隐私协议" prop="privacy" class="label-item">
+                                    <sc-editor v-model="sys.privacy" placeholder="请输入" :height="400"></sc-editor>
+                                    <div class="el-form-item-msg"></div>
+                                </el-form-item>
+                                <el-form-item label="服务协议" prop="agreements" class="label-item">
+                                    <sc-editor v-model="sys.agreements" placeholder="请输入" :height="400"></sc-editor>
+                                    <div class="el-form-item-msg"></div>
+                                </el-form-item>
+                            </el-form>
+                        </el-tab-pane>
+                        <el-tab-pane label="短信设置(非代理)" name="sms" v-loading="isLoading">
+                            <el-form :model="sys" :rules="rules2" ref="form" label-position="top" @keyup.enter="saveForm" v-if="activeName=='sms'">
+                                <el-form-item label="短信平台" prop="sms_type" class="label-item">
+                                    <el-select v-model="sys.sms_type" style="width: 100%;" placeholder="请选择渠道" size="large">
+                                        <el-option
+                                            v-for="(item, index) in smsRegion"
+                                            :key="index"
+                                            :label="item.name"
+                                            :value="item.type"
+                                        />
+                                    </el-select>
+                                    <div class="el-form-item-msg"></div>
+                                </el-form-item>
+                                <div v-if="sys.sms_type == 'aliyun'">
+                                    <el-form-item label="AccessKeyId" prop="AccessKeyId" class="label-item">
+                                        <el-input v-model="sys.AccessKeyId" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                    <el-form-item label="AccessKeySecret" prop="AccessKeySecret" class="label-item">
+                                        <el-input v-model="sys.AccessKeySecret" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                    <el-form-item label="短信签名 Sign" prop="sign" class="label-item">
+                                        <el-input v-model="sys.sign" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                </div>
+                                <div v-if="sys.sms_type == 'qcloud'">
+                                    <el-form-item label="SdkAppID" prop="SdkAppID" class="label-item">
+                                        <el-input v-model="sys.SdkAppID" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                    <el-form-item label="AccessKeyId" prop="AccessKeyId" class="label-item">
+                                        <el-input v-model="sys.AccessKeyId" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                    <el-form-item label="AccessKeySecret" prop="AccessKeySecret" class="label-item">
+                                        <el-input v-model="sys.AccessKeySecret" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                    <el-form-item label="短信签名 Sign" prop="sign" class="label-item">
+                                        <el-input v-model="sys.sign" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                </div>
+                                <div v-if="sys.sms_type == 'qiniu'">
+                                    <el-form-item label="AccessKeyId" prop="AccessKeyId" class="label-item">
+                                        <el-input v-model="sys.AccessKeyId" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                    <el-form-item label="SecretKey" prop="AccessKeySecret" class="label-item">
+                                        <el-input v-model="sys.AccessKeySecret" placeholder="请输入" clearable />
+                                        <div class="el-form-item-msg"></div>
+                                    </el-form-item>
+                                </div>
+                                
+                                <el-divider content-position="left">模板通知 (验证码)</el-divider>
+                                <el-form-item label="模板内容" class="label-item">
+                                    <el-input placeholder="请输入" v-model="sys.login_sms" disabled>
+                                        <template #prepend>
+                                            <el-button text icon="el-icon-document-copy" v-copy="sys.login_sms"></el-button>
+                                        </template>
+                                    </el-input>
+                                    <div class="el-form-item-msg">按照该内容向供应商申请</div>
+                                </el-form-item>
+                                <el-form-item label="模板ID/Code" prop="login" class="label-item">
+                                    <el-input v-model="sys.login" placeholder="请输入" clearable />
+                                    <div class="el-form-item-msg">例如:SMS_139800030</div>
+                                </el-form-item>
+                            </el-form>
+                        </el-tab-pane>
+                    </el-tabs>
+                </el-card>
+        </el-main>
+        <el-footer style="text-align: right;">
+            <el-button type="primary" :loading="isSaveing" @click="saveForm">保存</el-button>
+        </el-footer>
+    </el-container>
+</template>
+<script>
+import { defineAsyncComponent } from 'vue';
+const scEditor = defineAsyncComponent(() => import('@/components/scEditor'));
+export default {
+    components: {
+        scEditor
+    },
+    data(){
+        return {
+            activeName:"service",
+            isSaveing: false,
+            sys: {},
+            smsRegion:[],
+            rules2:{
+                sms_type: [
+                    { required: true, message: '请选择' }
+                ],
+                login: [
+                    { required: true, message: '请输入' }
+                ],
+            },
+            rules: {
+                title: [
+                    { required: true, message: '请输入' }
+                ],
+                tel: [
+                    { required: true, message: '请输入' }
+                ],
+                logo: [
+                    { required: true, message: '请选择', trigger:"change" }
+                ],
+                miitbeian: [
+                    { required: true, message: '请输入' }
+                ],
+                beian: [
+                    { required: true, message: '请输入' }
+                ],
+                copyright: [
+                    { required: true, message: '请输入' }
+                ]
+            },
+            isLoading:false
+        }
+    },
+    created() {
+        this.getConfig();
+    },
+    methods: {
+        handleClick(event){
+            this.activeName = event;
+            this.getConfig();
+        },
+        saveForm() {
+            this.isSaveing = true;
+            this.$refs.form.validate(async (valid) => {
+                console.log(valid)
+                if (valid) {
+                    var result = await this.$API.setting.configSave.post({type:this.activeName,data: this.sys });
+                    this.isSaveing = false;
+                    if(result.code == 0)
+                    {
+                        this.$message.error(result.msg);
+                        return false;
+                    }
+                    if (this.activeName == "service") {
+                        this.$TOOL.data.set("SERVICE", this.sys)
+                    }
+                    this.$message.success(result.msg);
+                } else {
+                    this.isSaveing = false;
+                    return false;
+                }
+            })
+        },
+        async getConfig() {
+            this.isLoading = true;
+            var result = await this.$API.setting.config.get({ type: this.activeName });
+            this.isLoading = false;
+            if (result.code == 0) {
+                this.$message.error(result.msg);
+                return;
+            }
+            if (this.activeName == "sms") {
+                this.smsRegion = result.data.channel
+            }
+            this.sys = result.data;
+        },
+    }
+}
+</script>

+ 175 - 0
src/views/manage/setting/menu/index.vue

@@ -0,0 +1,175 @@
+<template>
+    <el-container>
+		<el-aside width="300px" v-loading="menuloading">
+			<el-container>
+				<el-header>
+					<el-input placeholder="输入关键字进行过滤" v-model="menuFilterText" clearable></el-input>
+				</el-header>
+				<el-main class="nopadding">
+                    <div class="tabs">
+                        <el-tabs v-model="activeName" class="demo-tabs" @tab-change="handleClick">
+                            <el-tab-pane label="总管理" :name="1"></el-tab-pane>
+                            <el-tab-pane label="代理" :name="2"></el-tab-pane>
+                            <el-tab-pane label="门店" :name="3"></el-tab-pane>
+                            <el-tab-pane label="工厂" :name="4"></el-tab-pane>
+                        </el-tabs>
+                    </div>
+					<el-tree ref="menu" class="menu" node-key="id" :data="menuList" :props="menuProps" draggable highlight-current :expand-on-click-node="false" check-strictly :filter-node-method="menuFilterNode" @node-click="menuClick" @node-drop="nodeDrop">
+						<template #default="{node, data}">
+							<span class="custom-tree-node">
+								<span class="label">
+									{{ node.label }}
+								</span>
+								<span class="do">
+									<el-button icon="el-icon-plus" size="small" @click.stop="add(node, data)"></el-button>
+								</span>
+							</span>
+						</template>
+					</el-tree>
+				</el-main>
+				<el-footer style="height:51px;">
+					<el-button type="primary" size="small" icon="el-icon-plus" @click="add()"></el-button>
+					<el-button type="danger" size="small" plain icon="el-icon-delete" @click="delMenu"></el-button>
+				</el-footer>
+			</el-container>
+		</el-aside>
+		<el-container>
+			<el-main class="nopadding" style="padding:20px;" ref="main">
+				<save ref="save" @success="getMenu" :menu="menuList"></save>
+			</el-main>
+		</el-container>
+	</el-container>
+</template>
+
+<script>
+let newMenuIndex = 1;
+import save from './save'
+export default {
+    name: 'settingMenu',
+    components: {
+        save
+    },
+    data() {
+        return {
+            activeName:1,
+            menuloading: false,
+            menuList: [],
+            menuProps: {
+                label: (data)=>{
+                    return data.meta.title
+                }
+            },
+            menuFilterText: ""
+        }
+    },
+    watch: {
+        menuFilterText(val){
+            this.$refs.menu.filter(val);
+        }
+    },
+    mounted() {
+        this.getMenu();
+    },
+    methods: {
+        handleClick(e){
+            this.activeName = e;
+            this.getMenu();
+        },
+        //加载树数据
+        async getMenu(){
+            this.menuloading = true
+            var res = await this.$API.menu.list.get({"form":this.activeName,"type":2});
+            this.menuloading = false
+            if(res.code == 1)
+            {
+                this.menuList = res.data;
+            }
+        },
+        //树点击
+        menuClick(data, node){
+            var pid = node.level==1?undefined:node.parent.data.id;
+            this.$refs.save.setData(data, pid)
+            this.$refs.main.$el.scrollTop = 0
+        },
+        //树过滤
+        menuFilterNode(value, data){
+            if (!value) return true;
+            var targetText = data.meta.title;
+            return targetText.indexOf(value) !== -1;
+        },
+        //树拖拽
+        nodeDrop(draggingNode, dropNode, dropType){
+            this.$refs.save.setData({})
+            this.$message(`拖拽对象:${draggingNode.data.meta.title}, 释放对象:${dropNode.data.meta.title}, 释放对象的位置:${dropType}`)
+        },
+        //增加
+        async add(node, data){
+            var newMenuName = "未命名" + newMenuIndex++;
+            var newMenuData = {
+                pid: data ? data.id : 0,
+                title: newMenuName,
+                from:this.activeName,
+                path: "",
+                component: "",
+                type: "menu"
+            }
+            this.menuloading = true
+            var res = await this.$API.menu.save.post(newMenuData)
+            this.menuloading = false
+            if(res.code !== 1){
+                this.$message.warning(res.msg);
+                return ;
+            }
+            this.getMenu();
+        },
+        //删除菜单
+        async delMenu(){
+            var CheckedNodes = this.$refs.menu.getCheckedNodes()
+            if(CheckedNodes.length == 0){
+                this.$message.warning("请选择需要删除的项")
+                return false;
+            }
+
+            var confirm = await this.$confirm('确认删除已选择的菜单吗?','提示', {
+                type: 'warning',
+                confirmButtonText: '删除',
+                confirmButtonClass: 'el-button--danger'
+            }).catch(() => {})
+            if(confirm != 'confirm'){
+                return false
+            }
+
+            this.menuloading = true
+            var reqData = {
+                ids: CheckedNodes.map(item => item.id)
+            }
+            var res = await this.$API.demo.post.post(reqData)
+            this.menuloading = false
+
+            if(res.code == 200){
+                CheckedNodes.forEach(item => {
+                    var node = this.$refs.menu.getNode(item)
+                    if(node.isCurrent){
+                        this.$refs.save.setData({})
+                    }
+                    this.$refs.menu.remove(item)
+                })
+            }else{
+                this.$message.warning(res.message)
+            }
+        }
+    }
+}
+</script>
+<style scoped>
+	.menu:deep(.el-tree-node__label) {display: flex;flex: 1;height:100%;}
+	.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;height:100%;padding-right:24px;}
+	.custom-tree-node .label {display: flex;align-items: center;;height: 100%;}
+	.custom-tree-node .label .el-tag {margin-left: 5px;}
+	.custom-tree-node .do {display: none;}
+	.custom-tree-node .do i {margin-left:5px;color: #999;}
+	.custom-tree-node .do i:hover {color: #333;}
+
+	.custom-tree-node:hover .do {display: inline-block;}
+    .tabs{padding: 0 10px;}
+</style>

+ 153 - 0
src/views/manage/setting/menu/save.vue

@@ -0,0 +1,153 @@
+<template>
+	<el-row :gutter="40">
+		<el-col v-if="!form.id">
+			<el-empty description="请选择左侧菜单后操作" :image-size="100"></el-empty>
+		</el-col>
+		<template v-else>
+			<el-col :lg="12">
+				<h2>{{form.meta.title || "新增菜单"}}</h2>
+				<el-form :model="form" :rules="rules" ref="dialogForm" label-width="80px" label-position="left">
+					<el-form-item label="显示名称" prop="form.title">
+						<el-input v-model="form.title" clearable placeholder="菜单显示名字"></el-input>
+					</el-form-item>
+					<el-form-item label="上级菜单" prop="pid">
+						<el-cascader v-model="form.pid" :options="menuOptions" :props="menuProps" :show-all-levels="false" placeholder="顶级菜单" clearable disabled></el-cascader>
+					</el-form-item>
+					<el-form-item label="类型" prop="form.type">
+						<el-radio-group v-model="form.type">
+							<el-radio-button label="menu">菜单</el-radio-button>
+							<el-radio-button label="iframe">Iframe</el-radio-button>
+							<el-radio-button label="link">外链</el-radio-button>
+							<el-radio-button label="button">按钮</el-radio-button>
+						</el-radio-group>
+					</el-form-item>
+					<el-form-item label="别名" prop="name">
+						<el-input v-model="form.name" clearable placeholder="菜单别名"></el-input>
+						<div class="el-form-item-msg">系统唯一且与内置组件名一致,否则导致缓存失效。如类型为Iframe的菜单,别名将代替源地址显示在地址栏</div>
+					</el-form-item>
+					<el-form-item label="菜单图标" prop="meta.icon">
+						<sc-icon-select v-model="form.icon" clearable></sc-icon-select>
+					</el-form-item>
+					<el-form-item label="路由地址" prop="path">
+						<el-input v-model="form.path" clearable placeholder=""></el-input>
+					</el-form-item>
+					<el-form-item label="视图" prop="component">
+						<el-input v-model="form.component" clearable placeholder="">
+							<template #prepend>views/</template>
+						</el-input>
+						<div class="el-form-item-msg">如父节点、链接或Iframe等没有视图的菜单不需要填写</div>
+					</el-form-item>
+					<el-form-item label="路由描述" prop="descs">
+						<el-input v-model="form.descs" clearable placeholder=""></el-input>
+					</el-form-item>
+					<el-form-item>
+						<el-button type="primary" @click="save" :loading="loading">保 存</el-button>
+					</el-form-item>
+				</el-form>
+
+			</el-col>
+		</template>
+	</el-row>
+
+</template>
+
+<script>
+	import scIconSelect from '@/components/scIconSelect'
+
+	export default {
+		components: {
+			scIconSelect
+		},
+        emits: ['success'],
+		props: {
+			menu: { type: Object, default: () => {} },
+		},
+		data(){
+			return {
+				form: {
+					id: "",
+					pid: "",
+					name: "",
+					path: "",
+					component: "",
+					redirect: "",
+					apiList: []
+				},
+				menuOptions: [],
+				menuProps: {
+					value: 'id',
+					label: 'title',
+					checkStrictly: true
+				},
+				predefineColors: [
+					'#ff4500',
+					'#ff8c00',
+					'#ffd700',
+					'#67C23A',
+					'#00ced1',
+					'#409EFF',
+					'#c71585'
+				],
+				rules: [],
+				apiListAddTemplate: {
+					code: "",
+					url: ""
+				},
+				loading: false
+			}
+		},
+		watch: {
+			menu: {
+				handler(){
+					this.menuOptions = this.treeToMap(this.menu)
+				},
+				deep: true
+			}
+		},
+		mounted() {
+
+		},
+		methods: {
+			//简单化菜单
+			treeToMap(tree){
+				const map = []
+				tree.forEach(item => {
+					var obj = {
+						id: item.id,
+						parentId: item.parentId,
+						title: item.meta.title,
+						children: item.children&&item.children.length>0 ? this.treeToMap(item.children) : null
+					}
+					map.push(obj)
+				})
+				return map
+			},
+			//保存
+			async save(){
+				this.loading = true
+				var res = await this.$API.auth.menu.post(this.form)
+				this.loading = false
+				if(res.code == 1){
+                    this.$emit('success')
+					this.$message.success(res.msg)
+				}else{
+					this.$message.warning(res.msg)
+				}
+			},
+			//表单注入数据
+			setData(data, pid){
+				this.form = data
+				this.form.apiList = data.apiList || []
+				this.form.parentId = pid
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	h2 {font-size: 17px;color: #3c4a54;padding:0 0 30px 0;}
+	.apilist {border-left: 1px solid #eee;}
+
+	[data-theme="dark"] h2 {color: #fff;}
+	[data-theme="dark"] .apilist {border-color: #434343;}
+</style>

+ 6 - 0
src/views/manage/setting/role/index.vue

@@ -0,0 +1,6 @@
+<template>
+
+</template>
+<script>
+
+</script>

+ 312 - 0
src/views/manage/setting/storage/index.vue

@@ -0,0 +1,312 @@
+<template>
+    <el-container>
+        <el-main>
+            <el-card shadow="never" header="存储配置" class="borderNone">
+                <el-form :model="sys" :rules="rules" ref="form" label-position="top">
+                    <div class="tip-container">
+                        <el-alert title="文件将存储在本地服务器,默认保存在 public/upload 目录,文件以 HASH 命名。" show-icon description="文件存储的目录需要有读写权限,有足够的存储空间。特别注意,本地存储暂不支持图片压缩!" type="info" :closable="false" v-if="typeChange == 'public'" />
+                        <el-alert title="文件将上传到 七牛云 存储" description="对象存储需要配置为公开访问的 Bucket 空间" show-icon type="info" :closable="false" v-if="typeChange == 'qiniu'" />
+                        <el-alert title="文件将上传到 阿里云 OSS 存储,需要配置 OSS 公开访问及跨域策略" description="配置跨域访问 CORS 规则,设置:来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *" show-icon type="info" :closable="false" v-if="typeChange == 'oss'" />
+                        <el-alert title="文件将上传到 腾讯云 COS 存储,需要配置 COS 公有读私有写访问权限及跨域策略" description="配置跨域访问 CORS 规则,设置来源 Origin 为 *,允许 Methods 为 POST,允许 Headers 为 *" show-icon type="info" :closable="false" v-if="typeChange == 'cos'" />
+                    </div>
+                    <el-form-item label="默认上传方式:" prop="type" class="label-item">
+                        <el-col :span="11" :xs="24">
+                            <el-radio-group v-model="sys.type" @change="checkType">
+                                <el-radio label="public" border>本地 (不推荐)</el-radio>
+                                <el-radio label="qiniu" border>七牛云</el-radio>
+                                <el-radio label="oss" border>阿里云</el-radio>
+                                <el-radio label="cos" border>腾讯云</el-radio>
+                            </el-radio-group>
+                        </el-col>
+                    </el-form-item>
+                    <el-form-item label="允许类型:" prop="allow_exts" class="label-item">
+                        <el-col :span="11" :xs="24">
+                            <el-input v-model="sys.allow_exts" placeholder="请输入允许类型"></el-input>
+                        </el-col>
+                        <div class="el-form-item-msg">设置系统允许上传文件的后缀,多个以英文逗号隔开如:png,jpg,rar,doc,未设置允许上传的后缀</div>
+                    </el-form-item>
+                    <div v-if="typeChange == 'public'">
+                    </div>
+                    <div v-if="typeChange == 'qiniu'">
+                        <el-form-item label="访问协议:" prop="qiniu_http_protocol" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-radio-group v-model="sys.qiniu_http_protocol">
+                                    <el-radio label="http" border>HTTP</el-radio>
+                                    <el-radio label="https" border>HTTPS</el-radio>
+                                    <el-radio label="auto" border>AUTO</el-radio>
+                                </el-radio-group>
+                            </el-col>
+                            <div class="el-form-item-msg">七牛云存储访问协议,其中 HTTPS 需要配置证书才能使用( AUTO 为相对协议 )</div>
+                        </el-form-item>
+                        <el-form-item label="存储区域:" prop="qiniu_region" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-select v-model="sys.qiniu_region" style="width: 100%;" placeholder="请选择存储区域" size="large">
+                                    <el-option
+                                        v-for="(item, index) in qiniuRegin"
+                                        :key="index"
+                                        :label="item"
+                                        :value="index"
+                                    />
+                                </el-select>
+                            </el-col>
+                            <div class="el-form-item-msg">七牛云存储空间所在区域,需要严格对应储存所在区域才能上传文件</div>
+                        </el-form-item>
+                        <el-form-item label="空间名称:" prop="qiniu_bucket" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.qiniu_bucket" placeholder="请输入空间名称"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">填写七牛云存储空间名称,如:static</div>
+                        </el-form-item>
+                        <el-form-item label="访问域名:" prop="qiniu_http_domain" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.qiniu_http_domain" placeholder="请输入访问域名"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">填写七牛云存储访问域名,如:static.qiniu.tempfood.cn</div>
+                        </el-form-item>
+                        <el-form-item label="访问密钥:" prop="qiniu_access_key" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.qiniu_access_key" placeholder="请输入访问密钥"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">可以在 [ 七牛云 > 个人中心 ] 设置并获取到访问密钥</div>
+                        </el-form-item>
+                        <el-form-item label="安全密钥:" prop="qiniu_secret_key" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.qiniu_secret_key" placeholder="请输入安全密钥"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">可以在 [ 七牛云 > 个人中心 ] 设置并获取到安全密钥</div>
+                        </el-form-item>
+                    </div>
+                    <div v-if="typeChange == 'oss'">
+                        <el-form-item label="访问协议:" prop="oss_http_protocol" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-radio-group v-model="sys.oss_http_protocol">
+                                    <el-radio label="http" size="large">HTTP</el-radio>
+                                    <el-radio label="https" size="large">HTTPS</el-radio>
+                                    <el-radio label="auto" size="large">AUTO</el-radio>
+                                </el-radio-group>
+                            </el-col>
+                            <div class="el-form-item-msg">阿里云OSS存储访问协议,其中 HTTPS 需要配置证书才能使用(AUTO 为相对协议)</div>
+                        </el-form-item>
+                        <el-form-item label="存储区域:" prop="oss_region" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-select v-model="sys.oss_region" style="width: 100%;" placeholder="请选择存储区域" size="large">
+                                    <el-option
+                                        v-for="(item, index) in ossRegin"
+                                        :key="index"
+                                        :label="item"
+                                        :value="index"
+                                    />
+                                </el-select>
+                            </el-col>
+                            <div class="el-form-item-msg">阿里云存储空间所在区域,需要严格对应储存所在区域才能上传文件</div>
+                        </el-form-item>
+                        <el-form-item label="空间名称:" prop="oss_bucket" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.oss_bucket" placeholder="请输入空间名称"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">填写阿里云存储空间名称,如:static</div>
+                        </el-form-item>
+                        <el-form-item label="访问域名:" prop="oss_http_domain" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.oss_http_domain" placeholder="请输入访问域名"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">填写阿里云存储访问域名,如:static.qiniu.tempfood.cn</div>
+                        </el-form-item>
+                        <el-form-item label="访问密钥:" prop="oss_access_key" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.oss_access_key" placeholder="请输入访问密钥"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">可以在 [ 阿里云 > 个人中心 ] 设置并获取到访问密钥</div>
+                        </el-form-item>
+                        <el-form-item label="安全密钥:" prop="oss_secret_key" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.oss_secret_key" placeholder="请输入安全密钥"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">可以在 [ 阿里云 > 个人中心 ] 设置并获取到安全密钥</div>
+                        </el-form-item>
+                    </div>
+                    <div v-if="typeChange == 'cos'">
+                        <el-form-item label="访问协议:" prop="cos_http_protocol" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-radio-group v-model="sys.cos_http_protocol">
+                                    <el-radio label="http" size="large">HTTP</el-radio>
+                                    <el-radio label="https" size="large">HTTPS</el-radio>
+                                    <el-radio label="auto" size="large">AUTO</el-radio>
+                                </el-radio-group>
+                            </el-col>
+                            <div class="el-form-item-msg">阿里云OSS存储访问协议,其中 HTTPS 需要配置证书才能使用(AUTO 为相对协议)</div>
+                        </el-form-item>
+                        <el-form-item label="存储区域:" prop="cos_region" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-select v-model="sys.cos_region" style="width: 100%;" placeholder="请选择存储区域" size="large">
+                                    <el-option
+                                        v-for="(item, index) in cosRegin"
+                                        :key="index"
+                                        :label="item"
+                                        :value="index"
+                                    />
+                                </el-select>
+                            </el-col>
+                            <div class="el-form-item-msg">腾讯云存储空间所在区域,需要严格对应储存所在区域才能上传文件</div>
+                        </el-form-item>
+                        <el-form-item label="空间名称:" prop="cos_bucket" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.cos_bucket" placeholder="请输入空间名称"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">填写腾讯云存储空间名称,如:static</div>
+                        </el-form-item>
+                        <el-form-item label="访问域名:" prop="cos_http_domain" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.cos_http_domain" placeholder="请输入访问域名"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">填写腾讯云存储访问域名,如:static.tempfood.cn</div>
+                        </el-form-item>
+                        <el-form-item label="访问密钥:" prop="cos_access_key" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.cos_access_key" placeholder="请输入访问密钥"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">可以在 [ 腾讯云 > 个人中心 ] 设置并获取到访问密钥</div>
+                        </el-form-item>
+                        <el-form-item label="安全密钥:" prop="cos_secret_key" class="label-item">
+                            <el-col :span="11" :xs="24">
+                                <el-input v-model="sys.cos_secret_key" placeholder="请输入安全密钥"></el-input>
+                            </el-col>
+                            <div class="el-form-item-msg">可以在 [ 腾讯云 > 个人中心 ] 设置并获取到安全密钥</div>
+                        </el-form-item>
+                    </div>
+                </el-form>
+            </el-card>
+        </el-main>
+        <el-footer>
+            <el-button type="primary" :loading="isSaveing" @click="saveForm">保存</el-button>
+        </el-footer>
+    </el-container>
+</template>
+
+<script>
+
+export default {
+    data() {
+        return {
+            isSaveing: false,
+            sys: {},
+            configList: [],
+            rules: {
+                type: [
+                    { required: true, message: '请选择上传方式', trigger: 'change' }
+                ],
+                allow_exts: [
+                    { required: true, message: '请输入允许类型', trigger: 'blur' }
+                ],
+                qiniu_http_protocol: [
+                    { required: true, message: '请选择访问协议', trigger: 'change' }
+                ],
+                oss_http_protocol: [
+                    { required: true, message: '请选择访问协议', trigger: 'change' }
+                ],
+                cos_http_protocol: [
+                    { required: true, message: '请选择访问协议', trigger: 'change' }
+                ],
+                qiniu_region: [
+                    { required: true, message: '请选择存储区域', trigger: 'change' }
+                ],
+                oss_region: [
+                    { required: true, message: '请选择存储区域', trigger: 'change' }
+                ],
+                cos_region: [
+                    { required: true, message: '请选择存储区域', trigger: 'change' }
+                ],
+                qiniu_bucket: [
+                    { required: true, message: '请输入空间名称', trigger: 'blur' }
+                ],
+                oss_bucket: [
+                    { required: true, message: '请输入空间名称', trigger: 'blur' }
+                ],
+                cos_bucket: [
+                    { required: true, message: '请输入空间名称', trigger: 'blur' }
+                ],
+                qiniu_http_domain: [
+                    { required: true, message: '请输入访问域名', trigger: 'blur' }
+                ],
+                oss_http_domain: [
+                    { required: true, message: '请输入访问域名', trigger: 'blur' }
+                ],
+                cos_http_domain: [
+                    { required: true, message: '请输入访问域名', trigger: 'blur' }
+                ],
+                qiniu_access_key: [
+                    { required: true, message: '请输入访问密钥', trigger: 'blur' }
+                ],
+                oss_access_key: [
+                    { required: true, message: '请输入访问密钥', trigger: 'blur' }
+                ],
+                cos_access_key: [
+                    { required: true, message: '请输入访问密钥', trigger: 'blur' }
+                ],
+                qiniu_secret_key: [
+                    { required: true, message: '请输入安全密钥', trigger: 'blur' }
+                ],
+                oss_secret_key: [
+                    { required: true, message: '请输入安全密钥', trigger: 'blur' }
+                ],
+                cos_secret_key: [
+                    { required: true, message: '请输入安全密钥', trigger: 'blur' }
+                ],
+            },
+            typeChange:"public",
+            ossRegin:[],
+            cosRegin:[],
+            qiniuRegin: [],
+        }
+    },
+    created() {
+        this.getConfig();
+        this.getRegin();
+    },
+    methods: {
+        checkType(val){
+            this.typeChange = val;
+        },
+        saveForm() {
+            this.isSaveing = true;
+            this.$refs.form.validate(async (valid) => {
+                if (valid) {
+                    var result = await this.$API.setting.configSave.post({type:"storage",data: this.sys });
+                    this.isSaveing = false;
+                    if(result.code == 0)
+                    {
+                        this.$message.error(result.msg);
+                        return false;
+                    }
+                    this.$message.success(result.msg);
+                } else {
+                    this.isSaveing = false;
+                    return false;
+                }
+            })
+        },
+        async getConfig() {
+            var result = await this.$API.setting.config.get({ type: "storage" });
+            if (result.code == 0) {
+                this.$message.error(result.msg);
+                return;
+            }
+            this.typeChange = result.data.type;
+            this.sys = result.data;
+        },
+        async getRegin() {
+            var result = await this.$API.setting.regin.get();
+            if (result.code == 0) {
+                this.$message.error(result.msg);
+                return;
+            }
+            this.ossRegin = result.data.oss;
+            this.cosRegin = result.data.cos;
+            this.qiniuRegin = result.data.qiniu;
+        }
+    }
+}
+
+</script>
+<style>
+</style>

+ 6 - 0
src/views/manage/setting/user/index.vue

@@ -0,0 +1,6 @@
+<template>
+
+</template>
+<script>
+
+</script>