init
This commit is contained in:
347
src/views/system/apilog/failurelog/index.vue
Normal file
347
src/views/system/apilog/failurelog/index.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button type="danger" :size="size" @click="clearData.popup = true">
|
||||
<Icon :size="14" icon="ep:delete"></Icon>
|
||||
<span>清除日志</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu-before="{ row, size }">
|
||||
<el-button type="danger" :size="size" link @click="openViewText(row)"> 失败原因 </el-button>
|
||||
</template>
|
||||
<template #contentText="{ row }">
|
||||
<div>
|
||||
<avue-text-ellipsis
|
||||
:text="`${row.modelTitle}【${row.title}】【${row.requestMethod}】`"
|
||||
:height="30"
|
||||
use-tooltip
|
||||
placement="top"
|
||||
>
|
||||
<template #more>
|
||||
<small>...</small>
|
||||
</template>
|
||||
</avue-text-ellipsis>
|
||||
<avue-text-ellipsis
|
||||
:text="`${row.requestUri}${row.isParams ? '?' + row.requestParams : ''}`"
|
||||
:height="row.isParams ? 60 : 30"
|
||||
use-tooltip
|
||||
placement="top"
|
||||
>
|
||||
<template #more>
|
||||
<small>...</small>
|
||||
</template>
|
||||
</avue-text-ellipsis>
|
||||
<avue-text-ellipsis
|
||||
v-if="row.isData"
|
||||
:text="`请求参数:${row.requestParams}`"
|
||||
:height="30"
|
||||
use-tooltip
|
||||
placement="top"
|
||||
>
|
||||
<template #more>
|
||||
<small>...</small>
|
||||
</template>
|
||||
</avue-text-ellipsis>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<DesignPopup
|
||||
v-model="clearData.popup"
|
||||
title="清除日志"
|
||||
:width="400"
|
||||
:is-footer="true"
|
||||
:dialog-params="{}"
|
||||
>
|
||||
<div class="w-100% p-20px box-border">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-basis-80px flex-shrink-0">保留时间:</div>
|
||||
<avue-select
|
||||
v-model="clearData.clearType"
|
||||
placeholder="请选择内容"
|
||||
type="tree"
|
||||
:dic="clearDic"
|
||||
:clearable="false"
|
||||
></avue-select>
|
||||
</div>
|
||||
<avue-date
|
||||
v-show="clearData.clearType == 'custom'"
|
||||
class="ml-80px mt-5px"
|
||||
v-model="clearData.customValue"
|
||||
format="YYYY年MM月DD日"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择日期"
|
||||
></avue-date>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="clearData.popup = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleClear">确 定</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
|
||||
<DesignPopup
|
||||
v-model="viewData.popup"
|
||||
:title="viewData.title"
|
||||
control-type="drawer"
|
||||
width="80%"
|
||||
:is-body-full="true"
|
||||
>
|
||||
<template #default>
|
||||
<MonacoEditor
|
||||
v-model="viewData.text"
|
||||
language="java"
|
||||
:editor-option="{ readOnly: true }"
|
||||
></MonacoEditor>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import * as ApiLogApi from '@/api/system/apiLog'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'SystemApiFailureLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 160,
|
||||
labelWidth: 140,
|
||||
column: {
|
||||
contentText: {
|
||||
label: '请求信息',
|
||||
display: false
|
||||
},
|
||||
modelTitle: {
|
||||
label: '模块名称',
|
||||
overHidden: true,
|
||||
hide: true
|
||||
},
|
||||
title: {
|
||||
label: '请求名称',
|
||||
hide: true
|
||||
},
|
||||
requestUri: {
|
||||
label: '请求地址',
|
||||
search: true,
|
||||
hide: true
|
||||
},
|
||||
requestMethod: {
|
||||
label: '请求方式',
|
||||
hide: true
|
||||
},
|
||||
requestParams: {
|
||||
label: '请求参数',
|
||||
hide: true
|
||||
},
|
||||
createUserName: {
|
||||
label: '请求用户',
|
||||
width: 120,
|
||||
search: true
|
||||
},
|
||||
ip: {
|
||||
label: '操作IP地址',
|
||||
width: 110
|
||||
},
|
||||
methodClass: {
|
||||
label: '方法类',
|
||||
hide: true
|
||||
},
|
||||
methodName: {
|
||||
label: '方法名',
|
||||
hide: true
|
||||
},
|
||||
time: {
|
||||
label: '执行时间(ms)',
|
||||
width: 130,
|
||||
search: true,
|
||||
searchLabel: '执行时间',
|
||||
searchPrepend: '大于',
|
||||
searchAppend: 'ms'
|
||||
},
|
||||
createTime: {
|
||||
label: '请求时间',
|
||||
type: 'datetime',
|
||||
search: true,
|
||||
searchType: 'datetimerange',
|
||||
dataType: 'string',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
width: 100,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
tenantId: {
|
||||
label: '租户',
|
||||
search: userStore.user.id === 1,
|
||||
type: 'select',
|
||||
dataType: 'number',
|
||||
dicUrl: '/system/tenant/all',
|
||||
props: { label: 'name', value: 'id' },
|
||||
hide: userStore.user.id !== 1,
|
||||
width: 100,
|
||||
overHidden: true
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const clearData = ref({ popup: false, clearType: '7', customValue: '' })
|
||||
const clearDic = [
|
||||
{ label: '保留7天以内的数据', value: '7' },
|
||||
{ label: '保留一个月以内的数据', value: '30' },
|
||||
{ label: '保留三个月以内的数据', value: '90' },
|
||||
{ label: '保留一年以内的数据', value: '365' },
|
||||
{ label: '全部清除', value: 'all' },
|
||||
{ label: '自定义', value: 'custom' }
|
||||
]
|
||||
|
||||
const viewData = ref({ popup: false, text: '', title: '' })
|
||||
|
||||
const logType = 'error'
|
||||
const permission = getCurrPermi(['system:login-log'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
const handleClear = async () => {
|
||||
let clearTime = ''
|
||||
const { clearType, customValue } = clearData.value
|
||||
if (clearType == 'custom') {
|
||||
if (!customValue) return message.info('请先选择保留时间')
|
||||
clearTime = customValue
|
||||
} else {
|
||||
const date = new Date()
|
||||
let time = 0
|
||||
if (clearType == 'all') time = 3600 * 1000 * 24
|
||||
else time = 3600 * 1000 * 24 * (Number(clearType) + 1)
|
||||
date.setTime(date.getTime() - time)
|
||||
clearTime = formatDate(date, 'YYYY-MM-DD')
|
||||
}
|
||||
clearData.value.popup = false
|
||||
loading.value = true
|
||||
await ApiLogApi.deleteLog(logType, clearTime).catch(() => false)
|
||||
tablePage.value.currentPage = 1
|
||||
await getTableData()
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const openViewText = async (row) => {
|
||||
loading.value = true
|
||||
const data = await ApiLogApi.getLogDetail(logType, row.id)
|
||||
viewData.value.text = data.error
|
||||
viewData.value.title = `失败原因(${row.requestUri})`
|
||||
viewData.value.popup = true
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await ApiLogApi.getLogDetail(logType, tableForm.value.id)
|
||||
tableForm.value.createTime = formatDate(tableForm.value.createTime, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
done()
|
||||
}
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ApiLogApi.getLogPage(logType, searchObj)
|
||||
tableData.value = data.records.map((item) => {
|
||||
if (
|
||||
item.requestParams &&
|
||||
!['{}', '[]'].includes(item.requestParams) &&
|
||||
item.requestParams.length < 1000
|
||||
) {
|
||||
const list = item.requestParams.split('')
|
||||
if (
|
||||
(list[0] == '{' && list[list.length - 1] == '}') ||
|
||||
(list[0] == '[' && list[list.length - 1] == ']')
|
||||
) {
|
||||
item.isData = true
|
||||
} else item.isParams = true
|
||||
}
|
||||
return item
|
||||
})
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
316
src/views/system/apilog/successlog/index.vue
Normal file
316
src/views/system/apilog/successlog/index.vue
Normal file
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button type="danger" :size="size" @click="clearData.popup = true">
|
||||
<Icon :size="14" icon="ep:delete"></Icon>
|
||||
<span>清除日志</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<template #contentText="{ row }">
|
||||
<div>
|
||||
<avue-text-ellipsis
|
||||
:text="`${row.modelTitle}【${row.title}】【${row.requestMethod}】`"
|
||||
:height="30"
|
||||
use-tooltip
|
||||
placement="top"
|
||||
>
|
||||
<template #more>
|
||||
<small>...</small>
|
||||
</template>
|
||||
</avue-text-ellipsis>
|
||||
<avue-text-ellipsis
|
||||
:text="`${row.requestUri}${row.isParams ? '?' + row.requestParams : ''}`"
|
||||
:height="row.isParams ? 60 : 30"
|
||||
use-tooltip
|
||||
placement="top"
|
||||
>
|
||||
<template #more>
|
||||
<small>...</small>
|
||||
</template>
|
||||
</avue-text-ellipsis>
|
||||
<avue-text-ellipsis
|
||||
v-if="row.isData"
|
||||
:text="`请求参数:${row.requestParams}`"
|
||||
:height="30"
|
||||
use-tooltip
|
||||
placement="top"
|
||||
>
|
||||
<template #more>
|
||||
<small>...</small>
|
||||
</template>
|
||||
</avue-text-ellipsis>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<DesignPopup
|
||||
v-model="clearData.popup"
|
||||
title="清除日志"
|
||||
:width="400"
|
||||
:is-footer="true"
|
||||
:dialog-params="{}"
|
||||
>
|
||||
<div class="w-100% p-20px box-border">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-basis-80px flex-shrink-0">保留时间:</div>
|
||||
<avue-select
|
||||
v-model="clearData.clearType"
|
||||
placeholder="请选择内容"
|
||||
type="tree"
|
||||
:dic="clearDic"
|
||||
:clearable="false"
|
||||
></avue-select>
|
||||
</div>
|
||||
<avue-date
|
||||
v-show="clearData.clearType == 'custom'"
|
||||
class="ml-80px mt-5px"
|
||||
v-model="clearData.customValue"
|
||||
format="YYYY年MM月DD日"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择日期"
|
||||
></avue-date>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="clearData.popup = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleClear">确 定</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import * as ApiLogApi from '@/api/system/apiLog'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
defineOptions({ name: 'SystemApiSuccessLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
labelWidth: 140,
|
||||
column: {
|
||||
contentText: {
|
||||
label: '请求信息',
|
||||
display: false
|
||||
},
|
||||
modelTitle: {
|
||||
label: '模块名称',
|
||||
overHidden: true,
|
||||
hide: true
|
||||
},
|
||||
title: {
|
||||
label: '请求名称',
|
||||
hide: true
|
||||
},
|
||||
requestUri: {
|
||||
label: '请求地址',
|
||||
search: true,
|
||||
hide: true
|
||||
},
|
||||
requestMethod: {
|
||||
label: '请求方式',
|
||||
hide: true
|
||||
},
|
||||
requestParams: {
|
||||
label: '请求参数',
|
||||
hide: true
|
||||
},
|
||||
createUserName: {
|
||||
label: '请求用户',
|
||||
width: 120,
|
||||
search: true
|
||||
},
|
||||
ip: {
|
||||
label: '操作IP地址',
|
||||
width: 110
|
||||
},
|
||||
methodClass: {
|
||||
label: '方法类',
|
||||
hide: true
|
||||
},
|
||||
methodName: {
|
||||
label: '方法名',
|
||||
hide: true
|
||||
},
|
||||
time: {
|
||||
label: '执行时间(ms)',
|
||||
width: 130,
|
||||
search: true,
|
||||
searchLabel: '执行时间',
|
||||
searchPrepend: '大于',
|
||||
searchAppend: 'ms'
|
||||
},
|
||||
createTime: {
|
||||
label: '请求时间',
|
||||
type: 'datetime',
|
||||
search: true,
|
||||
searchType: 'datetimerange',
|
||||
dataType: 'string',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
width: 100,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
tenantId: {
|
||||
label: '租户',
|
||||
search: userStore.user.id === 1,
|
||||
type: 'select',
|
||||
dataType: 'number',
|
||||
dicUrl: '/system/tenant/all',
|
||||
props: { label: 'name', value: 'id' },
|
||||
hide: userStore.user.id !== 1,
|
||||
width: 100,
|
||||
overHidden: true
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const clearData = ref({ popup: false, clearType: '7', customValue: '' })
|
||||
const clearDic = [
|
||||
{ label: '保留7天以内的数据', value: '7' },
|
||||
{ label: '保留一个月以内的数据', value: '30' },
|
||||
{ label: '保留三个月以内的数据', value: '90' },
|
||||
{ label: '保留一年以内的数据', value: '365' },
|
||||
{ label: '全部清除', value: 'all' },
|
||||
{ label: '自定义', value: 'custom' }
|
||||
]
|
||||
|
||||
const logType = 'info'
|
||||
const permission = getCurrPermi(['system:login-log'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
const handleClear = async () => {
|
||||
let clearTime = ''
|
||||
const { clearType, customValue } = clearData.value
|
||||
if (clearType == 'custom') {
|
||||
if (!customValue) return message.info('请先选择保留时间')
|
||||
clearTime = customValue
|
||||
} else {
|
||||
const date = new Date()
|
||||
let time = 0
|
||||
if (clearType == 'all') time = 3600 * 1000 * 24
|
||||
else time = 3600 * 1000 * 24 * (Number(clearType) + 1)
|
||||
date.setTime(date.getTime() - time)
|
||||
clearTime = formatDate(date, 'YYYY-MM-DD')
|
||||
}
|
||||
clearData.value.popup = false
|
||||
loading.value = true
|
||||
await ApiLogApi.deleteLog(logType, clearTime).catch(() => false)
|
||||
tablePage.value.currentPage = 1
|
||||
await getTableData()
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await ApiLogApi.getLogDetail(logType, tableForm.value.id)
|
||||
tableForm.value.createTime = formatDate(tableForm.value.createTime, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
done()
|
||||
}
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ApiLogApi.getLogPage(logType, searchObj)
|
||||
tableData.value = data.records.map((item) => {
|
||||
if (
|
||||
item.requestParams &&
|
||||
!['{}', '[]'].includes(item.requestParams) &&
|
||||
item.requestParams.length < 1000
|
||||
) {
|
||||
const list = item.requestParams.split('')
|
||||
if (
|
||||
(list[0] == '{' && list[list.length - 1] == '}') ||
|
||||
(list[0] == '[' && list[list.length - 1] == ']')
|
||||
) {
|
||||
item.isData = true
|
||||
} else item.isParams = true
|
||||
}
|
||||
return item
|
||||
})
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
108
src/views/system/area/index.vue
Normal file
108
src/views/system/area/index.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-button style="margin-bottom: 20px" type="primary" @click="testPopup = true">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> IP 查询
|
||||
</el-button>
|
||||
<div style="width: 100%; height: 700px">
|
||||
<!-- AutoResizer 自动调节大小 -->
|
||||
<el-auto-resizer>
|
||||
<template #default="{ height, width }">
|
||||
<!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
|
||||
<el-table-v2
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:width="width"
|
||||
:height="height"
|
||||
expand-column-key="id"
|
||||
/>
|
||||
</template>
|
||||
</el-auto-resizer>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:查询 -->
|
||||
<DesignPopup v-model="testPopup" title="IP 查询" :is-footer="true" width="40%">
|
||||
<div class="p-20px">
|
||||
<avue-form
|
||||
ref="textFormRef"
|
||||
v-if="testPopup"
|
||||
v-model="testFormData"
|
||||
:option="testFormOption"
|
||||
></avue-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :loading="textLoading" type="primary" @click="testSubmitFun">检 测</el-button>
|
||||
<el-button @click="testPopup = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script setup lang="tsx">
|
||||
import type { Column } from 'element-plus'
|
||||
import * as AreaApi from '@/api/system/area'
|
||||
|
||||
defineOptions({ name: 'SystemArea' })
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// 表格的 column 字段
|
||||
const columns: Column[] = [
|
||||
{
|
||||
dataKey: 'id',
|
||||
title: '编号',
|
||||
width: 280,
|
||||
fixed: true,
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
dataKey: 'name',
|
||||
title: '地名',
|
||||
width: 200
|
||||
}
|
||||
]
|
||||
// 表格的数据
|
||||
const list = ref([])
|
||||
|
||||
/**
|
||||
* 获得数据列表
|
||||
*/
|
||||
const getList = async () => {
|
||||
list.value = await AreaApi.getAreaTree()
|
||||
}
|
||||
|
||||
/** 查询操作 */
|
||||
const testPopup = ref(false)
|
||||
const testFormData = ref<any>({})
|
||||
const testFormOption = ref({
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
span: 24,
|
||||
labelWidth: 80,
|
||||
column: {
|
||||
ip: { label: 'IP', rules: [{ required: true, message: 'IP不能为空', trigger: 'blur' }] },
|
||||
result: { label: '地址', placeholder: '展示查询 IP 结果', readonly: true }
|
||||
}
|
||||
})
|
||||
const textLoading = ref(false)
|
||||
const textFormRef = ref()
|
||||
|
||||
// 提交表单
|
||||
const testSubmitFun = () => {
|
||||
textFormRef.value.validate(async (valid, hide) => {
|
||||
if (!valid) return hide()
|
||||
try {
|
||||
textLoading.value = true
|
||||
testFormData.value.result = await AreaApi.getAreaByIp(testFormData.value.ip)
|
||||
message.success('查询成功')
|
||||
} finally {
|
||||
hide()
|
||||
textLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
343
src/views/system/dept/index.vue
Normal file
343
src/views/system/dept/index.vue
Normal file
@@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #type="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #parentId-form>
|
||||
<el-tree-select
|
||||
v-model="tableForm.parentId"
|
||||
:data="deptTree"
|
||||
:props="defaultProps"
|
||||
@change="parentIdChange"
|
||||
check-strictly
|
||||
default-expand-all
|
||||
placeholder="请选择上级部门"
|
||||
value-key="deptId"
|
||||
style="width: 366px"
|
||||
/>
|
||||
</template>
|
||||
<template #leaderUserId-form="scope">
|
||||
<UserSelect
|
||||
v-model="tableForm.leaderUserId"
|
||||
:column="scope.column"
|
||||
:disabled="scope.disabled"
|
||||
:size="scope.size"
|
||||
:type="scope.type"
|
||||
prop="leaderUserId"
|
||||
></UserSelect>
|
||||
</template>
|
||||
<!-- 自定义按钮 -->
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
plain
|
||||
:size="size"
|
||||
@click="defaultExpandAllShow = !defaultExpandAllShow"
|
||||
type="danger"
|
||||
icon="el-icon-sort"
|
||||
>展开/折叠</el-button
|
||||
>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { useLowStoreWithOut } from '@/store/modules/low'
|
||||
|
||||
defineOptions({ name: 'SystemDept' })
|
||||
|
||||
const crudRef = ref()
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const lowStore = useLowStoreWithOut()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const defaultExpandAllShow = ref(true) // 默认展开所有表项
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
rowKey: 'id',
|
||||
rowParentKey: 'parentId',
|
||||
defaultExpandAll: defaultExpandAllShow,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
parentId: {
|
||||
label: '上级部门',
|
||||
hide: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '部门名称',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
fullName: {
|
||||
label: '部门全称',
|
||||
minWidth: 100,
|
||||
display: false,
|
||||
},
|
||||
sort: {
|
||||
label: '排序',
|
||||
width: 80,
|
||||
span: 12,
|
||||
type: 'number',
|
||||
rules: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
leaderUserId: {
|
||||
label: '负责人',
|
||||
span: 12,
|
||||
minWidth: 90,
|
||||
findType: 'all',
|
||||
columnKey: ['mobile', 'sex', 'post', 'deptName'],
|
||||
defaultSearchDept: '',
|
||||
type: 'select',
|
||||
dicUrl: '/system/user/simple-list',
|
||||
dicFormatter: (data) => {
|
||||
const dicObj = {}
|
||||
data.forEach((item) => (dicObj[item.id] = item.nickname))
|
||||
lowStore.setDicObj('userSelect', dicObj)
|
||||
return data
|
||||
},
|
||||
props: {
|
||||
label: 'nickname',
|
||||
value: 'id'
|
||||
}
|
||||
},
|
||||
phone: {
|
||||
label: '联系电话',
|
||||
width: 120,
|
||||
rules: [
|
||||
{
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
message: '请输入正确的手机号码',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
email: {
|
||||
label: '邮箱',
|
||||
minWidth: 90,
|
||||
rules: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
width: 80,
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
isInternal: {
|
||||
label: '是否内部单位',
|
||||
width: 80,
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '是否内部单位不能为空', trigger: 'blur' }],
|
||||
dicData: [
|
||||
{ label: '是', value: CommonStatusEnum.DISABLE },
|
||||
{ label: '否', value: CommonStatusEnum.ENABLE }
|
||||
],
|
||||
// value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
campusId: {
|
||||
label: '所属园区',
|
||||
width: 80,
|
||||
hide: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicUrl: '/jeelowcode/dbform-data/list/1960952987557363713',
|
||||
dataType: 'array',
|
||||
dicMethod: 'post',
|
||||
props: { label: 'campus_name', value: 'campus_id' },
|
||||
dicFormatter: (data) => data.records,
|
||||
rules: [{ required: true, message: '所属园区不能为空', trigger: 'blur' }],
|
||||
},
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref()
|
||||
const tableSearch = ref({})
|
||||
const deptTree = ref() // 上级部门
|
||||
const permission = getCurrPermi(['system:dept'])
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await DeptApi.getDeptPage(searchObj)
|
||||
|
||||
tableData.value = handleTree(data)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
await getTree()
|
||||
const id = tableForm.value.id
|
||||
if (['edit', 'view'].includes(type) && id) {
|
||||
tableForm.value = await DeptApi.getDept(id)
|
||||
}
|
||||
tableOption.column.leaderUserId.defaultSearchDept = id || ''
|
||||
done()
|
||||
}
|
||||
|
||||
// 递归查找部门树中指定ID的部门名称
|
||||
const findDeptNameById = (tree, targetId) => {
|
||||
for (const node of tree) {
|
||||
if (node.id === targetId) {
|
||||
return node.name
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const result = findDeptNameById(node.children, targetId)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const assembleForm = (form) => {
|
||||
// 添加fullName字段
|
||||
// 查找父部门名称并构建完整路径
|
||||
const parentName = findDeptNameById(deptTree.value, form.parentId)
|
||||
if (parentName) {
|
||||
form.fullName = parentName + '-' + form.name
|
||||
} else {
|
||||
form.fullName = form.name
|
||||
}
|
||||
|
||||
return form
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
// 组装表单数据,添加fullName字段
|
||||
const assembledForm = assembleForm(form)
|
||||
let bool = await DeptApi.createDept(assembledForm).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
// 组装表单数据,添加fullName字段
|
||||
const assembledForm = assembleForm(form)
|
||||
let bool = await DeptApi.updateDept(assembledForm).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DeptApi.deleteDept(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 获得部门树 */
|
||||
const getTree = async () => {
|
||||
deptTree.value = []
|
||||
const data = await DeptApi.getSimpleDeptList()
|
||||
let dept: Tree = { id: 0, name: '顶级部门', children: [] }
|
||||
dept.children = handleTree(data)
|
||||
deptTree.value.push(dept)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => defaultExpandAllShow.value,
|
||||
() => {
|
||||
if (crudRef.value) crudRef.value?.refreshTable()
|
||||
}
|
||||
)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
285
src/views/system/dict/data/index.vue
Normal file
285
src/views/system/dict/data/index.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:dict:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as DictDataApi from '@/api/system/dict/dict.data'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemDictData' })
|
||||
|
||||
interface Props {
|
||||
dicType: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
calcHeight: 20,
|
||||
column: {
|
||||
dictType: {
|
||||
label: '所属字典',
|
||||
type: 'select',
|
||||
minWidth: 90,
|
||||
disabled: true,
|
||||
value: props.dicType,
|
||||
hide: true,
|
||||
clearable: false,
|
||||
filterable: true,
|
||||
dicUrl: '/system/dict-type/list-all-simple',
|
||||
props: { label: 'name', value: 'type' },
|
||||
dicFlag: true
|
||||
},
|
||||
label: {
|
||||
label: '标签',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '标签不能为空', trigger: 'blur' }]
|
||||
},
|
||||
value: {
|
||||
label: '键值',
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '键值不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sort: {
|
||||
label: '排序',
|
||||
type: 'number',
|
||||
span: 12,
|
||||
value: 0,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
width: 80,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
colorType: {
|
||||
label: '颜色类型',
|
||||
type: 'select',
|
||||
span: 12,
|
||||
minWidth: 90,
|
||||
dicData: [
|
||||
{ value: 'default', label: '默认' },
|
||||
{ value: 'primary', label: '主要' },
|
||||
{ value: 'success', label: '成功' },
|
||||
{ value: 'info', label: '信息' },
|
||||
{ value: 'warning', label: '警告' },
|
||||
{ value: 'danger', label: '危险' }
|
||||
]
|
||||
},
|
||||
cssClass: {
|
||||
label: 'CSS Class'
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 150
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({
|
||||
dictType: props.dicType
|
||||
})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:dict'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
const init = () => {
|
||||
tableData.value = []
|
||||
tablePage.value.total = 0
|
||||
tableOption.column.dictType.value = props.dicType
|
||||
tableSearch.value.dictType = props.dicType
|
||||
resetChange()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await DictDataApi.getDictDataPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await DictDataApi.getDictData(tableForm.value.id)
|
||||
}
|
||||
if (['edit'].includes(type)) {
|
||||
tableOption.column.dictType.disabled = true
|
||||
} else if (['add'].includes(type)) {
|
||||
tableOption.column.dictType.disabled = false
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await DictDataApi.createDictData(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await DictDataApi.updateDictData(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DictDataApi.deleteDictData(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await DictDataApi.exportDictData(searchObj)
|
||||
download.excel(data, '字典数据.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
init()
|
||||
})
|
||||
|
||||
defineExpose({ init })
|
||||
</script>
|
||||
263
src/views/system/dict/index.vue
Normal file
263
src/views/system/dict/index.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:dict:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button link type="primary" @click="openDicData(row)" class="ml-4px!">
|
||||
<Icon :size="14" icon="ep:document"></Icon>
|
||||
<span>数据</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<DesignPopup v-model="popupData.show" :title="popupData.title" width="80%" controlType="drawer">
|
||||
<div class="p-20px">
|
||||
<SystemDictData ref="dicDataRef" :dicType="popupData.dicType"></SystemDictData>
|
||||
</div>
|
||||
</DesignPopup>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as DictTypeApi from '@/api/system/dict/dict.type'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import SystemDictData from './data/index.vue'
|
||||
|
||||
defineOptions({ name: 'SystemDictType' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
name: {
|
||||
label: '字典名称',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
type: {
|
||||
label: '字典编码',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '字典编码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 120
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
width: 80,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:dict'])
|
||||
const popupData = ref({
|
||||
show: false,
|
||||
dicType: '',
|
||||
title: ''
|
||||
})
|
||||
const crudRef = ref()
|
||||
const dicDataRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
const openDicData = (row) => {
|
||||
popupData.value.dicType = row.type
|
||||
popupData.value.title = `${row.name}(${row.type})`
|
||||
setTimeout(() => {
|
||||
if (dicDataRef.value) dicDataRef.value?.init()
|
||||
popupData.value.show = true
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await DictTypeApi.getDictTypePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await DictTypeApi.getDictType(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await DictTypeApi.createDictType(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await DictTypeApi.updateDictType(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DictTypeApi.deleteDictType(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await DictTypeApi.exportDictType(searchObj)
|
||||
download.excel(data, '字典类型.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
232
src/views/system/duty/index.vue
Normal file
232
src/views/system/duty/index.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:before-open="beforeOpen"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:duty:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as DutyApi from '@/api/system/duty'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemDuty' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '职务编号',
|
||||
display: false,
|
||||
width: 90
|
||||
},
|
||||
name: {
|
||||
label: '职务名称',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '职务名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sort: {
|
||||
label: '职务顺序',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '职务顺序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
remark: {
|
||||
label: '职务备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 150,
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:duty'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await DutyApi.getDutyPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await DutyApi.getDuty(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await DutyApi.createDuty(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await DutyApi.updateDuty(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DutyApi.deleteDuty(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await DutyApi.exportDuty(searchObj)
|
||||
download.excel(data, '职务列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
239
src/views/system/errorCode/index.vue
Normal file
239
src/views/system/errorCode/index.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:error-code:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.type !== undefined"
|
||||
:type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE"
|
||||
:value="scope.row.type"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as ErrorCodeApi from '@/api/system/errorCode'
|
||||
|
||||
defineOptions({ name: 'SystemTenant' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
searchLabelWidth: 100,
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
type: {
|
||||
label: '类型',
|
||||
type: 'select',
|
||||
width: 100,
|
||||
search: true,
|
||||
display: false,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_ERROR_CODE_TYPE)
|
||||
},
|
||||
applicationName: {
|
||||
label: '应用名',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '应用名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
code: {
|
||||
label: '错误码编码',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '错误码编码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
message: {
|
||||
label: '错误码提示',
|
||||
search: true,
|
||||
minWidth: 140,
|
||||
rules: [{ required: true, message: '错误码提示不能为空', trigger: 'blur' }]
|
||||
},
|
||||
memo: {
|
||||
label: '备注',
|
||||
minWidth: 140
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:error-code'])
|
||||
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ErrorCodeApi.getErrorCodePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await ErrorCodeApi.getErrorCode(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await ErrorCodeApi.createErrorCode(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await ErrorCodeApi.updateErrorCode(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ErrorCodeApi.deleteErrorCode(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await ErrorCodeApi.excelErrorCode(searchObj)
|
||||
download.excel(data, '错误码列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
211
src/views/system/loginlog/index.vue
Normal file
211
src/views/system/loginlog/index.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:login-log:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<!-- 表格 -->
|
||||
<template #logType="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_LOGIN_TYPE"
|
||||
:value="scope.row.logType != undefined ? scope.row.logType : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #result="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_LOGIN_RESULT"
|
||||
:value="scope.row.result != undefined ? scope.row.result : ''"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as LoginLogApi from '@/api/system/loginLog'
|
||||
|
||||
defineOptions({ name: 'SystemLoginLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
logType: {
|
||||
label: '操作类型',
|
||||
width: 100,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_LOGIN_TYPE),
|
||||
value: ''
|
||||
},
|
||||
username: {
|
||||
label: '用户名称'
|
||||
},
|
||||
userIp: {
|
||||
label: '登录地址',
|
||||
width: 140
|
||||
},
|
||||
userAgent: {
|
||||
label: '浏览器',
|
||||
width: 400,
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
overHidden: true
|
||||
},
|
||||
searchCreateTime: {
|
||||
label: '登录日期',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
createTime: {
|
||||
label: '登录日期',
|
||||
type: 'datetime',
|
||||
width: 160,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
result: {
|
||||
label: '登陆结果',
|
||||
width: 90,
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_LOGIN_RESULT),
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:login-log'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
if (searchObj.searchCreateTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.searchCreateTime)
|
||||
}
|
||||
delete searchObj.searchCreateTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await LoginLogApi.getLoginLogPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
if (searchObj.searchCreateTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.searchCreateTime)
|
||||
}
|
||||
delete searchObj.searchCreateTime
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await LoginLogApi.exportLoginLog(searchObj)
|
||||
download.excel(data, '登录日志.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
220
src/views/system/mail/account/index.vue
Normal file
220
src/views/system/mail/account/index.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #sslEnable="scope">
|
||||
<template v-if="scope.row.sslEnable && scope.row.sslEnable === true">
|
||||
<el-tag type="success">是</el-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag type="danger">否</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MailAccountApi from '@/api/system/mail/account'
|
||||
|
||||
defineOptions({ name: 'SystemMailAccount' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: 'el-icon-view',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 140,
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
overHidden: true,
|
||||
column: {
|
||||
mail: {
|
||||
label: '邮箱',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [
|
||||
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
|
||||
{
|
||||
type: 'email',
|
||||
message: t('profile.rules.truemail'),
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
},
|
||||
username: {
|
||||
label: '用户名',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '用户名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
password: {
|
||||
label: '密码',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '密码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
host: {
|
||||
label: 'SMTP 服务器域名',
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: 'SMTP 服务器域名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
port: {
|
||||
label: 'SMTP 服务器端口',
|
||||
span: 12,
|
||||
type: 'number',
|
||||
value: 465,
|
||||
width: 90,
|
||||
rules: [{ required: true, message: 'SMTP 服务器端口不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sslEnable: {
|
||||
label: '是否开启 SSL',
|
||||
type: 'radio',
|
||||
width: 90,
|
||||
span: 12,
|
||||
dicData: [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false }
|
||||
],
|
||||
rules: [{ required: true, message: '是否开启 SSL不能为空', trigger: 'blur' }],
|
||||
value: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:mail-account'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await MailAccountApi.getMailAccountPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await MailAccountApi.getMailAccount(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await MailAccountApi.createMailAccount(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await MailAccountApi.updateMailAccount(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await MailAccountApi.deleteMailAccount(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
260
src/views/system/mail/log/index.vue
Normal file
260
src/views/system/mail/log/index.vue
Normal file
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #sendStatus="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_MAIL_SEND_STATUS"
|
||||
:value="scope.row.sendStatus != undefined ? scope.row.sendStatus : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #receiveStatus="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS"
|
||||
:value="scope.row.receiveStatus != undefined ? scope.row.receiveStatus : ''"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #templateParams-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
{{ JSON.stringify(value) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #sendStatus-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #receiveStatus-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #templateContent-form="{ value }">
|
||||
<div v-html="value"></div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as MailLogApi from '@/api/system/mail/log'
|
||||
defineOptions({ name: 'SystemMailLog' })
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewTitle: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 160,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
id: {
|
||||
label: '编号',
|
||||
width: 80
|
||||
},
|
||||
searchSendTime: {
|
||||
label: '发送时间',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
sendTime: {
|
||||
label: '发送时间',
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
toMail: {
|
||||
label: '接收邮箱',
|
||||
minWidth: 100
|
||||
},
|
||||
userId: {
|
||||
label: '用户编号',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
hide: true
|
||||
},
|
||||
templateTitle: {
|
||||
label: '邮件标题',
|
||||
overHidden: true,
|
||||
minWidth: 100
|
||||
},
|
||||
templateContent: {
|
||||
label: '邮件内容',
|
||||
hide: true
|
||||
},
|
||||
templateParams: {
|
||||
label: '邮箱参数',
|
||||
hide: true
|
||||
},
|
||||
sendStatus: {
|
||||
label: '发送状态',
|
||||
search: true,
|
||||
width: 100,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_MAIL_SEND_STATUS),
|
||||
value: ''
|
||||
},
|
||||
accountId: {
|
||||
label: '邮箱账号',
|
||||
search: true,
|
||||
type: 'select',
|
||||
hide: true,
|
||||
dicUrl: '/system/mail-account/simple-list',
|
||||
props: { label: 'mail', value: 'id' },
|
||||
dicFlag: true
|
||||
},
|
||||
fromMail: {
|
||||
label: '发送邮箱地址',
|
||||
overHidden: true
|
||||
},
|
||||
templateId: {
|
||||
label: '模板编号',
|
||||
minWidth: 90,
|
||||
search: true
|
||||
},
|
||||
templateCode: {
|
||||
label: '模板编码',
|
||||
hide: true
|
||||
},
|
||||
templateNickname: {
|
||||
label: '模版发送人名称',
|
||||
hide: true
|
||||
},
|
||||
sendMessageId: {
|
||||
label: '发送返回的消息编号',
|
||||
hide: true
|
||||
},
|
||||
sendException: {
|
||||
label: '发送异常',
|
||||
hide: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
type: 'datetime',
|
||||
hide: true,
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:mail-log'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.searchSendTime?.length) {
|
||||
searchObj.sendTime = getSearchDate(searchObj.searchSendTime)
|
||||
}
|
||||
delete searchObj.searchSendTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await MailLogApi.getMailLogPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await MailLogApi.getMailLog(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
380
src/views/system/mail/template/index.vue
Normal file
380
src/views/system/mail/template/index.vue
Normal file
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
v-if="row.status === 0"
|
||||
class="ml-4px!"
|
||||
link
|
||||
type="primary"
|
||||
@click="openSendForm(row.id)"
|
||||
v-hasPermi="['system:mail-template:send-mail']"
|
||||
>
|
||||
<Icon :size="14" icon="ep:tickets"></Icon>
|
||||
<span>测试</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:测试发送 -->
|
||||
<DesignPopup
|
||||
v-model="testPopup"
|
||||
title="发送邮箱模板测试"
|
||||
:is-footer="true"
|
||||
width="40%"
|
||||
class="mail-template-popup"
|
||||
>
|
||||
<div class="p-20px">
|
||||
<avue-form
|
||||
ref="textFormRef"
|
||||
v-if="testPopup"
|
||||
v-model="testFormData"
|
||||
:option="testFormOption"
|
||||
></avue-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :loading="textLoading" type="primary" @click="testSubmitFun">发 送</el-button>
|
||||
<el-button @click="testPopup = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as MailTemplateApi from '@/api/system/mail/template'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemMailTemplate' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
searchLabelWidth: 140,
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 160,
|
||||
span: 24,
|
||||
dialogWidth: '70%',
|
||||
overHidden: true,
|
||||
column: {
|
||||
code: {
|
||||
label: '模板编码',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '模板名称',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
title: {
|
||||
label: '模板标题',
|
||||
minWidth: 100,
|
||||
overHidden: true
|
||||
},
|
||||
content: {
|
||||
label: '模板内容',
|
||||
minWidth: 100,
|
||||
type: 'ueditor',
|
||||
rules: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!value || !value.replace(/<[\s\S]*?>|[' ']/g, '')) {
|
||||
callback(new Error('模板内容不能为空'))
|
||||
} else callback()
|
||||
},
|
||||
required: true,
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
accountId: {
|
||||
label: '邮箱账号',
|
||||
minWidth: 120,
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicUrl: '/system/mail-account/simple-list',
|
||||
props: { label: 'mail', value: 'id' },
|
||||
dicFlag: true,
|
||||
rules: [{ required: true, message: '邮箱账号不能为空', trigger: 'blur' }]
|
||||
},
|
||||
nickname: {
|
||||
label: '发送人名称',
|
||||
minWidth: 100,
|
||||
span: 12
|
||||
},
|
||||
status: {
|
||||
label: '开启状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
minWidth: 130
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:mail-template'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await MailTemplateApi.getMailTemplatePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await MailTemplateApi.getMailTemplate(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await MailTemplateApi.createMailTemplate(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await MailTemplateApi.updateMailTemplate(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await MailTemplateApi.deleteMailTemplate(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 发送短信按钮 */
|
||||
const testPopup = ref(false)
|
||||
const testFormData = ref<any>({})
|
||||
const testFormOption = ref({
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
span: 24,
|
||||
labelWidth: 120,
|
||||
column: {}
|
||||
})
|
||||
const textLoading = ref(false)
|
||||
const textFormRef = ref()
|
||||
// 打开测试表单
|
||||
const openSendForm = async (id: number) => {
|
||||
loading.value = true
|
||||
const formDatas = await MailTemplateApi.getMailTemplate(id)
|
||||
testFormData.value = {
|
||||
content: formDatas.content,
|
||||
params: formDatas.params,
|
||||
templateCode: formDatas.code,
|
||||
templateParams: {}
|
||||
}
|
||||
testFormOption.value.column = {
|
||||
content: {
|
||||
label: '模板内容',
|
||||
type: 'ueditor',
|
||||
disabled: true,
|
||||
className: 'mail-template-ueditor',
|
||||
customConfig: {
|
||||
toolbarKeys: []
|
||||
}
|
||||
},
|
||||
mail: {
|
||||
label: '收件邮箱',
|
||||
rules: [
|
||||
{ required: true, message: '收件邮箱不能为空', trigger: 'blur' },
|
||||
{
|
||||
type: 'email',
|
||||
message: t('profile.rules.truemail'),
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
// 设置动态参数
|
||||
if (formDatas.params?.length) {
|
||||
formDatas.params.forEach((item) => {
|
||||
testFormData.value.templateParams[item] = ''
|
||||
testFormOption.value.column[item] = {
|
||||
label: `参数 ${item}`,
|
||||
value: '',
|
||||
bind: `templateParams.${item}`,
|
||||
rules: [{ required: true, message: `参数 ${item} 不能为空`, trigger: 'blur' }]
|
||||
}
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
testPopup.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const testSubmitFun = () => {
|
||||
textFormRef.value.validate(async (valid, hide) => {
|
||||
if (!valid) return hide()
|
||||
try {
|
||||
textLoading.value = true
|
||||
const logId = await MailTemplateApi.sendMail({
|
||||
mail: testFormData.value.mail,
|
||||
templateCode: testFormData.value.templateCode,
|
||||
templateParams: testFormData.value.templateParams
|
||||
})
|
||||
if (logId) {
|
||||
message.success('提交发送成功!发送结果见邮件记录,编号:' + logId)
|
||||
}
|
||||
testPopup.value = false
|
||||
} finally {
|
||||
hide()
|
||||
textLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mail-template-popup {
|
||||
.el-form-item__label {
|
||||
height: auto;
|
||||
min-height: 32px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.mail-template-ueditor {
|
||||
.avue-ueditor {
|
||||
div:nth-child(2) {
|
||||
height: 200px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
732
src/views/system/menu/index.vue
Normal file
732
src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,732 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #icon-form="{ column, size, disabled }">
|
||||
<IconSelectInput
|
||||
v-model="tableForm.icon"
|
||||
:prop="column.prop"
|
||||
:column="column"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</template>
|
||||
<template #icon="{ row }">
|
||||
<Icon v-if="row.icon" :size="14" :icon="row.icon" />
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #parentId-form>
|
||||
<el-tree-select
|
||||
v-model="tableForm.parentId"
|
||||
:data="deptTree"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
placeholder="请选择上级菜单"
|
||||
value-key="deptId"
|
||||
style="width: 366px"
|
||||
/>
|
||||
</template>
|
||||
<template #nameI18n-form>
|
||||
<I18nInput v-model="tableForm.nameI18n" :def-name="tableForm.name"></I18nInput>
|
||||
</template>
|
||||
<!-- 自定义按钮 -->
|
||||
<template #menu-left>
|
||||
<el-button plain @click="expandAll = !expandAll" type="danger" icon="el-icon-sort"
|
||||
>展开/折叠</el-button
|
||||
>
|
||||
<el-button plain @click="refreshMenu">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
刷新菜单缓存
|
||||
</el-button>
|
||||
</template>
|
||||
<template #menu="{ row, size }">
|
||||
<el-button
|
||||
v-if="row.type === 1"
|
||||
icon="el-icon-circle-plus"
|
||||
:size="size"
|
||||
text
|
||||
type="primary"
|
||||
v-hasPermi="['system:menu:create']"
|
||||
@click="openForm(row)"
|
||||
>新增子级</el-button
|
||||
>
|
||||
<el-button v-if="row.type === 2" :size="size" text type="primary" @click="openBtnView(row)"
|
||||
>按钮管理</el-button
|
||||
>
|
||||
</template>
|
||||
</avue-crud>
|
||||
<DesignPopup
|
||||
v-model="btnDialog"
|
||||
:title="`【${currRow['name']}】按钮管理`"
|
||||
width="70%"
|
||||
:isBodyFull="true"
|
||||
>
|
||||
<template #default>
|
||||
<div class="h-100% p-20px box-border">
|
||||
<avue-crud
|
||||
ref="btnCrudRef"
|
||||
v-model="tableForm"
|
||||
:table-loading="btnLoading"
|
||||
:data="btnTableData"
|
||||
:option="btnTableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getBtnTableData"
|
||||
@selection-change="btnSelectionChange"
|
||||
>
|
||||
<template #menu-left="scope">
|
||||
<el-button
|
||||
plain
|
||||
:size="scope.size"
|
||||
@click="autoCreatedBtn"
|
||||
v-if="designOnlyVal.val"
|
||||
v-hasPermi="['system:menu:create']"
|
||||
>
|
||||
<Icon class="mr-2px" icon="ep:refresh" />
|
||||
<span>根据配置创建</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
:size="scope.size"
|
||||
:loading="selectLoading"
|
||||
@click="selectLowTable"
|
||||
v-else
|
||||
v-hasPermi="['system:menu:create']"
|
||||
>
|
||||
<Icon class="mr-2px" icon="ep:refresh" />
|
||||
<span>选择表单开发|报表设计创建</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="permission['delBtn']"
|
||||
type="danger"
|
||||
:size="scope.size"
|
||||
:disabled="btnTableSelect.length == 0"
|
||||
@click="btnBatchDel"
|
||||
>
|
||||
<Icon :size="14" icon="fluent:delete-16-regular" class="mr-3px" />批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</div>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ElMessageBox, ElSelect, ElOption } from 'element-plus'
|
||||
import { I18nInput } from '@/components/LowDesign/src/shareControl/index'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import * as TableApi from '@/api/design/table'
|
||||
import * as ReportApi from '@/api/design/report'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
defineOptions({ name: 'SystemMenu' })
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const expandAll = ref(false) // 默认展开所有表项
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
menuWidth: 260,
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 100,
|
||||
span: 24,
|
||||
rowKey: 'id',
|
||||
rowParentKey: 'parentId',
|
||||
defaultExpandAll: expandAll,
|
||||
virtualize: true,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
parentId: {
|
||||
label: '上级菜单',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '上级菜单不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '菜单名称',
|
||||
headerAlign: 'left',
|
||||
align: 'left',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
nameI18n: { label: '国际化配置', hide: true },
|
||||
type: {
|
||||
label: '菜单类型',
|
||||
type: 'radio',
|
||||
button: true,
|
||||
hide: true,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE).filter((item) => item.value !== 3),
|
||||
value: 1,
|
||||
control: (val) => {
|
||||
return {
|
||||
icon: { display: val !== 3 },
|
||||
path: { display: val !== 3 },
|
||||
visible: { display: val !== 3 },
|
||||
alwaysShow: { display: val !== 3 },
|
||||
component: { display: val === 2 },
|
||||
keepAlive: { display: val === 2 },
|
||||
componentName: { display: val === 2 },
|
||||
permission: { display: val !== 1 }
|
||||
}
|
||||
}
|
||||
},
|
||||
sort: {
|
||||
label: '排序',
|
||||
span: 12,
|
||||
type: 'number',
|
||||
width: 60,
|
||||
rules: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
icon: {
|
||||
label: '菜单图标',
|
||||
width: 85,
|
||||
span: 12,
|
||||
display: false
|
||||
},
|
||||
path: {
|
||||
label: '路由地址',
|
||||
hide: false,
|
||||
display: false,
|
||||
overHidden: true,
|
||||
labelTip: '如:`user`【顶级菜单下要以 / 开头】`/user`。外网地址以 `http(s)://` 开头',
|
||||
labelTipPlacement: 'right-end',
|
||||
rules: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }]
|
||||
},
|
||||
permission: {
|
||||
label: '权限标识',
|
||||
display: false,
|
||||
minWidth: 90,
|
||||
labelTip:
|
||||
'Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission("system:user:list")`)',
|
||||
labelTipPlacement: 'right-end'
|
||||
},
|
||||
component: {
|
||||
label: '组件路径',
|
||||
placeholder: '例如:system/user/index',
|
||||
overHidden: true,
|
||||
display: false,
|
||||
minWidth: 90,
|
||||
prepend: 'src/views/'
|
||||
},
|
||||
componentName: {
|
||||
label: '组件名称',
|
||||
placeholder: '例如:SystemUser',
|
||||
display: false,
|
||||
hide: true,
|
||||
minWidth: 90
|
||||
},
|
||||
status: {
|
||||
label: '菜单状态',
|
||||
width: 85,
|
||||
search: true,
|
||||
type: 'radio',
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '菜单状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
visible: {
|
||||
label: '显示状态',
|
||||
type: 'radio',
|
||||
border: true,
|
||||
display: false,
|
||||
hide: true,
|
||||
dicData: [
|
||||
{ label: '显示', value: true },
|
||||
{ label: '隐藏', value: false }
|
||||
],
|
||||
value: true,
|
||||
labelTip: '选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问',
|
||||
labelTipPlacement: 'right-end'
|
||||
},
|
||||
alwaysShow: {
|
||||
label: '总是显示',
|
||||
type: 'radio',
|
||||
border: true,
|
||||
display: false,
|
||||
hide: true,
|
||||
dicData: [
|
||||
{ label: '总是', value: true },
|
||||
{ label: '不是', value: false }
|
||||
],
|
||||
value: true,
|
||||
labelTip: '选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单',
|
||||
labelTipPlacement: 'right-end'
|
||||
},
|
||||
keepAlive: {
|
||||
label: '缓存状态',
|
||||
type: 'radio',
|
||||
border: true,
|
||||
display: false,
|
||||
hide: true,
|
||||
dicData: [
|
||||
{ label: '缓存', value: true },
|
||||
{ label: '不缓存', value: false }
|
||||
],
|
||||
value: true,
|
||||
labelTip: '选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段',
|
||||
labelTipPlacement: 'right-end'
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref()
|
||||
const tableSearch = ref({})
|
||||
const deptTree = ref() // 上级部门
|
||||
|
||||
const btnDialog = ref(false)
|
||||
const btnLoading = ref(false)
|
||||
const currRow = ref({})
|
||||
const btnTableData = ref([])
|
||||
const btnTableSelect = ref([])
|
||||
const btnTableOption = reactive({
|
||||
dialogWidth: '50%',
|
||||
calcHeight: 200,
|
||||
selection: true,
|
||||
reserveSelection: true,
|
||||
column: {
|
||||
name: {
|
||||
label: '按钮名称',
|
||||
row: true,
|
||||
rules: [{ required: true, message: '按钮名称不能为空', trigger: 'blur' }],
|
||||
span: 24
|
||||
},
|
||||
permission: {
|
||||
...tableOption.column.permission,
|
||||
span: 24,
|
||||
display: true,
|
||||
rules: [{ required: true, message: '权限标识不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sort: { ...tableOption.column.sort, span: 24 },
|
||||
status: {
|
||||
label: '按钮状态',
|
||||
type: 'radio',
|
||||
span: 24,
|
||||
width: 100,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '按钮状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
}
|
||||
}
|
||||
})
|
||||
const permission = getCurrPermi(['system:menu'])
|
||||
const crudRef = ref()
|
||||
const btnCrudRef = ref()
|
||||
|
||||
const designOnlyVal = computed(() => {
|
||||
let val = ''
|
||||
let type = ''
|
||||
const path = currRow.value['path'].split('?')[0] || ''
|
||||
if (path.indexOf('table/view/') === 0) {
|
||||
const pathList = path.split('/')
|
||||
const len = pathList.length - 1
|
||||
if (/\d$/.test(pathList[len])) {
|
||||
val = pathList[len]
|
||||
type = 'table'
|
||||
}
|
||||
} else if (path.indexOf('report/view/') === 0) {
|
||||
const pathList = path.split('/')
|
||||
const len = pathList.length - 1
|
||||
if (pathList[len]) {
|
||||
val = pathList[len]
|
||||
type = 'report'
|
||||
}
|
||||
}
|
||||
return { val, type }
|
||||
})
|
||||
|
||||
useCrudHeight([crudRef, btnCrudRef])
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
btnDisplay: 1
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await MenuApi.getMenuList(searchObj)
|
||||
tableData.value = handleTree(data)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
await getTree()
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await MenuApi.getMenu(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
if (btnDialog.value) {
|
||||
form.parentId = currRow.value['id']
|
||||
form.type = 3
|
||||
}
|
||||
let bool = await MenuApi.createMenu(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
if (btnDialog.value) getBtnTableData()
|
||||
else resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await MenuApi.updateMenu(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
if (btnDialog.value) getBtnTableData()
|
||||
else getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await MenuApi.deleteMenu(form.id ? [form.id] : form.ids, form.type)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
if (btnDialog.value) getBtnTableData()
|
||||
else getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const openForm = (form) => {
|
||||
tableForm.value.parentId = form.id
|
||||
crudRef.value.rowAdd()
|
||||
}
|
||||
|
||||
/** 刷新菜单缓存按钮操作 */
|
||||
const refreshMenu = async () => {
|
||||
try {
|
||||
await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
|
||||
// 清空,从而触发刷新
|
||||
wsCache.delete(CACHE_KEY.USER)
|
||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
||||
// 刷新浏览器
|
||||
location.reload()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 获得部门树 */
|
||||
const getTree = async () => {
|
||||
deptTree.value = []
|
||||
const data = await MenuApi.getSimpleMenusList()
|
||||
let dept: Tree = { id: 0, name: '顶级菜单', children: [] }
|
||||
dept.children = handleTree(data)
|
||||
deptTree.value.push(dept)
|
||||
}
|
||||
|
||||
const openBtnView = (row) => {
|
||||
currRow.value = row
|
||||
btnDialog.value = true
|
||||
getBtnTableData()
|
||||
}
|
||||
|
||||
const btnSelectionChange = (data) => {
|
||||
btnTableSelect.value = data
|
||||
}
|
||||
const btnBatchDel = () => {
|
||||
const ids = btnTableSelect.value.map((item) => item['id'])
|
||||
rowDel({ ids, type: '3' })
|
||||
}
|
||||
|
||||
const getBtnTableData = async () => {
|
||||
btnLoading.value = true
|
||||
let searchObj = {
|
||||
parentId: currRow.value['id']
|
||||
}
|
||||
try {
|
||||
const data = await MenuApi.getMenuList(searchObj)
|
||||
btnTableData.value = data
|
||||
btnTableData.value = data
|
||||
} finally {
|
||||
btnLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const autoCreatedBtn = async () => {
|
||||
let createData: any = {}
|
||||
const type = designOnlyVal.value.type
|
||||
const defaultMenu = { sort: 1, status: 0, type: 3, parentId: currRow.value['id'] }
|
||||
if (type == 'table') {
|
||||
createData = await createLowTable(defaultMenu, designOnlyVal.value.val)
|
||||
} else if (type == 'report') {
|
||||
createData = await createLowReport(defaultMenu, designOnlyVal.value.val)
|
||||
}
|
||||
const len = createData.apiData.length
|
||||
if (len) {
|
||||
await MenuApi.batchCreateMenu(createData.apiData)
|
||||
getBtnTableData()
|
||||
} else btnLoading.value = false
|
||||
const msg = len
|
||||
? `已创建 <span style="color:#409EFF">${len}</span> 条数据`
|
||||
: `未发现需要创建的按钮数据`
|
||||
const tip = `<div style="font-size:12px;display:${createData.tipData?.length ? 'block' : 'none'}">
|
||||
发现不存在的按钮: ${createData.tipData?.join('、')}
|
||||
<span style="color:#E6A23C">(注:如不需要请自行删除)</span>
|
||||
</div>`
|
||||
message.alert(
|
||||
`<div>
|
||||
<div>${msg}</div>
|
||||
${tip}
|
||||
</div>`,
|
||||
'提示',
|
||||
{ dangerouslyUseHTMLString: true }
|
||||
)
|
||||
}
|
||||
const createLowTable = (
|
||||
defaultMenu,
|
||||
onlyVal,
|
||||
isMsg = true
|
||||
): Promise<{ apiData: object[]; tipData: string[]; type: string }> => {
|
||||
return new Promise(async (resolve) => {
|
||||
if (isMsg) await message.confirm('是否确定根据表单开发配置创建按钮数据?')
|
||||
btnLoading.value = true
|
||||
const data = await TableApi.getWebConfig(onlyVal)
|
||||
const permissionKey = 'jeelowcode:dbform-data'
|
||||
const defaultBtnObj = {
|
||||
query: { name: '数据查询', permission: 'query' },
|
||||
addBtn: { name: '数据创建', permission: 'create' },
|
||||
editBtn: { name: '数据更新', permission: 'update' },
|
||||
delBtn: { name: '数据删除', permission: 'delete' },
|
||||
exportBtn: { name: '数据导出', permission: 'export' },
|
||||
importBtn: { name: '数据导入', permission: 'import' }
|
||||
}
|
||||
|
||||
const btnData: Array<any> = []
|
||||
const basicBtn = data.dbForm.basicFunction.split(',')
|
||||
for (const key in defaultBtnObj) {
|
||||
const { name, permission } = defaultBtnObj[key]
|
||||
const btnName = defaultMenu.postfix ? `${name}[${data.dbForm.tableDescribe}]` : name
|
||||
if (key == 'query' || basicBtn.includes(key))
|
||||
btnData.push({
|
||||
...defaultMenu,
|
||||
name: btnName,
|
||||
permission: `${permissionKey}:${permission}:${onlyVal}`
|
||||
})
|
||||
}
|
||||
data.buttonList.forEach((item) => {
|
||||
if (item.buttonShow == 'Y' && item.buttonAuth == 'Y') {
|
||||
const btnName = defaultMenu.postfix
|
||||
? `${item.buttonName}[${data.dbForm.tableDescribe}]`
|
||||
: item.buttonName
|
||||
btnData.push({
|
||||
...defaultMenu,
|
||||
name: btnName,
|
||||
permission: `${permissionKey}:${item.buttonCode}:${onlyVal}`
|
||||
})
|
||||
}
|
||||
})
|
||||
const btnName = btnData.map((item) => item.name)
|
||||
const tipName: string[] = []
|
||||
const nameList: string[] = btnTableData.value.map((item) => {
|
||||
if (!btnName.includes(item['name'])) tipName.push(item['name'])
|
||||
return item['name']
|
||||
})
|
||||
const apiData = btnData.filter((item) => !nameList.includes(item.name))
|
||||
|
||||
resolve({ apiData, tipData: tipName, type: 'table' })
|
||||
})
|
||||
}
|
||||
const createLowReport = (
|
||||
defaultMenu,
|
||||
onlyVal,
|
||||
isMsg = true
|
||||
): Promise<{ apiData: object[]; type: string }> => {
|
||||
return new Promise(async (resolve) => {
|
||||
if (isMsg) await message.confirm('是否确定根据报表配置创建按钮数据?')
|
||||
const permissionKey = 'jeelowcode:report-data'
|
||||
const defaultData = [
|
||||
{ name: '数据查询', permission: `${permissionKey}:query:${onlyVal}` },
|
||||
{ name: '数据导出', permission: `${permissionKey}:export:${onlyVal}` }
|
||||
]
|
||||
const keys: string[] = btnTableData.value.map((item) => item['permission'])
|
||||
const apiData: any = []
|
||||
defaultData.forEach((item) => {
|
||||
if (!keys.includes(item.permission)) {
|
||||
const btnName = defaultMenu.postfix
|
||||
? `${item.name}[${reportObj.value[onlyVal]}]`
|
||||
: item.name
|
||||
apiData.push({ ...defaultMenu, ...item, name: btnName })
|
||||
}
|
||||
})
|
||||
resolve({ apiData, type: 'report' })
|
||||
})
|
||||
}
|
||||
|
||||
const table_ids = ref<number[]>([])
|
||||
const report_ids = ref<string[]>([])
|
||||
const reportObj = ref({})
|
||||
const selectLoading = ref(false)
|
||||
const selectLowTable = async () => {
|
||||
selectLoading.value = true
|
||||
table_ids.value = []
|
||||
report_ids.value = []
|
||||
const tableList = await TableApi.getAllDbDicData({ onlyTableName: 'Y' })
|
||||
const reportRes = await ReportApi.getDbList({})
|
||||
selectLoading.value = false
|
||||
ElMessageBox({
|
||||
title: '请选择页面使用的表单开发|报表设计',
|
||||
message: () =>
|
||||
h('div', { class: 'flex flex-col mt-10px mb-5px' }, [
|
||||
h('div', { class: 'flex items-center' }, [
|
||||
h('div', '表单开发:'),
|
||||
h(
|
||||
ElSelect,
|
||||
{
|
||||
modelValue: table_ids.value,
|
||||
'onUpdate:modelValue': (val: number[]) => {
|
||||
table_ids.value = val
|
||||
},
|
||||
class: 'my-0!',
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
style: { width: '300px', margin: '20px 0px' }
|
||||
},
|
||||
() =>
|
||||
tableList.map((item) =>
|
||||
h(ElOption, {
|
||||
label: `${item.tableDescribe}[${item.tableId}]`,
|
||||
value: item.tableId
|
||||
})
|
||||
)
|
||||
)
|
||||
]),
|
||||
h('div', { class: 'flex items-center mt-10px' }, [
|
||||
h('div', '报表设计:'),
|
||||
h(
|
||||
ElSelect,
|
||||
{
|
||||
modelValue: report_ids.value,
|
||||
'onUpdate:modelValue': (val: string[]) => {
|
||||
report_ids.value = val
|
||||
},
|
||||
class: 'my-0!',
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
style: { width: '300px', margin: '20px 0px' }
|
||||
},
|
||||
() =>
|
||||
reportRes.records.map((item) => {
|
||||
reportObj.value[item.reportCode] = item.reportName
|
||||
return h(ElOption, {
|
||||
label: item.reportName,
|
||||
value: item.reportCode
|
||||
})
|
||||
})
|
||||
)
|
||||
])
|
||||
]),
|
||||
showCancelButton: true,
|
||||
closeOnClickModal: false,
|
||||
beforeClose: async (action, instance, done) => {
|
||||
if (action == 'confirm') {
|
||||
instance.confirmButtonLoading = true
|
||||
const apiList: any[] = []
|
||||
const promiseArr: Promise<{ apiData: object[]; type: string }>[] = []
|
||||
const defaultMenu = {
|
||||
sort: 1,
|
||||
status: 0,
|
||||
type: 3,
|
||||
parentId: currRow.value['id'],
|
||||
postfix: true
|
||||
}
|
||||
table_ids.value.forEach((tableId) => {
|
||||
promiseArr.push(createLowTable(cloneDeep(defaultMenu), tableId, false))
|
||||
})
|
||||
report_ids.value.forEach((reportId) => {
|
||||
promiseArr.push(createLowReport(cloneDeep(defaultMenu), reportId, false))
|
||||
})
|
||||
const resList = await Promise.all(promiseArr)
|
||||
resList.forEach((res) => apiList.push(...res.apiData))
|
||||
const len = apiList.length
|
||||
if (len) {
|
||||
await MenuApi.batchCreateMenu(apiList)
|
||||
getBtnTableData()
|
||||
}
|
||||
instance.confirmButtonLoading = false
|
||||
const msg = len
|
||||
? `已创建 <span style="color:#409EFF">${len}</span> 条数据`
|
||||
: `未发现需要创建的按钮数据`
|
||||
|
||||
message.alert(`<div>${msg}</div>`, '提示', { dangerouslyUseHTMLString: true })
|
||||
}
|
||||
|
||||
done()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(expandAll, () => {
|
||||
crudRef.value.refreshTable()
|
||||
})
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
238
src/views/system/notice/index.vue
Normal file
238
src/views/system/notice/index.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_NOTICE_TYPE"
|
||||
:value="scope.row.type ? scope.row.type : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
v-if="row.status !== 1"
|
||||
class="ml-4px!"
|
||||
link
|
||||
@click="handlePush(row.id)"
|
||||
type="primary"
|
||||
>
|
||||
<Icon :size="14" icon="ep:document"></Icon>
|
||||
<span>推送</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as NoticeApi from '@/api/system/notice'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemDictType' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '70%',
|
||||
column: {
|
||||
title: {
|
||||
label: '公告标题',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }]
|
||||
},
|
||||
content: {
|
||||
label: '公告内容',
|
||||
minWidth: 120,
|
||||
rules: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!value || !value.replace(/<[\s\S]*?>|[' ']/g, '')) {
|
||||
callback(new Error('公告内容不能为空'))
|
||||
} else callback()
|
||||
},
|
||||
required: true,
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
hide: true,
|
||||
type: 'ueditor'
|
||||
},
|
||||
type: {
|
||||
label: '公告类型',
|
||||
type: 'select',
|
||||
span: 12,
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE),
|
||||
rules: [{ required: true, message: '公告类型不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
width: 80,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:notice'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await NoticeApi.getNoticePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await NoticeApi.getNotice(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await NoticeApi.createNotice(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await NoticeApi.updateNotice(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await NoticeApi.deleteNotice(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 推送按钮操作 */
|
||||
const handlePush = async (id: number) => {
|
||||
try {
|
||||
// 推送的二次确认
|
||||
await message.confirm('是否推送所选中通知?')
|
||||
// 发起推送
|
||||
await NoticeApi.pushNotice(id)
|
||||
message.success(t('推送成功'))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
231
src/views/system/notify/message/index.vue
Normal file
231
src/views/system/notify/message/index.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #readStatus="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="scope.row.readStatus != undefined ? scope.row.readStatus : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #templateType="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE"
|
||||
:value="scope.row.templateType != undefined ? scope.row.templateType : ''"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #templateParams-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">{{ JSON.stringify(value) }}</div>
|
||||
</template>
|
||||
<template #readStatus-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #templateType-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as NotifyMessageApi from '@/api/system/notify/message'
|
||||
defineOptions({ name: 'SystemNotifyMessage' })
|
||||
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 160,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
id: {
|
||||
label: '编号',
|
||||
width: 80
|
||||
},
|
||||
userId: {
|
||||
label: '用户编号',
|
||||
search: true,
|
||||
minWidth: 90
|
||||
},
|
||||
templateId: {
|
||||
label: '模板编号',
|
||||
minWidth: 90,
|
||||
hide: true
|
||||
},
|
||||
templateCode: {
|
||||
label: '模板编码',
|
||||
minWidth: 90,
|
||||
search: true
|
||||
},
|
||||
templateNickname: {
|
||||
label: '发送人名称',
|
||||
minWidth: 100
|
||||
},
|
||||
templateContent: {
|
||||
label: '模版内容',
|
||||
width: 200,
|
||||
overHidden: true
|
||||
},
|
||||
templateParams: {
|
||||
label: '模版参数',
|
||||
width: 200,
|
||||
overHidden: true
|
||||
},
|
||||
templateType: {
|
||||
label: '模版类型',
|
||||
search: true,
|
||||
type: 'select',
|
||||
minWidth: 100,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE),
|
||||
value: ''
|
||||
},
|
||||
readStatus: {
|
||||
label: '是否已读',
|
||||
type: 'select',
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING),
|
||||
value: ''
|
||||
},
|
||||
readTime: {
|
||||
label: '阅读时间',
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
searchCreateTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:notify-message'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.searchCreateTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.searchCreateTime)
|
||||
}
|
||||
delete searchObj.searchCreateTime
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await NotifyMessageApi.getNotifyMessagePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['view'].includes(type) && tableForm.value.id) {
|
||||
// tableForm.value = await NotifyMessageApi.getDictType(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
248
src/views/system/notify/my/index.vue
Normal file
248
src/views/system/notify/my/index.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:before-open="beforeOpen"
|
||||
@selection-change="selectionChange"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button @click="handleUpdateList" :size="size" :disabled="!tableSelect.length">
|
||||
<Icon icon="ep:reading" class="mr-5px" /> 设置已读
|
||||
</el-button>
|
||||
<el-button @click="handleUpdateAll" :size="size">
|
||||
<Icon icon="ep:reading" class="mr-5px" /> 全部已读
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 表格 -->
|
||||
<template #readStatus="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="scope.row.readStatus != undefined ? scope.row.readStatus : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #templateType="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE"
|
||||
:value="scope.row.templateType != undefined ? scope.row.templateType : ''"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #readStatus-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.INFRA_BOOLEAN_STRING"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #templateType-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as NotifyMessageApi from '@/api/system/notify/message'
|
||||
defineOptions({ name: 'SystemNotifyMessage' })
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '查看',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 160,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
selection: true,
|
||||
reserveSelection: true,
|
||||
column: {
|
||||
templateNickname: {
|
||||
label: '发送人',
|
||||
width: 140
|
||||
},
|
||||
searchCreateTime: {
|
||||
label: '发送时间',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
createTime: {
|
||||
label: '发送时间',
|
||||
searchRange: true,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
templateType: {
|
||||
label: '类型',
|
||||
width: 120,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE)
|
||||
},
|
||||
templateContent: {
|
||||
label: '消息内容',
|
||||
type: 'textarea',
|
||||
overHidden: true
|
||||
},
|
||||
readStatus: {
|
||||
label: '是否已读',
|
||||
type: 'select',
|
||||
width: 90,
|
||||
search: true,
|
||||
dicData: getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)
|
||||
},
|
||||
readTime: {
|
||||
label: '阅读时间',
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
viewDisplay: true,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
},
|
||||
selectable: (row) => {
|
||||
return !row.readStatus
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const tableSelect = ref<any[]>([])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 标记一条站内信已读 */
|
||||
const handleReadOne = async (id) => {
|
||||
loading.value = true
|
||||
await NotifyMessageApi.updateNotifyMessageRead(id)
|
||||
await getTableData()
|
||||
}
|
||||
|
||||
/** 标记全部站内信已读 **/
|
||||
const handleUpdateAll = async () => {
|
||||
loading.value = true
|
||||
await NotifyMessageApi.updateAllNotifyMessageRead()
|
||||
message.success('全部已读成功!')
|
||||
crudRef.value.clearSelection()
|
||||
await getTableData()
|
||||
}
|
||||
|
||||
/** 标记一些站内信已读 **/
|
||||
const handleUpdateList = async () => {
|
||||
if (tableSelect.value.length === 0) {
|
||||
return
|
||||
}
|
||||
const ids = tableSelect.value.map((item) => item.id)
|
||||
await NotifyMessageApi.updateNotifyMessageRead(ids)
|
||||
message.success('批量已读成功!')
|
||||
crudRef.value.clearSelection()
|
||||
await getTableData()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.searchCreateTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.searchCreateTime)
|
||||
}
|
||||
delete searchObj.searchCreateTime
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await NotifyMessageApi.getMyNotifyMessagePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['view'].includes(type) && !tableForm.value.readStatus) {
|
||||
handleReadOne(tableForm.value.id)
|
||||
}
|
||||
tableOption.column.readTime.viewDisplay = tableForm.value.readStatus
|
||||
done()
|
||||
}
|
||||
|
||||
const selectionChange = (data) => {
|
||||
tableSelect.value = data
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
346
src/views/system/notify/template/index.vue
Normal file
346
src/views/system/notify/template/index.vue
Normal file
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE"
|
||||
:value="scope.row.type ? scope.row.type : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
v-if="row.status === 0"
|
||||
class="ml-4px!"
|
||||
v-hasPermi="['system:notify-template:send-notify']"
|
||||
link
|
||||
@click="openSendForm(row)"
|
||||
type="primary"
|
||||
>
|
||||
<Icon :size="14" icon="ep:document"></Icon>
|
||||
<span>测试</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:测试发送 -->
|
||||
<DesignPopup
|
||||
v-model="testPopup"
|
||||
title="发送站内信模板测试"
|
||||
:is-footer="true"
|
||||
width="40%"
|
||||
class="notify-template-popup"
|
||||
>
|
||||
<div class="p-20px">
|
||||
<avue-form ref="textFormRef" v-if="testPopup" v-model="testFormData" :option="testFormOption">
|
||||
<template #userId="{ column, disabled }">
|
||||
<UserSelect
|
||||
v-model="testFormData.userId"
|
||||
:column="column"
|
||||
:disabled="disabled"
|
||||
type="edit"
|
||||
prop="userId"
|
||||
></UserSelect>
|
||||
</template>
|
||||
</avue-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :loading="textLoading" type="primary" @click="testSubmitFun">发 送</el-button>
|
||||
<el-button @click="testPopup = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as NotifyTemplateApi from '@/api/system/notify/template'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'NotifySmsTemplate' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 100,
|
||||
span: 24,
|
||||
dialogWidth: '55%',
|
||||
overHidden: true,
|
||||
searchLabelWidth: 90,
|
||||
column: {
|
||||
code: {
|
||||
label: '模板编码',
|
||||
minWidth: 90,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '模板名称',
|
||||
search: true,
|
||||
span: 12,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
nickname: {
|
||||
label: '发送人名称',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '发送人名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
content: {
|
||||
label: '模板内容',
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }],
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4
|
||||
},
|
||||
type: {
|
||||
label: '类型',
|
||||
type: 'select',
|
||||
span: 12,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE),
|
||||
rules: [{ required: true, message: '类型不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
span: 12,
|
||||
width: 80,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
type: 'textarea',
|
||||
minWidth: 130,
|
||||
minRows: 2,
|
||||
maxRows: 4
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:notify-template'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await NotifyTemplateApi.getNotifyTemplatePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await NotifyTemplateApi.getNotifyTemplate(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await NotifyTemplateApi.createNotifyTemplate(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await NotifyTemplateApi.updateNotifyTemplate(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await NotifyTemplateApi.deleteNotifyTemplate(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 发送站内信按钮 */
|
||||
// 测试表单配置
|
||||
const testPopup = ref(false)
|
||||
const testFormData = ref<any>({})
|
||||
const testFormOption = ref({
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
span: 24,
|
||||
labelWidth: 120,
|
||||
column: {}
|
||||
})
|
||||
const textLoading = ref(false)
|
||||
const textFormRef = ref()
|
||||
// 打开测试表单
|
||||
const openSendForm = async (row: NotifyTemplateApi.NotifyTemplateVO) => {
|
||||
loading.value = true
|
||||
const formDatas = await NotifyTemplateApi.getNotifyTemplate(row.id || 0)
|
||||
testFormData.value = {
|
||||
content: formDatas.content,
|
||||
params: formDatas.params,
|
||||
templateCode: formDatas.code,
|
||||
templateParams: {}
|
||||
}
|
||||
testFormOption.value.column = {
|
||||
content: { label: '模板内容', type: 'textarea', disabled: true },
|
||||
userId: {
|
||||
label: '接收人',
|
||||
findType: 'all',
|
||||
multiple: true,
|
||||
columnKey: ['sex', 'post', 'deptName'],
|
||||
rules: [{ required: true, message: `接收人不能为空`, trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
// 设置动态参数
|
||||
if (formDatas.params?.length) {
|
||||
formDatas.params.forEach((item) => {
|
||||
testFormData.value.templateParams[item] = ''
|
||||
testFormOption.value.column[item] = {
|
||||
label: `参数 ${item} `,
|
||||
value: '',
|
||||
bind: `templateParams.${item}`,
|
||||
rules: [{ required: true, message: `参数 ${item} 不能为空`, trigger: 'blur' }]
|
||||
}
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
testPopup.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const testSubmitFun = () => {
|
||||
textFormRef.value.validate(async (valid, hide) => {
|
||||
if (!valid) return hide()
|
||||
try {
|
||||
textLoading.value = true
|
||||
await NotifyTemplateApi.sendNotify({
|
||||
userIdList: testFormData.value.userId.split(','),
|
||||
userType: 2,
|
||||
templateCode: testFormData.value.templateCode,
|
||||
templateParams: testFormData.value.templateParams
|
||||
})
|
||||
testPopup.value = false
|
||||
} finally {
|
||||
hide()
|
||||
textLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.notify-template-popup {
|
||||
.el-form-item__label {
|
||||
height: auto;
|
||||
min-height: 32px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
333
src/views/system/oauth2/client/index.vue
Normal file
333
src/views/system/oauth2/client/index.vue
Normal file
@@ -0,0 +1,333 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #authorizedGrantTypes="scope">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(authorizedGrantType, index) in scope.row.authorizedGrantTypes"
|
||||
:index="index"
|
||||
class="mr-5px"
|
||||
>
|
||||
{{ authorizedGrantType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #scopes-form="scope">
|
||||
<el-select
|
||||
v-model="tableForm.scopes"
|
||||
filterable
|
||||
multiple
|
||||
allow-create
|
||||
placeholder="请输入授权范围"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="item in scope.value" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</template>
|
||||
<template #redirectUris-form="scope">
|
||||
<el-select
|
||||
v-model="tableForm.redirectUris"
|
||||
filterable
|
||||
multiple
|
||||
allow-create
|
||||
placeholder="请输入可重定向的 URI 地址"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="item in scope.value" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions, getDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as ClientApi from '@/api/system/oauth2/client'
|
||||
|
||||
defineOptions({ name: 'SystemOAuth2Client' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
labelWidth: 160,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '80%',
|
||||
menuWidth: 160,
|
||||
column: {
|
||||
clientId: {
|
||||
label: '客户端编号',
|
||||
minWidth: 120,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '客户端编号不能为空', trigger: 'blur' }]
|
||||
},
|
||||
secret: {
|
||||
label: '客户端密钥',
|
||||
minWidth: 100,
|
||||
span: 12,
|
||||
rules: [{ required: true, message: '客户端密钥不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '应用名',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '应用名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
logo: {
|
||||
label: '应用图标',
|
||||
span: 24,
|
||||
minWidth: 90,
|
||||
fileType: 'img',
|
||||
listType: 'picture-img',
|
||||
type: 'upload',
|
||||
action: '/infra/file/jeelowcode/upload',
|
||||
data: { updateSupport: 0 },
|
||||
propsHttp: { url: 'fileUrl' }
|
||||
},
|
||||
description: {
|
||||
label: '应用描述',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
hide: true
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
span: 24,
|
||||
width: 80,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
value: CommonStatusEnum.ENABLE,
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
},
|
||||
accessTokenValiditySeconds: {
|
||||
label: '访问令牌的有效期',
|
||||
controlsPosition: '',
|
||||
type: 'number',
|
||||
span: 12,
|
||||
width: 90,
|
||||
value: 30 * 60,
|
||||
tip: '单位:秒',
|
||||
rules: [{ required: true, message: '访问令牌的有效期不能为空', trigger: 'blur' }]
|
||||
},
|
||||
refreshTokenValiditySeconds: {
|
||||
label: '刷新令牌的有效期',
|
||||
controlsPosition: '',
|
||||
type: 'number',
|
||||
span: 12,
|
||||
value: 30 * 24 * 60,
|
||||
tip: '单位:秒',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '刷新令牌的有效期不能为空', trigger: 'blur' }]
|
||||
},
|
||||
authorizedGrantTypes: {
|
||||
label: '授权类型',
|
||||
clearable: false,
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
minWidth: 130,
|
||||
span: 12,
|
||||
dicData: getDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE),
|
||||
rules: [{ required: true, message: '授权类型不能为空', trigger: 'blur' }]
|
||||
},
|
||||
scopes: {
|
||||
label: '授权范围',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
span: 12,
|
||||
hide: true,
|
||||
control: (val) => {
|
||||
let dicData = []
|
||||
if (val?.length) {
|
||||
dicData = val.map((item) => {
|
||||
return { label: item, value: item }
|
||||
})
|
||||
}
|
||||
return {
|
||||
autoApproveScopes: { dicData }
|
||||
}
|
||||
}
|
||||
},
|
||||
autoApproveScopes: {
|
||||
label: '自动授权范围',
|
||||
type: 'select',
|
||||
span: 12,
|
||||
multiple: true,
|
||||
hide: true,
|
||||
dicData: []
|
||||
},
|
||||
redirectUris: {
|
||||
label: '可重定向的 URI 地址',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
span: 12,
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '可重定向的 URI 地址不能为空', trigger: 'blur' }]
|
||||
},
|
||||
authorities: {
|
||||
label: '权限',
|
||||
span: 12,
|
||||
hide: true
|
||||
},
|
||||
resourceIds: {
|
||||
label: '资源',
|
||||
span: 12,
|
||||
hide: true
|
||||
},
|
||||
additionalInformation: {
|
||||
label: '附加信息',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
hide: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:oauth2-client'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await ClientApi.getOAuth2ClientPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await ClientApi.getOAuth2Client(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await ClientApi.createOAuth2Client(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await ClientApi.updateOAuth2Client(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ClientApi.deleteOAuth2Client(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
192
src/views/system/oauth2/token/index.vue
Normal file
192
src/views/system/oauth2/token/index.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #type="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleForceLogout(row.accessToken)"
|
||||
v-hasPermi="['system:oauth2-token:delete']"
|
||||
>
|
||||
强退
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as OAuth2AccessTokenApi from '@/api/system/oauth2/token'
|
||||
|
||||
defineOptions({ name: 'SystemTokenClient' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
accessToken: {
|
||||
label: '访问令牌',
|
||||
search: true,
|
||||
minWidth: 200
|
||||
},
|
||||
refreshToken: {
|
||||
label: '刷新令牌',
|
||||
minWidth: 200
|
||||
},
|
||||
userId: {
|
||||
label: '用户编号',
|
||||
search: true,
|
||||
width: 90
|
||||
},
|
||||
expiresTime: {
|
||||
label: '过期时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:oauth2-token'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await OAuth2AccessTokenApi.getAccessTokenPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await OAuth2AccessTokenApi.deleteAccessToken(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 强制退出操作 */
|
||||
const handleForceLogout = async (accessToken: string) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('是否要强制退出用户')
|
||||
// 发起删除
|
||||
await OAuth2AccessTokenApi.deleteAccessToken(accessToken)
|
||||
message.success(t('common.success'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
274
src/views/system/operatelog/index.vue
Normal file
274
src/views/system/operatelog/index.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:operate-log:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<!-- 表格 -->
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_OPERATE_TYPE"
|
||||
:value="scope.row.type != undefined ? scope.row.type : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #resultCode="scope">
|
||||
<span>{{ scope.row.resultCode === 0 ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
<template #duration="scope">
|
||||
<span>{{ scope.row.duration }} ms</span>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #resultCode-form="scope">
|
||||
<span>{{ scope.value === 0 ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
<template #requestUrl-form="scope">
|
||||
<span>{{ tableForm.requestMethod }} {{ scope.value }}</span>
|
||||
</template>
|
||||
<template #duration-form="scope">
|
||||
<span>{{ scope.value }} ms</span>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as OperateLogApi from '@/api/system/operatelog'
|
||||
|
||||
defineOptions({ name: 'SystemOperateLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
traceId: {
|
||||
label: '链路追踪',
|
||||
hide: true
|
||||
},
|
||||
userId: {
|
||||
label: '操作人编号',
|
||||
hide: true
|
||||
},
|
||||
userIp: {
|
||||
label: '操作人 IP',
|
||||
hide: true
|
||||
},
|
||||
userAgent: {
|
||||
label: '操作人 UA',
|
||||
hide: true,
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4
|
||||
},
|
||||
module: {
|
||||
label: '操作模块',
|
||||
search: true,
|
||||
minWidth: 90
|
||||
},
|
||||
name: {
|
||||
label: '操作名',
|
||||
minWidth: 90
|
||||
},
|
||||
content: {
|
||||
label: '操作内容',
|
||||
hide: true
|
||||
},
|
||||
exts: {
|
||||
label: '操作拓展参数',
|
||||
hide: true
|
||||
},
|
||||
requestMethod: {
|
||||
label: '请求方式',
|
||||
display: false,
|
||||
hide: true
|
||||
},
|
||||
requestUrl: {
|
||||
label: '请求 URL',
|
||||
hide: true
|
||||
},
|
||||
javaMethod: {
|
||||
label: 'Java方法名',
|
||||
hide: true
|
||||
},
|
||||
javaMethodArgs: {
|
||||
label: 'Java方法参数',
|
||||
hide: true,
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4
|
||||
},
|
||||
type: {
|
||||
label: '操作类型',
|
||||
width: 90,
|
||||
search: true,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_OPERATE_TYPE),
|
||||
value: ''
|
||||
},
|
||||
userNickname: {
|
||||
label: '操作人',
|
||||
search: true
|
||||
},
|
||||
resultCode: {
|
||||
label: '操作状态',
|
||||
width: 90,
|
||||
type: 'select'
|
||||
},
|
||||
searchStartTime: {
|
||||
label: '操作时间',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
startTime: {
|
||||
label: '操作时间',
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
label: '执行时长',
|
||||
minWidth: 90
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:operate-log'])
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.searchStartTime?.length) {
|
||||
searchObj.startTime = getSearchDate(searchObj.searchStartTime)
|
||||
}
|
||||
delete searchObj.searchStartTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await OperateLogApi.getOperateLogPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['view'].includes(type) && tableForm.value.id) {
|
||||
// tableForm.value = await OperateLogApi.getDictType(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await OperateLogApi.exportOperateLog(searchObj)
|
||||
download.excel(data, '操作日志.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
232
src/views/system/position/index.vue
Normal file
232
src/views/system/position/index.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:before-open="beforeOpen"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:position:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as PositionApi from '@/api/system/position'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemPosition' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '职位编号',
|
||||
display: false,
|
||||
width: 90
|
||||
},
|
||||
name: {
|
||||
label: '职位名称',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '职位名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sort: {
|
||||
label: '职位顺序',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '职位顺序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
remark: {
|
||||
label: '职位备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 150,
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:position'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await PositionApi.getPositionPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await PositionApi.getPosition(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await PositionApi.createPosition(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await PositionApi.updatePosition(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await PositionApi.deletePosition(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await PositionApi.exportPosition(searchObj)
|
||||
download.excel(data, '职位列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
232
src/views/system/post/index.vue
Normal file
232
src/views/system/post/index.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:before-open="beforeOpen"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:post:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemPost' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '岗位编号',
|
||||
display: false,
|
||||
width: 90
|
||||
},
|
||||
name: {
|
||||
label: '岗位名称',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '岗位名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sort: {
|
||||
label: '岗位顺序',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '岗位顺序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
remark: {
|
||||
label: '岗位备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 150,
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:post'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await PostApi.getPostPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await PostApi.getPost(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await PostApi.createPost(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await PostApi.updatePost(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await PostApi.deletePost(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await PostApi.exportPost(searchObj)
|
||||
download.excel(data, '岗位列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
232
src/views/system/rank/index.vue
Normal file
232
src/views/system/rank/index.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:before-open="beforeOpen"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:rank:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as RankApi from '@/api/system/rank'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemRank' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
id: {
|
||||
label: '职级编号',
|
||||
display: false,
|
||||
width: 90
|
||||
},
|
||||
name: {
|
||||
label: '职级名称',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '职级名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sort: {
|
||||
label: '职级顺序',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '职级顺序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
remark: {
|
||||
label: '职级备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 150,
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:rank'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await RankApi.getRankPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await RankApi.getRank(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await RankApi.createRank(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await RankApi.updateRank(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await RankApi.deleteRank(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await RankApi.exportRank(searchObj)
|
||||
download.excel(data, '职级列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
168
src/views/system/risk/RiskImportForm.vue
Normal file
168
src/views/system/risk/RiskImportForm.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<DesignPopup v-model="dialogVisible" title="风险隐患数据上传" width="40%" :is-footer="true">
|
||||
<div class="p-20px flex">
|
||||
<el-upload style="flex: 1;" ref="uploadRef" v-model:file-list="fileList" :action="importUrl" :auto-upload="true"
|
||||
:before-upload="beforeUpload" :disabled="formLoading" :headers="uploadHeaders" :limit="1"
|
||||
:on-error="submitFormError" :on-exceed="handleExceed" :on-success="submitFormSuccess" accept=".xlsx, .xls" drag>
|
||||
<Icon :size="50" icon="ep:upload-filled" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<span>仅允许导入 xls、xlsx 格式文件。</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<!-- <span v-if="errorMsg">{{ errorMsg }}</span> -->
|
||||
|
||||
<span v-html="errorMsg" style="font-size: 12px; color: red; max-width: 40%;"></span>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as RiskApi from '@/api/system/risk'
|
||||
import * as TableApi from '@/api/design/table'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
|
||||
defineOptions({ name: 'SystemRiskImportForm' })
|
||||
|
||||
interface Props {
|
||||
dbformId: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const uploadRef = ref()
|
||||
// const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/risk/import'
|
||||
const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/jeelowcode/excel/viewExcel/1963446160885366786'
|
||||
const uploadHeaders = ref() // 上传 Header 头
|
||||
const fileList = ref([]) // 文件列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
fileList.value = []
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
|
||||
const tableData = ref([])
|
||||
const errorMsg = ref('')
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
errorMsg.value = ""
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
|
||||
let valid = true
|
||||
tableData.value.forEach((el: { dataJson }, idx) => {
|
||||
const dataJson = el.dataJson
|
||||
if (dataJson.check_people != dataJson.corrective_confirm_people) {
|
||||
errorMsg.value += `第${idx + 1}行:检查人员、整改确认人 必须是同一人 <br>`
|
||||
valid = false
|
||||
}
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
message.error('请修改后重新上传')
|
||||
return
|
||||
}
|
||||
// 导入上传数据
|
||||
await TableApi.importUploadData(props.dbformId, viewExcel.value?.batchCode)
|
||||
|
||||
console.log("tableData.value>>>>", tableData.value);
|
||||
message.alert("上传成功")
|
||||
// 发送操作成功的事件
|
||||
emits('success')
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
// 文件上传前
|
||||
const beforeUpload = () => {
|
||||
// 设置header
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 文件上传成功 */
|
||||
const emits = defineEmits(['success'])
|
||||
interface FileResp {
|
||||
fileId: string
|
||||
dbFormId: string
|
||||
batchCode: string
|
||||
totalCou: number
|
||||
}
|
||||
const viewExcel = ref<FileResp>()
|
||||
|
||||
const submitFormSuccess = (response: any) => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
viewExcel.value = response.data
|
||||
console.log('response>>>>>>', viewExcel.value);
|
||||
|
||||
getuploadData()
|
||||
// let text = '上传成功数量:' + data.createUsernames.length + ';'
|
||||
// for (let username of data.createUsernames) {
|
||||
// text += '< ' + username + ' >'
|
||||
// }
|
||||
// text += '更新成功数量:' + data.updateUsernames.length + ';'
|
||||
// for (const username of data.updateUsernames) {
|
||||
// text += '< ' + username + ' >'
|
||||
// }
|
||||
// text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
|
||||
// for (const username in data.failureUsernames) {
|
||||
// text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
|
||||
// }
|
||||
// message.alert(text)
|
||||
// 发送操作成功的事件
|
||||
emits('success')
|
||||
}
|
||||
|
||||
// 获取上传的导入数据
|
||||
const getuploadData = async () => {
|
||||
let searchObj = {
|
||||
pageNo: 1,
|
||||
pageSize: 1000,
|
||||
batchCode: viewExcel.value?.batchCode
|
||||
}
|
||||
const data = await TableApi.getUploadImportData(props.dbformId, searchObj)
|
||||
tableData.value = data.records.map((item) => {
|
||||
item.dataJson = JSON.parse(item.dataJson)
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
|
||||
</script>
|
||||
45
src/views/system/risk/index.vue
Normal file
45
src/views/system/risk/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="risk-container">
|
||||
<div style="display: flex; justify-content: end; margin-bottom: 10px;">
|
||||
<el-button type="default" plain icon="el-icon-download" @click="importTemplate">模板下载</el-button>
|
||||
<el-button type="default" plain icon="el-icon-upload" @click="handleImport">导入文件</el-button>
|
||||
</div>
|
||||
<LowTable ref="tableRef" :tableId="tableId"></LowTable>
|
||||
<RiskImportForm ref="importFormRef" :dbformId="tableId" @success="onSuccess"></RiskImportForm>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as RiskApi from '@/api/system/risk'
|
||||
import download from '@/utils/download'
|
||||
import RiskImportForm from './RiskImportForm.vue'
|
||||
|
||||
const tableId = ref<string>("1963446160885366786")
|
||||
// 模板下载
|
||||
const importTemplate = async () => {
|
||||
const res = await RiskApi.importRiskTemplate()
|
||||
download.excel(res, '风险隐患数据导入模版.xlsx')
|
||||
}
|
||||
|
||||
// 导入文件
|
||||
const importFormRef = ref()
|
||||
const handleImport = () => {
|
||||
importFormRef.value.open()
|
||||
}
|
||||
|
||||
// 导入成功
|
||||
const tableRef = ref()
|
||||
const onSuccess = () => {
|
||||
// 刷新table数据
|
||||
tableRef.value.resetChange()
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.risk-container {
|
||||
min-height: 500px;
|
||||
border: 1px solid rgb(233 233 233 / 100%);
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 4.71%);
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
170
src/views/system/role/RoleAssignMenuForm.vue
Normal file
170
src/views/system/role/RoleAssignMenuForm.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<DesignPopup v-model="dialogVisible" title="菜单权限" :is-footer="true" width="40%">
|
||||
<div class="p-20px">
|
||||
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
|
||||
<el-form-item label="角色名称">
|
||||
<el-tag>{{ formData.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色标识">
|
||||
<el-tag>{{ formData.code }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单权限">
|
||||
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
|
||||
<template #header>
|
||||
全选/全不选:
|
||||
<el-switch
|
||||
v-model="treeNodeAll"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
inline-prompt
|
||||
@change="handleCheckedTreeNodeAll"
|
||||
/>
|
||||
全部展开/折叠:
|
||||
<el-switch
|
||||
v-model="menuExpand"
|
||||
active-text="展开"
|
||||
inactive-text="折叠"
|
||||
inline-prompt
|
||||
@change="handleCheckedTreeExpand"
|
||||
/>
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="menuOptions"
|
||||
:props="defaultProps"
|
||||
empty-text="加载中,请稍候"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import * as PermissionApi from '@/api/system/permission'
|
||||
|
||||
defineOptions({ name: 'SystemRoleAssignMenuForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
code: '',
|
||||
menuIds: []
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const menuOptions = ref<any[]>([]) // 菜单树形结构
|
||||
const menuExpand = ref(false) // 展开/折叠
|
||||
const treeRef = ref() // 菜单树组件 Ref
|
||||
const treeNodeAll = ref(false) // 全选/全不选
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (row: RoleApi.RoleVO) => {
|
||||
dialogVisible.value = true
|
||||
formLoading.value = true
|
||||
resetForm()
|
||||
// 加载 Menu 列表。注意,必须放在前面,不然下面 setChecked 没数据节点
|
||||
menuOptions.value = handleTree(await MenuApi.getSimpleMenusList())
|
||||
// 设置数据
|
||||
formData.value.id = row.id
|
||||
formData.value.name = row.name
|
||||
formData.value.code = row.code
|
||||
try {
|
||||
formData.value.menuIds = await PermissionApi.getRoleMenuList(row.id)
|
||||
// 设置选中
|
||||
formData.value.menuIds.forEach((menuId: number) => {
|
||||
treeRef.value.setChecked(menuId, true, false)
|
||||
})
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
roleId: formData.value.id,
|
||||
menuIds: [
|
||||
...(treeRef.value.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点
|
||||
...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点
|
||||
]
|
||||
}
|
||||
await PermissionApi.assignRoleMenu(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置选项
|
||||
treeNodeAll.value = false
|
||||
menuExpand.value = false
|
||||
// 重置表单
|
||||
formData.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
code: '',
|
||||
menuIds: []
|
||||
}
|
||||
treeRef.value?.setCheckedNodes([])
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 全选/全不选 */
|
||||
const handleCheckedTreeNodeAll = () => {
|
||||
treeRef.value.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])
|
||||
}
|
||||
|
||||
/** 展开/折叠全部 */
|
||||
const handleCheckedTreeExpand = () => {
|
||||
const nodes = treeRef.value?.store.nodesMap
|
||||
for (let node in nodes) {
|
||||
if (nodes[node].expanded === menuExpand.value) {
|
||||
continue
|
||||
}
|
||||
nodes[node].expanded = menuExpand.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.cardHeight {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
overflow-y: scroll;
|
||||
|
||||
::v-deep(.el-card__header) {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
::v-deep(.el-card__body) {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
177
src/views/system/role/RoleDataPermissionForm.vue
Normal file
177
src/views/system/role/RoleDataPermissionForm.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<DesignPopup v-model="dialogVisible" title="数据权限" width="800" :is-footer="true">
|
||||
<div class="p-20px">
|
||||
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
|
||||
<el-form-item label="角色名称">
|
||||
<el-tag>{{ formData.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色标识">
|
||||
<el-tag>{{ formData.code }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限范围">
|
||||
<el-select v-model="formData.dataScope">
|
||||
<el-option
|
||||
v-for="item in getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form-item
|
||||
v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM"
|
||||
label="权限范围"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
|
||||
<template #header>
|
||||
<div class="flex items-center">
|
||||
<span>全选/全不选:</span>
|
||||
<el-switch
|
||||
v-model="treeNodeAll"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
inline-prompt
|
||||
@change="handleCheckedTreeNodeAll()"
|
||||
/>
|
||||
<span class="ml-10px">全部展开/折叠:</span>
|
||||
<el-switch
|
||||
v-model="deptExpand"
|
||||
active-text="展开"
|
||||
inactive-text="折叠"
|
||||
inline-prompt
|
||||
@change="handleCheckedTreeExpand"
|
||||
/>
|
||||
<span class="ml-10px">父子联动(选中父节点,自动选择子节点):</span>
|
||||
<el-switch
|
||||
v-model="checkStrictly"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
inline-prompt
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:check-strictly="!checkStrictly"
|
||||
:data="deptOptions"
|
||||
:props="defaultProps"
|
||||
default-expand-all
|
||||
empty-text="加载中,请稍后"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import { SystemDataScopeEnum } from '@/utils/constants'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PermissionApi from '@/api/system/permission'
|
||||
|
||||
defineOptions({ name: 'SystemRoleDataPermissionForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
code: '',
|
||||
dataScope: 1,
|
||||
dataScopeDeptIds: []
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const deptOptions = ref<any[]>([]) // 部门树形结构
|
||||
const deptExpand = ref(true) // 展开/折叠
|
||||
const treeRef = ref() // 菜单树组件 Ref
|
||||
const treeNodeAll = ref(false) // 全选/全不选
|
||||
const checkStrictly = ref(true) // 是否严格模式,即父子不关联
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (row: RoleApi.RoleVO) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
// 加载 Dept 列表。注意,必须放在前面,不然下面 setChecked 没数据节点
|
||||
deptOptions.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
// 设置数据
|
||||
formData.value.id = row.id
|
||||
formData.value.name = row.name
|
||||
formData.value.code = row.code
|
||||
formData.value.dataScope = row.dataScope
|
||||
await nextTick()
|
||||
row.dataScopeDeptIds?.forEach((deptId: number) => {
|
||||
treeRef.value.setChecked(deptId, true, false)
|
||||
})
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
roleId: formData.value.id,
|
||||
dataScope: formData.value.dataScope,
|
||||
dataScopeDeptIds:
|
||||
formData.value.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM
|
||||
? []
|
||||
: treeRef.value.getCheckedKeys(false)
|
||||
}
|
||||
await PermissionApi.assignRoleDataScope(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置选项
|
||||
treeNodeAll.value = false
|
||||
deptExpand.value = true
|
||||
checkStrictly.value = true
|
||||
// 重置表单
|
||||
formData.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
code: '',
|
||||
dataScope: 1,
|
||||
dataScopeDeptIds: []
|
||||
}
|
||||
treeRef.value?.setCheckedNodes([])
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 全选/全不选 */
|
||||
const handleCheckedTreeNodeAll = () => {
|
||||
treeRef.value.setCheckedNodes(treeNodeAll.value ? deptOptions.value : [])
|
||||
}
|
||||
|
||||
/** 展开/折叠全部 */
|
||||
const handleCheckedTreeExpand = () => {
|
||||
const nodes = treeRef.value?.store.nodesMap
|
||||
for (let node in nodes) {
|
||||
if (nodes[node].expanded === deptExpand.value) {
|
||||
continue
|
||||
}
|
||||
nodes[node].expanded = deptExpand.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
331
src/views/system/role/index.vue
Normal file
331
src/views/system/role/index.vue
Normal file
@@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:role:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-dropdown @command="menuHandle">
|
||||
<div class="mt--1px cursor-pointer flex-basis-55px flex-shrink-0 py-8px px-4px">
|
||||
<el-text type="primary">
|
||||
{{ t('Avue.crud.moreBtn') }}
|
||||
<Icon class="ml--2px" icon="iconamoon:arrow-down-2-light" />
|
||||
</el-text>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
:command="{ type: 'menu', row }"
|
||||
v-if="checkPermi(['system:permission:assign-role-data-scope'])"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<Icon icon="ep:menu" />
|
||||
<span>菜单权限</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:command="{ type: 'data', row }"
|
||||
v-if="checkPermi(['system:permission:assign-role-data-scope'])"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<Icon icon="ep:menu" />
|
||||
<span>数据权限</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
:command="{ type: 'del', row }"
|
||||
v-if="checkPermi(['system:role:delete'])"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<Icon icon="ep:delete" />
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:菜单权限 -->
|
||||
<RoleAssignMenuForm ref="assignMenuFormRef" @success="getTableData" />
|
||||
<!-- 表单弹窗:数据权限 -->
|
||||
<RoleDataPermissionForm ref="dataPermissionFormRef" @success="getTableData" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import download from '@/utils/download'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import RoleAssignMenuForm from './RoleAssignMenuForm.vue'
|
||||
import RoleDataPermissionForm from './RoleDataPermissionForm.vue'
|
||||
|
||||
defineOptions({ name: 'SystemRole' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
menuWidth: 160,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
delBtn: false,
|
||||
column: {
|
||||
name: {
|
||||
label: '角色名称',
|
||||
search: true,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
type: {
|
||||
label: '角色类型',
|
||||
type: 'select',
|
||||
width: 85,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_ROLE_TYPE),
|
||||
rules: [{ required: true, message: '角色类型不能为空', trigger: 'blur' }],
|
||||
display: false
|
||||
},
|
||||
code: {
|
||||
label: '角色标识',
|
||||
search: true,
|
||||
disabled: false,
|
||||
minWidth: 90,
|
||||
rules: [{ required: true, message: '角色标识不能为空', trigger: 'blur' }]
|
||||
},
|
||||
sort: {
|
||||
label: '显示顺序',
|
||||
width: 85,
|
||||
type: 'number',
|
||||
rules: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'select',
|
||||
width: 85,
|
||||
span: 24,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 120,
|
||||
span: 24
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 160,
|
||||
display: false,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:role'])
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const menuHandle = ({ row, type }) => {
|
||||
if (type == 'menu') openAssignMenuForm(row)
|
||||
else if (type == 'data') openDataPermissionForm(row)
|
||||
else if (type == 'del') rowDel(row)
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await RoleApi.getRolePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
const res = await RoleApi.getRole(tableForm.value.id)
|
||||
if (type == 'edit') tableOption.column.code.disabled = res.type === 1
|
||||
tableForm.value = res
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await RoleApi.createRole(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await RoleApi.updateRole(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await RoleApi.deleteRole(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 数据权限操作 */
|
||||
const dataPermissionFormRef = ref()
|
||||
const openDataPermissionForm = async (row: RoleApi.RoleVO) => {
|
||||
dataPermissionFormRef.value.open(row)
|
||||
}
|
||||
|
||||
/** 菜单权限操作 */
|
||||
const assignMenuFormRef = ref()
|
||||
const openAssignMenuForm = async (row: RoleApi.RoleVO) => {
|
||||
assignMenuFormRef.value.open(row)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await RoleApi.exportRole(searchObj)
|
||||
download.excel(data, '角色列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.cardHeight {
|
||||
width: 100%;
|
||||
max-height: 340px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
310
src/views/system/sensitiveWord/index.vue
Normal file
310
src/views/system/sensitiveWord/index.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #tags="scope">
|
||||
<el-tag v-for="tag in scope.row.tags" :key="tag" :disable-transitions="true" class="mr-5px">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:sensitive-word:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
<el-button :size="size" plain type="warning" @click="openTest">
|
||||
<Icon class="mr-5px" icon="ep:document-checked" />
|
||||
测试
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:测试敏感词 -->
|
||||
<DesignPopup v-model="testPopup" title="敏感词测试" :is-footer="true" width="40%">
|
||||
<div class="p-20px">
|
||||
<avue-form
|
||||
ref="textFormRef"
|
||||
v-if="testPopup"
|
||||
v-model="testFormData"
|
||||
:option="testFormOption"
|
||||
></avue-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :loading="textLoading" type="primary" @click="testSubmitFun">检 测</el-button>
|
||||
<el-button @click="testPopup = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemSensitiveWord' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
name: {
|
||||
label: '敏感词',
|
||||
width: 80,
|
||||
search: true,
|
||||
rules: [{ required: true, message: '敏感词不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
width: 80,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
description: {
|
||||
label: '描述',
|
||||
minWidth: 150
|
||||
},
|
||||
tags: {
|
||||
label: '标签',
|
||||
search: true,
|
||||
searchType: 'input',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
allowCreate: true,
|
||||
dicUrl: '/system/sensitive-word/get-tags',
|
||||
dicFormatter: (data) => {
|
||||
data = [...new Set(data)]
|
||||
return data.map((tag) => {
|
||||
return { label: tag, value: tag }
|
||||
})
|
||||
},
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '标签不能为空', trigger: 'blur' }],
|
||||
value: ''
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:sensitive-word'])
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await SensitiveWordApi.getSensitiveWordPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
crudRef.value.dicInit()
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await SensitiveWordApi.getSensitiveWord(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await SensitiveWordApi.createSensitiveWord(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await SensitiveWordApi.updateSensitiveWord(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await SensitiveWordApi.deleteSensitiveWord(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await SensitiveWordApi.exportSensitiveWord(searchObj)
|
||||
download.excel(data, '敏感词列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试敏感词按钮操作 */
|
||||
const testPopup = ref(false)
|
||||
const testFormData = ref<any>({})
|
||||
const testFormOption = ref({
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
span: 24,
|
||||
labelWidth: 120,
|
||||
column: {
|
||||
text: { label: '测试文本', type: 'textarea' },
|
||||
tags: tableOption.column.tags
|
||||
}
|
||||
})
|
||||
const textLoading = ref(false)
|
||||
const textFormRef = ref()
|
||||
|
||||
const openTest = () => {
|
||||
testFormData.value = {}
|
||||
testPopup.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const testSubmitFun = () => {
|
||||
textFormRef.value.validate(async (valid, hide) => {
|
||||
if (!valid) return hide()
|
||||
try {
|
||||
textLoading.value = true
|
||||
const data = await SensitiveWordApi.validateText({
|
||||
text: testFormData.value.text,
|
||||
tag: testFormData.value.tags
|
||||
})
|
||||
if (data.length === 0) message.success('不包含敏感词!')
|
||||
else message.warning('包含敏感词:' + data.join(', '))
|
||||
} finally {
|
||||
hide()
|
||||
textLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
225
src/views/system/sms/channel/index.vue
Normal file
225
src/views/system/sms/channel/index.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #code="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE"
|
||||
:value="scope.row.code ? scope.row.code : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemSmsChannel' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 140,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 160,
|
||||
overHidden:true,
|
||||
column: {
|
||||
id: {
|
||||
label: '编号',
|
||||
width: 80,
|
||||
display: false
|
||||
},
|
||||
signature: {
|
||||
label: '短信签名',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
code: {
|
||||
label: '渠道编码',
|
||||
type: 'select',
|
||||
span: 12,
|
||||
minWidth: 100,
|
||||
dicData: getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE),
|
||||
rules: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '启用状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
span: 12,
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
minWidth: 100,
|
||||
overHidden: true
|
||||
},
|
||||
apiKey: {
|
||||
label: '短信 API 的账号',
|
||||
overHidden: true,
|
||||
minWidth: 150,
|
||||
rules: [{ required: true, message: '短信 API 的账号不能为空', trigger: 'blur' }]
|
||||
},
|
||||
apiSecret: {
|
||||
label: '短信 API 的密钥',
|
||||
minWidth: 150,
|
||||
overHidden: true
|
||||
},
|
||||
callbackUrl: {
|
||||
label: '短信发送回调 URL',
|
||||
minWidth: 120
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:sms-channel'])
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await SmsChannelApi.getSmsChannelPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await SmsChannelApi.getSmsChannel(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await SmsChannelApi.createSmsChannel(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await SmsChannelApi.updateSmsChannel(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await SmsChannelApi.deleteSmsChannel(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
341
src/views/system/sms/log/index.vue
Normal file
341
src/views/system/sms/log/index.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:sms-log:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<!-- 表格 -->
|
||||
<template #sendStatus="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS"
|
||||
:value="scope.row.sendStatus != undefined ? scope.row.sendStatus : ''"
|
||||
/>
|
||||
<div>{{ formatDate(scope.row.sendTime) }}</div>
|
||||
</template>
|
||||
<template #receiveStatus="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS"
|
||||
:value="scope.row.receiveStatus != undefined ? scope.row.receiveStatus : ''"
|
||||
/>
|
||||
<div>{{ formatDate(tableForm.receiveTime) }}</div>
|
||||
</template>
|
||||
<template #templateType="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE"
|
||||
:value="scope.row.templateType != undefined ? scope.row.templateType : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #channelCode="scope">
|
||||
<div>
|
||||
{{ channelObj[scope.row.channelId] }}
|
||||
</div>
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE"
|
||||
:value="scope.row.channelCode ? scope.row.channelCode : ''"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #channelCode-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<span class="mr-5px"> {{ channelObj[tableForm.channelId] }}</span>
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #templateType-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<span class="mr-5px">{{ tableForm.templateId }} | {{ tableForm.templateCode }}</span>
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #templateParams-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<span>{{ JSON.stringify(value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #sendStatus-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #receiveStatus-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS"
|
||||
:value="value != undefined ? value : ''"
|
||||
/>
|
||||
{{ formatDate(tableForm.receiveTime) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #apiReceiveMsg-form="{ value }">
|
||||
<div class="el-input__wrapper" style="box-shadow: none">
|
||||
{{ tableForm.apiReceiveCode }} | {{ value }}
|
||||
</div>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDate, dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
|
||||
import * as SmsLogApi from '@/api/system/sms/smsLog'
|
||||
|
||||
defineOptions({ name: 'SystemOperateLog' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 160,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
id: {
|
||||
label: '日志编号',
|
||||
width: 90
|
||||
},
|
||||
channelCode: {
|
||||
label: '短信渠道',
|
||||
type: 'select',
|
||||
minWidth: 110,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE),
|
||||
value: ''
|
||||
},
|
||||
templateId: {
|
||||
label: '模板编号',
|
||||
display: false,
|
||||
minWidth: 90
|
||||
},
|
||||
templateType: {
|
||||
label: '短信类型',
|
||||
type: 'select',
|
||||
minWidth: 100,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE),
|
||||
value: ''
|
||||
},
|
||||
apiTemplateId: {
|
||||
label: 'API 的模板编号',
|
||||
hide: true
|
||||
},
|
||||
mobile: {
|
||||
label: '手机号',
|
||||
width: 120
|
||||
},
|
||||
templateContent: {
|
||||
label: '短信内容',
|
||||
width: 300,
|
||||
overHidden: true
|
||||
},
|
||||
templateParams: {
|
||||
label: '短信参数',
|
||||
hide: true
|
||||
},
|
||||
searchCreateTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
sendStatus: {
|
||||
label: '发送状态',
|
||||
width: 200,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS),
|
||||
value: ''
|
||||
},
|
||||
searchSendTime: {
|
||||
label: '发送时间',
|
||||
search: true,
|
||||
hide: true,
|
||||
display: false,
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间'
|
||||
},
|
||||
sendTime: {
|
||||
label: '发送时间',
|
||||
hide: true,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
apiSendMsg: {
|
||||
label: 'API 发送结果',
|
||||
hide: true
|
||||
},
|
||||
apiSerialNo: {
|
||||
label: 'API 短信编号',
|
||||
hide: true
|
||||
},
|
||||
apiRequestId: {
|
||||
label: 'API 请求编号',
|
||||
display: false,
|
||||
hide: true
|
||||
},
|
||||
receiveStatus: {
|
||||
label: 'API 接收状态',
|
||||
width: 110,
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS),
|
||||
value: ''
|
||||
},
|
||||
apiReceiveMsg: {
|
||||
label: 'API 接收结果',
|
||||
hide: true
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const channelObj = ref<object>({}) // 短信渠道
|
||||
const permission = getCurrPermi(['system:sms-log'])
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.searchCreateTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.searchCreateTime)
|
||||
}
|
||||
delete searchObj.searchCreateTime
|
||||
if (searchObj.searchSendTime?.length) {
|
||||
searchObj.sendTime = getSearchDate(searchObj.searchSendTime)
|
||||
}
|
||||
delete searchObj.searchSendTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await SmsLogApi.getSmsLogPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await SmsLogApi.exportSmsLog(searchObj)
|
||||
download.excel(data, '短信日志.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
// 加载渠道列表
|
||||
const channelList = await SmsChannelApi.getSimpleSmsChannelList()
|
||||
channelList.forEach((item) => {
|
||||
channelObj.value[item.id] = item.signature
|
||||
})
|
||||
})
|
||||
</script>
|
||||
434
src/views/system/sms/template/index.vue
Normal file
434
src/views/system/sms/template/index.vue
Normal file
@@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:sms-template:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<!-- 表格 -->
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE"
|
||||
:value="scope.row.type ? scope.row.type : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<template #channelCode="{ row }">
|
||||
<div>
|
||||
{{ channelObj[row.channelId] }}
|
||||
</div>
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE"
|
||||
:value="row.channelCode ? row.channelCode : ''"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 自定义操作栏 -->
|
||||
<template #menu="{ row }">
|
||||
<el-button
|
||||
v-if="row.status === 0"
|
||||
class="ml-4px!"
|
||||
link
|
||||
type="primary"
|
||||
@click="openSendForm(row.id)"
|
||||
v-hasPermi="['system:sms-template:send-sms']"
|
||||
>
|
||||
<Icon :size="14" icon="ep:tickets"></Icon>
|
||||
<span>测试</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:测试发送 -->
|
||||
<DesignPopup
|
||||
v-model="testPopup"
|
||||
title="发送短信模板测试"
|
||||
:is-footer="true"
|
||||
width="40%"
|
||||
class="sms-template-popup"
|
||||
>
|
||||
<div class="p-20px">
|
||||
<avue-form
|
||||
ref="textFormRef"
|
||||
v-if="testPopup"
|
||||
v-model="testFormData"
|
||||
:option="testFormOption"
|
||||
></avue-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :loading="textLoading" type="primary" @click="testSubmitFun">发 送</el-button>
|
||||
<el-button @click="testPopup = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemSmsTemplate' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const channelObj = ref({})
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
labelWidth: 120,
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
channelId: {
|
||||
label: '短信渠道编号',
|
||||
type: 'select',
|
||||
hide: true,
|
||||
order: 10,
|
||||
searchOrder: 1,
|
||||
span: 12,
|
||||
dicUrl: '/system/sms-channel/simple-list',
|
||||
dicFormatter: (data) => {
|
||||
data = data.map((item) => {
|
||||
channelObj.value[item.id] = item.signature
|
||||
return {
|
||||
label: `${item.signature}【${getDictLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, item.code)}】`,
|
||||
value: item.id
|
||||
}
|
||||
})
|
||||
return data
|
||||
},
|
||||
dicFlag: true,
|
||||
search: true,
|
||||
searchLabel: '短信渠道',
|
||||
rules: [{ required: true, message: '短信渠道编号不能为空', trigger: 'change' }]
|
||||
},
|
||||
|
||||
code: {
|
||||
label: '模板编码',
|
||||
search: true,
|
||||
overHidden: true,
|
||||
minWidth: 100,
|
||||
span: 12,
|
||||
order: 8,
|
||||
searchOrder: 5,
|
||||
rules: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
name: {
|
||||
label: '模板名称',
|
||||
overHidden: true,
|
||||
minWidth: 120,
|
||||
span: 12,
|
||||
order: 7,
|
||||
rules: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
content: {
|
||||
label: '模板内容',
|
||||
overHidden: true,
|
||||
type: 'textarea',
|
||||
minWidth: 120,
|
||||
order: 6,
|
||||
rules: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }]
|
||||
},
|
||||
type: {
|
||||
label: '短信类型',
|
||||
search: true,
|
||||
type: 'select',
|
||||
span: 12,
|
||||
minWidth: 100,
|
||||
order: 9,
|
||||
searchOrder: 4,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE),
|
||||
rules: [{ required: true, message: '短信类型不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
span: 24,
|
||||
width: 70,
|
||||
order: 5,
|
||||
searchOrder: 3,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
order: 3,
|
||||
minWidth: 150
|
||||
},
|
||||
apiTemplateId: {
|
||||
label: '短信 API 的模板编号',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
order: 4,
|
||||
searchOrder: 2,
|
||||
searchLabelWidth: 160,
|
||||
rules: [{ required: true, message: '短信 API 的模板编号不能为空', trigger: 'blur' }]
|
||||
},
|
||||
channelCode: {
|
||||
label: '短信渠道',
|
||||
display: false,
|
||||
span: 12,
|
||||
minWidth: 100
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
search: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const permission = getCurrPermi(['system:sms-template'])
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await SmsTemplateApi.getSmsTemplatePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await SmsTemplateApi.getSmsTemplate(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await SmsTemplateApi.createSmsTemplate(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await SmsTemplateApi.updateSmsTemplate(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await SmsTemplateApi.deleteSmsTemplate(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await SmsTemplateApi.exportSmsTemplate(searchObj)
|
||||
download.excel(data, '短信模板列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 发送短信按钮 */
|
||||
const testPopup = ref(false)
|
||||
const testFormData = ref<any>({})
|
||||
const testFormOption = ref({
|
||||
submitBtn: false,
|
||||
emptyBtn: false,
|
||||
span: 24,
|
||||
labelWidth: 120,
|
||||
column: {}
|
||||
})
|
||||
const textLoading = ref(false)
|
||||
const textFormRef = ref()
|
||||
|
||||
// 打开测试表单
|
||||
const openSendForm = async (id: number) => {
|
||||
loading.value = true
|
||||
const formDatas = await SmsTemplateApi.getSmsTemplate(id)
|
||||
testFormData.value = {
|
||||
content: formDatas.content,
|
||||
params: formDatas.params,
|
||||
templateCode: formDatas.code,
|
||||
templateParams: {}
|
||||
}
|
||||
testFormOption.value.column = {
|
||||
content: { label: '模板内容', type: 'textarea', disabled: true },
|
||||
mobile: {
|
||||
label: '手机号',
|
||||
rules: [{ required: true, message: `手机号不能为空`, trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
// 设置动态参数
|
||||
if (formDatas.params?.length) {
|
||||
formDatas.params.forEach((item) => {
|
||||
testFormData.value.templateParams[item] = ''
|
||||
testFormOption.value.column[item] = {
|
||||
label: `参数 ${item} `,
|
||||
value: '',
|
||||
bind: `templateParams.${item}`,
|
||||
rules: [{ required: true, message: `参数 ${item} 不能为空`, trigger: 'blur' }]
|
||||
}
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
testPopup.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const testSubmitFun = () => {
|
||||
textFormRef.value.validate(async (valid, hide) => {
|
||||
if (!valid) return hide()
|
||||
try {
|
||||
textLoading.value = true
|
||||
const logId = await SmsTemplateApi.sendSms({
|
||||
mobile: testFormData.value.mobile,
|
||||
templateCode: testFormData.value.templateCode,
|
||||
templateParams: testFormData.value.templateParams
|
||||
})
|
||||
if (logId) {
|
||||
message.success('提交发送成功!发送结果见短信日志,日志编号:' + logId)
|
||||
}
|
||||
testPopup.value = false
|
||||
} finally {
|
||||
hide()
|
||||
textLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sms-template-popup {
|
||||
.el-form-item__label {
|
||||
height: auto;
|
||||
min-height: 32px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
217
src/views/system/social/client/index.vue
Normal file
217
src/views/system/social/client/index.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #socialType="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SOCIAL_TYPE"
|
||||
:value="scope.row.socialType ? scope.row.socialType : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #userType="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.USER_TYPE"
|
||||
:value="scope.row.userType ? scope.row.userType : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status != undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as SocialClientApi from '@/api/system/social/client'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SocialClient' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelWidth: 100,
|
||||
searchLabelWidth: 100,
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
name: {
|
||||
label: '应用名',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
rules: [{ required: true, message: '应用名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
socialType: {
|
||||
label: '社交平台',
|
||||
type: 'radio',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE),
|
||||
rules: [{ required: true, message: '社交平台不能为空', trigger: 'blur' }]
|
||||
},
|
||||
clientId: {
|
||||
label: '客户端编号',
|
||||
search: true,
|
||||
minWidth: 120,
|
||||
rules: [{ required: true, message: '客户端编号不能为空', trigger: 'blur' }]
|
||||
},
|
||||
clientSecret: {
|
||||
label: '客户端密钥',
|
||||
hide: true,
|
||||
rules: [{ required: true, message: '客户端密钥不能为空', trigger: 'blur' }]
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
type: 'radio',
|
||||
search: true,
|
||||
span: 12,
|
||||
width: 80,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:social-client'])
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await SocialClientApi.getSocialClientPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
tableForm.value = await SocialClientApi.getSocialClient(tableForm.value.id)
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await SocialClientApi.createSocialClient(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
let bool = await SocialClientApi.updateSocialClient(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await SocialClientApi.deleteSocialClient(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
219
src/views/system/social/user/index.vue
Normal file
219
src/views/system/social/user/index.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<template #type="scope">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.SYSTEM_SOCIAL_TYPE"
|
||||
:value="scope.row.type ? scope.row.type : ''"
|
||||
/>
|
||||
</template>
|
||||
<template #avatar="scope">
|
||||
<el-image
|
||||
:src="scope.row.avatar"
|
||||
class="h-30px w-30px"
|
||||
@click="imagePreview(scope.row.avatar)"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表单 -->
|
||||
<template #avatar-form>
|
||||
<el-image :src="tableForm.avatar" class="h-30px w-30px" />
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import * as SocialUserApi from '@/api/system/social/user'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
|
||||
defineOptions({ name: 'SocialUser' })
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
viewBtn: true,
|
||||
viewBtnText: '详情',
|
||||
viewBtnIcon: ' ',
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
searchLabelWidth: 100,
|
||||
labelWidth: 160,
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 120,
|
||||
column: {
|
||||
type: {
|
||||
label: '社交平台',
|
||||
type: 'select',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE)
|
||||
},
|
||||
openid: {
|
||||
label: '社交 openid',
|
||||
search: true,
|
||||
minWidth: 100,
|
||||
display: false
|
||||
},
|
||||
nickname: {
|
||||
label: '用户昵称',
|
||||
minWidth: 100,
|
||||
search: true
|
||||
},
|
||||
avatar: {
|
||||
label: '用户头像',
|
||||
type: 'upload',
|
||||
span: 24,
|
||||
formslot: true,
|
||||
minWidth: 100,
|
||||
slot: true
|
||||
},
|
||||
token: {
|
||||
label: '社交 token',
|
||||
hide: true
|
||||
},
|
||||
rawTokenInfo: {
|
||||
label: '原始 Token 数据',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
hide: true
|
||||
},
|
||||
rawUserInfo: {
|
||||
label: '原始 User 数据',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
hide: true
|
||||
},
|
||||
code: {
|
||||
label: '最后一次的认证 code',
|
||||
hide: true
|
||||
},
|
||||
state: {
|
||||
label: '最后一次的认证 state',
|
||||
hide: true
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
updateTime: {
|
||||
label: '更新时间',
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'datetime',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi([])
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await SocialUserApi.getSocialUserPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
320
src/views/system/tenant/index.vue
Normal file
320
src/views/system/tenant/index.vue
Normal file
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:tenant:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #menu="{ size, row }">
|
||||
<el-button
|
||||
v-if="row.packageId !== 0"
|
||||
type="primary"
|
||||
link
|
||||
:size="size"
|
||||
icon="el-icon-delete"
|
||||
@click="rowDel(row)"
|
||||
v-hasPermi="['system:tenant:delete']"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
<template #packageId="scope">
|
||||
<el-tag v-if="scope.row.packageId === 0" type="danger">系统租户</el-tag>
|
||||
<el-tag v-else type="success">
|
||||
{{ scope.row.$packageId }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #accountCount="scope">
|
||||
<el-tag>{{ scope.row.accountCount }}</el-tag>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as TenantApi from '@/api/system/tenant'
|
||||
import { getTenantPackageList } from '@/api/system/tenantPackage'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'SystemTenant' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
span: 12,
|
||||
dialogWidth: '50%',
|
||||
menuWidth: 160,
|
||||
column: {
|
||||
name: {
|
||||
label: '租户名',
|
||||
search: true,
|
||||
minWidth: 80,
|
||||
rules: [{ required: true, message: '租户名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
packageId: {
|
||||
label: '租户套餐',
|
||||
type: 'select',
|
||||
dicData: [] as { name: string; id: number }[],
|
||||
dicFlag: true,
|
||||
props: {
|
||||
label: 'name',
|
||||
value: 'id'
|
||||
},
|
||||
width: 100,
|
||||
overHidden: false,
|
||||
editDisabled: false,
|
||||
rules: [{ required: true, message: '租户套餐不能为空', trigger: 'blur' }]
|
||||
},
|
||||
contactName: {
|
||||
label: '联系人',
|
||||
width: 80,
|
||||
search: true,
|
||||
rules: [{ required: true, message: '联系人不能为空', trigger: 'blur' }]
|
||||
},
|
||||
contactMobile: {
|
||||
label: '联系手机',
|
||||
width: 115,
|
||||
search: true
|
||||
},
|
||||
username: {
|
||||
label: '用户账号',
|
||||
rules: [{ required: true, message: '用户账号不能为空', trigger: 'blur' }],
|
||||
hide: true,
|
||||
showColumn: false,
|
||||
editDisplay: false,
|
||||
value: 'admin'
|
||||
},
|
||||
password: {
|
||||
label: '用户密码',
|
||||
rules: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
|
||||
type: 'password',
|
||||
hide: true,
|
||||
showColumn: false,
|
||||
editDisplay: false
|
||||
},
|
||||
accountCount: {
|
||||
label: '账号额度',
|
||||
width: 90,
|
||||
rules: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }],
|
||||
type: 'number'
|
||||
},
|
||||
expireTime: {
|
||||
label: '过期时间',
|
||||
rules: [{ required: true, message: '过期时间不能为空', trigger: 'blur' }],
|
||||
type: 'date',
|
||||
width: 160,
|
||||
valueFormat: 'x',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
},
|
||||
website: {
|
||||
label: '绑定域名',
|
||||
rules: [{ required: true, message: '绑定域名不能为空', trigger: 'blur' }],
|
||||
alone: true,
|
||||
type: 'url',
|
||||
dataType: 'string',
|
||||
minWidth: 120
|
||||
},
|
||||
status: {
|
||||
label: '租户状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
display: false,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 160,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:tenant'])
|
||||
permission['delBtn'] = false
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await TenantApi.getTenantPage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
const packageId = tableOption.column.packageId
|
||||
packageId.dicData = packageId.dicData.filter((item) => item.id !== 0)
|
||||
packageId.editDisabled = false
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
loading.value = true
|
||||
tableForm.value = await TenantApi.getTenant(tableForm.value.id)
|
||||
if (tableForm.value.packageId === 0) {
|
||||
packageId.dicData.push({ name: '系统租户', id: 0 })
|
||||
packageId.editDisabled = true
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
let bool = await TenantApi.createTenant(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
delete form.password
|
||||
delete form.username
|
||||
let bool = await TenantApi.updateTenant(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await TenantApi.deleteTenant(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await TenantApi.exportTenant(searchObj)
|
||||
download.excel(data, '租户列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
getTenantPackageList().then((data) => {
|
||||
tableOption.column.packageId.dicData = data
|
||||
})
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
296
src/views/system/tenantPackage/index.vue
Normal file
296
src/views/system/tenantPackage/index.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@row-del="rowDel"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #status="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== undefined"
|
||||
:type="DICT_TYPE.COMMON_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
</template>
|
||||
<template #menuIds-form="">
|
||||
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
|
||||
<template #header>
|
||||
全选/全不选:
|
||||
<el-switch
|
||||
v-model="treeNodeAll"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
inline-prompt
|
||||
@change="handleCheckedTreeNodeAll"
|
||||
/>
|
||||
全部展开/折叠:
|
||||
<el-switch
|
||||
v-model="menuExpand"
|
||||
active-text="展开"
|
||||
inactive-text="折叠"
|
||||
inline-prompt
|
||||
@change="handleCheckedTreeExpand"
|
||||
/>
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="menuOptions"
|
||||
:props="defaultProps"
|
||||
empty-text="加载中,请稍候"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import * as TenantPackageApi from '@/api/system/tenantPackage'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import { ElTree } from 'element-plus'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
defineOptions({ name: 'SystemTenantPackage' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = reactive({
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 24,
|
||||
dialogWidth: '50%',
|
||||
column: {
|
||||
name: {
|
||||
label: '套餐名',
|
||||
search: true,
|
||||
minWidth: 80,
|
||||
rules: [{ required: true, message: '套餐名不能为空', trigger: 'blur' }]
|
||||
},
|
||||
menuIds: {
|
||||
label: '套餐编号',
|
||||
hide: true
|
||||
},
|
||||
status: {
|
||||
label: '状态',
|
||||
search: true,
|
||||
type: 'radio',
|
||||
width: 90,
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
value: CommonStatusEnum.ENABLE
|
||||
},
|
||||
remark: {
|
||||
label: '备注',
|
||||
type: 'textarea',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
minWidth: 120
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
display: false,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) //表格配置
|
||||
const tableForm = ref<{ id?: number }>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
const permission = getCurrPermi(['system:tenant-package'])
|
||||
|
||||
const crudRef = ref()
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
// 菜单权限表单
|
||||
const menuOptions = ref<any[]>([]) // 树形结构数据
|
||||
const menuExpand = ref(false) // 展开/折叠
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件 Ref
|
||||
const treeNodeAll = ref(false) // 全选/全不选
|
||||
const formData = ref({
|
||||
id: null,
|
||||
name: null,
|
||||
remark: null,
|
||||
menuIds: [],
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await TenantPackageApi.getTenantPackagePage(searchObj)
|
||||
tableData.value = data.list
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
// 加载 Menu 列表。注意,必须放在前面,不然下面 setChecked 没数据节点
|
||||
menuOptions.value = handleTree(await MenuApi.getSimpleMenusList())
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
loading.value = true
|
||||
const res = await TenantPackageApi.getTenantPackage(tableForm.value.id)
|
||||
tableForm.value = res
|
||||
// 设置选中
|
||||
formData.value = res
|
||||
|
||||
// 设置选中
|
||||
res.menuIds.forEach((menuId: number) => {
|
||||
nextTick(() => {
|
||||
treeRef.value!.setChecked(menuId, true, false)
|
||||
})
|
||||
})
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
form.menuIds = [
|
||||
...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点
|
||||
...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点
|
||||
]
|
||||
let bool = await TenantPackageApi.createTenantPackage(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
form.menuIds = [
|
||||
...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点
|
||||
...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点
|
||||
]
|
||||
|
||||
let bool = await TenantPackageApi.updateTenantPackage(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form, index) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await TenantPackageApi.deleteTenantPackage(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
/** 全选/全不选 */
|
||||
const handleCheckedTreeNodeAll = () => {
|
||||
treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])
|
||||
}
|
||||
|
||||
/** 展开/折叠全部 */
|
||||
const handleCheckedTreeExpand = () => {
|
||||
const nodes = treeRef.value?.store.nodesMap
|
||||
for (let node in nodes) {
|
||||
if (nodes[node].expanded === menuExpand.value) {
|
||||
continue
|
||||
}
|
||||
nodes[node].expanded = menuExpand.value
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getTableData()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.cardHeight {
|
||||
width: 100%;
|
||||
max-height: 340px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
140
src/views/system/user/UserImportForm.vue
Normal file
140
src/views/system/user/UserImportForm.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<DesignPopup v-model="dialogVisible" title="用户导入" width="400" :is-footer="true">
|
||||
<div class="p-20px">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="importUrl + '?updateSupport=' + updateSupport"
|
||||
:auto-upload="false"
|
||||
:disabled="formLoading"
|
||||
:headers="uploadHeaders"
|
||||
:limit="1"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
accept=".xlsx, .xls"
|
||||
drag
|
||||
>
|
||||
<Icon :size="50" icon="ep:upload-filled" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<div class="el-upload__tip flex items-center justify-center">
|
||||
<el-checkbox v-model="updateSupport" />
|
||||
<span class="ml-5px text-14px">是否更新已经存在的用户数据</span>
|
||||
</div>
|
||||
<span>仅允许导入 xls、xlsx 格式文件。</span>
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline"
|
||||
type="primary"
|
||||
@click="importTemplate"
|
||||
v-loading="downLoading"
|
||||
>
|
||||
下载模板
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">开始导入</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</DesignPopup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import download from '@/utils/download'
|
||||
|
||||
defineOptions({ name: 'SystemUserImportForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const downLoading = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importUrl =
|
||||
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'
|
||||
const uploadHeaders = ref() // 上传 Header 头
|
||||
const fileList = ref([]) // 文件列表
|
||||
const updateSupport = ref(0) // 是否更新已经存在的用户数据
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
fileList.value = []
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
formLoading.value = true
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功 */
|
||||
const emits = defineEmits(['success'])
|
||||
const submitFormSuccess = (response: any) => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
// 拼接提示语
|
||||
const data = response.data
|
||||
let text = '上传成功数量:' + data.createUsernames.length + ';'
|
||||
for (let username of data.createUsernames) {
|
||||
text += '< ' + username + ' >'
|
||||
}
|
||||
text += '更新成功数量:' + data.updateUsernames.length + ';'
|
||||
for (const username of data.updateUsernames) {
|
||||
text += '< ' + username + ' >'
|
||||
}
|
||||
text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
|
||||
for (const username in data.failureUsernames) {
|
||||
text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
|
||||
}
|
||||
message.alert(text)
|
||||
// 发送操作成功的事件
|
||||
emits('success')
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
|
||||
/** 下载模板操作 */
|
||||
const importTemplate = async () => {
|
||||
downLoading.value = true
|
||||
const res = await UserApi.importUserTemplate()
|
||||
download.excel(res, '用户导入模版.xls')
|
||||
downLoading.value = false
|
||||
}
|
||||
</script>
|
||||
670
src/views/system/user/index.vue
Normal file
670
src/views/system/user/index.vue
Normal file
@@ -0,0 +1,670 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<ContentWrap>
|
||||
<avue-tree
|
||||
ref="treeRef"
|
||||
:option="treeOption"
|
||||
:data="treeData"
|
||||
@node-click="nodeClick"
|
||||
></avue-tree>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<ContentWrap>
|
||||
<avue-crud
|
||||
ref="crudRef"
|
||||
v-model="tableForm"
|
||||
v-model:page="tablePage"
|
||||
v-model:search="tableSearch"
|
||||
:table-loading="loading"
|
||||
:data="tableData"
|
||||
:option="tableOption"
|
||||
:permission="permission"
|
||||
:before-open="beforeOpen"
|
||||
@search-change="searchChange"
|
||||
@search-reset="resetChange"
|
||||
@row-save="rowSave"
|
||||
@row-update="rowUpdate"
|
||||
@refresh-change="getTableData"
|
||||
@size-change="sizeChange"
|
||||
@current-change="currentChange"
|
||||
>
|
||||
<template #menu-left="{ size }">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
@click="handleImport"
|
||||
v-hasPermi="['system:user:import']"
|
||||
>
|
||||
<Icon icon="ep:upload" /> 导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:size="size"
|
||||
icon="el-icon-download"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:user:export']"
|
||||
>导出</el-button
|
||||
>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
<el-dropdown
|
||||
@command="(command) => handleCommand(command, row)"
|
||||
v-hasPermi="[
|
||||
'system:user:delete',
|
||||
'system:user:update-password',
|
||||
'system:permission:assign-user-role'
|
||||
]"
|
||||
>
|
||||
<div class="pt-3px pr-2px pb-4px pl-2px cursor-pointer">
|
||||
<el-text type="primary">
|
||||
<span>更多</span>
|
||||
<Icon :size="16" icon="iconamoon:arrow-down-2-light" />
|
||||
</el-text>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
command="handleResetPwd"
|
||||
v-if="checkPermi(['system:user:update-password'])"
|
||||
>
|
||||
<Icon icon="ep:key" />重置密码
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
command="handleDelete"
|
||||
v-if="checkPermi(['system:user:delete'])"
|
||||
>
|
||||
<Icon icon="ep:delete" />删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template #status="scope">
|
||||
<el-switch
|
||||
v-if="scope.row.status != undefined"
|
||||
v-model="scope.row.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
<template #deptName="{ row }">
|
||||
<div class="flex flex-wrap gap-2px justify-center">
|
||||
<div v-for="(dept, deptIndex) in row.deptInfoList" :key="dept.deptId">
|
||||
<el-popover placement="right" width="240" trigger="hover">
|
||||
<template #reference>
|
||||
<el-tag type="primary" class="cursor-pointer">{{ dept.deptName }}</el-tag>
|
||||
</template>
|
||||
<div class="flex flex-col gap-y-4px">
|
||||
<template v-for="type in keyList" :key="type.value">
|
||||
<div class="flex" v-if="row.deptInfoList[deptIndex][type.value]">
|
||||
<span class="flex-basis-42px flex-shrink-0 c-#909399"
|
||||
>{{ type.label }}:</span
|
||||
>
|
||||
<span class="flex-1">{{ row.deptInfoList[deptIndex][type.value] }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #deptInfoList-form>
|
||||
<avue-crud ref="deptCrudRef" :option="deptOption" :data="tableForm.deptInfoList">
|
||||
<template #menu="{ size, index }">
|
||||
<el-button text :size="size" type="danger" @click="delDeptTableRow(index)">
|
||||
<Icon icon="ep:delete"></Icon>
|
||||
<span>删除</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<template #deptId-form="{ index, column, disabled }">
|
||||
<DeptSelect
|
||||
v-model="tableForm.deptInfoList[index].deptId"
|
||||
:column="column"
|
||||
:disabled="disabled"
|
||||
type="edit"
|
||||
prop="deptId"
|
||||
></DeptSelect>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 用户导入对话框 -->
|
||||
<UserImportForm ref="importFormRef" @success="getTableData" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { dateFormatter, getSearchDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { useLowStoreWithOut } from '@/store/modules/low'
|
||||
import UserImportForm from './UserImportForm.vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
defineOptions({ name: 'SystemUser' })
|
||||
|
||||
const keyList = [
|
||||
{ label: '部门', value: 'deptName' },
|
||||
{ label: '角色', value: 'roleInfoList' },
|
||||
{ label: '职务', value: 'dutyInfoList' },
|
||||
{ label: '职位', value: 'positionInfoList' },
|
||||
{ label: '岗位', value: 'postInfoList' }
|
||||
]
|
||||
// 树数据
|
||||
const treeOption = reactive({
|
||||
defaultExpandAll: true,
|
||||
addBtn: false,
|
||||
formOption: {
|
||||
column: [{ label: '自定义项', prop: 'test' }]
|
||||
},
|
||||
props: { label: 'name', value: 'id', children: 'children' }
|
||||
})
|
||||
const treeData = ref<Tree[]>([])
|
||||
const crudRef = ref()
|
||||
const treeRef = ref()
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { getCurrPermi } = useCrudPermi()
|
||||
const lowStore = useLowStoreWithOut()
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const tableOption = ref({
|
||||
delBtn: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
searchMenuSpan: 6,
|
||||
searchMenuPosition: 'left',
|
||||
labelSuffix: ' ',
|
||||
span: 12,
|
||||
dialogWidth: '80%',
|
||||
menuWidth: 160,
|
||||
dialogCustomClass: 'system-user-form',
|
||||
column: {
|
||||
username: { label: '用户账号', search: true, minWidth: 90, display: false },
|
||||
nickname: { label: '用户昵称', search: true, minWidth: 100, display: false },
|
||||
deptName: { label: '部门', minWidth: 100, display: false },
|
||||
mobile: { label: '手机号码', search: true, width: 120, display: false },
|
||||
status: {
|
||||
label: '状态',
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
|
||||
search: true,
|
||||
display: false
|
||||
},
|
||||
createTime: {
|
||||
label: '创建时间',
|
||||
search: true,
|
||||
searchRange: true,
|
||||
type: 'date',
|
||||
searchType: 'daterange',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
width: 180,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
formatter: (row, val, value, column) => {
|
||||
return dateFormatter(row, column, val)
|
||||
},
|
||||
display: false
|
||||
}
|
||||
},
|
||||
group: [
|
||||
{
|
||||
label: '基本信息',
|
||||
collapse: true,
|
||||
prop: 'group_info',
|
||||
column: {
|
||||
username: {
|
||||
label: '用户账号',
|
||||
addDisplay: true,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
rules: [{ required: true, message: '用户账号不能为空', trigger: 'blur' }]
|
||||
},
|
||||
password: {
|
||||
label: '用户密码',
|
||||
hide: true,
|
||||
addDisplay: true,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
rules: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
nickname: {
|
||||
label: '用户昵称',
|
||||
rules: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
mobile: { label: '手机号码' },
|
||||
email: {
|
||||
label: '邮箱',
|
||||
rules: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (
|
||||
value === '' ||
|
||||
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value)
|
||||
) {
|
||||
callback()
|
||||
} else callback(new Error('请输入正确的邮箱'))
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
sex: {
|
||||
label: '用户性别',
|
||||
type: 'select',
|
||||
dicData: getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX),
|
||||
value: ''
|
||||
},
|
||||
rankIds: {
|
||||
label: '职级',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
cell: true,
|
||||
dicUrl: '/system/rank/simple-list',
|
||||
dataType: 'array',
|
||||
props: { label: 'name', value: 'id' }
|
||||
},
|
||||
remark: { label: '备注', span: 24, type: 'textarea', minRows: 2, maxRows: 4 }
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '部门信息',
|
||||
collapse: true,
|
||||
prop: 'group_dept',
|
||||
labelWidth: 20,
|
||||
display: true,
|
||||
column: {
|
||||
deptInfoList: {
|
||||
hide: false,
|
||||
span: 24,
|
||||
dataType: 'array',
|
||||
value: [
|
||||
{ $cellEdit: true, deptId: '', roleIds: [], dutyIds: [], positionIds: [], postIds: [] }
|
||||
],
|
||||
rules: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value?.length) callback()
|
||||
else callback(new Error('请添加用户部门信息'))
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}) //表格配置
|
||||
const tableForm = ref<any>({})
|
||||
const tableData = ref([])
|
||||
const tableSearch = ref<any>({})
|
||||
const tablePage = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
deptId: null
|
||||
})
|
||||
const permission = getCurrPermi(['system:user'])
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
const deptOption = ref({
|
||||
addBtn: false,
|
||||
editBtn: false,
|
||||
addRowBtn: true,
|
||||
refreshBtn: false,
|
||||
columnBtn: false,
|
||||
addBtnText: '新增部门',
|
||||
index: true,
|
||||
menuWidth: 120,
|
||||
height: undefined,
|
||||
calcHeight: undefined,
|
||||
maxHeight: 300,
|
||||
column: {
|
||||
deptId: {
|
||||
label: '所属部门',
|
||||
findType: 'all',
|
||||
multiple: false,
|
||||
cell: true,
|
||||
rules: [{ required: true, message: '请选择所属部门', trigger: 'change' }]
|
||||
},
|
||||
roleIds: {
|
||||
label: '角色',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
cell: true,
|
||||
dataType: 'array',
|
||||
dicUrl: '/system/role/simple-list',
|
||||
props: { label: 'name', value: 'id' },
|
||||
dicFormatter: (res) => {
|
||||
//剔除超级管理员和租户管理员
|
||||
return res.filter((item) => !['super_admin', 'tenant_admin'].includes(item.code))
|
||||
}
|
||||
},
|
||||
dutyIds: {
|
||||
label: '职务',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
cell: true,
|
||||
dataType: 'array',
|
||||
dicUrl: '/system/duty/simple-list',
|
||||
props: { label: 'name', value: 'id' }
|
||||
},
|
||||
positionIds: {
|
||||
label: '职位',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
cell: true,
|
||||
dataType: 'array',
|
||||
dicUrl: '/system/position/simple-list',
|
||||
props: { label: 'name', value: 'id' }
|
||||
},
|
||||
postIds: {
|
||||
label: '岗位',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
cell: true,
|
||||
dataType: 'array',
|
||||
dicUrl: '/system/post/simple-list',
|
||||
props: { label: 'name', value: 'id' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const deptCrudRef = ref()
|
||||
|
||||
const nodeClick = (row: any) => {
|
||||
if (tablePage.value.deptId == row.id) {
|
||||
tablePage.value.deptId = null
|
||||
treeRef.value.setCurrentKey(null)
|
||||
} else {
|
||||
tablePage.value.deptId = row.id
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
let searchObj = {
|
||||
...tableSearch.value,
|
||||
pageNo: tablePage.value.currentPage,
|
||||
pageSize: tablePage.value.pageSize,
|
||||
deptId: tablePage.value.deptId
|
||||
}
|
||||
|
||||
if (searchObj.createTime?.length) {
|
||||
searchObj.createTime = getSearchDate(searchObj.createTime)
|
||||
} else delete searchObj.createTime
|
||||
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
try {
|
||||
const data = await UserApi.getUserPage(searchObj)
|
||||
tableData.value = data.list.map((item) => {
|
||||
item.deptInfoList = item.deptInfoList.map((dept) => {
|
||||
keyList.forEach(({ value }) => {
|
||||
if (value === 'deptName') return
|
||||
if (dept[value]?.length) dept[value] = dept[value].map((info) => info.name).join('、')
|
||||
else dept[value] = ''
|
||||
})
|
||||
return dept
|
||||
})
|
||||
return item
|
||||
})
|
||||
tablePage.value.total = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
useCrudHeight(crudRef)
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const searchChange = (params, done) => {
|
||||
tablePage.value.currentPage = 1
|
||||
getTableData().finally(() => {
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
/** 清空按钮操作 */
|
||||
const resetChange = () => {
|
||||
searchChange({}, () => {})
|
||||
}
|
||||
|
||||
const sizeChange = (pageSize) => {
|
||||
tablePage.value.pageSize = pageSize
|
||||
resetChange()
|
||||
}
|
||||
|
||||
const currentChange = (currentPage) => {
|
||||
tablePage.value.currentPage = currentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
/** 表单打开前 */
|
||||
const beforeOpen = async (done, type) => {
|
||||
let bool = true
|
||||
if (['edit', 'view'].includes(type) && tableForm.value.id) {
|
||||
loading.value = true
|
||||
const currData = await UserApi.getUser(tableForm.value.id)
|
||||
if (currData.rankInfoList?.length) {
|
||||
currData.rankIds = currData.rankInfoList.map((item) => item.id)
|
||||
}
|
||||
delete currData.rankInfoList
|
||||
if (currData.deptInfoList?.length) {
|
||||
const deptObj = {}
|
||||
currData.deptInfoList = currData.deptInfoList.map((dept) => {
|
||||
keyList.forEach(({ value }) => {
|
||||
dept.deptId = dept.deptId + ''
|
||||
if (value === 'deptName') deptObj[dept.deptId] = dept[value]
|
||||
else {
|
||||
const key = value.split('InfoList')[0] + 'Ids'
|
||||
dept[key] = dept[value].map((info) => info.id)
|
||||
}
|
||||
delete dept[value]
|
||||
})
|
||||
if (type == 'edit') dept.$cellEdit = true
|
||||
return dept
|
||||
})
|
||||
lowStore.setDicObj('deptSelect', deptObj)
|
||||
} else {
|
||||
currData.deptInfoList = cloneDeep(tableOption.value.group[1].column.deptInfoList?.value)
|
||||
}
|
||||
if (currData.supAdminFlag || currData.tenantAdminFlag) {
|
||||
delete currData.deptInfoList
|
||||
bool = false
|
||||
}
|
||||
tableForm.value = currData
|
||||
loading.value = false
|
||||
}
|
||||
tableOption.value.group[1].display = bool
|
||||
done()
|
||||
}
|
||||
|
||||
const verifydeptTable = () => {
|
||||
return new Promise((resolve) => {
|
||||
deptCrudRef.value.validateCellForm().then((res) => {
|
||||
if (res) {
|
||||
const dom = document.querySelector('.system-user-form .el-dialog__body')
|
||||
if (dom) dom.scrollTop = dom.scrollHeight
|
||||
}
|
||||
resolve(!res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 新增操作 */
|
||||
const rowSave = async (form, done, loading) => {
|
||||
const verify = await verifydeptTable()
|
||||
if (!verify) return loading()
|
||||
let bool = await UserApi.createUser(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.createSuccess'))
|
||||
resetChange()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 编辑操作 */
|
||||
const rowUpdate = async (form, index, done, loading) => {
|
||||
const verify = await verifydeptTable()
|
||||
if (!verify) return loading()
|
||||
delete form.password
|
||||
if (form.supAdminFlag || form.tenantAdminFlag) delete form.deptInfoList
|
||||
let bool = await UserApi.updateUser(form).catch(() => false)
|
||||
if (bool) {
|
||||
message.success(t('common.updateSuccess'))
|
||||
getTableData()
|
||||
done()
|
||||
} else loading()
|
||||
}
|
||||
|
||||
/** 操作分发 */
|
||||
const handleCommand = (command: string, row: UserApi.UserVO) => {
|
||||
switch (command) {
|
||||
case 'handleDelete':
|
||||
rowDel(row)
|
||||
break
|
||||
case 'handleResetPwd':
|
||||
handleResetPwd(row)
|
||||
break
|
||||
case 'handleRole':
|
||||
handleRole(row)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const rowDel = async (form) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await UserApi.deleteUser(form.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 重置密码 */
|
||||
const handleResetPwd = async (row: UserApi.UserVO) => {
|
||||
try {
|
||||
// 重置的二次确认
|
||||
const result = await message.prompt(
|
||||
'请输入"' + row.username + '"的新密码',
|
||||
t('common.reminder')
|
||||
)
|
||||
const password = result.value
|
||||
// 发起重置
|
||||
await UserApi.resetUserPwd(row.id, password)
|
||||
message.success('修改成功,新密码是:' + password)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 分配角色 */
|
||||
const assignRoleFormRef = ref()
|
||||
const handleRole = (row: UserApi.UserVO) => {
|
||||
assignRoleFormRef.value.open(row)
|
||||
}
|
||||
|
||||
/** 修改用户状态 */
|
||||
const handleStatusChange = async (row: UserApi.UserVO) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
|
||||
await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?')
|
||||
// 发起修改状态
|
||||
await UserApi.updateUserStatus(row.id, row.status)
|
||||
// 刷新列表
|
||||
await getTableData()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.status =
|
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||
}
|
||||
}
|
||||
|
||||
/** 用户导入 */
|
||||
const importFormRef = ref()
|
||||
const handleImport = () => {
|
||||
importFormRef.value.open()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
let searchObj = { ...tableSearch.value }
|
||||
if (tablePage.value.deptId) {
|
||||
searchObj.deptId = tablePage.value.deptId
|
||||
}
|
||||
for (let key in searchObj) if (searchObj[key] === '') delete searchObj[key]
|
||||
const data = await UserApi.exportUser(searchObj)
|
||||
download.excel(data, '用户管理列表.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const delDeptTableRow = (index) => {
|
||||
tableForm.value.deptInfoList.splice(index, 1)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
DeptApi.getSimpleDeptList().then((res) => {
|
||||
const tree = handleTree(res)
|
||||
treeData.value = tree
|
||||
})
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.system-user-form {
|
||||
.el-dialog__body {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.avue-crud {
|
||||
.avue-crud__empty {
|
||||
padding: 0;
|
||||
|
||||
.el-empty {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-empty__description {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user