增加 系统管理模块

This commit is contained in:
wyy 2023-11-10 16:36:38 +08:00
parent a7d18b2d5e
commit 552513a534
30 changed files with 2237 additions and 4 deletions

View File

@ -21,6 +21,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "2.1.0",
"@smallwei/avue": "^3.2.22",
"@tinymce/tinymce-vue": "^5.1.0",
"axios": "1.3.4",
"big.js": "6.2.1",

70
src/crud/admin/form.js Normal file
View File

@ -0,0 +1,70 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '表单名称',
prop: 'formName',
search: true
}, {
label: '按钮文本',
prop: 'buttonName',
search: true
}, {
label: '提交次数',
prop: 'submitNum',
type: 'select',
dicData: [
{
label: '不做限制',
value: 0
}, {
label: '每个IP限填一次',
value: 1
}
]
}, {
label: '开启验证',
prop: 'needValidation',
type: 'select',
dicData: [
{
label: '不需要',
value: 0
}, {
label: '需要',
value: 1
}
]
}, {
label: '提交权限',
prop: 'submitPerm',
type: 'select',
dicData: [
{
label: '所有人',
value: 0
}, {
label: '仅会员可提交',
value: 1
}
]
}]
}

View File

@ -0,0 +1,47 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '轮播图片',
prop: 'imgUrl',
type: 'upload',
slot: true,
listType: 'picture-img'
}, {
label: '顺序',
prop: 'seq'
}, {
width: 150,
label: '状态',
prop: 'status',
search: true,
type: 'select',
dicData: [
{
label: '禁用',
value: 0
}, {
label: '正常',
value: 1
}
]
}]
}

52
src/crud/admin/message.js Normal file
View File

@ -0,0 +1,52 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: false,
indexLabel: '序号',
selection: true,
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '创建时间',
prop: 'createTime'
},
{
label: '姓名',
prop: 'userName',
search: true
}, {
label: '邮箱',
prop: 'email'
}, {
label: '联系方式',
prop: 'contact'
}, {
label: '审核',
prop: 'status',
search: true,
slot: true,
type: 'select',
dicData: [
{
label: '未审核',
value: 0
}, {
label: '审核通过',
value: 1
}
]
}]
}

77
src/crud/prod/prodComm.js Normal file
View File

@ -0,0 +1,77 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
align: 'center',
addBtn: false,
editBtn: false,
delBtn: false,
column: [
{
label: '商品名',
prop: 'prodName',
search: true
},
{
label: '用户昵称',
prop: 'nickName',
slot: true
},
{
label: '记录时间',
prop: 'recTime',
width: '200'
},
{
label: '回复时间',
slot: true,
prop: 'replyTime',
width: '200',
dicData: [
{
label: '无',
value: ''
}
]
},
{
label: '评价得分',
prop: 'score'
},
{
label: '是否匿名',
prop: 'isAnonymous',
dicData: [
{
label: '否',
value: 0
}, {
label: '是',
value: 1
}
]
},
{
prop: 'status',
label: '审核状态',
search: true,
type: 'select',
dicData: [
{
label: '待审核',
value: 0
}, {
label: '审核通过',
value: 1
}, {
label: '审核未通过',
value: -1
}
]
}
]
}

59
src/crud/prod/prodList.js Normal file
View File

@ -0,0 +1,59 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '产品名字',
prop: 'prodName',
search: true
}, {
label: '商品原价',
prop: 'oriPrice'
}, {
label: '商品现价',
prop: 'price'
}, {
label: '商品库存',
prop: 'totalStocks'
}, {
label: '产品图片',
prop: 'pic',
type: 'upload',
width: 150,
listType: 'picture-img'
}, {
width: 150,
label: '状态',
prop: 'status',
search: true,
slot: true,
type: 'select',
dicData: [
{
label: '未上架',
value: 0
}, {
label: '上架',
value: 1
}
]
}]
}

46
src/crud/prod/prodTag.js Normal file
View File

