This commit is contained in:
熊猫 2024-02-22 20:48:08 +08:00
parent ac4a4fc26b
commit 5d3741e95a
23 changed files with 763 additions and 159 deletions

7
env.d.ts vendored
View File

@ -1,3 +1,10 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
//解决ts文件引入vue文件出现红色警告问题
declare module '*.vue' {
import { defineComponent } from 'vue'
const Component: ReturnType<typeof defineComponent>
export default Component
}
/// <reference types="vite/client" />
declare module 'element-plus/dist/locale/zh-cn.mjs' declare module 'element-plus/dist/locale/zh-cn.mjs'
declare module 'mockjs' declare module 'mockjs'

View File

@ -13,6 +13,7 @@
"axios": "^1.3.3", "axios": "^1.3.3",
"echarts": "^5.4.1", "echarts": "^5.4.1",
"element-plus": "2.3.1", "element-plus": "2.3.1",
"js-cookie": "^3.0.5",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"sass": "^1.58.3", "sass": "^1.58.3",
"uuid": "^9.0.1", "uuid": "^9.0.1",
@ -22,7 +23,8 @@
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.11.12", "@types/js-cookie": "^3.0.6",
"@types/node": "^18.19.17",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",

53
src/api/acl/user/index.ts Normal file
View File

@ -0,0 +1,53 @@
/*
//用户管理模块的接口
import request from '@/utils/request'
import type {
UserResponseData,
User,
AllRoleResponseData,
SetRoleData,
} from './type'
//枚举地址
enum API {
//获取全部已有用户账号信息
ALLUSER_URL = '/admin/acl/user/',
//添加一个新的用户账号
ADDUSER_URL = '/admin/acl/user/save',
//更新已有的用户账号
UPDATEUSER_URL = '/admin/acl/user/update',
//获取全部职位,当前账号拥有的职位接口
ALLROLEURL = '/admin/acl/user/toAssign/',
//给已有的用户分配角色接口
SETROLE_URL = '/admin/acl/user/doAssignRole',
//删除某一个账号
DELETEUSER_URL = '/admin/acl/user/remove/',
//批量删除的接口
DELETEALLUSER_URL = '/admin/acl/user/batchRemove',
}
//获取用户账号信息的接口
export const reqUserInfo = (page: number, limit: number, username: string) =>
request.get<any, UserResponseData>(
API.ALLUSER_URL + `${page}/${limit}/?username=${username}`,
)
//添加用户与更新已有用户的接口
export const reqAddOrUpdateUser = (data: User) => {
//携带参数有ID更新
if (data.id) {
return request.put<any, any>(API.UPDATEUSER_URL, data)
} else {
return request.post<any, any>(API.ADDUSER_URL, data)
}
}
//获取全部职位以及包含当前用户的已有的职位
export const reqAllRole = (userId: number) =>
request.get<any, AllRoleResponseData>(API.ALLROLEURL + userId)
//分配职位
export const reqSetUserRole = (data: SetRoleData) =>
request.post<any, any>(API.SETROLE_URL, data)
//删除某一个账号的信息
export const reqRemoveUser = (userId: number) =>
request.delete<any, any>(API.DELETEUSER_URL + userId)
//批量删除的接口
export const reqSelectUser = (idList: number[]) =>
request.delete(API.DELETEALLUSER_URL, { data: idList })
*/

55
src/api/acl/user/type.ts Normal file
View File

@ -0,0 +1,55 @@
/*
//账号信息的ts类型
export interface ResponseData {
code: number
message: string
ok: boolean
}
//代表一个账号信息的ts类型
export interface User {
id?: number
createTime?: string
updateTime?: string
username?: string
password?: string
name?: string
phone?: null
roleName?: string
}
//数组包含全部的用户信息
export type Records = User[]
//获取全部用户信息接口返回的数据ts类型
export interface UserResponseData extends ResponseData {
data: {
records: Records
total: number
size: number
current: number
pages: number
}
}
//代表一个职位的ts类型
export interface RoleData {
id?: number
createTime?: string
updateTime?: string
roleName: string
remark: null
}
//全部职位的列表
export type AllRole = RoleData[]
//获取全部职位的接口返回的数据ts类型
export interface AllRoleResponseData extends ResponseData {
data: {
assignRoles: AllRole
allRolesList: AllRole
}
}
//给用户分配职位接口携带参数的ts类型
export interface SetRoleData {
roleIdList: number[]
userId: number
}
*/

