新增账户管理页面,修复一些问题

This commit is contained in:
洛洛希雅Lolosia 2023-05-12 15:53:06 +08:00
parent 914dc61571
commit 5028451710
62 changed files with 963 additions and 1622 deletions

1
.gitignore vendored
View File

@ -49,3 +49,4 @@ pnpm*
#auto-imports.d.ts
#components.d.ts
stats.html
ts-out-dir

View File

@ -89,6 +89,7 @@
"useConfigStore": true,
"useCssModule": true,
"useCssVars": true,
"useDebuggerStore": true,
"useElement": true,
"useErrorLog": true,
"useLink": true,

View File

@ -44,7 +44,7 @@ export default [
{
order_no: '@guid()',
timestamp: +Mock.Random.date('T'),
userName: '@name()',
username: '@name()',
price: '@float(1000, 15000, 0, 2)',
'status|1': ['success', 'pending']
}

View File

@ -4,7 +4,7 @@ export function getList(data) {
return request({
url: '/role/queryRoleByPage',
method: 'post',
data,
data
})
}
@ -12,14 +12,14 @@ export function doAdd(data) {
return request({
url: '/role/create',
method: 'post',
data,
data
})
}
export function doUpdate(data) {
return request({
url: '/role/update',
method: 'post',
data,
data
})
}
@ -27,7 +27,7 @@ export function doDelete(data) {
return request({
url: '/role/destroy',
method: 'post',
data,
data
})
}
/**
@ -37,7 +37,7 @@ export function doDelete(data) {
export function getRoleList() {
return request({
url: '/role/list',
method: 'get',
method: 'post'
})
}
@ -47,9 +47,9 @@ export function getRoleList() {
export function getRoleByUserId(userId) {
return request({
url: '/userRole/getByUserId',
method: 'get',
params: {
userId,
},
method: 'post',
data: {
userId
}
})
}

View File

@ -1,5 +1,6 @@
//获取用户信息
import { post } from '@/utils/request'
export interface IUser {
id: string
userName: string
@ -36,3 +37,12 @@ export async function login(data): Promise<IUser> {
export async function logout(): Promise<void> {
return post('/logout')
}
/**
*
* @param keys
* @return {Promise<any[]>}
*/
export function userSearching(keys) {
return post('user/searching', { keys })
}

42
src/api/userManagement.ts Normal file
View File

@ -0,0 +1,42 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/user/list',
method: 'post',
data
})
}
export function doEdit(data) {
return request({
url: '/user/edit',
method: 'post',
data
})
}
export function doCreate(data) {
return request({
url: '/user/create',
method: 'post',
data
})
}
export function doDelete(data) {
return request({
url: '/user/delete',
method: 'post',
data
})
}
// 查询学生信息
export function getStudentInfoByStudentId(data) {
return request({
url: '/user/getStudentInfoByStudentId',
method: 'post',
data
})
}

View File

@ -30,14 +30,14 @@
<!-- <SizeSelect />-->
<!-- <LangSelect />-->
<el-dropdown trigger="click" size="medium">
<div class="avatar-wrapper" :title="(userInfo?.realName || userInfo?.userName) + ' - 在线'">
<div class="avatar-wrapper" :title="(userInfo?.realName || userInfo?.username) + ' - 在线'">
<img :src="userInfo.avatar ?? userImage" alt="用户头像" class="user-avatar" />
<div class="user-avatar-status" />
</div>
<template #dropdown>
<el-dropdown-menu class="drop-down">
<el-dropdown-item class="welcome-user">
{{ time }}<b>{{ userInfo.realName || userImage.userName || "平台用户" }}</b>
{{ time }}<b>{{ userInfo.realName || userImage.username || "平台用户" }}</b>
</el-dropdown-item>
<router-link to="/">
<el-dropdown-item divided>{{ langTitle("Home") }}</el-dropdown-item>

View File

@ -0,0 +1,34 @@
<template>
<div class="debugger" title="调试器">
<el-button class="btn" type="primary" link @click="data.dialog = true">
<menu-icon icon="bug-fill" />
</el-button>
<el-dialog v-model="data.dialog" :append-to-body="true" title="Debugger" :width="600">
<div>
显示调试网格
<el-switch v-model="showLayoutGrid" />
</div>
</el-dialog>
<div v-if="showLayoutGrid" v-html="data.gridHtml" />
</div>
</template>
<script setup lang="ts">
import MenuIcon from "@/layout/sidebar/MenuIcon.vue";
import { useDebuggerStore } from "@/store/debuger";
const store = useDebuggerStore()
const {showLayoutGrid} = storeToRefs(store)
const data = reactive({
dialog: false,
gridHtml: `<style>*{ box-shadow: 0 0 1px 0 blue }</style>`
});
</script>
<style scoped lang="scss">
.btn{
padding: 0.3em !important;
}
</style>

View File