@ -0,0 +1,46 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
align: 'center',
addBtn: false,
editBtn: false,
delBtn: false,
column: [
{
label: '标签名称',
prop: 'title',
search: true,
slot: true
},
{
label: '状态',
prop: 'status',
type: 'select',
slot: true,
search: true,
dicData: [
{
label: '禁用',
value: 0
}, {
label: '正常',
value: 1
}
]
},
{
label: '默认类型',
prop: 'isDfault',
slot: true
},
{
label: '排序',
prop: 'seq'
}
]
}

30
src/crud/prod/spec.js Normal file
View File

@ -0,0 +1,30 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
viewBtn: false,
delBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '属性名称',
prop: 'propName',
search: true
}, {
label: '属性值',
prop: 'prodPropValues',
slot: true
}]
}

View File

@ -0,0 +1,52 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: false,
selection: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
align: 'center',
addBtn: false,
editBtn: false,
delBtn: false,
column: [
{
label: '热搜标题',
prop: 'title',
search: true
},
{
label: '热搜内容',
prop: 'content',
search: true
},
{
label: '录入时间',
prop: 'recDate',
sortable: true
},
{
label: '顺序',
prop: 'seq',
sortable: true
},
{
label: '启用状态',
prop: 'status',
type: 'select',
slot: true,
search: true,
dicData: [
{
label: '未启用',
value: 0
}, {
label: '启用',
value: 1
}
]
}
]
}

52
src/crud/shop/notice.js Normal file
View File

@ -0,0 +1,52 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
align: 'center',
addBtn: false,
editBtn: false,
delBtn: false,
column: [
{
label: '公告内容',
prop: 'title',
search: true
},
{
label: '状态',
prop: 'status',
search: true,
slot: true,
type: 'select',
dicData: [
{
label: '撤销',
value: 0
}, {
label: '公布',
value: 1
}
]
},
{
label: '是否置顶',
prop: 'isTop',
search: true,
slot: true,
type: 'select',
dicData: [
{
label: '否',
value: 0
}, {
label: '是',
value: 1
}
]
}
]
}

43
src/crud/shop/pickAddr.js Normal file
View File

@ -0,0 +1,43 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: false,
selection: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
viewBtn: false,
delBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [
{
label: '自提点名称',
prop: 'addrName',
search: true
}, {
label: '手机号',
prop: 'mobile'
}, {
label: '省份',
prop: 'province'
}, {
label: '城市',
prop: 'city'
}, {
label: '区/县',
prop: 'area'
}, {
label: '地址',
prop: 'addr'
}]
}

View File

@ -0,0 +1,27 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: false,
selection: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '模板名称',
prop: 'transName',
search: true
}]
}

30
src/crud/sys/area.js Normal file
View File

@ -0,0 +1,30 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
align: 'center',
addBtn: false,
editBtn: false,
column: [
{
label: '',
prop: 'areaId'
},
{
label: '',
prop: 'areaName'
},
{
label: '',
prop: 'parentId'
},
{
label: '',
prop: 'level'
}
]
}

33
src/crud/sys/config.js Normal file
View File

@ -0,0 +1,33 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '参数名',
prop: 'paramKey',
search: true
}, {
label: '参数值',
prop: 'paramValue'
}, {
label: '备注',
prop: 'remark'
}]
}

47
src/crud/sys/log.js Normal file
View File

@ -0,0 +1,47 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
menu: false, // 移除操作栏
selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '用户名',
prop: 'username',
search: true
}, {
label: '用户操作',
prop: 'operation',
search: true
}, {
label: '请求方法',
prop: 'method'
}, {
label: '请求参数',
prop: 'params'
}, {
label: '执行时长(毫秒)',
prop: 'time'
}, {
label: 'IP地址',
prop: 'ip'
}, {
label: '创建时间',
prop: 'createDate'
}]
}

33
src/crud/sys/role.js Normal file
View File

@ -0,0 +1,33 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '角色名称',
prop: 'roleName',
search: true
}, {
label: '备注',
prop: 'remark'
}, {
label: '创建时间',
prop: 'createTime'
}]
}

50
src/crud/sys/user.js Normal file
View File