View File

@ -1,9 +1,9 @@
:root { :root {
--main-color: #006080; --main-color: #085a75;
--el-color-primary: var(--main-color); --el-color-primary: var(--main-color);
--el-font-size-base: 16px; --el-font-size-base: 16px;
} }
$main-color: #006080; $main-color: #085a75;
$red: #ea3323; $red: #ea3323;
$border-color: #EBEEF5; $border-color: #EBEEF5;
$border1-color: #E4E7ED; $border1-color: #E4E7ED;

View File

@ -0,0 +1,159 @@
<template>
<div></div>
</template>
<script setup lang="ts" name="global-websocket">
import { ElNotification } from 'element-plus';
import { Session } from '/@/utils/storage';
const emit = defineEmits(['rollback']);
const props = defineProps({
uri: {
type: String,
},
});
const state = reactive({
webSocket: ref(), // webSocket
lockReconnect: false, //
maxReconnect: 6, // -1
reconnectTime: 0, //
heartbeat: {
interval: 30 * 1000, //
timeout: 10 * 1000, //
pingTimeoutObj: ref(), //
pongTimeoutObj: ref(), //
pingMessage: JSON.stringify({ type: 'ping' }), //
},
});
const token = computed(() => {
return Session.getToken();
});
const tenant = computed(() => {
return Session.getTenant();
});
onMounted(() => {
initWebSocket();
});
onUnmounted(() => {
state.webSocket.close();
clearTimeoutObj(state.heartbeat);
});
const initWebSocket = () => {
// ws
let host = window.location.host;
// baseURL
let baseURL = import.meta.env.VITE_API_URL;
let wsUri = `ws://${host}${baseURL}${props.uri}?access_token=${token.value}&TENANT-ID=${tenant.value}`;
//
state.webSocket = new WebSocket(wsUri);
//
state.webSocket.onopen = onOpen;
//
state.webSocket.onerror = onError;
//
state.webSocket.onmessage = onMessage;
//
state.webSocket.onclose = onClose;
};
const reconnect = () => {
if (!token) {
return;
}
if (state.lockReconnect || (state.maxReconnect !== -1 && state.reconnectTime > state.maxReconnect)) {
return;
}
state.lockReconnect = true;
setTimeout(() => {
state.reconnectTime++;
//
initWebSocket();
state.lockReconnect = false;
}, 5000);
};
/**
* 清空定时器
*/
const clearTimeoutObj = (heartbeat: any) => {
heartbeat.pingTimeoutObj && clearTimeout(heartbeat.pingTimeoutObj);
heartbeat.pongTimeoutObj && clearTimeout(heartbeat.pongTimeoutObj);
};
/**
* 开启心跳
*/
const startHeartbeat = () => {
const webSocket = state.webSocket;
const heartbeat = state.heartbeat;
//
clearTimeoutObj(heartbeat);
//
heartbeat.pingTimeoutObj = setTimeout(() => {
//
if (webSocket.readyState === 1) {
//
webSocket.send(heartbeat.pingMessage);
//
heartbeat.pongTimeoutObj = setTimeout(() => {
webSocket.close();
}, heartbeat.timeout);
} else {
//
reconnect();
}
}, heartbeat.interval);
};
/**
* 连接成功事件
*/
const onOpen = () => {
//
startHeartbeat();
state.reconnectTime = 0;
};
/**
* 连接失败事件
* @param e
*/
const onError = () => {
//
reconnect();
};
/**
* 连接关闭事件
* @param e
*/
const onClose = () => {
//
reconnect();
};
/**
* 接收服务器推送的信息
* @param msgEvent
*/
const onMessage = (msgEvent: any) => {
//
startHeartbeat();
const text = msgEvent.data;
if (text.indexOf('pong') > 0) {
return;
}
ElNotification.warning({
title: '消息提醒',
dangerouslyUseHTMLString: true,
message: text + '请及时处理',
offset: 60,
});
emit('rollback', text);
};
</script>