@ -11,7 +11,7 @@ export const useBasicStore = defineStore('basic', {
token: '',
getUserInfo: false,
userInfo: {
userName: '',
username: '',
realName: '',
avatar: '',
phone: '',
@ -72,7 +72,7 @@ export const useBasicStore = defineStore('basic', {
state.filterAsyncRoutes = []
//reset userInfo
state.userInfo = {
userName: '',
username: '',
realName: '',
avatar: '',
phone: '',

18
src/store/debuger.ts Normal file
View File

@ -0,0 +1,18 @@
import { defineStore } from "pinia";
export const useDebuggerStore = defineStore('debugger',{
state(){
return {
showLayoutGrid: false
}
},
persist:{
storage: localStorage,
paths: ['showLayoutGrid']
},
actions:{
setShowLayoutGrid(value){
this.showLayoutGrid = value
}
}
})

337
src/utils/validate.ts Normal file
View File

@ -0,0 +1,337 @@
/**
* @description
* @param path
* @returns {boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* @description 6
* @param str
* @returns {boolean}
*/
export function isPassword(str) {
return str.length >= 6
}
/**
* @description
* @param value
* @returns {boolean}
*/
export function isNumber(value) {
const reg = /^[0-9]*$/
return reg.test(value)
}
/**
* @description
* @param value
* @returns {boolean}
*/
export function isName(value) {
const reg = /^[\u4E00-\u9FA5a-zA-Z0-9]+$/
return reg.test(value)
}
/**
* @description IP
* @param ip
* @returns {boolean}
*/
export function isIP(ip) {
const reg =
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
return reg.test(ip)
}
/**
* @description
* @param url
* @returns {boolean}
*/
export function isUrl(url) {
const reg =
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isLowerCase(str) {
const reg = /^[a-z]+$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isUpperCase(str) {
const reg = /^[A-Z]+$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isAlphabets(str) {
const reg = /^[A-Za-z]+$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isString(str) {
return typeof str === 'string' || str instanceof String
}
/**
* @description
* @param arg
* @returns {arg is any[]|boolean}
*/
export function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}
/**
* @description
* @param arg
* @returns {arg is any[]|boolean}
*/
export function isObject(arg) {
return Object.prototype.toString.call(arg) === '[object Object]'
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isPort(str) {
const reg =
/^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isPhone(str) {
const reg = /^1\d{10}$/
return reg.test(str)
}
/**
* @description ()
* @param str
* @returns {boolean}
*/
export function isIdCard(str) {
const reg =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isEmail(str) {
const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isChina(str) {
const reg = /^[\u4E00-\u9FA5]{2,4}$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isBlank(str) {
return (
str == null ||
false ||
str === '' ||
str.trim() === '' ||
str.toLocaleLowerCase().trim() === 'null'
)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isTel(str) {
const reg =
/^(400|800)([0-9\\-]{7,10})|(([0-9]{4}|[0-9]{3})(-| )?)?([0-9]{7,8})((-| |转)*([0-9]{1,4}))?$/
return reg.test(str)
}
/**
* @description
* @param str
* @returns {boolean}
*/
export function isNum(str) {
const reg = /^\d+(\.\d{1,2})?$/
return reg.test(str)
}
/**
* 18
* @param cid 18
* @return Boolean
**/
export function isIdentityNumber(cid) {
const arrExp = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] //加权因子
const arrValid = '1 0 X 9 8 7 6 5 4 3 2'.split(' ') //校检码
if (/^\d{17}\d|x$/i.test(cid)) {
let sum = 0
for (let i = 0; i < cid.length - 1; i++) {
// 对前17位数字与权值乘积求和
sum += Number.parseInt(cid.substr(i, 1), 10) * arrExp[i]
}
// 计算模(固定算法)
const idx = sum % 11
// 检验第18为是否与校验码相等
return arrValid[idx] === cid.slice(17, 18).toUpperCase()
} else {
return false
}
}
/**
* EL表单检测
* @author
*/
export class ElValidate {
/**
* Validate
* @param canEmpty {boolean | string}
* @param validate {[any]} Validate列表
* @return {[any]}
*/
static createValidate(canEmpty, validate) {
if (isString(canEmpty) || (typeof canEmpty == 'boolean' && canEmpty)) {
validate.forEach((v) => {
v.required = undefined
const va = v.validator
v.validator = function (a, b, c) {
if (!b) c()
else va(a, b, c)
}
})
return validate
} else {
validate.push({
required: true,
trigger: 'blur',
message: isString(canEmpty) ? canEmpty : '请输入文本',
})
return validate
}
}
/**
*
* @return {[any]}
*/
static getIsPhoneRule(canEmpty = false) {
return this.createValidate(canEmpty ? '请输入手机号' : false, [
{
trigger: 'blur',
message: '请输入有效的手机号',
validator (rule, value, callback) {
if (/^1[3456789]\d{9}$/g.test(value)) callback()
else callback(new Error('无效的手机号'))
},
},
])
}
/**
*
* @return {[any]}
*/
static getIsIdentityNumberRule(canEmpty = false) {
return this.createValidate(canEmpty ? '请输入身份证号' : false, [
{
trigger: 'blur',
message: '请输入有效的身份证号',
validator (rule, value, callback) {
if (isIdentityNumber(value)) {
callback()
} else {
callback(new Error('无效的身份证号'))
}
},
},
])
}
/**
*
* @param canEmpty
* @param max -1
* @param min 0
* @return {[any]}
*/
static getLengthLimitRule(canEmpty = false, max = -1, min = 0) {
let lint = `文本长度应在${min}${max}之间`
if (min <= 0) lint = `最大输入${max}个字`
else if (max >= -1) lint = `最少输入${min}个字`
return this.createValidate(canEmpty ? '请输入文本' : false, [
{
trigger: 'change',
message: lint,
validator (rule, value, callback) {
if (value.length < min) {
callback(new Error(`输入文本太短,最少长度为${min}`))
return
}
if (max > -1 && value.length > max) {
callback(`输入文本太长,最长长度为${max}`)
return
}
callback()
},
},
])
}
/**
*
* @param str
* @return {[any]}
*/
static getIsEmptyRule(str = '请输入文本') {
return [{ required: true, trigger: 'blur', message: str }]
}
}

View File

@ -11,7 +11,7 @@
<span class="svg-container">
<ElSvgIcon name="User" :size="14" />
</span>
<el-input v-model="subForm.userName" placeholder="用户名" />
<el-input v-model="subForm.username" placeholder="用户名" />
<!--占位-->
</el-form-item>
<el-form-item prop="password" :rules="formRules.isNotNull('密码')">
@ -47,6 +47,7 @@ import { elMessage, useElement } from "@/hooks/use-element";
import { getMyRole, login } from "@/api/user";
import type { FormInstance, InputInstance } from "element-plus";
import { settings as viteSettings } from "@/settings";
import { ElMessage } from "element-plus";
/* listen router change and set the query */
const { settings } = useBasicStore();
@ -54,7 +55,7 @@ const { settings } = useBasicStore();
const formRules = useElement().formRules;
//form
const subForm = reactive({
userName: "",
username: "",
password: ""
});
const state: any = reactive({
@ -94,8 +95,13 @@ const refLoginForm: FormInstance = $ref(null);
function handleLogin() {
refLoginForm.validate((valid) => {
if (valid) {
subLoading = true;
if (valid) loginFunc();
loginFunc();
}
else {
ElMessage.error("请检查输入是否正确")
}
});
}

View File

@ -0,0 +1,264 @@
<template>
<el-drawer v-model="drawer" :title="title" :before-close="close">
<div v-loading="loading" class="drawer__content">
<el-form
ref="form"
:model="form"
:rules="rules"
label-width="80px"
:disabled="detail"
size="default"
>
<el-form-item label="姓名" prop="realName">
<el-popover
v-model="searchShow"
:trigger="title === '新增' ? 'hover' : ''"
:width="300"
>
<template #reference>
<el-input
v-model.trim="form.realName"
autocomplete="off"
/>
</template>
<el-table
:data="searchTable"
size="small"
:show-header="false"
@row-click="selectItem"
>
<el-table-column
property="realName"
label="姓名"
:width="100"
/>
<el-table-column
property="userName"
label="账号"
:width="200"
/>
</el-table>
</el-popover>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model.trim="form.phone" autocomplete="off"/>
</el-form-item>
<el-form-item label="用户账号" prop="userName">
<el-input
:value="form.userName"
autocomplete="off"
readonly
placeholder="与手机号相同"
/>
</el-form-item>
<!--<el-form-item label="密码" prop="password">-->
<!-- <el-input-->
<!-- v-model.trim="form.password"-->
<!-- type="password"-->
<!-- autocomplete="off"-->
<!-- ></el-input>-->
<!-- </el-form-item>-->
<el-form-item label="角色" prop="roleId">
<el-radio-group v-model="form.roleId">
<el-radio v-for="item in roleList" :key="item.id" :label="item.id">
{{ item.roleName }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="isUse">
<el-radio-group v-model="form.isUse">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div v-show="!detail" class="drawer__footer">
<el-button size="default" @click="close"> </el-button>
<el-button size="default" type="primary" @click="save"> </el-button>
</div>
</div>
</el-drawer>
</template>
<script>
import { doCreate, doEdit } from '@/api/userManagement'
import { getRoleByUserId, getRoleList } from '@/api/roleManagement'
import { isPhone } from '@/utils/validate'
import { userSearching } from '@/api/user'
import { ElMessage, ElMessageBox } from "element-plus";
export default {
name: 'UserManagementEdit',
data() {
return {
form: {
password: '',
phone: '',
realName: '',
userName: undefined,
roleId: '',
isUse: 1,
},
editForm: {},
rules: {
password: [
{ required: false, trigger: 'blur', message: '请输入密码' },
],
phone: [
{
required: true,
validator: (rule, value, callback) => {
if (!isPhone(value)) {
callback(new Error('请输入正确的手机号'))
} else {
callback()
}
},
trigger: 'blur',
},
],
realName: [
{ required: true, trigger: 'blur', message: '请输入姓名' },
],
roleId: [{ required: true, trigger: 'blur', message: '请选择角色' }],
},
title: '',
loading: false,
drawer: false,
detail: false,
roleList: [],
searchTable: [],
searchWaiter: 0,
searchShow: false,
formChanging: false,
}
},
watch: {
'form.realName': function(value) {
const key = Math.random()
this.searchWaiter = key
setTimeout(async () => {
if (this.searchWaiter !== key) return
try {
this.searchTable = await userSearching(value)
} catch {}
}, 1000)
},
'form.phone': function() {
if (this.formChanging) return
this.form.userName = undefined
},
},
created() {
this.fetchData()
},
methods: {
showEdit(row, detail) {
this.loading = true
this.formChanging = true
if (!row) {
this.title = '新增'
} else {
this.title = detail ? '查看' : '编辑'
this.detail = detail
this.form = Object.assign({}, row)
// form
this.editForm = JSON.parse(JSON.stringify(this.form))
this.getRole()
}
this.drawer = true
setTimeout(() => {
this.loading = false
this.formChanging = false
}, 1000)
},
close(confirm) {
let formChanged = false
//
if (this.title === '新增') {
formChanged = !(
JSON.stringify(this.form) ===
JSON.stringify(this.$options.data().form)
)
} else {
formChanged = !(
JSON.stringify(this.form) === JSON.stringify(this.editForm)
)
}
if (confirm && formChanged && this.title !== '查看') {
ElMessageBox.confirm(
'编辑数据将会清空,确认退出?',
'请仔细确认'
).then(() => this.closeEdit(), () => {})
} else {
this.closeEdit()
}
},
selectItem(item) {
this.formChanging = true
this.form.phone = item.phone
this.form.userName = item.userName
this.form.realName = item.realName
this.searchShow = false
this.$nextTick(() => (this.formChanging = false))
},
closeEdit() {
this.$refs['form'].resetFields()
this.form = this.$options.data().form
this.detail = false
this.drawer = false
},
save() {
this.$refs['form'].validate(async (valid) => {
if (valid) {
if (this.form.id) {
const { msg } = await doEdit(this.form)
ElMessage.success(msg)
} else {
const { msg } = await doCreate(this.form)
ElMessage.success(msg)
}
this.$emit('fetch-data')
this.close()
} else {
return false
}
})
},
async fetchData() {
this.listLoading = true
//
const req5 = await getRoleList()
this.roleList = req5.data.filter((item) => !item.type.includes('admin'))
setTimeout(() => {
this.listLoading = false
}, 300)
},
//
async getRole() {
const { data } = await getRoleByUserId(this.form.id)
this.form.roleId = ref(data.roleId)
},
},
}
</script>
<style lang="scss" scoped>
.drawer__content {
display: flex;
flex-direction: column;
height: 100%;
form {
flex: 1;
}
.drawer__footer {
display: flex;
button {
flex: 1;
}
}
}
</style>

View File

@ -0,0 +1,231 @@
<template>
<app-page class="userManagement-container">
<div>
<el-form :inline="true" :model="queryForm" @submit.prevent>
<!-- <el-form-item>
<el-select v-model="queryForm.post" placeholder="专业方向">
<el-option
v-for="item in postList"
:key="item.code"
:label="item.value"
:value="item.code"
></el-option>
</el-select>
</el-form-item> -->
<el-form-item>
<el-input
v-model.trim="queryForm.keys"
placeholder="请输入关键字"
@keyup.enter="queryData"
>
<template #suffix>
<i
class="el-icon-search el-input__icon i-search"
@click="queryData"
/>
</template>
</el-input>
</el-form-item>
<el-form-item>
<!-- <el-button icon="el-icon-search" type="primary" @click="queryData">
查询
</el-button> -->
<el-button type="primary" @click="handleEdit">新增</el-button>
<!-- <el-button type="danger" @click="handleExport">导出</el-button> -->
</el-form-item>
</el-form>
</div>
<el-table
v-loading="listLoading"
:data="list"
:element-loading-text="elementLoadingText"
:header-cell-style="{ background: '#F7F9FB', textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
@selection-change="setSelectRows"
>
<!-- <el-table-column show-overflow-tooltip type="selection"></el-table-column> -->
<el-table-column label="序号" type="index" />
<el-table-column
:show-overflow-tooltip="true"
prop="userName"
label="账号"
/>
<el-table-column
:show-overflow-tooltip="true"
prop="realName"
label="姓名"
/>
<el-table-column :show-overflow-tooltip="true" label="账号状态">
<template #default="{ row }">
<el-tag v-if="row.isUse === 1" effect="dark">正常</el-tag>
<el-tag v-else type="danger" effect="dark">禁用</el-tag>
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" label="所属角色">
<template #default="{ row }">
<el-tag effect="dark" :style="getRoleTagStyle(row)">
{{ row.roleName || "未知" }}
</el-tag>
</template>
</el-table-column>
<el-table-column
:show-overflow-tooltip="true"
prop="updatedAt"
label="修改时间"
/>
<el-table-column label="操作" width="100">
<template #default="{ row }">
<div class="table-btn-list">
<el-button class="detail" link type="primary" @click="handleEdit(row, true)">
<menu-icon icon="file-earmark-richtext-fill" />
</el-button>
<el-button class="edit" link type="primary" @click="handleEdit(row, false)">
<menu-icon icon="pencil-square" />
</el-button>
<el-button class="del" link type="primary" @click="handleDelete(row)">
<menu-icon icon="trash-fill" />
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
background
:current-page="queryForm.pageNo"
:page-size="queryForm.pageSize"
:layout="layout"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<edit ref="edit" @fetch-data="fetchData" />
</app-page>
</template>
<script>
import { doDelete, getList } from "@/api/userManagement";
import Edit from "./components/UserManagementEdit.vue";
import { sha1 } from "hash.js";
import MenuIcon from "@/layout/sidebar/MenuIcon.vue";
import { ElMessage, ElMessageBox } from "element-plus";
//
export default {
name: "UserManagement",
components: { MenuIcon, Edit },
data() {
return {
list: null,
listLoading: true,
layout: "total, sizes, prev, pager, next, jumper",
total: 0,
selectRows: "",
elementLoadingText: "正在加载...",
queryForm: {
pageNo: 1,
pageSize: 10,
keys: ""
}
};
},
created() {
this.fetchData();
},
methods: {
setSelectRows(val) {
this.selectRows = val;
},
handleEdit(row, detail) {
if (row.id) {
this.$refs["edit"].showEdit(row, detail);
} else {
this.$refs["edit"].showEdit();
}
},
async handleDelete(row) {
if (row.id) {
const rs = await ElMessageBox.confirm("你确定要删除当前项吗?");
if (rs !== "confirm") return;
const { msg } = await doDelete({ ids: row.id });
ElMessage.success(msg);
await this.fetchData();
} else {
if (this.selectRows.length > 0) {
const ids = this.selectRows.map((item) => item.id).join();
const rs = await ElMessageBox.confirm("你确定要删除当前项吗?");
if (rs !== "confirm") return;
const { msg } = await doDelete({ ids });
ElMessage.success(msg);
await this.fetchData();
} else {
ElMessage.error("未选中任何行");
return false;
}
}
},
handleSizeChange(val) {
this.queryForm.pageSize = val;
this.fetchData();
},
handleCurrentChange(val) {
this.queryForm.pageNo = val;
this.fetchData();
},
queryData() {
this.queryForm.pageNo = 1;
this.fetchData();
},
switchChange(row) {
},
async fetchData() {
this.listLoading = true;
const { data } = await getList(this.queryForm);
this.list = data.data;
this.total = data.total;
setTimeout(() => {
this.listLoading = false;
}, 300);
},
/**
* 获取标签的颜色
* @param row 角色对象
* @return {{backgroundColor: string, borderColor: string, color?: string}}
*/
getRoleTagStyle(row) {
const style = {
backgroundColor: undefined,
borderColor: undefined,
color: undefined
};
const type = row.roleType || "unknown";
if (type.includes("teacher")) style.backgroundColor = "red";
else if (type.includes("admin")) style.backgroundColor = "darkorange";
else if (type.includes("student")) style.backgroundColor = "#62CAD7";
else if (!row.roleName) style.backgroundColor = "darkgray";
else {
//
const [r, g, b] = sha1().update(type).digest();
const gray = r * 0.299 + g * 0.587 + b * 0.114;
style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
if (gray < 128) {
style.color = "black";
style.borderColor = "gray";
}
}
if (!style.borderColor) style.borderColor = style.backgroundColor;
return style;
}
}
};
</script>
<style lang="scss" scoped>
.table-btn-list {
display: flex;
:deep(.el-button) {
padding: 0;
}
}
</style>

View File

@ -1,124 +0,0 @@
{
"name": "vue3-admin-ts",
"version": "2.0.0-rc2",
"license": "MIT",
"author": "kuanghua",
"packageManager": "pnpm@7.9.0",
"scripts": {
"dev": "vite --mode serve-dev",
"test": "vite --mode serve-test",
"build:test": "vite build --mode build-test",
"build": "vite build --mode build",
"preview:build": "npm run build && vite preview ",
"preview": "vite preview ",
"lint": "eslint --ext .js,.jsx,.vue,.ts,.tsx src --fix",
"prepare": "husky install",
"test:unit": "vue-cli-service test:unit",
"test:watchAll": "vue-cli-service test:unit --watchAll",
"test:cov": "vue-cli-service test:unit --coverage",
"test:majestic": "majestic",
"vitest": "vitest --ui",
"tsc-check": "tsc",
"coverage": "vitest run --coverage"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.4",
"axios": "0.21.3",
"echarts": "5.3.2",
"element-plus": "^2.2.9",
"js-error-collection": "^1.0.7",
"mitt": "3.0.0",
"moment-mini": "2.22.1",
"nprogress": "0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.1",
"pinia": "^2.0.16",
"pinia-plugin-persistedstate": "2.3.0",
"vue": "^3.2.37",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.1.5"
},
"devDependencies": {
"@babel/eslint-parser": "7.16.3",
"@types/echarts": "4.9.7",
"@types/mockjs": "1.0.6",
"@types/node": "^17.0.35",
"@types/path-browserify": "^1.0.0",
"@typescript-eslint/eslint-plugin": "5.30.0",
"@typescript-eslint/parser": "5.30.0",
"@vitejs/plugin-legacy": "^2.2.0",
"@vitejs/plugin-vue": "^2.3.3",
"@vitejs/plugin-vue-jsx": "^2.0.1",
"@vitest/coverage-c8": "^0.22.1",
"@vitest/ui": "^0.22.1",
"@vue/cli-plugin-unit-jest": "4.5.17",
"@vue/cli-service": "4.5.17",
"@vue/test-utils": "^2.0.2",
"@vueuse/core": "^8.7.5",
"eslint": "8.18.0",
"eslint-config-prettier": "8.5.0",
"eslint-define-config": "1.5.1",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsonc": "^2.3.0",
"eslint-plugin-markdown": "^3.0.0",
"eslint-plugin-prettier": "4.1.0",
"eslint-plugin-unicorn": "^43.0.2",
"eslint-plugin-vue": "9.1.1",
"husky": "7.0.2",
"jsdom": "16.4.0",
"jsonc-eslint-parser": "^2.1.0",
"majestic": "1.8.1",
"mockjs": "1.1.0",
"prettier": "2.2.1",
"resize-observer-polyfill": "^1.5.1",
"rollup-plugin-visualizer": "^5.8.3",
"sass": "^1.52.1",
"svg-sprite-loader": "6.0.11",
"typescript": "^4.7.2",
"unocss": "^0.33.5",
"unplugin-auto-import": "^0.11.2",
"unplugin-vue-components": "^0.22.8",
"unplugin-vue-define-options": "^0.12.2",
"vite": "^3.1.8",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mkcert": "^1.7.2",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.22.1",
"vue-tsc": "^0.34.16"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"html-webpack-plugin",
"vite-plugin-mock",
"unplugin-auto-import",
"unplugin-vue-components",
"vue-template-compiler",
"unocss",
"unplugin",
"vite-plugin-mock",
"@vitejs/plugin-legacy",
"@vitejs/plugin-vue",
"@vitejs/*",
"@babel/*",
"vite",
"vue",
"@unocss/vite",
"rollup",
"vue-jest",
"@babel/*"
]
}
},
"browserslist": [
"> 1%",
"not ie 11",
"not op_mini all"
],
"engines": {
"node": ">= 16 <18",
"pnpm": ">= 6 <8"
}
}

View File

@ -1,3 +0,0 @@
export declare const userInfoReq: () => Promise<any>;
export declare const loginReq: (subForm: any) => import("axios").AxiosPromise<any>;
export declare const loginOutReq: () => import("axios").AxiosPromise<any>;

View File

@ -1,26 +0,0 @@
import request from '@/utils/request'
export const userInfoReq = () => {
return new Promise((resolve) => {
const reqConfig = {
url: '/basis-func/user/getUserInfo',
params: { plateFormId: 2 },
method: 'post'
}
request(reqConfig).then(({ data }) => {
resolve(data)
})
})
}
export const loginReq = (subForm) => {
return request({
url: '/basis-func/user/loginValid',
params: subForm,
method: 'post'
})
}
export const loginOutReq = () => {
return request({
url: '/basis-func/user/loginValid',
method: 'post'
})
}

View File

@ -1,5 +0,0 @@
declare const _default: {
mounted(el: any, binding: any): void;
componentUpdated(el: any, binding: any): void;
};
export default _default;

View File

@ -1,22 +0,0 @@
import { useBasicStore } from '@/store/basic';
function checkPermission(el, { value }) {
if (value && Array.isArray(value)) {
if (value.length) {
const permissionRoles = value;
const hasPermission = useBasicStore().buttonCodes?.some((code) => permissionRoles.includes(code));
if (!hasPermission)
el.parentNode && el.parentNode.removeChild(el);
}
}
else {
throw new Error(`need roles! Like v-permission="['admin','editor']"`);
}
}
export default {
mounted(el, binding) {
checkPermission(el, binding);
},
componentUpdated(el, binding) {
checkPermission(el, binding);
}
};

View File

@ -1,5 +0,0 @@
declare const _default: {
mounted(el: any, binding: any): void;
componentUpdated(el: any, binding: any): void;
};
export default _default;

View File

@ -1,22 +0,0 @@
import { useBasicStore } from '@/store/basic';
function checkPermission(el, { value }) {
if (value && Array.isArray(value)) {
if (value.length > 0) {
const permissionRoles = value;
const hasPermission = useBasicStore().codes?.some((role) => permissionRoles.includes(role));
if (!hasPermission)
el.parentNode && el.parentNode.removeChild(el);
}
}
else {
throw new Error(`need codes! Like v-codes-permission="['admin','editor']"`);
}
}
export default {
mounted(el, binding) {
checkPermission(el, binding);
},
componentUpdated(el, binding) {
checkPermission(el, binding);
}
};

View File

@ -1 +0,0 @@
export default function (app: any): void;

View File

@ -1,8 +0,0 @@
import buttonCodes from './button-codes';
import codesPermission from './codes-permission';
import rolesPermission from './roles-permission';
export default function (app) {
app.directive('ButtonCodes', buttonCodes);
app.directive('CodesPermission', codesPermission);
app.directive('RolesPermission', rolesPermission);
}

View File

@ -1,5 +0,0 @@
declare const _default: {
mounted(el: any, binding: any): void;
componentUpdated(el: any, binding: any): void;
};
export default _default;

View File

@ -1,22 +0,0 @@
import { useBasicStore } from '@/store/basic';
function checkPermission(el, { value }) {
if (value && Array.isArray(value)) {
if (value.length > 0) {
const permissionRoles = value;
const hasPermission = useBasicStore().roles?.some((role) => permissionRoles.includes(role));
if (!hasPermission)
el.parentNode && el.parentNode.removeChild(el);
}
}
else {
throw new Error(`need roles! Like v-roles-permission="['admin','editor']"`);
}
}
export default {
mounted(el, binding) {
checkPermission(el, binding);
},
componentUpdated(el, binding) {
checkPermission(el, binding);
}
};

View File

@ -1,10 +0,0 @@
export declare const sleepTimeout: (time: number) => Promise<unknown>;
export declare const useCommon: () => {
totalPage: import("vue").Ref<number>;
startEndArr: import("vue").Ref<never[]>;
searchForm: import("vue").Ref<{}>;
dialogTitle: import("vue").Ref<string>;
detailDialog: import("vue").Ref<boolean>;
};
export declare function cloneDeep(value: any): any;
export declare const copyValueToClipboard: (value: any) => void;

View File

@ -1,31 +0,0 @@
import { reactive, toRefs } from 'vue';
import useClipboard from 'vue-clipboard3';
import { ElMessage } from 'element-plus';
export const sleepTimeout = (time) => {
return new Promise((resolve) => {
const timer = setTimeout(() => {
clearTimeout(timer);
resolve(null);
}, time);
});
};
export const useCommon = () => {
const state = reactive({
totalPage: 0,
startEndArr: [],
searchForm: {},
dialogTitle: '',
detailDialog: false
});
return {
...toRefs(state)
};
};
export function cloneDeep(value) {
return JSON.parse(JSON.stringify(value));
}
const { toClipboard } = useClipboard();
export const copyValueToClipboard = (value) => {
toClipboard(JSON.stringify(value));
ElMessage.success('复制成功');
};

View File

@ -1,67 +0,0 @@
import type { EpPropMergeType } from 'element-plus/es/utils';
export declare const useElement: () => {
tableData: import("vue").Ref<never[]>;
rowDeleteIdArr: import("vue").Ref<never[]>;
loadingId: import("vue").Ref<null>;
formModel: import("vue").Ref<{}>;
subForm: import("vue").Ref<{}>;
searchForm: import("vue").Ref<{}>;
formRules: import("vue").Ref<{
isNull: (msg: string) => {
required: boolean;
message: string;
trigger: string;
}[];
isNotNull: (msg: string) => {
required: boolean;
message: string;
trigger: string;
}[];
upZeroInt: (msg: string) => {
required: boolean;
validator: (rule: any, value: any, callback: any) => void;
trigger: string;
}[];
zeroInt: (msg: string) => {
required: boolean;
validator: (rule: any, value: any, callback: any) => void;
trigger: string;
}[];
money: (msg: string) => {
required: boolean;
validator: (rule: any, value: any, callback: any) => void;
trigger: string;
}[];
phone: (msg: string) => {
required: boolean;
validator: (rule: any, value: any, callback: any) => void;
trigger: string;
}[];
email: (msg: string) => {
required: boolean;
validator: (rule: any, value: any, callback: any) => void;
trigger: string;
}[];
}>;
datePickerOptions: import("vue").Ref<{
disabledDate: (time: any) => boolean;
}>;
startEndArr: import("vue").Ref<never[]>;
dialogTitle: import("vue").Ref<string>;
detailDialog: import("vue").Ref<boolean>;
isDialogEdit: import("vue").Ref<boolean>;
dialogVisible: import("vue").Ref<boolean>;
tableLoading: import("vue").Ref<boolean>;
treeData: import("vue").Ref<never[]>;
defaultProps: import("vue").Ref<{
children: string;
label: string;
}>;
};
export declare const elMessage: (message: string, type?: any) => void;
export declare const elLoading: () => void;
export declare const closeLoading: () => void;
export declare const elNotify: (message: string, type: EpPropMergeType<any, any, any> | undefined, title: string, duration: number) => void;
export declare const elConfirmNoCancelBtn: (title: string, message: string) => Promise<import("element-plus").MessageBoxData>;
export declare const elConfirm: (title: string, message: string) => Promise<import("element-plus").MessageBoxData>;
export declare const casHandleChange: () => void;

View File

@ -1,158 +0,0 @@
import { reactive, ref, toRefs } from 'vue';
import { ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-plus';
export const useElement = () => {
const upZeroInt = (rule, value, callback, msg) => {
if (!value) {
callback(new Error(`${msg}不能为空`));
}
if (/^\+?[1-9]\d*$/.test(value)) {
callback();
}
else {
callback(new Error(`${msg}输入有误`));
}
};
const zeroInt = (rule, value, callback, msg) => {
if (!value) {
callback(new Error(`${msg}不能为空`));
}
if (/^\+?[0-9]\d*$/.test(value)) {
callback();
}
else {
callback(new Error(`${msg}输入有误`));
}
};
const money = (rule, value, callback, msg) => {
if (!value) {
callback(new Error(`${msg}不能为空`));
}
if (/((^[1-9]\d*)|^0)(\.\d{0,2}){0,1}$/.test(value)) {
callback();
}
else {
callback(new Error(`${msg}输入有误`));
}
};
const phone = (rule, value, callback, msg) => {
if (!value) {
callback(new Error(`${msg}不能为空`));
}
if (/^0?1[0-9]{10}$/.test(value)) {
callback();
}
else {
callback(new Error(`${msg}输入有误`));
}
};
const email = (rule, value, callback, msg) => {
if (!value) {
callback(new Error(`${msg}不能为空`));
}
if (/(^([a-zA-Z]|[0-9])(\w|-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4}))$/.test(value)) {
callback();
}
else {
callback(new Error(`${msg}`));
}
};
const state = reactive({
tableData: [],
rowDeleteIdArr: [],
loadingId: null,
formModel: {},
subForm: {},
searchForm: {},
formRules: {
isNull: (msg) => [{ required: false, message: `${msg}`, trigger: 'blur' }],
isNotNull: (msg) => [{ required: true, message: `${msg}`, trigger: 'blur' }],
upZeroInt: (msg) => [
{ required: true, validator: (rule, value, callback) => upZeroInt(rule, value, callback, msg), trigger: 'blur' }
],
zeroInt: (msg) => [
{ required: true, validator: (rule, value, callback) => zeroInt(rule, value, callback, msg), trigger: 'blur' }
],
money: (msg) => [
{ required: true, validator: (rule, value, callback) => money(rule, value, callback, msg), trigger: 'blur' }
],
phone: (msg) => [
{ required: true, validator: (rule, value, callback) => phone(rule, value, callback, msg), trigger: 'blur' }
],
email: (msg) => [
{ required: true, validator: (rule, value, callback) => email(rule, value, callback, msg), trigger: 'blur' }
]
},
datePickerOptions: {
disabledDate: (time) => {
return time.getTime() < Date.now() - 86400000;
}
},
startEndArr: [],
dialogTitle: '添加',
detailDialog: false,
isDialogEdit: false,
dialogVisible: false,
tableLoading: false,
treeData: [],
defaultProps: {
children: 'children',
label: 'label'
}
});
return {
...toRefs(state)
};
};
export const elMessage = (message, type) => {
ElMessage({
showClose: true,
message: message || '成功',
type: type || 'success',
center: false
});
};
let loadingId = null;
export const elLoading = () => {
loadingId = ElLoading.service({
lock: true,
text: '数据载入中',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.1)'
});
};
export const closeLoading = () => {
loadingId.close();
};
export const elNotify = (message, type, title, duration) => {
ElNotification({
title: title || '提示',
type: type || 'success',
message: message || '请传入提示消息',
position: 'top-right',
duration: duration || 2500,
offset: 40
});
};
export const elConfirmNoCancelBtn = (title, message) => {
return ElMessageBox({
message: message || '你确定要删除吗',
title: title || '确认框',
confirmButtonText: '确定',
cancelButtonText: '取消',
showCancelButton: false,
type: 'warning'
});
};
export const elConfirm = (title, message) => {
return ElMessageBox({
message: message || '你确定要删除吗',
title: title || '确认框',
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
};
const cascaderKey = ref();
export const casHandleChange = () => {
cascaderKey.value += cascaderKey.value;
};

View File

@ -1 +0,0 @@
export declare const useErrorLog: () => void;

View File

@ -1,27 +0,0 @@
import { jsErrorCollection } from 'js-error-collection'
import pack from '../../package.json'
import settings from '@/settings'
import bus from '@/utils/bus'
import request from '@/utils/request'
const reqUrl = '/integration-front/errorCollection/insert'
const errorLogReq = (errLog) => {
request({
url: reqUrl,
data: {
pageUrl: window.location.href,
errorLog: errLog,
browserType: navigator.userAgent,
version: pack.version
},
method: 'post'
}).then(() => {
bus.emit('reloadErrorPage', {})
})
}
export const useErrorLog = () => {
if (settings.errorLog?.includes(import.meta.env.VITE_APP_ENV)) {
jsErrorCollection({ runtimeError: true, rejectError: true, consoleError: true }, (errLog) => {
if (!errLog.includes(reqUrl)) errorLogReq(errLog)
})
}
}

View File

@ -1,2 +0,0 @@
export declare function isExternal(path: any): boolean;
export declare function resizeHandler(): void;

View File

@ -1,38 +0,0 @@
import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue';
import { useBasicStore } from '@/store/basic';
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}
export function resizeHandler() {
const { body } = document;
const WIDTH = 992;
const basicStore = useBasicStore();
const isMobile = () => {
const rect = body.getBoundingClientRect();
return rect.width - 1 < WIDTH;
};
const resizeHandler = () => {
if (!document.hidden) {
if (isMobile()) {
basicStore.setSidebarOpen(false);
}
else {
basicStore.setSidebarOpen(true);
}
}
};
onBeforeMount(() => {
window.addEventListener('resize', resizeHandler);
});
onMounted(() => {
if (isMobile()) {
basicStore.setSidebarOpen(false);
}
else {
basicStore.setSidebarOpen(true);
}
});
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler);
});
}

View File

@ -1,15 +0,0 @@
import type { RouterTypes } from '~/basic';
import 'nprogress/nprogress.css';
export declare const filterAsyncRoutesByMenuList: (menuList: any) => RouterTypes;
export declare function filterAsyncRoutesByRoles(routes: any, roles: any): RouterTypes;
export declare function filterAsyncRouterByCodes(codesRoutes: any, codes: any): RouterTypes;
export declare function filterAsyncRouter({ menuList, roles, codes }: {
menuList: any;
roles: any;
codes: any;
}): void;
export declare function resetRouter(): void;
export declare function resetState(): void;
export declare function freshRouter(data: any): void;
export declare const progressStart: () => void;
export declare const progressClose: () => void;

View File

@ -1,146 +0,0 @@
import NProgress from 'nprogress';
import Layout from '@/layout/index.vue';
import router, { asyncRoutes, constantRoutes, roleCodeRoutes } from '@/router';
import 'nprogress/nprogress.css';
import { useBasicStore } from '@/store/basic';
const buttonCodes = [];
export const filterAsyncRoutesByMenuList = (menuList) => {
const filterRouter = [];
menuList.forEach((route) => {
if (route.category === 3) {
buttonCodes.push(route.code);
}
else {
const itemFromReqRouter = getRouteItemFromReqRouter(route);
if (route.children?.length) {
itemFromReqRouter.children = filterAsyncRoutesByMenuList(route.children);
}
filterRouter.push(itemFromReqRouter);
}
});
return filterRouter;
};
const getRouteItemFromReqRouter = (route) => {
const tmp = { meta: { title: '' } };
const routeKeyArr = ['path', 'component', 'redirect', 'alwaysShow', 'name', 'hidden'];
const metaKeyArr = ['title', 'activeMenu', 'elSvgIcon', 'icon'];
const modules = import.meta.glob('../views/**/**.vue');
routeKeyArr.forEach((fItem) => {
if (fItem === 'component') {
if (route[fItem] === 'Layout') {
tmp[fItem] = Layout;
}
else {
tmp[fItem] = modules[`../views/${route[fItem]}`];
}
}
else if (fItem === 'path' && route.parentId === 0) {
tmp[fItem] = `/${route[fItem]}`;
}
else if (['hidden', 'alwaysShow'].includes(fItem)) {
tmp[fItem] = !!route[fItem];
}
else if (['name'].includes(fItem)) {
tmp[fItem] = route['code'];
}
else if (route[fItem]) {
tmp[fItem] = route[fItem];
}
});
metaKeyArr.forEach((fItem) => {
if (route[fItem] && tmp.meta)
tmp.meta[fItem] = route[fItem];
});
if (route.extra) {
Object.entries(route.extra.parse(route.extra)).forEach(([key, value]) => {
if (key === 'meta' && tmp.meta) {
tmp.meta[key] = value;
}
else {
tmp[key] = value;
}
});
}
return tmp;
};
export function filterAsyncRoutesByRoles(routes, roles) {
const res = [];
routes.forEach((route) => {
const tmp = { ...route };
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutesByRoles(tmp.children, roles);
}
res.push(tmp);
}
});
return res;
}
function hasPermission(roles, route) {
if (route?.meta?.roles) {
return roles?.some((role) => route.meta.roles.includes(role));
}
else {
return true;
}
}
export function filterAsyncRouterByCodes(codesRoutes, codes) {
const filterRouter = [];
codesRoutes.forEach((routeItem) => {
if (hasCodePermission(codes, routeItem)) {
if (routeItem.children)
routeItem.children = filterAsyncRouterByCodes(routeItem.children, codes);
filterRouter.push(routeItem);
}
});
return filterRouter;
}
function hasCodePermission(codes, routeItem) {
if (routeItem.meta?.code) {
return codes.includes(routeItem.meta.code) || routeItem.hidden;
}
else {
return true;
}
}
export function filterAsyncRouter({ menuList, roles, codes }) {
const basicStore = useBasicStore();
let accessRoutes = [];
const permissionMode = basicStore.settings?.permissionMode;
if (permissionMode === 'rbac') {
accessRoutes = filterAsyncRoutesByMenuList(menuList);
}
else if (permissionMode === 'roles') {
accessRoutes = filterAsyncRoutesByRoles(roleCodeRoutes, roles);
}
else {
accessRoutes = filterAsyncRouterByCodes(roleCodeRoutes, codes);
}
accessRoutes.forEach((route) => router.addRoute(route));
asyncRoutes.forEach((item) => router.addRoute(item));
basicStore.setFilterAsyncRoutes(accessRoutes);
}
export function resetRouter() {
const routeNameSet = new Set();
router.getRoutes().forEach((fItem) => {
if (fItem.name)
routeNameSet.add(fItem.name);
});
routeNameSet.forEach((setItem) => router.removeRoute(setItem));
constantRoutes.forEach((feItem) => router.addRoute(feItem));
}
export function resetState() {
resetRouter();
useBasicStore().resetState();
}
export function freshRouter(data) {
resetRouter();
filterAsyncRouter(data);
}
NProgress.configure({ showSpinner: false });
export const progressStart = () => {
NProgress.start();
};
export const progressClose = () => {
NProgress.done();
};

View File

@ -1,4 +0,0 @@
export declare const getQueryParam: () => any;
export declare const routerPush: (name: any, params: any) => void;
export declare const routerReplace: (name: any, params: any) => void;
export declare const routerBack: () => void;

View File

@ -1,40 +0,0 @@
import router from '@/router';
export const getQueryParam = () => {
const route = router.currentRoute;
if (route.value?.query.params) {
return JSON.parse(route.value.query.params);
}
};
export const routerPush = (name, params) => {
let data = {};
if (params) {
data = {
params: JSON.stringify(params)
};
}
else {
data = {};
}
router.push({
name,
query: data
});
};
export const routerReplace = (name, params) => {
let data = {};
if (params) {
data = {
params: JSON.stringify(params)
};
}
else {
data = {};
}
router.replace({
name,
query: data
});
};
export const routerBack = () => {
router.go(-1);
};

View File

@ -1,15 +0,0 @@
export declare const useTable: (searchForm: any, selectPageReq: any) => {
pageNum: import("vue").Ref<number>;
pageSize: import("vue").Ref<number>;
totalPage: import("vue").Ref<number>;
tableListData: import("vue").Ref<never[]>;
tableListReq: (config: any) => import("axios").AxiosPromise<any>;
dateRangePacking: (timeArr: any) => void;
multipleSelection: import("vue").Ref<ObjKeys[]>;
handleSelectionChange: (val: any) => void;
handleCurrentChange: (val: any) => void;
handleSizeChange: (val: any) => void;
resetPageReq: () => void;
multiDelBtnDill: (reqConfig: any) => void;
tableDelDill: (row: any, reqConfig: any) => void;
};

View File

@ -1,105 +0,0 @@
import { ref } from 'vue'
import momentMini from 'moment-mini'
import { elConfirm, elMessage } from './use-element'
export const useTable = (searchForm, selectPageReq) => {
const tableListData = ref([])
const totalPage = ref(0)
const pageNum = ref(1)
const pageSize = ref(20)
const tableListReq = (config) => {
const data = Object.assign(
{
pageNum: pageNum.value,
pageSize: pageSize.value
},
JSON.parse(JSON.stringify(searchForm))
)
Object.keys(data).forEach((fItem) => {
if (['', null, undefined, Number.NaN].includes(data[fItem])) delete data[fItem]
if (config.method === 'get') {
if (Array.isArray(data[fItem])) delete data[fItem]
if (data[fItem] instanceof Object) delete data[fItem]
}
})
const reqConfig = {
data,
...config
}
return request(reqConfig)
}
const dateRangePacking = (timeArr) => {
if (timeArr && timeArr.length === 2) {
searchForm.startTime = timeArr[0]
if (searchForm.endTime) {
searchForm.endTime = momentMini(timeArr[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
}
} else {
searchForm.startTime = ''
searchForm.endTime = ''
}
}
const handleCurrentChange = (val) => {
pageNum.value = val
selectPageReq()
}
const handleSizeChange = (val) => {
pageSize.value = val
selectPageReq()
}
const resetPageReq = () => {
pageNum.value = 1
selectPageReq()
}
const multipleSelection = ref([])
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
const multiDelBtnDill = (reqConfig) => {
let rowDeleteIdArr = []
let deleteNameTitle = ''
rowDeleteIdArr = multipleSelection.value.map((mItem) => {
deleteNameTitle = `${deleteNameTitle + mItem.id},`
return mItem.id
})
if (rowDeleteIdArr.length === 0) {
elMessage('表格选项不能为空', 'warning')
return
}
const stringLength = deleteNameTitle.length - 1
elConfirm('删除', `您确定要删除【${deleteNameTitle.slice(0, stringLength)}】吗`).then(() => {
const data = rowDeleteIdArr
request({
data,
method: 'DELETE',
bfLoading: true,
...reqConfig
}).then(() => {
elMessage('删除成功')
resetPageReq()
})
})
}
const tableDelDill = (row, reqConfig) => {
elConfirm('确定', `您确定要删除【${row.id}】吗?`).then(() => {
request(reqConfig).then(() => {
resetPageReq()
elMessage(`${row.id}】删除成功`)
})
})
}
return {
pageNum,
pageSize,
totalPage,
tableListData,
tableListReq,
dateRangePacking,
multipleSelection,
handleSelectionChange,
handleCurrentChange,
handleSizeChange,
resetPageReq,
multiDelBtnDill,
tableDelDill
}
}

View File

@ -1 +0,0 @@
export default function (app: any): void;

View File

@ -1,7 +0,0 @@
import * as AllComponent from 'element-plus';
const elementPlusComponentNameArr = ['ElButton'];
export default function (app) {
elementPlusComponentNameArr.forEach((component) => {
app.component(component, AllComponent[component]);
});
}

View File

@ -1,6 +0,0 @@
import '@/styles/index.scss';
import 'virtual:svg-icons-register';
import './permission';
import './theme/index.scss';
import 'uno.css';
import 'element-plus/dist/index.css';

View File

@ -1,22 +0,0 @@
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
import router from './router';
import '@/styles/index.scss';
import 'virtual:svg-icons-register';
import svgIcon from '@/icons/SvgIcon.vue';
import directive from '@/directives';
import './permission';
import './theme/index.scss';
import 'uno.css';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
app.use(ElementPlus);
app.component('SvgIcon', svgIcon);
directive(app);
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);
app.use(router).mount('#app');

View File

@ -1 +0,0 @@
export {};

View File

@ -1,44 +0,0 @@
import router from '@/router';
import { filterAsyncRouter, progressClose, progressStart } from '@/hooks/use-permission';
import { useBasicStore } from '@/store/basic';
import { userInfoReq } from '@/api/user';
const whiteList = ['/login', '/404', '/401'];
router.beforeEach(async (to) => {
progressStart();
const basicStore = useBasicStore();
if (basicStore.token) {
if (to.path === '/login') {
return '/';
}
else {
if (!basicStore.getUserInfo) {
try {
const userData = await userInfoReq();
filterAsyncRouter(userData);
basicStore.setUserInfo(userData);
return { ...to, replace: true };
}
catch (e) {
console.error(`route permission error${e}`);
basicStore.resetState();
progressClose();
return `/login?redirect=${to.path}`;
}
}
else {
return true;
}
}
}
else {
if (!whiteList.includes(to.path)) {
return `/login?redirect=${to.path}`;
}
else {
return true;
}
}
});
router.afterEach(() => {
progressClose();
});

View File

@ -1,6 +0,0 @@
import type { RouterTypes } from '~/basic';
export declare const constantRoutes: RouterTypes;
export declare const roleCodeRoutes: RouterTypes;
export declare const asyncRoutes: RouterTypes;
declare const router: import("vue-router").Router;
export default router;

View File

@ -1,203 +0,0 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import Layout from '@/layout/index.vue';
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index.vue'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404.vue'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error-page/401.vue'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: { title: 'Dashboard', elSvgIcon: 'Fold' }
}
]
},
{
path: '/setting-switch',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/setting-switch/index.vue'),
name: 'SettingSwitch',
meta: { title: 'Setting Switch', icon: 'example', affix: true }
}
]
},
{
path: '/error-collection',
component: Layout,
meta: { title: 'Error Collection', icon: 'eye' },
alwaysShow: true,
children: [
{
path: 'error-collection-table-query',
component: () => import('@/views/error-collection/ErrorCollectionTableQuery.vue'),
name: 'ErrorCollectionTableQuery',
meta: { title: 'Index' }
},
{
path: 'error-log-test',
component: () => import('@/views/error-log/ErrorLogTest.vue'),
name: 'ErrorLogTest',
meta: { title: 'ErrorLog Test' }
}
]
},
{
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
name: 'Nested',
meta: {
title: 'Nested',
icon: 'nested'
},
children: [
{
path: 'menu1',
component: () => import('@/views/nested/menu1/index.vue'),
name: 'Menu1',
meta: { title: 'Menu1' },
children: [
{
path: 'menu1-1',
component: () => import('@/views/nested/menu1/menu1-1/index.vue'),
name: 'Menu1-1',
meta: { title: 'Menu1-1' }
},
{
path: 'menu1-2',
component: () => import('@/views/nested/menu1/menu1-2/index.vue'),
name: 'Menu1-2',
meta: { title: 'Menu1-2' },
children: [
{
path: 'menu1-2-1',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1/index.vue'),
name: 'Menu1-2-1',
meta: { title: 'Menu1-2-1' }
},
{
path: 'menu1-2-2',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2/index.vue'),
name: 'Menu1-2-2',
meta: { title: 'Menu1-2-2' }
}
]
},
{
path: 'menu1-3',
component: () => import('@/views/nested/menu1/menu1-3/index.vue'),
name: 'Menu1-3',
meta: { title: 'Menu1-3' }
}
]
},
{
path: 'menu2',
component: () => import('@/views/nested/menu2/index.vue'),
name: 'Menu2',
meta: { title: 'menu2' }
}
]
},
{
path: '/external-link',
component: Layout,
children: [
{
component: () => { },
path: 'https://github.com/jzfai/vue3-admin-ts.git',
meta: { title: 'External Link', icon: 'link' }
}
]
}
];
export const roleCodeRoutes = [
{
path: '/roles-codes',
component: Layout,
redirect: '/roles-codes/page',
alwaysShow: true,
name: 'Permission',
meta: {
title: 'Permission',
icon: 'lock',
roles: ['admin', 'editor']
},
children: [
{
path: 'index',
component: () => import('@/views/roles-codes/index.vue'),
name: 'RolesCodes',
meta: {
title: 'index'
}
},
{
path: 'roleIndex',
component: () => import('@/views/roles-codes/role-index.vue'),
name: 'RoleIndex',
meta: {
title: 'Role Index',
roles: ['admin']
}
},
{
path: 'code-index',
component: () => import('@/views/roles-codes/code-index.vue'),
name: 'CodeIndex',
meta: {
title: 'Code Index',
code: 16
}
},
{
path: 'button-permission',
component: () => import('@/views/roles-codes/button-permission.vue'),
name: 'ButtonPermission',
meta: {
title: 'Button Permission'
}
}
]
}
];
export const asyncRoutes = [
{ path: '/:catchAll(.*)', name: 'CatchAll', redirect: '/404', hidden: true }
];
const router = createRouter({
history: createWebHashHistory(),
scrollBehavior: () => ({ top: 0 }),
routes: constantRoutes
});
export default router;

View File

@ -1,3 +0,0 @@
import type { SettingsConfig } from '~/basic';
declare const settings: SettingsConfig;
export default settings;

View File

@ -1,21 +0,0 @@
const settings = {
title: 'Vue3 Admin Template',
sidebarLogo: true,
showNavbarTitle: false,
ShowDropDown: true,
showHamburger: true,
showLeftMenu: true,
showTagsView: true,
tagsViewNum: 6,
showTopNavbar: true,
mainNeedAnimation: true,
isNeedNprogress: true,
isNeedLogin: true,
permissionMode: 'roles',
openProdMock: true,
errorLog: ['prod'],
delWindowHeight: '210px',
tmpToken: 'tmp_token',
viteBasePath: './'
};
export default settings;

View File

@ -1,41 +0,0 @@
import type { RouterTypes } from '~/basic';
export declare const useBasicStore: import("pinia").StoreDefinition<"basic", {
token: string;
getUserInfo: boolean;
userInfo: {
username: string;
avatar: string;
};
allRoutes: RouterTypes;
buttonCodes: never[];
filterAsyncRoutes: never[];
roles: string[];
codes: number[];
cachedViews: string[];
cachedViewsDeep: string[];
sidebar: {
opened: boolean;
};
axiosPromiseArr: ObjKeys[];
settings: import("~/basic").SettingsConfig;
}, {}, {
setToken(data: any): void;
setFilterAsyncRoutes(routes: any): void;
setUserInfo({ userInfo, roles, codes }: {
userInfo: any;
roles: any;
codes: any;
}): void;
resetState(): void;
resetStateAndToLogin(): void;
M_settings(data: any): void;
setSidebarOpen(data: any): void;
setToggleSideBar(): void;
addCachedView(view: any): void;
delCachedView(view: any): void;
M_RESET_CACHED_VIEW(): void;
addCachedViewDeep(view: any): void;
setCacheViewDeep(view: any): void;
M_RESET_CACHED_VIEW_DEEP(): void;
A_sidebar_opened(data: any): void;
}>;

View File

@ -1,122 +0,0 @@
import { nextTick } from 'vue'
import { defineStore } from 'pinia'
import defaultSettings from '@/settings'
import router, { constantRoutes } from '@/router'
export const useBasicStore = defineStore('basic', {
state: () => {
return {
token: '',
getUserInfo: false,
userInfo: {
userName: '',
avatar: ''
},
allRoutes: [],
buttonCodes: [],
filterAsyncRoutes: [],
roles: [],
codes: [],
cachedViews: [],
cachedViewsDeep: [],
sidebar: { opened: true },
axiosPromiseArr: [],
settings: defaultSettings
}
},
persist: {
storage: localStorage,
paths: ['token']
},
actions: {
setToken(data) {
this.token = data
},
setFilterAsyncRoutes(routes) {
this.$patch((state) => {
state.filterAsyncRoutes = routes
state.allRoutes = constantRoutes.concat(routes)
})
},
setUserInfo({ userInfo, roles, codes }) {
const { username, avatar } = userInfo
this.$patch((state) => {
state.roles = roles
state.codes = codes
state.getUserInfo = true
state.userInfo.username = username
state.userInfo.avatar = avatar
})
},
resetState() {
this.$patch((state) => {
state.token = ''
state.roles = []
state.codes = []
state.allRoutes = []
state.buttonCodes = []
state.filterAsyncRoutes = []
state.userInfo.username = ''
state.userInfo.avatar = ''
})
this.getMyInfo = false
},
resetStateAndToLogin() {
this.resetState()
nextTick(() => {
router.push({ path: '/login' })
})
},
M_settings(data) {
this.$patch((state) => {
state.settings = { ...state.settings, ...data }
})
},
setSidebarOpen(data) {
this.$patch((state) => {
state.sidebar.opened = data
})
},
setToggleSideBar() {
this.$patch((state) => {
state.sidebar.opened = !state.sidebar.opened
})
},
addCachedView(view) {
this.$patch((state) => {
if (state.cachedViews.includes(view)) return
state.cachedViews.push(view)
})
},
delCachedView(view) {
this.$patch((state) => {
const index = state.cachedViews.indexOf(view)
index > -1 && state.cachedViews.splice(index, 1)
})
},
M_RESET_CACHED_VIEW() {
this.$patch((state) => {
state.cachedViews = []
})
},
addCachedViewDeep(view) {
this.$patch((state) => {
if (state.cachedViewsDeep.includes(view)) return
state.cachedViewsDeep.push(view)
})
},
setCacheViewDeep(view) {
this.$patch((state) => {
const index = state.cachedViewsDeep.indexOf(view)
index > -1 && state.cachedViewsDeep.splice(index, 1)
})
},
M_RESET_CACHED_VIEW_DEEP() {
this.$patch((state) => {
state.cachedViewsDeep = []
})
},
A_sidebar_opened(data) {
this.setSidebarOpen(data)
}
}
})

View File

@ -1,8 +0,0 @@
export declare const useTagsViewStore: import("pinia").StoreDefinition<"tagsView", {
visitedViews: never[];
}, {}, {
addVisitedView(view: any): void;
delVisitedView(view: any): Promise<unknown>;
delOthersVisitedViews(view: any): Promise<unknown>;
delAllVisitedViews(): Promise<unknown>;
}>;

View File

@ -1,59 +0,0 @@
import { defineStore } from 'pinia';
import setting from '@/settings';
export const useTagsViewStore = defineStore('tagsView', {
state: () => {
return {
visitedViews: []
};
},
actions: {
addVisitedView(view) {
this.$patch((state) => {
if (state.visitedViews.some((v) => v.path === view.path))
return;
if (state.visitedViews.length >= setting.tagsViewNum) {
state.visitedViews.pop();
state.visitedViews.push(Object.assign({}, view, {
title: view.meta.title || 'no-name'
}));
}
else {
state.visitedViews.push(Object.assign({}, view, {
title: view.meta.title || 'no-name'
}));
}
});
},
delVisitedView(view) {
return new Promise((resolve) => {
this.$patch((state) => {
for (const [i, v] of state.visitedViews.entries()) {
if (v.path === view.path) {
state.visitedViews.splice(i, 1);
break;
}
}
resolve([...state.visitedViews]);
});
});
},
delOthersVisitedViews(view) {
return new Promise((resolve) => {
this.$patch((state) => {
state.visitedViews = state.visitedViews.filter((v) => {
return v.meta.affix || v.path === view.path;
});
resolve([...state.visitedViews]);
});
});
},
delAllVisitedViews() {
return new Promise((resolve) => {
this.$patch((state) => {
state.visitedViews = state.visitedViews.filter((tag) => tag.meta?.affix);
resolve([...state.visitedViews]);
});
});
}
}
});

View File

@ -1 +0,0 @@
export default function request(config: any): import('axios').AxiosPromise<any>

View File

@ -1,56 +0,0 @@
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useBasicStore } from '@/store/basic'
const service = axios.create()
service.interceptors.request.use(
(req) => {
const { token, axiosPromiseArr } = useBasicStore()
req.cancelToken = new axios.CancelToken((cancel) => {
axiosPromiseArr.push({
url: req.url,
cancel
})
})
req.headers['AUTHORIZE_TOKEN'] = token
if ('get'.includes(req.method?.toLowerCase())) req.params = req.data
return req
},
(err) => {
Promise.reject(err)
}
)
service.interceptors.response.use(
(res) => {
const { code } = res.data
const successCode = '0,200,20000'
const noAuthCode = '401,403'
if (successCode.includes(code)) {
return res.data
} else {
if (noAuthCode.includes(code) && !location.href.includes('/login')) {
ElMessageBox.confirm('请重新登录', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
useBasicStore().resetStateAndToLogin()
})
}
return Promise.reject(res.data)
}
},
(err) => {
ElMessage.error({
message: err,
duration: 2 * 1000
})
return Promise.reject(err)
}
)
export default function request(config) {
return service({
baseURL: import.meta.env.VITE_APP_BASE_URL,
timeout: 8000,
...config
})
}

View File

@ -1,2 +0,0 @@
declare const _default: import("mitt").Emitter<Record<import("mitt").EventType, unknown>>;
export default _default;

View File

@ -1,2 +0,0 @@
import mitt from 'mitt';
export default mitt();

View File

@ -1,16 +0,0 @@
declare const _default: {
getWeek(): string;
mobilePhone(str: any): boolean;
toSplitNumFor(num: any, numToSpace: any): any;
bankCardNo(str: any): boolean;
regEmail(str: any): boolean;
idCardNumber(str: any): boolean;
deleteArrItem(arr: any, arrItem: any): void;
arrToRepeat(arr: any): any;
deRepeatArr(seriesArr: any): unknown[];
byArrObjDeleteArrObj2(arrObj: any, arrObj2: any, objKey: any): any;
deleteArrObjByKey(arrObj: any, objKey: any, value: any): any;
findArrObjByKey(arrObj: any, objKey: any, value: any): any;
byArrObjFindArrObj2(arrObj: any, arrObj2: any, objKey: any): any[];
};
export default _default;

View File

@ -1,66 +0,0 @@
export default {
getWeek() {
return `星期${'日一二三四五六'.charAt(new Date().getDay())}`;
},
mobilePhone(str) {
const reg = /^0?1[0-9]{10}$/;
return reg.test(str);
},
toSplitNumFor(num, numToSpace) {
return num.replace(/(.{4})/g, '$1 ');
},
bankCardNo(str) {
const reg = /^\d{15,20}$/;
return reg.test(str);
},
regEmail(str) {
const reg = /^([a-zA-Z]|[0-9])(\w|-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
return reg.test(str);
},
idCardNumber(str) {
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
return reg.test(str);
},
deleteArrItem(arr, arrItem) {
arr.splice(arr.indexOf(arrItem), 1);
},
arrToRepeat(arr) {
return arr.filter((ele, index, thisArr) => {
return thisArr.indexOf(ele) === index;
});
},
deRepeatArr(seriesArr) {
return [...new Set(seriesArr)];
},
byArrObjDeleteArrObj2(arrObj, arrObj2, objKey) {
arrObj
.map((value) => {
return value[objKey];
})
.forEach((value2) => {
arrObj2.splice(arrObj2.findIndex((item) => item[objKey] === value2), 1);
});
return arrObj2;
},
deleteArrObjByKey(arrObj, objKey, value) {
arrObj.splice(arrObj.findIndex((item) => item[objKey] === value), 1);
return arrObj;
},
findArrObjByKey(arrObj, objKey, value) {
return arrObj[arrObj.findIndex((item) => item[objKey] == value)];
},
byArrObjFindArrObj2(arrObj, arrObj2, objKey) {
const arrObj3 = [];
arrObj
.map((value) => {
return value[objKey];
})
.forEach((value2) => {
const arrIndex = arrObj2.findIndex((item) => item[objKey] === value2);
if (arrIndex !== -1) {
arrObj3.push(arrObj2[arrIndex]);
}
});
return arrObj3;
}
};

View File

@ -1,2 +0,0 @@
declare const _default: import("vue").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -1,13 +0,0 @@
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
const route = useRoute();
const router = useRouter();
onBeforeMount(() => {
const { params, query } = route;
const { path } = params;
router.replace({ path: `/${path}`, query });
});
return () => <div> </div>;
}
});

View File

@ -90,6 +90,7 @@ declare global {
const useConfigStore: typeof import('../src/store/config')['useConfigStore']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useDebuggerStore: typeof import('../src/store/debuger')['useDebuggerStore']
const useElement: typeof import('../src/hooks/use-element')['useElement']
const useErrorLog: typeof import('../src/hooks/use-error-log')['useErrorLog']
const useLink: typeof import('vue-router')['useLink']