@ -0,0 +1,50 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '用户名',
prop: 'username',
search: true
}, {
label: '邮箱',
prop: 'email'
}, {
label: '手机号',
prop: 'mobile'
}, {
label: '创建时间',
prop: 'createTime'
}, {
label: '状态',
prop: 'status',
type: 'select',
dicData: [
{
label: '禁用',
value: 0
}, {
label: '正常',
value: 1
}
]
}]
}

80
src/crud/user/addr.js Normal file
View File

@ -0,0 +1,80 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
index: true,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
align: 'center',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
column: [
{
label: '收货人名称',
prop: 'receiver'
},
{
label: '省',
prop: 'province'
},
{
label: '城市',
prop: 'city'
},
{
label: '区',
prop: 'area'
},
{
label: '地址',
prop: 'addr'
},
{
label: '邮编',
prop: 'postCode'
},
{
label: '手机',
prop: 'mobile'
},
{
label: '状态',
prop: 'status',
search: true,
type: 'select',
dicData: [
{
label: '无效',
value: 0
}, {
label: '正常',
value: 1
}
]
},
{
label: '默认地址',
prop: 'commonAddr',
dicData: [
{
label: '否',
value: 0
}, {
label: '是',
value: 1
}
]
},
{
label: '建立时间',
prop: 'createTime'
},
{
label: '更新时间',
prop: 'updateTime'
}
]
}

53
src/crud/user/user.js Normal file
View File

@ -0,0 +1,53 @@
export const tableOption = {
searchMenuSpan: 6,
columnBtn: false,
border: true,
// selection: true,
index: false,
indexLabel: '序号',
stripe: true,
menuAlign: 'center',
menuWidth: 350,
align: 'center',
refreshBtn: true,
searchSize: 'mini',
addBtn: false,
editBtn: false,
delBtn: false,
viewBtn: false,
props: {
label: 'label',
value: 'value'
},
column: [{
label: '用户昵称',
prop: 'nickName',
search: true
}, {
label: '用户头像',
prop: 'pic',
type: 'upload',
imgWidth: 150,
listType: 'picture-img',
slot: true
}, {
label: '状态',
prop: 'status',
search: true,
type: 'select',
slot: true,
dicData: [
{
label: '禁用',
value: 0
}, {
label: '正常',
value: 1
}
]
}, {
label: '注册时间',
prop: 'userRegtime',
imgWidth: 150
}]
}

View File

@ -6,6 +6,10 @@ import moment from 'moment'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import router from '@/router'
import locale from 'element-plus/lib/locale/lang/zh-cn'
import Avue from '@smallwei/avue'
import '@smallwei/avue/lib/index.css'
// 全局样式
import '@/styles/index.scss'
// svg
@ -34,8 +38,10 @@ app.use(pinia)
app.component('SvgIcon', svgIcon)
// Avue
app.use(Avue)
// element-plus
app.use(ElementPlus)
app.use(ElementPlus, { locale })
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}

View File

@ -0,0 +1,147 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.areaId ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="100px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="地区名称"
prop="areaName"
>
<el-input
v-model="dataForm.areaName"
placeholder="请输入地区名称"
maxlength="50"
show-word-limit
/>
</el-form-item>
<el-form-item
label="上级地区"
prop="parentId"
>
<el-cascader
v-model="selectedOptions"
expand-trigger="hover"
:options="areaList"
:props="categoryTreeProps"
change-on-select
filterable
@change="handleChange"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button
@click="visible = false"
>
取消
</el-button>
<el-button
type="primary"
@click="onSubmit()"
>
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { treeDataTranslate } from '@/utils'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const dataRule = reactive({
areaName: [
{ required: true, message: '区域名称不能为空', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的区域名称', trigger: 'blur' }
]
})
const visible = ref(false)
const areaList = ref([])
const dataForm = ref({
areaId: '',
areaName: null,
parentId: null,
level: null
})
const categoryTreeProps = reactive({
value: 'areaId',
label: 'areaName'
})
const selectedOptions = ref([])
const dataFormRef = ref(null)
const init = (areaId) => {
dataForm.value.areaId = areaId || 0
visible.value = true
selectedOptions.value = []
nextTick(() => {
dataFormRef.value?.resetFields()
if (dataForm.value.areaId) {
http({
url: http.adornUrl('/admin/area/info/' + dataForm.value.areaId),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
dataForm.value = data
selectedOptions.value.push(dataForm.value.parentId)
categoryTreeProps.areaId = dataForm.value.areaId
categoryTreeProps.areaName = dataForm.value.areaName
})
}
http({
url: http.adornUrl('/admin/area/list'),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
areaList.value = treeDataTranslate(data, 'areaId', 'parentId')
})
})
}
defineExpose({ init })
const page = {
total: 0, //
currentPage: 1, //
pageSize: 20 //
}
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (valid) {
http({
url: http.adornUrl('/admin/area'),
method: dataForm.value.areaId ? 'put' : 'post',
data: http.adornData(dataForm.value)
}).then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList', page)
}
})
})
}
})
})
const handleChange = (val) => {
dataForm.value.parentId = val[val.length - 1]
}
</script>