View File

@ -78,16 +78,23 @@ const validatorPassword = (rule: any, value: any, callback: any) => {
} }
const validatorPhone = (rule: any, value: any, callback: any) => { const validatorPhone = (rule: any, value: any, callback: any) => {
if (value.length == 11) { var isPhone = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
callback();
if (value.indexOf('****') >= 0) {
return callback().trim();
}
if (!isPhone.test(value)) {
callback(new Error('请输入合法手机号'));
} else { } else {
callback(new Error('手机号码长度为11位')); callback();
} }
} }
const rules = reactive({ const rules = reactive({
phone: [ phone: [
{ required: true, validator:validatorPhone, trigger: 'change' }, { required: true, validator:validatorPhone, trigger: 'blur' },
], ],
name: [ name: [
{ required: true, message: '请输入姓名', trigger: 'blur' }, { required: true, message: '请输入姓名', trigger: 'blur' },

View File

@ -8,6 +8,7 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { ElDialog } from 'element-plus' import { ElDialog } from 'element-plus'
import TableAbility from '@/components/table-ability.vue' import TableAbility from '@/components/table-ability.vue'
import 'element-plus/dist/index.css'; import 'element-plus/dist/index.css';
import './assets/css/global.scss'; import './assets/css/global.scss';
import './assets/font/iconfont.css'; import './assets/font/iconfont.css';

View File

@ -0,0 +1,22 @@
/*
import type { RouteRecordRaw } from 'vue-router'
import type { CategoryObj } from '@/api/product/attr/type'
//定义小仓库数据state类型
export interface UserState {
token: string | null
menuRoutes: RouteRecordRaw[]
username: string
avatar: string
buttons: string[]
}
//定义分类仓库state对象的ts类型
export interface CategoryState {
c1Id: string | number
c1Arr: CategoryObj[]
c2Arr: CategoryObj[]
c2Id: string | number
c3Arr: CategoryObj[]
c3Id: string | number
}
*/

112
src/stores/modules/user.ts Normal file
View File

@ -0,0 +1,112 @@
/*
//创建用户相关的小仓库
import { defineStore } from 'pinia'
//引入接口
import { reqLogin, reqUserInfo, reqLogout } from '@/api/user'
import type {
loginFormData,
loginResponseData,
userInfoReponseData,
} from '@/api/user/type'
import type { UserState } from './types/type'
//引入操作本地存储的工具方法
import { SET_TOKEN, GET_TOKEN, REMOVE_TOKEN } from '@/utils/token'
//引入路由(常量路由)
import { constantRoute, asnycRoute, anyRoute } from '@/router/routes'
//引入深拷贝方法
//@ts-expect-error
import cloneDeep from 'lodash/cloneDeep'
import router from '@/router'
//用于过滤当前用户需要展示的异步路由
function filterAsyncRoute(asnycRoute: any, routes: any) {
return asnycRoute.filter((item: any) => {
if (routes.includes(item.name)) {
if (item.children && item.children.length > 0) {
//硅谷333账号:product\trademark\attr\sku
item.children = filterAsyncRoute(item.children, routes)
}
return true
}
})
}
//创建用户小仓库
const useUserStore = defineStore('User', {
//小仓库存储数据地方
state: (): UserState => {
return {
token: GET_TOKEN(), //用户唯一标识token
menuRoutes: constantRoute, //仓库存储生成菜单需要数组(路由)
username: '',
avatar: '',
//存储当前用户是否包含某一个按钮
buttons: [],
}
},
//异步|逻辑的地方
actions: {
//用户登录的方法
async userLogin(data: loginFormData) {
//登录请求
const result: loginResponseData = await reqLogin(data)
//登录请求:成功200->token
//登录请求:失败201->登录失败错误的信息
if (result.code == 200) {
//pinia仓库存储一下token
//由于pinia|vuex存储数据其实利用js对象
this.token = result.data as string
//本地存储持久化存储一份
SET_TOKEN(result.data as string)
//能保证当前async函数返回一个成功的promise
return 'ok'
} else {
return Promise.reject(new Error(result.data))
}
},
//获取用户信息方法
async userInfo() {
//获取用户信息进行存储仓库当中[用户头像、名字]
const result: userInfoReponseData = await reqUserInfo()
//如果获取用户信息成功,存储一下用户信息
if (result.code == 200) {
this.username = result.data.name
this.avatar = result.data.avatar
this.buttons = result.data.buttons
//计算当前用户需要展示的异步路由
const userAsyncRoute = filterAsyncRoute(
cloneDeep(asnycRoute),
result.data.routes,
)
//菜单需要的数据整理完毕
this.menuRoutes = [...constantRoute, ...userAsyncRoute, anyRoute]
//目前路由器管理的只有常量路由:用户计算完毕异步路由、任意路由动态追加
;[...userAsyncRoute, anyRoute].forEach((route: any) => {
router.addRoute(route)
})
return 'ok'
} else {
return Promise.reject(new Error(result.message))
}
},
//退出登录
async userLogout() {
//退出登录请求
const result: any = await reqLogout()
if (result.code == 200) {
//目前没有mock接口:退出登录接口(通知服务器本地用户唯一标识失效)
this.token = ''
this.username = ''
this.avatar = ''
REMOVE_TOKEN()
return 'ok'
} else {
return Promise.reject(new Error(result.message))
}
},
},
getters: {},
})
//对外暴露获取小仓库方法
export default useUserStore
*/

View File

@ -1,4 +1,10 @@
//进行axios二次封装:使用请求与响应拦截器
import axios from "axios"; import axios from "axios";
/*
import { ElMessage } from 'element-plus'
//引入用户相关的仓库
import useUserStore from '@/stores/modules/user'
*/
export const HOST = 'http://localhost:9999'; export const HOST = 'http://localhost:9999';
const BASE_URL = import.meta.env.BASE_URL const BASE_URL = import.meta.env.BASE_URL
@ -30,3 +36,64 @@ export const getData = (url: string, params?: any) => {
export const postData = (url: string, params?: any) => { export const postData = (url: string, params?: any) => {
return axios.post(url, params) return axios.post(url, params)
} }
/*
//第一步:利用axios对象的create方法,去创建axios实例(其他的配置:基础路径、超时的时间)
const request = axios.create({
//基础路径
baseURL: import.meta.env.VITE_APP_BASE_API, //基础路径上会携带/api
timeout: 5000, //超时的时间的设置
})
//第二步:request实例添加请求与响应拦截器
request.interceptors.request.use((config) => {
//获取用户相关的小仓库:获取仓库内部token,登录成功以后携带给服务器
const userStore = useUserStore()
if (userStore.token) {
config.headers.token = userStore.token
}
//config配置对象,headers属性请求头,经常给服务器端携带公共参数
//返回配置对象
return config
})
//第三步:响应拦截器
request.interceptors.response.use(
(response) => {
//成功回调
//简化数据
return response.data
},
(error) => {
//失败回调:处理http网络错误的
//定义一个变量:存储网络错误信息
let message = ''
//http状态码
const status = error.response.status
switch (status) {
case 401:
message = 'TOKEN过期'
break
case 403:
message = '无权访问'
break
case 404:
message = '请求地址错误'
break
case 500:
message = '服务器出现问题'
break
default:
message = '网络出现问题'
break
}
//提示错误信息
ElMessage({
type: 'error',
message,
})
return Promise.reject(error)
},
)
//对外暴露
export default request*/

78
src/utils/storage.ts Normal file
View File

@ -0,0 +1,78 @@
/*
import Cookies from 'js-cookie';
/!**
* window.localStorage
* @method set
* @method get
* @method remove
* @method clear
*!/
export const Local = {
// 查看 v2.4.3版本更新日志
setKey(key: string) {
// @ts-ignore
return `${__NEXT_NAME__}:${key}`;
},
// 设置永久缓存
set<T>(key: string, val: T) {
window.localStorage.setItem(Local.setKey(key), JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
let json = <string>window.localStorage.getItem(Local.setKey(key));
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(Local.setKey(key));
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
},
};
/!**
* window.sessionStorage
* @method set
* @method get
* @method remove
* @method clear
*!/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
if (key === 'token' || key === 'refresh_token') {
Cookies.set(key, val);
}
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
if (key === 'token' || key === 'refresh_token') return Cookies.get(key);
let json = <string>window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
if (key === 'token' || key === 'refresh_token') return Cookies.remove(key);
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
Cookies.remove('token');
Cookies.remove('refresh_token');
Cookies.remove('tenantId');
window.sessionStorage.clear();
},
// 获取当前存储的 token
getToken() {
return this.get('token');
},
// 获取当前的租户
getTenant() {
return Local.get('tenantId') ? Local.get('tenantId') : 1;
},
};
*/

View File

@ -4,14 +4,20 @@ export const getTime = () => {
//通过内置构造函数Date //通过内置构造函数Date
const hours = new Date().getHours() const hours = new Date().getHours()
//情况的判断 //情况的判断
if (hours <= 9) { if (hours < 9) {
message = '早上' message = '早上'
} else if (hours <= 12) { } else if (hours < 12) {
message = '上午' message = '上午'
} else if (hours <= 18) { } else if (hours < 14) {
message = '中午'
} else if (hours < 17) {
message = '下午' message = '下午'
} else { } else if (hours < 19) {
message = '傍晚'
} else if (hours < 22) {
message = '晚上' message = '晚上'
} else {
message = '夜里'
} }
return message return message
} }

View File

@ -68,6 +68,7 @@ import TimeBarChart from "./time-bar-chart.vue";
import WeekCalendar from "./week-calendar.vue"; import WeekCalendar from "./week-calendar.vue";
import SystemLogs from "@/components/system-logs.vue"; import SystemLogs from "@/components/system-logs.vue";
const router = useRouter() const router = useRouter()
const userInfo = useLoginStore().getlogin() const userInfo = useLoginStore().getlogin()

View File

@ -207,15 +207,16 @@ function fn1(res:any) {
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
.menu-item { .menu-item {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 17px; font-size: 20px;
font-weight: 600; font-weight: 600;
color: $main-color; color: $main-color;
height: 70px; height: 70px;
padding: 0 10px; padding: 0 15px;
margin: 0; margin: 0;
transition: all .5s; transition: all .5s;
-webkit-transition: all .5s; -webkit-transition: all .5s;

View File

@ -114,8 +114,8 @@
<el-button type="primary" @click="register"> </el-button> <el-button type="primary" @click="register"> </el-button>
<span @click="isShowRegister = false">已有账号?</span> <span @click="isShowRegister = false">已有账号?</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<SliderVerify v-model:isShowSelf="sliderVConf.isShowSelf" :width="sliderVConf.width" :imgUrl="sliderImgUrl" <SliderVerify v-model:isShowSelf="sliderVConf.isShowSelf" :width="sliderVConf.width" :imgUrl="sliderImgUrl"
@ -123,6 +123,9 @@
</template> </template>
<script lang='ts' setup> <script lang='ts' setup>
import { ElNotification } from 'element-plus';
//
import { getTime } from '@/utils/time';
import {onMounted, reactive, ref, toRefs, watch} from 'vue' import {onMounted, reactive, ref, toRefs, watch} from 'vue'
import {useRouter,useRoute} from 'vue-router' import {useRouter,useRoute} from 'vue-router'
import {ElMessage, ElMessageBox} from 'element-plus' import {ElMessage, ElMessageBox} from 'element-plus'
@ -131,9 +134,8 @@ import {getHospitalsData, getPhoneAreasData} from '@/static-data/core'
import {v4} from "uuid"; import {v4} from "uuid";
import {HOST} from "@/utils/request"; import {HOST} from "@/utils/request";
import SliderVerify from "@/components/SliderVerify/index.vue"; import SliderVerify from "@/components/SliderVerify/index.vue";
import { ElNotification } from 'element-plus';
//
import { getTime } from '@/utils/time';
// //
//import useUserStore from "@/stores/user-info-store"; //import useUserStore from "@/stores/user-info-store";
//let useStore = useUserStore(); //let useStore = useUserStore();
@ -171,11 +173,19 @@ const validatorPassword = (rule: any, value: any, callback: any) => {
} }
const validatorPhone = (rule: any, value: any, callback: any) => { const validatorPhone = (rule: any, value: any, callback: any) => {
if (value.length == 11) { var isPhone = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
callback();
} else {
callback(new Error('手机号码长度为11位')); if (value.indexOf('****') >= 0) {
return callback().trim();
} }
if (!isPhone.test(value)) {
callback(new Error('请输入合法手机号'));
} else {
callback();
}
} }
const loginRules = reactive({ const loginRules = reactive({
@ -186,7 +196,7 @@ const loginRules = reactive({
{ required: true, validator: validatorPassword, trigger: 'change' }, { required: true, validator: validatorPassword, trigger: 'change' },
], ],
phone: [ phone: [
{ required: true, validator:validatorPhone, trigger: 'change' }, { required: true, validator:validatorPhone, trigger: 'blur' },
], ],
code: [ code: [
{ required: true, message: '请输入验证码', trigger: 'blur' }, { required: true, message: '请输入验证码', trigger: 'blur' },
@ -308,30 +318,33 @@ const login = async (type: string) => {
if (valid) { if (valid) {
// console.log('submit!') // console.log('submit!')
sliderVConf.value.isShowSelf = true sliderVConf.value.isShowSelf = true
} else { } else {
// console.log('error submit!', fields) // console.log('error submit!', fields)
} }
//: //:
loading.value = true; loading.value = true;
/*try {
//
await useStore.userLogin(loginFormRef);
}*/
//
//,queryquery
let redirect: any = $route.query.redirect;
$router.push({ path: redirect || '/' });
//
ElNotification({ ElNotification({
type: 'success', type: 'success',
message: '欢迎回来', message: '欢迎回来',
title: `HI,${getTime()}` title: `HI,${getTime()}`
}); });
/*try {
//
await useStore.userLogin(loginFormRef);
//
//,queryquery
let redirect: any = $route.query.redirect;
$router.push({ path: redirect || '/' });
// //
loading.value = false; loading.value = false;
}*/
/*} catch (error) { /*} catch (error) {
// //
loading.value = false; loading.value = false;
@ -342,6 +355,8 @@ const login = async (type: string) => {
})*/ })*/
}) })
//
loading.value = false;
} }
function getCaptchaCode() { function getCaptchaCode() {
@ -395,7 +410,6 @@ const toHome = () => {
background: url(@/assets/imgs/login/login_bck.png) no-repeat; background: url(@/assets/imgs/login/login_bck.png) no-repeat;
background-size: cover; background-size: cover;
} }
.right-content { .right-content {
position: relative; position: relative;
width: 50%; width: 50%;
@ -407,8 +421,8 @@ const toHome = () => {
.select-hospital-box { .select-hospital-box {
position: absolute; position: absolute;
top: 50px; top: 25px;
right: 50px; right: 25px;
} }
.logo { .logo {
@ -535,5 +549,6 @@ const toHome = () => {
} }
} }
} }
} }
</style> </style>

View File

@ -31,10 +31,10 @@ function initChart(chartData: any) {
} }
}, },
grid: { grid: {
left: 20, left: 30,
right: 50, right: 50,
bottom: 5, bottom: 5,
top: 20, top: 50,
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
@ -50,6 +50,7 @@ function initChart(chartData: any) {
data: chartData.xData, data: chartData.xData,
}, },
yAxis: { yAxis: {
name: '数量',
show: true, show: true,
type: 'value', type: 'value',
min: 0, min: 0,
@ -62,6 +63,7 @@ function initChart(chartData: any) {
type: 'line', type: 'line',
symbol: 'none', symbol: 'none',
smooth: true, smooth: true,
color: 'rgb(0, 96, 128)',
showSymbol: false, showSymbol: false,
lineStyle: { lineStyle: {
color: 'rgb(0, 96, 128)', color: 'rgb(0, 96, 128)',

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="table-page custom-table-table"> <div class="table-page custom-table-table">
<h3 class="main-color" style="font-size: 30px;line-height: 1;padding: 10px 0;">近30天登陆汇总</h3> <h3 class="main-color" style="font-size: 25px;line-height: 1;padding: 8px 0;">近30天登陆汇总</h3>
<div style="width: 100%;height: 35%;"> <div style="width: 100%;height: 35%;">
<LoginChart /> <LoginChart />
</div> </div>

View File

@ -64,6 +64,22 @@ const roleOption = [
{ label: '中级管理员', value: '中级管理员' }, { label: '中级管理员', value: '中级管理员' },
{ label: '高级管理员', value: '高级管理员' }, { label: '高级管理员', value: '高级管理员' },
] ]
const validatorPhone = (rule: any, value: any, callback: any) => {
var isPhone = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
if (value.indexOf('****') >= 0) {
return callback().trim();
}
if (!isPhone.test(value)) {
callback(new Error('请输入合法手机号'));
} else {
callback();
}
}
const rules = reactive({ const rules = reactive({
userName: [ userName: [
{ required: true, message: '请输入用户名', trigger: ['blur', 'change'] }, { required: true, message: '请输入用户名', trigger: ['blur', 'change'] },
@ -75,7 +91,7 @@ const rules = reactive({
{ required: true, message: '请选择角色', trigger: ['blur', 'change'] }, { required: true, message: '请选择角色', trigger: ['blur', 'change'] },
], ],
phone: [ phone: [
{ required: true, message: '请输入电话', trigger: ['blur', 'change'] }, { required: true, validator:validatorPhone, trigger: ['blur', 'change'] },
], ],
}) })

View File

@ -96,6 +96,7 @@ const breakRemote = () => {
.input-box { .input-box {
display: flex; display: flex;
align-items: center; align-items: center;
&>span { &>span {
flex-shrink: 0; flex-shrink: 0;
} }

View File

@ -254,8 +254,8 @@ $size: 20px;
} }
.right-box .value { .right-box .value {
color: #f8b300; color: $main-color;
border-color: #f8b300; border-color: $main-color;
} }
.row-item.alarm { .row-item.alarm {
@ -341,12 +341,12 @@ $size: 20px;
.row-item.yellow { .row-item.yellow {
.label { .label {
background: #f8b300; background: $main-color;
} }
.value { .value {
color: #f8b300; color: $main-color;
border-color: #f8b300; border-color: $main-color;
} }
} }
} }

View File

@ -242,7 +242,7 @@ const breakRemote = () => {
.right-box { .right-box {
@extend .common-box; @extend .common-box;
.label { .label {
background: #f8b300; background: $main-color;
} }
} }
.row-item { .row-item {
@ -288,8 +288,8 @@ const breakRemote = () => {
} }
} }
.right-box .value { .right-box .value {
color: #f8b300; color: $main-color;
border-color: #f8b300; border-color: $main-color;
} }
.row-item.alarm { .row-item.alarm {
.label { .label {
@ -361,11 +361,11 @@ const breakRemote = () => {
} }
.row-item.yellow { .row-item.yellow {
.label { .label {
background: #f8b300; background: $main-color;
} }
.value { .value {
color: #f8b300; color:$main-color;
border-color: #f8b300; border-color: $main-color;
} }
} }
} }

View File

@ -63,7 +63,6 @@ onMounted(() => {
.main-box { .main-box {
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
height: calc(100% - 60px);
background: white; background: white;
padding: 20px; padding: 20px;
} }