init: 橘视厅酒店版会员系统前端
This commit is contained in:
219
src/views/modules/sys/api-jst-call-log.vue
Executable file
219
src/views/modules/sys/api-jst-call-log.vue
Executable file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="mod-api-jst-call-log">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()">
|
||||
<el-form-item label="设备 MAC">
|
||||
<el-input v-model="dataForm.mac" placeholder="MAC 模糊" clearable style="width: 160px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间区间">
|
||||
<el-date-picker
|
||||
v-model="createdAtRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:default-time="createdAtRangeDefaultTime"
|
||||
unlink-panels
|
||||
clearable
|
||||
style="width: 380px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口路径">
|
||||
<el-input v-model="dataForm.apiPath" placeholder="路径关键字,后端 LIKE 匹配" clearable style="width: 220px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求体">
|
||||
<el-input v-model="dataForm.requestBody" placeholder="请求体关键字,后端 LIKE 匹配" clearable style="width: 240px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-page-wrap">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
prop="mac"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
:show-overflow-tooltip="true"
|
||||
label="设备 MAC"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="deviceSn"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="140"
|
||||
:show-overflow-tooltip="true"
|
||||
label="序列号"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="deviceUid"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="120"
|
||||
:show-overflow-tooltip="true"
|
||||
label="设备 UID"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="170"
|
||||
label="创建时间"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="apiPath"
|
||||
header-align="center"
|
||||
align="left"
|
||||
min-width="200"
|
||||
:show-overflow-tooltip="true"
|
||||
label="接口路径"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="requestBody"
|
||||
header-align="center"
|
||||
align="left"
|
||||
min-width="220"
|
||||
:show-overflow-tooltip="true"
|
||||
label="请求体 requestBody"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="responseBody"
|
||||
header-align="center"
|
||||
align="left"
|
||||
min-width="220"
|
||||
:show-overflow-tooltip="true"
|
||||
label="请求结果"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="errorMessage"
|
||||
header-align="center"
|
||||
align="left"
|
||||
min-width="220"
|
||||
:show-overflow-tooltip="true"
|
||||
label="错误信息"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="storeOrgName"
|
||||
header-align="center"
|
||||
align="center"
|
||||
min-width="140"
|
||||
:show-overflow-tooltip="true"
|
||||
label="组织名称"
|
||||
/>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { getApiJstCallLogList } from '@/api/sys'
|
||||
import type { ApiJstCallLogInfo } from '@/api/sys'
|
||||
import type { PageParams, PageResult } from '@/types/api'
|
||||
|
||||
interface DataForm {
|
||||
mac: string
|
||||
apiPath: string
|
||||
requestBody: string
|
||||
}
|
||||
|
||||
type JstLogListResponsePayload = {
|
||||
data?: { page?: PageResult<ApiJstCallLogInfo> } & Partial<PageResult<ApiJstCallLogInfo>>
|
||||
page?: PageResult<ApiJstCallLogInfo>
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
mac: '',
|
||||
apiPath: '',
|
||||
requestBody: ''
|
||||
})
|
||||
|
||||
/** 选日期后默认起始 00:00:00、结束 23:59:59,避免只选同一天时区间过窄 */
|
||||
const createdAtRangeDefaultTime: [Date, Date] = [
|
||||
new Date(2000, 0, 1, 0, 0, 0),
|
||||
new Date(2000, 0, 1, 23, 59, 59)
|
||||
]
|
||||
|
||||
const createdAtRange = ref<[string, string] | null>(null)
|
||||
const dataList = ref<ApiJstCallLogInfo[]>([])
|
||||
const pageIndex = ref<number>(1)
|
||||
const pageSize = ref<number>(10)
|
||||
const totalPage = ref<number>(0)
|
||||
const dataListLoading = ref<boolean>(false)
|
||||
|
||||
/**
|
||||
* GET /sys/apiJstCallLog/list 查询参数:
|
||||
* - mac(模糊由后端 LIKE)
|
||||
* - beginTime / endTime:创建时间闭区间(与 audit-log 一致)
|
||||
* - apiPath:路径模糊(由后端 LIKE)
|
||||
* - requestBody:请求体模糊(由后端 LIKE)
|
||||
*/
|
||||
function buildListParams (): PageParams {
|
||||
const p: PageParams = { page: pageIndex.value, limit: pageSize.value }
|
||||
const t = (s: string | null | undefined): string => (s != null && String(s).trim() !== '' ? String(s).trim() : '')
|
||||
const mac = t(dataForm.mac)
|
||||
if (mac) p.mac = mac
|
||||
const apiPath = t(dataForm.apiPath)
|
||||
if (apiPath) p.apiPath = apiPath
|
||||
const requestBody = t(dataForm.requestBody)
|
||||
if (requestBody) p.requestBody = requestBody
|
||||
const r = createdAtRange.value
|
||||
if (Array.isArray(r) && r.length === 2 && r[0] && r[1]) {
|
||||
p.beginTime = r[0]
|
||||
p.endTime = r[1]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
function getDataList (): void {
|
||||
dataListLoading.value = true
|
||||
getApiJstCallLogList(buildListParams()).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
const raw = data as JstLogListResponsePayload
|
||||
const page = raw.data?.page ?? raw.page ?? raw.data
|
||||
if (page && (Array.isArray(page.list) || page.totalCount != null)) {
|
||||
dataList.value = page.list || []
|
||||
totalPage.value = page.totalCount || 0
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
}
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
}
|
||||
dataListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function sizeChangeHandle (val: number): void {
|
||||
pageSize.value = val
|
||||
pageIndex.value = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function currentChangeHandle (val: number): void {
|
||||
pageIndex.value = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
211
src/views/modules/sys/audit-log.vue
Executable file
211
src/views/modules/sys/audit-log.vue
Executable file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="mod-audit-log">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()">
|
||||
<el-form-item label="操作者">
|
||||
<el-input v-model="dataForm.actorName" placeholder="按用户名模糊" clearable style="width: 180px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作行为">
|
||||
<el-input v-model="dataForm.head" placeholder="按操作行为模糊" clearable style="width: 200px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口">
|
||||
<el-input v-model="dataForm.api" placeholder="类#方法 等" clearable style="width: 220px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="createdAtRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
clearable
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-page-wrap">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="id"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="80"
|
||||
label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="100"
|
||||
label="操作者">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="head"
|
||||
header-align="center"
|
||||
align="center"
|
||||
min-width="160"
|
||||
:show-overflow-tooltip="true"
|
||||
label="操作行为">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="170"
|
||||
label="创建时间">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="api"
|
||||
header-align="center"
|
||||
align="center"
|
||||
min-width="200"
|
||||
:show-overflow-tooltip="true"
|
||||
label="接口">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="param"
|
||||
header-align="center"
|
||||
align="left"
|
||||
min-width="200"
|
||||
:show-overflow-tooltip="true"
|
||||
label="参数">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="paramDesc"
|
||||
header-align="center"
|
||||
align="left"
|
||||
min-width="200"
|
||||
:show-overflow-tooltip="true"
|
||||
label="参数描述">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="result"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="100"
|
||||
:show-overflow-tooltip="true"
|
||||
label="结果">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="ip"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
:show-overflow-tooltip="true"
|
||||
label="IP">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="deviceInfo"
|
||||
header-align="center"
|
||||
align="left"
|
||||
min-width="180"
|
||||
:show-overflow-tooltip="true"
|
||||
label="客户端">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { getAuditLogList } from '@/api/sys'
|
||||
import type { AuditLogInfo } from '@/api/sys'
|
||||
import type { PageParams, PageResult } from '@/types/api'
|
||||
|
||||
interface DataForm {
|
||||
actorName: string
|
||||
head: string
|
||||
api: string
|
||||
}
|
||||
|
||||
/** 列表接口可能返回根级 page,或包在 data / data.page 下 */
|
||||
type AuditListResponsePayload = {
|
||||
data?: { page?: PageResult<AuditLogInfo> } & Partial<PageResult<AuditLogInfo>>
|
||||
page?: PageResult<AuditLogInfo>
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
actorName: '',
|
||||
head: '',
|
||||
api: ''
|
||||
})
|
||||
|
||||
const createdAtRange = ref<[string, string] | null>(null)
|
||||
const dataList = ref<AuditLogInfo[]>([])
|
||||
const pageIndex = ref<number>(1)
|
||||
const pageSize = ref<number>(10)
|
||||
const totalPage = ref<number>(0)
|
||||
const dataListLoading = ref<boolean>(false)
|
||||
|
||||
function buildListParams (): PageParams {
|
||||
const p: PageParams = { page: pageIndex.value, limit: pageSize.value }
|
||||
const t = (s: string | null | undefined): string => (s != null && String(s).trim() !== '' ? String(s).trim() : '')
|
||||
const actor = t(dataForm.actorName)
|
||||
if (actor) p.actorName = actor
|
||||
const head = t(dataForm.head)
|
||||
if (head) p.head = head
|
||||
const api = t(dataForm.api)
|
||||
if (api) p.api = api
|
||||
const r = createdAtRange.value
|
||||
if (Array.isArray(r) && r.length === 2 && r[0] && r[1]) {
|
||||
p.beginTime = r[0]
|
||||
p.endTime = r[1]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
function getDataList (): void {
|
||||
dataListLoading.value = true
|
||||
getAuditLogList(buildListParams()).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
// 兼容:{ code, page };或 { code, data: { page } };或 data 本体即分页字段
|
||||
const raw = data as AuditListResponsePayload
|
||||
const page = raw.data?.page ?? raw.page ?? raw.data
|
||||
if (page && (Array.isArray(page.list) || page.totalCount != null)) {
|
||||
dataList.value = page.list || []
|
||||
totalPage.value = page.totalCount || 0
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
}
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
}
|
||||
dataListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function sizeChangeHandle (val: number): void {
|
||||
pageSize.value = val
|
||||
pageIndex.value = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function currentChangeHandle (val: number): void {
|
||||
pageIndex.value = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
108
src/views/modules/sys/components/config-add-or-update.vue
Executable file
108
src/views/modules/sys/components/config-add-or-update.vue
Executable file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="!dataForm.id ? '新增' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
v-model="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataFormRef" @keyup.enter="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="参数名" prop="paramKey">
|
||||
<el-input v-model="dataForm.paramKey" placeholder="参数名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="参数值" prop="paramValue">
|
||||
<el-input v-model="dataForm.paramValue" placeholder="参数值"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, nextTick, useTemplateRef } from 'vue'
|
||||
import { getConfigInfo, saveConfig } from '@/api/sys'
|
||||
import { ElMessage, type FormInstance, type FormItemRule } from 'element-plus'
|
||||
|
||||
const emit = defineEmits<{
|
||||
refreshDataList: []
|
||||
}>()
|
||||
|
||||
const visible = ref<boolean>(false)
|
||||
|
||||
interface DataForm {
|
||||
id: number | null
|
||||
paramKey: string
|
||||
paramValue: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
id: null,
|
||||
paramKey: '',
|
||||
paramValue: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const dataRule: Record<string, FormItemRule[]> = {
|
||||
paramKey: [
|
||||
{ required: true, message: '参数名不能为空', trigger: 'blur' }
|
||||
],
|
||||
paramValue: [
|
||||
{ required: true, message: '参数值不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const dataFormRef = useTemplateRef<FormInstance>('dataFormRef')
|
||||
|
||||
function init (id?: number): void {
|
||||
dataForm.id = id || null
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
if (dataForm.id) {
|
||||
getConfigInfo(dataForm.id).then((data) => {
|
||||
if (data && data.code === 0 && data.data) {
|
||||
const config = data.data
|
||||
dataForm.paramKey = config.paramKey
|
||||
dataForm.paramValue = config.paramValue
|
||||
dataForm.remark = config.remark || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 表单提交
|
||||
function dataFormSubmit (): void {
|
||||
dataFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
saveConfig({
|
||||
id: dataForm.id || undefined,
|
||||
paramKey: dataForm.paramKey,
|
||||
paramValue: dataForm.paramValue,
|
||||
remark: dataForm.remark
|
||||
}).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
315
src/views/modules/sys/components/menu-add-or-update.vue
Executable file
315
src/views/modules/sys/components/menu-add-or-update.vue
Executable file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="!dataForm.id ? '新增' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
v-model="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataFormRef" @keyup.enter="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-radio-group v-model="dataForm.type">
|
||||
<el-radio v-for="(type, index) in dataForm.typeList" :value="index" :key="index">{{ type }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="dataForm.typeList[dataForm.type] + '名称'" prop="name">
|
||||
<el-input v-model="dataForm.name" :placeholder="dataForm.typeList[dataForm.type] + '名称'"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="上级菜单" prop="parentName">
|
||||
<el-popover
|
||||
ref="menuListPopoverRef"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
:width="300">
|
||||
<template #reference>
|
||||
<el-input v-model="dataForm.parentName" readonly placeholder="点击选择上级菜单" class="menu-list__input"></el-input>
|
||||
</template>
|
||||
<el-tree
|
||||
:data="menuList"
|
||||
:props="menuListTreeProps"
|
||||
node-key="id"
|
||||
ref="menuListTreeRef"
|
||||
@current-change="menuListTreeCurrentChangeHandle"
|
||||
:default-expand-all="true"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false">
|
||||
</el-tree>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.type === 1" label="菜单路由" prop="url">
|
||||
<el-input v-model="dataForm.url" placeholder="菜单路由"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.type !== 0" label="授权标识" prop="perms">
|
||||
<el-input v-model="dataForm.perms" placeholder="多个用逗号分隔, 如: user:list,user:create"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.type !== 2" label="排序号" prop="orderNum">
|
||||
<el-input-number v-model="dataForm.orderNum" controls-position="right" :min="0" label="排序号"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.type !== 2" label="是否展示" prop="show">
|
||||
<el-radio-group v-model="dataForm.show">
|
||||
<el-radio :value="10">展示</el-radio>
|
||||
<el-radio :value="20">隐藏</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.type !== 2" label="菜单图标" prop="icon">
|
||||
<el-row>
|
||||
<el-col :span="22">
|
||||
<el-popover
|
||||
ref="iconListPopoverRef"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
popper-class="mod-menu__icon-popover"
|
||||
:width="458">
|
||||
<template #reference>
|
||||
<el-input :model-value="''" readonly placeholder="点击选择菜单图标" class="icon-list__input">
|
||||
<template v-if="dataForm.icon && iconMap[dataForm.icon as keyof typeof iconMap]" #prefix>
|
||||
<el-icon><component :is="iconMap[dataForm.icon as keyof typeof iconMap]" /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
<div class="mod-menu__icon-inner">
|
||||
<div class="mod-menu__icon-list">
|
||||
<el-button
|
||||
v-for="(iconName, index) in iconList"
|
||||
:key="index"
|
||||
@click="iconActiveHandle(iconName)"
|
||||
:class="{ 'is-active': iconName === dataForm.icon }">
|
||||
<el-icon :size="18">
|
||||
<component :is="iconMap[iconName as keyof typeof iconMap]" />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</el-col>
|
||||
<el-col :span="2" class="icon-list__tips">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>
|
||||
使用 Element Plus Icons,保存的值为图标组件名
|
||||
</template>
|
||||
<span class="icon-list__tips-icon">?</span>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, nextTick, useTemplateRef } from 'vue'
|
||||
import { getMenuSelect, getMenuInfo, saveMenu } from '@/api/sys'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import { treeDataTranslate } from '@/utils'
|
||||
import { ElMessage, type FormInstance, type FormItemRule, type TreeInstance } from 'element-plus'
|
||||
import type { MenuInfo } from '@/api/sys'
|
||||
|
||||
// Element Plus Icons 图标名列表(用于菜单图标选择)
|
||||
const iconNames = Object.keys(ElementPlusIconsVue).filter(
|
||||
key => key !== 'default' && (typeof ElementPlusIconsVue[key as keyof typeof ElementPlusIconsVue] === 'object' || typeof ElementPlusIconsVue[key as keyof typeof ElementPlusIconsVue] === 'function') && ElementPlusIconsVue[key as keyof typeof ElementPlusIconsVue] != null
|
||||
)
|
||||
|
||||
const iconList = iconNames
|
||||
const iconMap = ElementPlusIconsVue
|
||||
|
||||
const emit = defineEmits<{
|
||||
refreshDataList: []
|
||||
}>()
|
||||
|
||||
const visible = ref<boolean>(false)
|
||||
|
||||
interface DataForm {
|
||||
id: number | null
|
||||
type: number
|
||||
typeList: string[]
|
||||
name: string
|
||||
parentId: number
|
||||
parentName: string
|
||||
url: string
|
||||
perms: string
|
||||
orderNum: number
|
||||
icon: string
|
||||
show: number
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
id: null,
|
||||
type: 1,
|
||||
typeList: ['目录', '菜单', '按钮'],
|
||||
name: '',
|
||||
parentId: 0,
|
||||
parentName: '',
|
||||
url: '',
|
||||
perms: '',
|
||||
orderNum: 0,
|
||||
icon: '',
|
||||
show: 10
|
||||
})
|
||||
|
||||
const validateUrl: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
if (dataForm.type === 1 && !/\S/.test(value as string)) {
|
||||
callback(new Error('菜单URL不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const dataRule: Record<string, FormItemRule[]> = {
|
||||
name: [
|
||||
{ required: true, message: '菜单名称不能为空', trigger: 'blur' }
|
||||
],
|
||||
parentName: [
|
||||
{ required: true, message: '上级菜单不能为空', trigger: 'change' }
|
||||
],
|
||||
url: [
|
||||
{ validator: validateUrl, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const menuList = ref<any[]>([])
|
||||
const menuListTreeProps = {
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
const dataFormRef = useTemplateRef<FormInstance>('dataFormRef')
|
||||
const menuListTreeRef = useTemplateRef<TreeInstance>('menuListTreeRef')
|
||||
|
||||
function init (id?: number): void {
|
||||
dataForm.id = id || null
|
||||
getMenuSelect().then((data) => {
|
||||
if (!data || data.code !== 0) return
|
||||
const body = data as unknown as { data?: { menuList?: MenuInfo[] }; menuList?: MenuInfo[] }
|
||||
const rawList = body.data?.menuList ?? body.menuList ?? []
|
||||
menuList.value = treeDataTranslate(rawList, 'id')
|
||||
}).then(() => {
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
})
|
||||
}).then(() => {
|
||||
if (!dataForm.id) {
|
||||
// 新增
|
||||
menuListTreeSetCurrentNode()
|
||||
} else {
|
||||
// 修改
|
||||
getMenuInfo(dataForm.id).then((data) => {
|
||||
if (!data || data.code !== 0) return
|
||||
const body = data as unknown as { data?: MenuInfo; menu?: MenuInfo }
|
||||
const menu = body.data ?? body.menu
|
||||
if (menu && typeof menu === 'object') {
|
||||
dataForm.id = menu.id
|
||||
dataForm.type = menu.type ?? 1
|
||||
dataForm.name = menu.name
|
||||
dataForm.parentId = menu.parentId ?? 0
|
||||
dataForm.url = menu.url || ''
|
||||
dataForm.perms = menu.perms || ''
|
||||
dataForm.orderNum = menu.orderNum ?? 0
|
||||
dataForm.icon = menu.icon || ''
|
||||
const rawShow = menu.show != null ? menu.show : (menu as any).isShow
|
||||
dataForm.show = rawShow != null ? Number(rawShow) : 10
|
||||
menuListTreeSetCurrentNode()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 菜单树选中
|
||||
function menuListTreeCurrentChangeHandle (data: MenuInfo): void {
|
||||
dataForm.parentId = data.id
|
||||
dataForm.parentName = data.name
|
||||
}
|
||||
|
||||
// 菜单树设置当前选中节点
|
||||
function menuListTreeSetCurrentNode (): void {
|
||||
menuListTreeRef.value?.setCurrentKey(dataForm.parentId)
|
||||
const currentNode = menuListTreeRef.value?.getCurrentNode()
|
||||
dataForm.parentName = (currentNode as MenuInfo | undefined)?.name || ''
|
||||
}
|
||||
|
||||
// 图标选中
|
||||
function iconActiveHandle (iconName: string): void {
|
||||
dataForm.icon = iconName
|
||||
}
|
||||
|
||||
// 表单提交
|
||||
function dataFormSubmit (): void {
|
||||
dataFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
saveMenu({
|
||||
id: dataForm.id || undefined,
|
||||
type: dataForm.type,
|
||||
name: dataForm.name,
|
||||
parentId: dataForm.parentId,
|
||||
url: dataForm.url,
|
||||
perms: dataForm.perms,
|
||||
orderNum: dataForm.orderNum,
|
||||
icon: dataForm.icon,
|
||||
show: dataForm.show
|
||||
}).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mod-menu {
|
||||
.menu-list__input,
|
||||
.icon-list__input {
|
||||
> .el-input__inner {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&__icon-popover {
|
||||
width: 458px;
|
||||
overflow: hidden;
|
||||
}
|
||||
&__icon-inner {
|
||||
width: 478px;
|
||||
max-height: 258px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
&__icon-list {
|
||||
width: 458px;
|
||||
padding: 0;
|
||||
margin: -8px 0 0 -8px;
|
||||
> .el-button {
|
||||
padding: 8px;
|
||||
margin: 8px 0 0 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
> .el-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-list__tips {
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
color: #e6a23c;
|
||||
cursor: pointer;
|
||||
.icon-list__tips-icon { font-weight: bold; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
158
src/views/modules/sys/components/role-add-or-update.vue
Executable file
158
src/views/modules/sys/components/role-add-or-update.vue
Executable file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="!dataForm.id ? '新增' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
v-model="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataFormRef" @keyup.enter="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="角色名称" prop="roleName">
|
||||
<el-input v-model="dataForm.roleName" placeholder="角色名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item size="small" label="授权">
|
||||
<el-tree
|
||||
:data="menuList"
|
||||
:props="menuListTreeProps"
|
||||
node-key="id"
|
||||
ref="menuListTreeRef"
|
||||
:default-expand-all="true"
|
||||
show-checkbox>
|
||||
</el-tree>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, nextTick, useTemplateRef } from 'vue'
|
||||
import { getMenuList, getRoleInfo, saveRole } from '@/api/sys'
|
||||
import { ElMessage, type FormInstance, type FormItemRule, type TreeInstance } from 'element-plus'
|
||||
import { treeDataTranslate } from '@/utils'
|
||||
import type { RoleInfo } from '@/api/sys'
|
||||
|
||||
const emit = defineEmits<{
|
||||
refreshDataList: []
|
||||
}>()
|
||||
|
||||
const visible = ref<boolean>(false)
|
||||
const menuList = ref<any[]>([])
|
||||
const menuListTreeProps = {
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
interface DataForm {
|
||||
id: number | null
|
||||
roleName: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
id: null,
|
||||
roleName: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const dataRule: Record<string, FormItemRule[]> = {
|
||||
roleName: [
|
||||
{ required: true, message: '角色名称不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const tempKey = -666666 // 临时key, 用于解决tree半选中状态项不能传给后台接口问题. # 待优化
|
||||
|
||||
const dataFormRef = useTemplateRef<FormInstance>('dataFormRef')
|
||||
const menuListTreeRef = useTemplateRef<TreeInstance>('menuListTreeRef')
|
||||
|
||||
function init (id?: number): void {
|
||||
dataForm.id = id || null
|
||||
getMenuList().then((resp) => {
|
||||
if (!resp || resp.code !== 0) return
|
||||
const r = resp as unknown as Record<string, any>
|
||||
const dRaw = r.data
|
||||
const d =
|
||||
dRaw != null &&
|
||||
typeof dRaw === 'object' &&
|
||||
!Array.isArray(dRaw)
|
||||
? (dRaw as Record<string, any>)
|
||||
: ({} as Record<string, any>)
|
||||
let list: any[] = []
|
||||
if (Array.isArray(d.menuList)) list = d.menuList
|
||||
else if (Array.isArray(r.menuList)) list = r.menuList
|
||||
else if (d.page?.list) list = d.page.list
|
||||
else if (Array.isArray(d.list)) list = d.list
|
||||
else if (Array.isArray(r.list)) list = r.list
|
||||
else {
|
||||
const bucket = d.page ?? d
|
||||
list = Array.isArray(bucket) ? bucket : bucket?.list ?? []
|
||||
}
|
||||
menuList.value = treeDataTranslate(list, 'id', 'parentId')
|
||||
}).then(() => {
|
||||
visible.value = true
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields()
|
||||
menuListTreeRef.value?.setCheckedKeys([])
|
||||
})
|
||||
}).then(() => {
|
||||
if (dataForm.id) {
|
||||
getRoleInfo(dataForm.id).then((body) => {
|
||||
if (!body || body.code !== 0) return
|
||||
const env = body as unknown as {
|
||||
data?: RoleInfo & { menuIdList?: number[] }
|
||||
role?: RoleInfo & { menuIdList?: number[] }
|
||||
}
|
||||
const rolePayload = env.data ?? env.role
|
||||
if (!rolePayload || typeof rolePayload !== 'object') return
|
||||
const role = rolePayload as RoleInfo & { menuIdList: number[] }
|
||||
dataForm.roleName = role.roleName || ''
|
||||
dataForm.remark = role.remark || ''
|
||||
const menuIdList = Array.isArray(role.menuIdList) ? role.menuIdList : []
|
||||
const idx = menuIdList.indexOf(tempKey)
|
||||
if (idx !== -1) {
|
||||
menuIdList.splice(idx, menuIdList.length - idx)
|
||||
}
|
||||
menuIdList.forEach((mid: number) => {
|
||||
menuListTreeRef.value?.setChecked(mid, true, false)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function dataFormSubmit (): void {
|
||||
dataFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
const checkedKeys = (menuListTreeRef.value?.getCheckedKeys() || []) as number[]
|
||||
const halfCheckedKeys = (menuListTreeRef.value?.getHalfCheckedKeys() || []) as number[]
|
||||
saveRole({
|
||||
id: dataForm.id || undefined,
|
||||
roleName: dataForm.roleName,
|
||||
remark: dataForm.remark,
|
||||
menuIdList: [...checkedKeys, ...halfCheckedKeys].filter((id: number) => id !== tempKey)
|
||||
}).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
332
src/views/modules/sys/components/user-add-or-update.vue
Executable file
332
src/views/modules/sys/components/user-add-or-update.vue
Executable file
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="!dataForm.id ? '添加用户' : '修改'"
|
||||
:close-on-click-modal="false"
|
||||
v-model="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataFormRef" @keyup.enter="dataFormSubmit()" label-width="100px">
|
||||
<template v-if="!dataForm.id">
|
||||
<el-form-item label="归属方式" prop="bindScopeType">
|
||||
<el-radio-group v-model="dataForm.bindScopeType" @change="onBindScopeChange">
|
||||
<el-radio value="channel_agent">渠道 / 代理商</el-radio>
|
||||
<el-radio value="store">门店</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dataForm.bindScopeType === 'channel_agent'"
|
||||
label="所属组织"
|
||||
prop="selectedOrgId">
|
||||
<org-tree-select
|
||||
v-model="dataForm.selectedOrgId"
|
||||
:hide-node-types="orgTreeHideTypes"
|
||||
height="320px"
|
||||
@change="onOrgTreeChange" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="所属门店" prop="storeOrgId">
|
||||
<store-select
|
||||
v-model="dataForm.storeOrgId"
|
||||
width="100%"
|
||||
@change="onStoreSelectChange" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item label="用户名" prop="userName">
|
||||
<el-input v-model="dataForm.userName" placeholder="登录帐号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password" :class="{ 'is-required': !dataForm.id }">
|
||||
<el-input
|
||||
v-model="dataForm.password"
|
||||
type="password"
|
||||
:placeholder="dataForm.id ? '不改请留空' : '密码'"
|
||||
autocomplete="new-password"></el-input>
|
||||
<div v-if="!dataForm.id" class="form-tip" style="margin-top: 4px;">最短8位,且须同时包含小写、大写、数字、特殊字符4种</div>
|
||||
<div v-else class="form-tip" style="margin-top: 4px;">不修改密码请留空</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="comfirmPassword" :class="{ 'is-required': !dataForm.id }">
|
||||
<el-input
|
||||
v-model="dataForm.comfirmPassword"
|
||||
type="password"
|
||||
:placeholder="dataForm.id ? '不改请留空' : '确认密码'"
|
||||
autocomplete="new-password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="dataForm.email" placeholder="选填"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="dataForm.mobile" placeholder="选填"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, nextTick } from 'vue'
|
||||
import { getUserInfo, saveUser, type UserInfo } from '@/api/sys'
|
||||
import { isEmail, isMobile } from '@/utils/validate'
|
||||
import { ElMessage, type FormInstance, type FormItemRule } from 'element-plus'
|
||||
import OrgTreeSelect from '@/components/org-tree-select/index.vue'
|
||||
import StoreSelect from '@/components/store-select/index.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
refreshDataList: []
|
||||
}>()
|
||||
|
||||
interface OrgTreePayload {
|
||||
orgId: string | number | null
|
||||
channelOrgId?: string | number | null
|
||||
agentOrgId?: string | number | null
|
||||
}
|
||||
|
||||
/** 组织树仅展示渠道/代理商,不包含门店与平台根 */
|
||||
const orgTreeHideTypes = ['PLATFORM', 'STORE']
|
||||
|
||||
const visible = ref<boolean>(false)
|
||||
const dataFormRef = ref<FormInstance | null>(null)
|
||||
|
||||
const orgBindings = reactive<OrgTreePayload>({
|
||||
orgId: null,
|
||||
channelOrgId: null,
|
||||
agentOrgId: null
|
||||
})
|
||||
|
||||
interface DataForm {
|
||||
id: number
|
||||
bindScopeType: 'channel_agent' | 'store'
|
||||
selectedOrgId: string | number | undefined
|
||||
storeOrgId: string | number | undefined
|
||||
userName: string
|
||||
password: string
|
||||
comfirmPassword: string
|
||||
salt: string
|
||||
email: string
|
||||
mobile: string
|
||||
status: number
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
id: 0,
|
||||
bindScopeType: 'channel_agent',
|
||||
selectedOrgId: undefined,
|
||||
storeOrgId: undefined,
|
||||
userName: '',
|
||||
password: '',
|
||||
comfirmPassword: '',
|
||||
salt: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
status: 1
|
||||
})
|
||||
|
||||
function onOrgTreeChange (payload: OrgTreePayload): void {
|
||||
orgBindings.orgId = payload.orgId
|
||||
orgBindings.channelOrgId = payload.channelOrgId
|
||||
orgBindings.agentOrgId = payload.agentOrgId
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.validateField('selectedOrgId')
|
||||
})
|
||||
}
|
||||
|
||||
function onBindScopeChange (): void {
|
||||
dataForm.selectedOrgId = undefined
|
||||
dataForm.storeOrgId = undefined
|
||||
orgBindings.orgId = null
|
||||
orgBindings.channelOrgId = null
|
||||
orgBindings.agentOrgId = null
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.clearValidate(['selectedOrgId', 'storeOrgId'])
|
||||
})
|
||||
}
|
||||
|
||||
function onStoreSelectChange (): void {
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.validateField('storeOrgId')
|
||||
})
|
||||
}
|
||||
|
||||
function validatePasswordStrength (password: string): { valid: boolean; message?: string } {
|
||||
if (!password || password.length < 8) return { valid: false, message: '密码长度不能少于8位' }
|
||||
const hasLower = /[a-z]/.test(password)
|
||||
const hasUpper = /[A-Z]/.test(password)
|
||||
const hasDigit = /[0-9]/.test(password)
|
||||
const hasSpecial = /[^A-Za-z0-9]/.test(password)
|
||||
if (!hasLower) return { valid: false, message: '密码须包含小写字母' }
|
||||
if (!hasUpper) return { valid: false, message: '密码须包含大写字母' }
|
||||
if (!hasDigit) return { valid: false, message: '密码须包含数字' }
|
||||
if (!hasSpecial) return { valid: false, message: '密码须包含特殊字符' }
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
const validatePassword: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
const v = typeof value === 'string' ? value.trim() : ''
|
||||
if (!dataForm.id && !v) {
|
||||
callback(new Error('密码不能为空'))
|
||||
} else if (!dataForm.id && v) {
|
||||
const r = validatePasswordStrength(v)
|
||||
if (!r.valid) return callback(new Error(r.message))
|
||||
callback()
|
||||
} else if (dataForm.id && v) {
|
||||
const r = validatePasswordStrength(v)
|
||||
if (!r.valid) return callback(new Error(r.message))
|
||||
callback()
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateComfirmPassword: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
const confirm = typeof value === 'string' ? value.trim() : ''
|
||||
const pwd = typeof dataForm.password === 'string' ? dataForm.password.trim() : ''
|
||||
if (!dataForm.id && !confirm) {
|
||||
callback(new Error('确认密码不能为空'))
|
||||
} else if (!pwd && confirm) {
|
||||
callback(new Error('请先填写密码'))
|
||||
} else if (pwd !== confirm) {
|
||||
callback(new Error('确认密码与密码输入不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateEmail: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
if (!value || !String(value).trim()) {
|
||||
return callback()
|
||||
}
|
||||
if (!isEmail(value as string)) {
|
||||
callback(new Error('邮箱格式错误'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateMobile: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
if (!value || !String(value).trim()) {
|
||||
return callback()
|
||||
}
|
||||
if (!isMobile(value as string)) {
|
||||
callback(new Error('手机号格式错误'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateChannelAgentOrg: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
if (dataForm.id || dataForm.bindScopeType !== 'channel_agent') return callback()
|
||||
if (value == null || value === '') {
|
||||
return callback(new Error('请选择渠道或代理商'))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const validateStoreOrg: FormItemRule['validator'] = (_rule, value, callback) => {
|
||||
if (dataForm.id || dataForm.bindScopeType !== 'store') return callback()
|
||||
if (value == null || value === '') {
|
||||
return callback(new Error('请选择门店'))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const dataRule: Record<string, FormItemRule[]> = {
|
||||
selectedOrgId: [{ validator: validateChannelAgentOrg, trigger: 'change' }],
|
||||
storeOrgId: [{ validator: validateStoreOrg, trigger: 'change' }],
|
||||
userName: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
|
||||
password: [{ validator: validatePassword, trigger: 'blur' }],
|
||||
comfirmPassword: [{ validator: validateComfirmPassword, trigger: 'blur' }],
|
||||
email: [{ validator: validateEmail, trigger: 'blur' }],
|
||||
mobile: [{ validator: validateMobile, trigger: 'blur' }]
|
||||
}
|
||||
|
||||
function init (id?: number | string | null): void {
|
||||
dataForm.id = id ? Number(id) : 0
|
||||
visible.value = true
|
||||
|
||||
nextTick(() => {
|
||||
if (dataFormRef.value && dataFormRef.value.clearValidate) {
|
||||
dataFormRef.value.clearValidate()
|
||||
}
|
||||
dataForm.bindScopeType = 'channel_agent'
|
||||
dataForm.selectedOrgId = undefined
|
||||
dataForm.storeOrgId = undefined
|
||||
orgBindings.orgId = null
|
||||
orgBindings.channelOrgId = null
|
||||
orgBindings.agentOrgId = null
|
||||
dataForm.userName = ''
|
||||
dataForm.password = ''
|
||||
dataForm.comfirmPassword = ''
|
||||
dataForm.salt = ''
|
||||
dataForm.email = ''
|
||||
dataForm.mobile = ''
|
||||
dataForm.status = 1
|
||||
})
|
||||
|
||||
if (id !== null && id !== undefined && id !== '') {
|
||||
getUserInfo(Number(id)).then((body) => {
|
||||
if (!body || body.code !== 0) return
|
||||
const env = body as unknown as { data?: UserInfo; user?: UserInfo }
|
||||
const user = env.data ?? env.user
|
||||
if (!user || typeof user !== 'object') return
|
||||
dataForm.id = user.id
|
||||
dataForm.userName = user.username || ''
|
||||
dataForm.salt = user.salt || ''
|
||||
dataForm.email = user.email || ''
|
||||
dataForm.mobile = user.mobile || ''
|
||||
dataForm.status = user.status != null ? Number(user.status) : 1
|
||||
dataForm.password = ''
|
||||
dataForm.comfirmPassword = ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function dataFormSubmit (): void {
|
||||
if (!dataFormRef.value) return
|
||||
dataFormRef.value.validate((valid) => {
|
||||
if (!valid) return
|
||||
const pwdTrim = typeof dataForm.password === 'string' ? dataForm.password.trim() : ''
|
||||
const payload: Record<string, any> = {
|
||||
id: dataForm.id || undefined,
|
||||
username: dataForm.userName,
|
||||
salt: dataForm.salt,
|
||||
email: dataForm.email,
|
||||
mobile: dataForm.mobile,
|
||||
status: dataForm.status
|
||||
}
|
||||
if (dataForm.id) {
|
||||
if (pwdTrim) payload.password = pwdTrim
|
||||
} else {
|
||||
payload.password = pwdTrim
|
||||
}
|
||||
if (!dataForm.id) {
|
||||
if (dataForm.bindScopeType === 'channel_agent') {
|
||||
payload.orgId = orgBindings.orgId ?? dataForm.selectedOrgId
|
||||
if (orgBindings.channelOrgId != null) {
|
||||
payload.channelOrgId = orgBindings.channelOrgId
|
||||
}
|
||||
if (orgBindings.agentOrgId) {
|
||||
payload.agentOrgId = orgBindings.agentOrgId
|
||||
}
|
||||
} else {
|
||||
payload.orgId = dataForm.storeOrgId
|
||||
}
|
||||
}
|
||||
saveUser(payload).then((res) => {
|
||||
if (res && res.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
visible.value = false
|
||||
emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '请求失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
168
src/views/modules/sys/config.vue
Executable file
168
src/views/modules/sys/config.vue
Executable file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.paramKey" placeholder="参数名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-page-wrap">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
@selection-change="selectionChangeHandle"
|
||||
style="width: 100%;">
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="50">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="id"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="80"
|
||||
label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="paramKey"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="参数名">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="paramValue"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="参数值">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="备注">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
label="操作">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" size="small" @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button link type="danger" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdateRef" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onActivated, nextTick, useTemplateRef } from 'vue'
|
||||
import AddOrUpdate from './components/config-add-or-update.vue'
|
||||
import { getConfigList, deleteConfig } from '@/api/sys'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { ConfigInfo } from '@/api/sys'
|
||||
|
||||
interface DataForm {
|
||||
paramKey: string
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
paramKey: ''
|
||||
})
|
||||
|
||||
const dataList = ref<ConfigInfo[]>([])
|
||||
const pageIndex = ref<number>(1)
|
||||
const pageSize = ref<number>(10)
|
||||
const totalPage = ref<number>(0)
|
||||
const dataListLoading = ref<boolean>(false)
|
||||
const dataListSelections = ref<ConfigInfo[]>([])
|
||||
const addOrUpdateVisible = ref<boolean>(false)
|
||||
const addOrUpdateRef = useTemplateRef<InstanceType<typeof AddOrUpdate>>('addOrUpdateRef')
|
||||
|
||||
function getDataList (): void {
|
||||
dataListLoading.value = true
|
||||
getConfigList({
|
||||
page: pageIndex.value,
|
||||
limit: pageSize.value,
|
||||
paramKey: dataForm.paramKey
|
||||
}).then((data) => {
|
||||
if (data && data.code === 0 && data.data) {
|
||||
const page = data.data.page || data.data
|
||||
dataList.value = page.list || []
|
||||
totalPage.value = page.totalCount || 0
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
}
|
||||
dataListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function sizeChangeHandle (val: number): void {
|
||||
pageSize.value = val
|
||||
pageIndex.value = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function currentChangeHandle (val: number): void {
|
||||
pageIndex.value = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function selectionChangeHandle (val: ConfigInfo[]): void {
|
||||
dataListSelections.value = val
|
||||
}
|
||||
|
||||
async function addOrUpdateHandle (id?: number): Promise<void> {
|
||||
addOrUpdateVisible.value = true
|
||||
await nextTick()
|
||||
addOrUpdateRef.value?.init(id)
|
||||
}
|
||||
|
||||
function deleteHandle (id?: number): void {
|
||||
const ids = id ? [id] : dataListSelections.value.map(item => item.id)
|
||||
ElMessageBox.confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteConfig(ids as number[]).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
onActivated(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
138
src/views/modules/sys/log.vue
Executable file
138
src/views/modules/sys/log.vue
Executable file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="mod-log">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.key" placeholder="用户名/用户操作" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-page-wrap">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="id"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="80"
|
||||
label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="username"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="用户名">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="operation"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="用户操作">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="method"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
:show-overflow-tooltip="true"
|
||||
label="请求方法">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="params"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
:show-overflow-tooltip="true"
|
||||
label="请求参数">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="time"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="执行时长(毫秒)">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="ip"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
label="IP地址">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createDate"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="180"
|
||||
label="创建时间">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { getLogList } from '@/api/sys'
|
||||
import type { LogInfo } from '@/api/sys'
|
||||
|
||||
interface DataForm {
|
||||
key: string
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
key: ''
|
||||
})
|
||||
|
||||
const dataList = ref<LogInfo[]>([])
|
||||
const pageIndex = ref<number>(1)
|
||||
const pageSize = ref<number>(10)
|
||||
const totalPage = ref<number>(0)
|
||||
const dataListLoading = ref<boolean>(false)
|
||||
|
||||
function getDataList (): void {
|
||||
dataListLoading.value = true
|
||||
getLogList({
|
||||
page: pageIndex.value,
|
||||
limit: pageSize.value,
|
||||
key: dataForm.key
|
||||
}).then((data) => {
|
||||
if (data && data.code === 0 && data.data) {
|
||||
const page = data.data.page || data.data
|
||||
dataList.value = page.list || []
|
||||
totalPage.value = page.totalCount || 0
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
}
|
||||
dataListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function sizeChangeHandle (val: number): void {
|
||||
pageSize.value = val
|
||||
pageIndex.value = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function currentChangeHandle (val: number): void {
|
||||
pageIndex.value = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
165
src/views/modules/sys/menu.vue
Executable file
165
src/views/modules/sys/menu.vue
Executable file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="mod-menu">
|
||||
<el-form :inline="true" :model="dataForm">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
row-key="id"
|
||||
border
|
||||
style="width: 100%; ">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
header-align="center"
|
||||
min-width="150"
|
||||
label="名称" >
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="parentName"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="120"
|
||||
label="上级菜单">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="图标">
|
||||
<template #default="scope">
|
||||
<el-icon v-if="scope.row.icon && iconMap[scope.row.icon as keyof typeof iconMap]">
|
||||
<component :is="iconMap[scope.row.icon as keyof typeof iconMap]" />
|
||||
</el-icon>
|
||||
<i v-else-if="scope.row.icon" :class="'el-icon-' + scope.row.icon"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="类型">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.type === 0" size="small">目录</el-tag>
|
||||
<el-tag v-else-if="scope.row.type === 1" size="small" type="success">菜单</el-tag>
|
||||
<el-tag v-else-if="scope.row.type === 2" size="small" type="info">按钮</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="orderNum"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="排序号">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="show"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="90"
|
||||
label="是否展示">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.show == '10'" size="small" type="success">展示</el-tag>
|
||||
<el-tag v-else size="small" type="info">隐藏</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="url"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
:show-overflow-tooltip="true"
|
||||
label="菜单URL">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="perms"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
:show-overflow-tooltip="true"
|
||||
label="授权标识">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
label="操作">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" size="small" @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button link type="danger" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdateRef" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, nextTick, useTemplateRef } from 'vue'
|
||||
import AddOrUpdate from './components/menu-add-or-update.vue'
|
||||
import { getMenuList, deleteMenu } from '@/api/sys'
|
||||
import { treeDataTranslate } from '@/utils'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
const iconMap = ElementPlusIconsVue
|
||||
|
||||
const dataForm = reactive<Record<string, any>>({})
|
||||
const dataList = ref<any[]>([])
|
||||
const dataListLoading = ref<boolean>(false)
|
||||
const addOrUpdateVisible = ref<boolean>(false)
|
||||
const addOrUpdateRef = useTemplateRef<InstanceType<typeof AddOrUpdate>>('addOrUpdateRef')
|
||||
|
||||
function getDataList (): void {
|
||||
dataListLoading.value = true
|
||||
getMenuList().then((data) => {
|
||||
if (data && data.code === 0 && data.data) {
|
||||
const d = data.data as Record<string, any>
|
||||
let list: any[] = []
|
||||
if (Array.isArray(d.menuList)) list = d.menuList
|
||||
else if (d.page?.list) list = d.page.list
|
||||
else if (Array.isArray(d.list)) list = d.list
|
||||
else {
|
||||
const bucket = d.page ?? d
|
||||
list = Array.isArray(bucket) ? bucket : bucket?.list ?? []
|
||||
}
|
||||
dataList.value = treeDataTranslate(list, 'id')
|
||||
}
|
||||
dataListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
async function addOrUpdateHandle (id?: number): Promise<void> {
|
||||
addOrUpdateVisible.value = true
|
||||
await nextTick()
|
||||
addOrUpdateRef.value?.init(id)
|
||||
}
|
||||
|
||||
function deleteHandle (id: number): void {
|
||||
ElMessageBox.confirm(`确定对[id=${id}]进行[删除]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteMenu(id).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
194
src/views/modules/sys/role.vue
Executable file
194
src/views/modules/sys/role.vue
Executable file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div class="mod-role">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.roleName" placeholder="角色名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-tooltip content="选中角色中存在子集时不允许删除" :disabled="!(dataListSelections.length > 0 && dataListSelections.some(item => idsWithChildren.has(item.id)))">
|
||||
<span class="pl-3">
|
||||
<el-button type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0 || dataListSelections.some(item => idsWithChildren.has(item.id))">批量删除</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-page-wrap">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
@selection-change="selectionChangeHandle"
|
||||
style="width: 100%;">
|
||||
<el-table-column
|
||||
type="selection"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="50">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="id"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="80"
|
||||
label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="roleName"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="角色名称">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="备注">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="180"
|
||||
label="创建时间">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="updatedAt"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="180"
|
||||
label="更新时间">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
label="操作">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" size="small" @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-tooltip content="存在子集时不允许删除" :disabled="!idsWithChildren.has(scope.row.id)">
|
||||
<span>
|
||||
<el-button link type="danger" size="small" :disabled="idsWithChildren.has(scope.row.id)" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdateRef" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, nextTick, useTemplateRef } from 'vue'
|
||||
import AddOrUpdate from './components/role-add-or-update.vue'
|
||||
import { getRoleList, deleteRole } from '@/api/sys'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { RoleInfo } from '@/api/sys'
|
||||
|
||||
interface DataForm {
|
||||
roleName: string
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
roleName: ''
|
||||
})
|
||||
|
||||
const dataList = ref<RoleInfo[]>([])
|
||||
const pageIndex = ref<number>(1)
|
||||
const pageSize = ref<number>(10)
|
||||
const totalPage = ref<number>(0)
|
||||
const dataListLoading = ref<boolean>(false)
|
||||
const dataListSelections = ref<RoleInfo[]>([])
|
||||
const addOrUpdateVisible = ref<boolean>(false)
|
||||
const addOrUpdateRef = useTemplateRef<InstanceType<typeof AddOrUpdate>>('addOrUpdateRef')
|
||||
const idsWithChildren = ref<Set<number | string>>(new Set())
|
||||
|
||||
function getDataList (): void {
|
||||
dataListLoading.value = true
|
||||
getRoleList({
|
||||
page: pageIndex.value,
|
||||
limit: pageSize.value,
|
||||
roleName: dataForm.roleName
|
||||
}).then((data) => {
|
||||
if (data && data.code === 0 && data.data) {
|
||||
const page = data.data.page || data.data
|
||||
dataList.value = page.list || []
|
||||
totalPage.value = page.totalCount || 0
|
||||
const list = dataList.value || []
|
||||
const parentIds = list.map(r => r.parentId).filter(p => p != null && p !== '')
|
||||
idsWithChildren.value = new Set(parentIds)
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
idsWithChildren.value = new Set()
|
||||
}
|
||||
dataListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function sizeChangeHandle (val: number): void {
|
||||
pageSize.value = val
|
||||
pageIndex.value = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function currentChangeHandle (val: number): void {
|
||||
pageIndex.value = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function selectionChangeHandle (val: RoleInfo[]): void {
|
||||
dataListSelections.value = val
|
||||
}
|
||||
|
||||
async function addOrUpdateHandle (id?: number): Promise<void> {
|
||||
addOrUpdateVisible.value = true
|
||||
await nextTick()
|
||||
addOrUpdateRef.value?.init(id)
|
||||
}
|
||||
|
||||
function deleteHandle (id?: number): void {
|
||||
const ids = id ? [id] : dataListSelections.value.map(item => item.id)
|
||||
const withChildren = ids.filter(id => idsWithChildren.value.has(id))
|
||||
if (withChildren.length > 0) {
|
||||
ElMessage.warning('以下角色存在子集,不允许删除:' + withChildren.join('、'))
|
||||
return
|
||||
}
|
||||
ElMessageBox.confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteRole(ids as number[]).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
185
src/views/modules/sys/user.vue
Executable file
185
src/views/modules/sys/user.vue
Executable file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div class="mod-user">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.userName" placeholder="用户名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="addHandle()">添加用户</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-page-wrap">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
style="width: 100%;">
|
||||
<el-table-column
|
||||
prop="index"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="80"
|
||||
label="序号">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="username"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="用户名">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="orgName"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="所属组织">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="mobile"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="电话">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === 0" size="small" type="danger">禁用</el-tag>
|
||||
<el-tag v-else size="small">启用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="180"
|
||||
label="创建时间">
|
||||
<template #default="scope">
|
||||
{{ scope.row.createdAt || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
label="操作">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" size="small" @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button link type="danger" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<AddOrUpdate
|
||||
v-show="addOrUpdateVisible"
|
||||
ref="addOrUpdateRef"
|
||||
@refreshDataList="getDataList"></AddOrUpdate>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, nextTick, useTemplateRef } from 'vue'
|
||||
import AddOrUpdate from './components/user-add-or-update.vue'
|
||||
import { getUserList, deleteUser } from '@/api/sys'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { UserInfo } from '@/api/sys'
|
||||
|
||||
interface DataForm {
|
||||
userName: string
|
||||
}
|
||||
|
||||
const dataForm = reactive<DataForm>({
|
||||
userName: ''
|
||||
})
|
||||
|
||||
const dataList = ref<UserInfo[]>([])
|
||||
const pageIndex = ref<number>(1)
|
||||
const pageSize = ref<number>(10)
|
||||
const totalPage = ref<number>(0)
|
||||
const dataListLoading = ref<boolean>(false)
|
||||
|
||||
const addOrUpdateVisible = ref<boolean>(false)
|
||||
const addOrUpdateRef = useTemplateRef<InstanceType<typeof AddOrUpdate>>('addOrUpdateRef')
|
||||
|
||||
function getDataList (): void {
|
||||
dataListLoading.value = true
|
||||
getUserList({
|
||||
page: pageIndex.value,
|
||||
limit: pageSize.value,
|
||||
username: dataForm.userName
|
||||
}).then((data) => {
|
||||
if (data && data.code === 0 && data.data) {
|
||||
const page = data.data.page || data.data
|
||||
dataList.value = page.list || []
|
||||
totalPage.value = page.totalCount || 0
|
||||
} else {
|
||||
dataList.value = []
|
||||
totalPage.value = 0
|
||||
}
|
||||
dataListLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function sizeChangeHandle (val: number): void {
|
||||
pageSize.value = val
|
||||
pageIndex.value = 1
|
||||
getDataList()
|
||||
}
|
||||
|
||||
function currentChangeHandle (val: number): void {
|
||||
pageIndex.value = val
|
||||
getDataList()
|
||||
}
|
||||
|
||||
async function addOrUpdateHandle (id: number): Promise<void> {
|
||||
addOrUpdateVisible.value = true
|
||||
await nextTick()
|
||||
addOrUpdateRef.value?.init(id)
|
||||
}
|
||||
|
||||
async function addHandle (): Promise<void> {
|
||||
addOrUpdateVisible.value = true
|
||||
await nextTick()
|
||||
addOrUpdateRef.value?.init(null)
|
||||
}
|
||||
|
||||
function deleteHandle (id: number): void {
|
||||
ElMessageBox.confirm(`确定对[id=${id}]进行[删除]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteUser([id]).then((data) => {
|
||||
if (data && data.code === 0) {
|
||||
ElMessage.success({
|
||||
message: '操作成功',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user