View File

@ -0,0 +1,162 @@
<template>
<div class="mod-sys-area">
<el-input
v-model="areaName"
class="area-search-input"
placeholder="地区关键词"
/>
<el-button
type="primary"
class="area-add-btn"
@click="onAddOrUpdate()"
>
新增
</el-button>
<el-tree
ref="tree2Ref"
:data="areaTreeData"
node-key="areaId"
:filter-node-method="filterNode"
:props="props"
:expand-on-click-node="false"
>
<template #default="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
type="text"
icon="el-icon-edit"
@click="() => update(node, data)"
>
修改
</el-button>
<el-button
type="text"
icon="el-icon-delete"
@click="() => remove(node, data)"
>
删除
</el-button>
</span>
</template>
</el-tree>
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { ElMessage, ElMessageBox } from 'element-plus'
import AddOrUpdate from './add-or-update.vue'
import { treeDataTranslate } from '@/utils'
const areaName = ref('')
const props = {
id: 'areaId',
label: 'areaName',
children: 'children'
}
const tree2Ref = ref(null)
watch(
() => areaName,
(val) => {
tree2Ref.value?.filter(val)
}
)
onMounted(() => {
getDataList(page)
})
const page = reactive({
total: 0, //
currentPage: 1, //
pageSize: 10 //
})
const areaTreeData = ref(null)
const getDataList = (pageParam, params) => {
http({
url: http.adornUrl('/admin/area/list'),
method: 'get',
params: http.adornParams(Object.assign({
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
}, params))
}).then(({ data }) => {
areaTreeData.value = treeDataTranslate(data, 'areaId', 'parentId')
})
}
const addOrUpdateRef = ref(null)
const addOrUpdateVisible = ref(false)
/**
* 新增 / 修改
* @param id
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 删除
* @param areaId
*/
const onDelete = (areaId) => {
ElMessageBox.confirm('确定进行删除操作?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
http({
url: http.adornUrl('/admin/area/' + areaId),
method: 'delete',
data: http.adornData({})
}).then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList(page)
}
})
})
}).catch(() => { })
}
// eslint-disable-next-line no-unused-vars
const update = (node, data) => {
onAddOrUpdate(data.areaId)
}
// eslint-disable-next-line no-unused-vars
const remove = (node, data) => {
onDelete(data.areaId)
}
const filterNode = (value, data) => {
if (!value) return true
return data.areaName.indexOf(value) !== -1
}
</script>
<style lang="scss" scoped>
.mod-sys-area {
.area-search-input {
width: 30%;
}
.area-add-btn {
float: right;
}
}
</style>

View File

@ -0,0 +1,124 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="参数名"
prop="paramKey"
>
<el-input
v-model="dataForm.paramKey"
placeholder="参数名"
/>
</el-form-item>
<el-form-item
label="参数值"
prop="paramValue"
>
<el-input
v-model="dataForm.paramValue"
placeholder="参数值"
/>
</el-form-item>
<el-form-item
label="备注"
prop="remark"
>
<el-input
v-model="dataForm.remark"
placeholder="备注"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const visible = ref(false)
const dataForm = reactive({
id: 0,
paramKey: '',
paramValue: '',
remark: ''
})
const dataRule = {
paramKey: [
{ required: true, message: '参数名不能为空', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的参数名', trigger: 'blur' }
],
paramValue: [
{ required: true, message: '参数值不能为空', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的参数值', trigger: 'blur' }
]
}
const dataFormRef = ref(null)
const init = (id) => {
dataForm.id = id || 0
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
if (dataForm.id) {
http({
url: http.adornUrl(`/sys/config/info/${dataForm.id}`),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
dataForm.paramKey = data.paramKey
dataForm.paramValue = data.paramValue
dataForm.remark = data.remark
})
}
})
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (valid) {
http({
url: http.adornUrl('/sys/config'),
method: dataForm.id ? 'put' : 'post',
data: http.adornData({
id: dataForm.id || undefined,
paramKey: dataForm.paramKey,
paramValue: dataForm.paramValue,
remark: dataForm.remark
})
}).then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList')
}
})
})
}
})
})
</script>

View File

@ -0,0 +1,144 @@
<template>
<div class="mod-config">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:option="tableOption"
@search-change="onSearch"
@selection-change="selectionChange"
@on-load="getDataList"
>
<template #menu-left>
<el-button
type="primary"
icon="el-icon-plus"
@click.stop="onAddOrUpdate()"
>
新增
</el-button>
<el-button
type="danger"
:disabled="dataListSelections.length <= 0"
@click="onDelete()"
>
批量删除
</el-button>
</template>
<template #menu="scope">
<el-button
type="primary"
icon="el-icon-edit"
@click.stop="onAddOrUpdate(scope.row.id)"
>
编辑
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
@click.stop="onDelete(scope.row.id)"
>
删除
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { ElMessage, ElMessageBox } from 'element-plus'
import { tableOption } from '@/crud/sys/config.js'
const dataList = ref([])
const page = reactive({
total: 0, //
currentPage: 1, //
pageSize: 10 //
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
http({
url: http.adornUrl('/sys/config/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
},
params
)
)
})
.then(({ data }) => {
dataList.value = data.records
page.total = data.total
if (done) done()
})
}
/**
* 条件查询
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
const dataListSelections = ref([])
/**
* 多选变化
*/
const selectionChange = (val) => {
dataListSelections.value = val
}
const addOrUpdateVisible = ref(false)
const addOrUpdate = ref(null)
/**
* 新增 / 修改
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdate.value?.init(id)
})
}
/**
* 删除
*/
const onDelete = (id) => {
const ids = id ? [id] : dataListSelections.value?.map(item => {
return item.id
})
ElMessageBox.confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
http({
url: http.adornUrl('/sys/config'),
method: 'delete',
data: http.adornData(ids, false)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList()
}
})
})
}).catch(() => { })
}
</script>

View File

@ -0,0 +1,57 @@
<template>
<div class="mod-log">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:option="tableOption"
@search-change="onSearch"
@on-load="getDataList"
/>
</div>
</template>
<script setup>
import { tableOption } from '@/crud/sys/log.js'
const dataList = ref([])
const page = reactive({
total: 0, //
currentPage: 1, //
pageSize: 10 //
})
onMounted(() => {
getDataList()
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
http({
url: http.adornUrl('/sys/log/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
},
params
)
)
})
.then(({ data }) => {
dataList.value = data.records
page.total = data.total
if (done) done()
})
}
/**
* 条件查询
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
</script>

View File

@ -197,7 +197,9 @@ onMounted(() => {
onLoadIcons()
})
const iconList = []
//
/**
* 加载图标
*/
const onLoadIcons = () => {
const icons = import.meta.glob('@/icons/svg/*.svg')
for (const icon in icons) {
@ -251,11 +253,16 @@ defineExpose({ init })
const handleSelectMenuChange = (val) => {
dataForm.parentId = val[val.length - 1]
}
//
/**
* 图标选中
* @param iconName
*/
const iconActiveHandle = (iconName) => {
dataForm.icon = iconName
}
//
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (valid) {

View File

@ -0,0 +1,151 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="角色名称"
prop="roleName"
>
<el-input
v-model="dataForm.roleName"
placeholder="角色名称"
/>
</el-form-item>
<el-form-item
label="备注"
prop="remark"
>
<el-input
v-model="dataForm.remark"
placeholder="备注"
/>
</el-form-item>
<el-form-item label="授权">
<el-tree
ref="menuListTreeRef"
:data="menuList"
:props="menuListTreeProps"
node-key="menuId"
show-checkbox
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { treeDataTranslate } from '@/utils'
import { Debounce } from '@/utils/debounce'
const emit = defineEmits(['refreshDataList'])
const tempKey = -666666 // key) tree. #
const visible = ref(false)
const menuList = ref([])
const menuListTreeProps = {
label: 'name',
children: 'children'
}
const dataForm = reactive({
id: 0,
roleName: '',
remark: ''
})
const dataRule = reactive({
roleName: [
{ required: true, message: '角色名称不能为空', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的角色名称', trigger: 'blur' }
],
remark: [
{ required: false, pattern: /\s\S+|S+\s|\S/, message: '输入格式有误', trigger: 'blur' }
]
})
const dataFormRef = ref(null)
const menuListTreeRef = ref(null)
const init = (id) => {
dataForm.id = id || 0
http({
url: http.adornUrl('/sys/menu/table'),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
menuList.value = treeDataTranslate(data, 'menuId', 'parentId')
})
.then(() => {
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
menuListTreeRef.value?.setCheckedKeys([])
})
})
.then(() => {
if (dataForm.id) {
http({
url: http.adornUrl(`/sys/role/info/${dataForm.id}`),
method: 'get',
params: http.adornParams()
})
.then(({ data }) => {
dataForm.roleName = data.roleName
dataForm.remark = data.remark
const idx = data.menuIdList.indexOf(tempKey)
if (idx !== -1) {
data.menuIdList.splice(idx, data.menuIdList.length - idx)
}
menuListTreeRef.value?.setCheckedKeys(data.menuIdList)
})
}
})
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (valid) {
http({
url: http.adornUrl('/sys/role'),
method: dataForm.id ? 'put' : 'post',
data: http.adornData({
roleId: dataForm.id || undefined,
roleName: dataForm.roleName,
remark: dataForm.remark,
menuIdList: [].concat(menuListTreeRef.value?.getCheckedKeys(), [tempKey], menuListTreeRef.value?.getHalfCheckedKeys())
})
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList')
}
})
})
}
})
})
</script>

View File

@ -0,0 +1,153 @@
<template>
<div class="mod-role">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:option="tableOption"
@search-change="onSearch"
@selection-change="selectionChange"
@on-load="getDataList"
>
<template #menu-left>
<el-button
v-if="isAuth('sys:role:save')"
type="primary"
icon="el-icon-plus"
@click.stop="onAddOrUpdate()"
>
新增
</el-button>
<el-button
v-if="isAuth('sys:role:delete')"
type="danger"
:disabled="dataListSelections.length <= 0"
@click="onDelete()"
>
批量删除
</el-button>
</template>
<template #menu="scope">
<el-button
v-if="isAuth('sys:role:update')"
type="primary"
icon="el-icon-edit"
@click.stop="onAddOrUpdate(scope.row.roleId)"
>
编辑
</el-button>
<el-button
v-if="isAuth('sys:role:delete')"
type="danger"
icon="el-icon-delete"
@click.stop="onDelete(scope.row.roleId)"
>
删除
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage, ElMessageBox } from 'element-plus'
import { tableOption } from '@/crud/sys/role.js'
import AddOrUpdate from './add-or-update.vue'
const dataList = ref([])
const dataListSelections = ref([])
const page = reactive({
total: 0, //
currentPage: 1, //
pageSize: 10 //
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
http({
url: http.adornUrl('/sys/role/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
},
params
)
)
})
.then(({ data }) => {
dataList.value = data.records
page.total = data.total
if (done) {
done()
}
})
}
/**
* 条件查询
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
/**
* 多选变化
*/
const selectionChange = (val) => {
dataListSelections.value = val
}
const addOrUpdateVisible = ref(false)
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 删除
*/
const onDelete = (id) => {
const ids = id ? [id] : dataListSelections.value?.map(item => {
return item.roleId
})
ElMessageBox.confirm(`确定进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
http({
url: http.adornUrl('/sys/role'),
method: 'delete',
data: http.adornData(ids, false)
})
.then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList()
}
})
})
}).catch(() => { })
}
</script>

View File

@ -0,0 +1,245 @@
<template>
<el-dialog
v-model="visible"
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
>
<el-form
ref="dataFormRef"
:model="dataForm"
:rules="dataRule"
label-width="80px"
@keyup.enter="onSubmit()"
>
<el-form-item
label="用户名"
prop="userName"
>
<el-input
v-model="dataForm.userName"
placeholder="登录帐号"
/>
</el-form-item>
<el-form-item
label="密码"
prop="password"
:class="{ 'is-required': !dataForm.id }"
>
<el-input
v-model="dataForm.password"
type="password"
placeholder="密码"
/>
</el-form-item>
<el-form-item
label="确认密码"
prop="comfirmPassword"
:class="{ 'is-required': !dataForm.id }"
>
<el-input
v-model="dataForm.comfirmPassword"
type="password"
placeholder="确认密码"
/>
</el-form-item>
<el-form-item
label="邮箱"
prop="email"
>
<el-input
v-model="dataForm.email"
placeholder="邮箱"
/>
</el-form-item>
<el-form-item
label="手机号"
prop="mobile"
>
<el-input
v-model="dataForm.mobile"
maxlength="11"
placeholder="手机号"
/>
</el-form-item>
<el-form-item
label="角色"
prop="roleIdList"
>
<el-checkbox-group v-model="dataForm.roleIdList">
<el-checkbox
v-for="role in roleList"
:key="role.roleId"
:label="role.roleId"
>
{{ role.roleName }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item
label="状态"
prop="status"
>
<el-radio-group v-model="dataForm.status">
<el-radio :label="0">
禁用
</el-radio>
<el-radio :label="1">
正常
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button
type="primary"
@click="onSubmit()"
>确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { isEmail, isMobile } from '@/utils/validate'
import { Debounce } from '@/utils/debounce'
import { encrypt } from '@/utils/crypto'
const emit = defineEmits(['refreshDataList'])
const visible = ref(false)
const dataForm = reactive({
id: 0,
userName: '',
password: '',
comfirmPassword: '',
email: '',
mobile: '',
roleIdList: [],
status: 1
})
// eslint-disable-next-line no-unused-vars
const validatePassword = (rule, value, callback) => {
if (!dataForm.id && !/\S/.test(value)) {
callback(new Error('密码不能为空'))
} else {
callback()
}
}
// eslint-disable-next-line no-unused-vars
const validateComfirmPassword = (rule, value, callback) => {
if (!dataForm.id && !/\S/.test(value)) {
dataForm.password = ''
callback(new Error('确认密码不能为空'))
} else if (dataForm.password !== value) {
callback(new Error('确认密码与密码输入不一致'))
} else {
callback()
}
}
// eslint-disable-next-line no-unused-vars
const validateEmail = (rule, value, callback) => {
if (!isEmail(value)) {
callback(new Error('邮箱格式错误'))
} else {
callback()
}
}
// eslint-disable-next-line no-unused-vars
const validateMobile = (rule, value, callback) => {
if (!isMobile(value)) {
callback(new Error('手机号格式错误'))
} else {
callback()
}
}
const dataRule = {
userName: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ pattern: /\s\S+|S+\s|\S/, message: '请输入正确的用户名', trigger: 'blur' }
],
password: [
{ validator: validatePassword, trigger: 'blur' }
],
comfirmPassword: [
{ validator: validateComfirmPassword, trigger: 'blur' }
],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ validator: validateEmail, trigger: 'blur' }
],
mobile: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ validator: validateMobile, trigger: 'blur' }
]
}
const roleList = ref([])
const dataFormRef = ref(null)
const init = (id) => {
dataForm.id = id || 0
http({
url: http.adornUrl('/sys/role/list'),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
roleList.value = data
}).then(() => {
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
})
}).then(() => {
if (dataForm.id) {
http({
url: http.adornUrl(`/sys/user/info/${dataForm.id}`),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
dataForm.userName = data.username
dataForm.email = data.email
dataForm.mobile = data.mobile
dataForm.roleIdList = data.roleIdList
dataForm.status = data.status
})
}
})
}
defineExpose({ init })
/**
* 表单提交
*/
const onSubmit = Debounce(() => {
dataFormRef.value?.validate((valid) => {
if (valid) {
http({
url: http.adornUrl('/sys/user'),
method: dataForm.id ? 'put' : 'post',
data: http.adornData({
userId: dataForm.id || undefined,
username: dataForm.userName,
password: encrypt(dataForm.password),
email: dataForm.email,
mobile: dataForm.mobile,
status: dataForm.status,
roleIdList: dataForm.roleIdList
})
}).then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
visible.value = false
emit('refreshDataList')
}
})
})
}
})
})
</script>

View File

@ -0,0 +1,155 @@
<template>
<div class="mod-user">
<avue-crud
ref="crudRef"
:page="page"
:data="dataList"
:option="tableOption"
@search-change="onSearch"
@selection-change="selectionChange"
@on-load="getDataList"
>
<template #menu-left>
<el-button
v-if="isAuth('sys:user:save')"
type="primary"
icon="el-icon-plus"
@click.stop="onAddOrUpdate()"
>
新增
</el-button>
<el-button
v-if="isAuth('sys:user:delete')"
type="danger"
:disabled="dataListSelections.length <= 0"
@click="onDelete()"
>
批量删除
</el-button>
</template>
<template
#menu="scope"
>
<el-button
v-if="isAuth('sys:user:update')"
type="primary"
icon="el-icon-edit"
@click.stop="onAddOrUpdate(scope.row.userId)"
>
编辑
</el-button>
<el-button
v-if="isAuth('sys:user:delete')"
type="danger"
icon="el-icon-delete"
@click.stop="onDelete(scope.row.userId)"
>
删除
</el-button>
</template>
</avue-crud>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdateRef"
@refresh-data-list="getDataList"
/>
</div>
</template>
<script setup>
import { isAuth } from '@/utils'
import { ElMessage, ElMessageBox } from 'element-plus'
import { tableOption } from '@/crud/sys/user.js'
import AddOrUpdate from './add-or-update.vue'
const dataList = ref([])
const dataListLoading = ref(false)
const dataListSelections = ref([])
const addOrUpdateVisible = ref(false)
const page = reactive({
total: 0, //
currentPage: 1, //
pageSize: 10 //
})
/**
* 获取数据列表
*/
const getDataList = (pageParam, params, done) => {
dataListLoading.value = true
http({
url: http.adornUrl('/sys/user/page'),
method: 'get',
params: http.adornParams(
Object.assign(
{
current: pageParam == null ? page.currentPage : pageParam.currentPage,
size: pageParam == null ? page.pageSize : pageParam.pageSize
},
params
)
)
}).then(({ data }) => {
dataList.value = data.records
page.total = data.total
dataListLoading.value = false
if (done) done()
})
}
/**
* 条件查询
*/
const onSearch = (params, done) => {
getDataList(page, params, done)
}
/**
* 多选变化
*/
const selectionChange = (val) => {
dataListSelections.value = val
}
const addOrUpdateRef = ref(null)
/**
* 新增 / 修改
*/
const onAddOrUpdate = (id) => {
addOrUpdateVisible.value = true
nextTick(() => {
addOrUpdateRef.value?.init(id)
})
}
/**
* 删除
*/
const onDelete = (id) => {
const userIds = id ? [id] : dataListSelections.value?.map(item => {
return item.userId
})
ElMessageBox.confirm(`确定对[id=${userIds.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
http({
url: http.adornUrl('/sys/user'),
method: 'delete',
data: http.adornData(userIds, false)
}).then(() => {
ElMessage({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
getDataList()
}
})
})
}).catch(() => { })
}
</script>