This commit is contained in:
zhangtao 2022-10-09 11:51:00 +08:00
parent 61273892d8
commit bfc7805172
47 changed files with 2248 additions and 2027 deletions

View File

@ -28,7 +28,11 @@
:pagination="pagination"
@update:page="updatePage"
@update:page-size="updatePageSize"
/>
>
<template #empty>
<slot name="empty" />
</template>
</n-data-table>
</div>
</template>

View File

@ -50,7 +50,10 @@ export default defineComponent({
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return data.permissionList.includes(action.auth) || action.auth === ''
if (!Object.keys(action).includes('show')) {
action.show = Object.keys(action).includes('hidden') ? !action.hidden : true
}
return (data.permissionList.includes(action.auth) || action.auth === '') && action.show
})
})

View File

@ -58,10 +58,10 @@ export default defineComponent({
const { filters } = unref(props)
function getFilter(value) {
const data = filters.find(item => {
return item.key === value
return item.value === value
})
return data || {
key: value,
value: value,
label: value
}
}

View File

@ -29,6 +29,8 @@ export const tableProps = {
pageField: 'page',
// 每页数量字段名
sizeField: 'limit',
// 接口返回的字段名
listPageField: 'current',
// 接口返回的数据字段名
listField: 'records',
// 接口返回总页数字段名

View File

@ -4,7 +4,7 @@ import { toTreeData } from './toTree'
export function useDataSource(propsRef, { getPaginationInfo, setPagination, setLoading, tableData }, emit) {
const dataSourceRef = ref([])
const paginationPage = ref(1)
async function fetch(opt) {
try {
/* 设置loading */
@ -14,6 +14,7 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
if (!request) return
/* 获取分页信息 */
const pageField = paginationSetting.pageField
const listPageField = paginationSetting.listPageField
const sizeField = paginationSetting.sizeField
const totalField = paginationSetting.totalField
const listField = paginationSetting.listField
@ -25,6 +26,7 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
pageParams = {}
} else {
pageParams[pageField] = (opt && opt[pageField]) || page
paginationPage.value = pageParams[pageField]
pageParams[sizeField] = pageSize
}
const params = {
@ -33,7 +35,7 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
const response = await request(params)
const res = noPagination ? response : response.data
const resultTotal = res[totalField] || 0
const currentPage = res[pageField]
const currentPage = res[listPageField]
// 如果数据异常,需获取正确的页码再次执行
if (resultTotal) {
if (page > Math.ceil(resultTotal / pageSize)) {
@ -45,10 +47,11 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
}
// 处理数据结构
const resultInfo = res[listField] ? res[listField] : res
dataSourceRef.value = dataType === 'tree' ? dealTree(resultInfo) : resultInfo
dataSourceRef.value = dataType === 'tree' ? dealTree(resultInfo.data) : resultInfo
setPagination({
[pageField]: currentPage,
[totalField]: Math.ceil(resultTotal / pageSize)
[totalField]: Math.ceil(resultTotal / pageSize),
itemCount: resultTotal
})
/* 更新页码数据 */
if (opt && opt[pageField]) {
@ -105,13 +108,13 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
await fetch(opt)
}
async function reFetch(opt) {
async function reFetch(opt, reload = true) {
const { paginationSetting } = unref(propsRef)
const pageField = paginationSetting.pageField
const sizeField = paginationSetting.sizeField
const pageSize = paginationSetting.pageSize
setPagination({
[pageField]: 1,
[pageField]: reload ? 1 : paginationPage.value,
[sizeField]: pageSize
})
await fetch(opt)

View File

@ -17,7 +17,10 @@ export function usePagination(refProps) {
showQuickJumper: paginationSetting.showQuickJumper,
...(isBoolean(pagination) ? {} : pagination),
...unref(configRef),
pageCount: unref(configRef)[paginationSetting.totalField]
pageCount: unref(configRef)[paginationSetting.totalField],
prefix({ itemCount }) {
return `${itemCount}`
}
}
})

View File

@ -1,5 +1,6 @@
<template>
<n-modal
ref="modalRef"
v-bind="getModalOptions"
:style="`width:${getModalOptions.width}px`"
:title="options.title"
@ -12,7 +13,7 @@
<script>
import { defineComponent, computed } from 'vue'
import { defineComponent, computed, ref } from 'vue'
export default defineComponent({
name: 'CardDialogModal',
props: {
@ -28,6 +29,7 @@ export default defineComponent({
}
},
setup(props, { emit }) {
const modalRef = ref(null)
const getModalOptions = computed(() => {
return {
...props.options,
@ -43,7 +45,45 @@ export default defineComponent({
const handleClose = function() {
emit('onClose', true)
}
// setTimeout(() => {
// const dialogHeaderEl = document.querySelector('.n-card-header')
// const dragDom = document.querySelector('.n-modal')
// dragDom.style.overflow = 'auto'
// dialogHeaderEl.style.cursor = 'move'
// const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
// const moveDown = (e) => {
// //
// const disX = e.clientX - dialogHeaderEl.offsetLeft
// const disY = e.clientY - dialogHeaderEl.offsetTop
// // px
// let styL, styT
// // ie 50% px
// if (sty.left.includes('%')) {
// styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
// styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
// } else {
// styL = +sty.left.replace(/\px/g, '')
// styT = +sty.top.replace(/\px/g, '')
// }
// document.onmousemove = function(e) {
// //
// const l = e.clientX - disX
// const t = e.clientY - disY
// //
// dragDom.style.left = `${l + styL}px`
// dragDom.style.top = `${t + styT}px`
// }
// document.onmouseup = function(e) {
// document.onmousemove = null
// document.onmouseup = null
// }
// }
// dialogHeaderEl.onmousedown = moveDown
// })
return {
modalRef,
getModalOptions,
handleConfirm,
handleClose
@ -53,4 +93,9 @@ export default defineComponent({
</script>
<style scoped lang='scss'>
::v-deep(.n-scrollbar-content){
&:first-child{
display: none;
}
}
</style>

View File

@ -2,21 +2,15 @@
<div>
<n-form ref="formRef" v-bind="getFormOptions">
<template v-for="(item, index) in getFormOptions.info" :key="`${index}-${item.label}`">
<n-form-item v-if="['input'].includes(item.type) || !item.type" :label="item.label">
<n-input v-model:value="getFormOptions.form[item.key]" v-bind="item.props" />
</n-form-item>
<n-form-item v-if="['select'].includes(item.type) " :label="item.label">
<n-select v-model:value="getFormOptions.form[item.key]" v-bind="item.props" />
</n-form-item>
<n-form-item v-if="['area'].includes(item.type) " :label="item.label">
<AreaCascader :ref="el=>{itemRefs[item.refIndex] = el}" v-model:value="getFormOptions.form[item.key]" v-bind="item.props" @selectd="handleSelect" />
</n-form-item>
<n-form-item v-if="['date'].includes(item.type) " :label="item.label">
<n-date-picker v-model="getFormOptions.form[item.key]" v-bind="item.props" />
<n-form-item :class="{'hidden-item': index > showItemNum}" :label="item.label">
<n-input v-if="['input'].includes(item.type) || !item.type" v-model:value="getFormOptions.form[item.key]" v-bind="item.props" />
<n-select v-if="['select'].includes(item.type)" v-model:value="getFormOptions.form[item.key]" v-bind="item.props" />
<AreaCascader v-if="['area'].includes(item.type)" :ref="el=>{itemRefs[item.refIndex] = el}" v-model:value="getFormOptions.form[item.key]" v-bind="item.props" @selectd="handleSelect" />
<n-date-picker v-if="['date'].includes(item.type)" v-model:formatted-value="getFormOptions.form[item.key]" v-bind="item.props" />
</n-form-item>
</template>
<n-form-item class="form__button">
<n-button @click="handleSearch">查询</n-button>
<n-button type="primary" @click="handleSearch">查询</n-button>
<n-button @click="handleReset">重置</n-button>
<n-button
v-if="showButton"
@ -29,7 +23,7 @@
</template>
<script>
import { computed, ref, unref } from 'vue'
import { computed, ref, unref, watch } from 'vue'
import { NForm } from 'naive-ui'
import AreaCascader from '@/components/AreaCascader/index.vue'
export default {
@ -42,9 +36,9 @@ export default {
default: () => []
}
},
emits: ['search', 'reset'],
emits: ['search', 'change', 'reset'],
setup(props, { emit }) {
const showItemNum = ref(3)
const showItemNum = ref(30)
const len = ref(props.info.length - 1)
const itemRefs = ref([])
const showButton = ref(!!(showItemNum.value < len.value))
@ -52,12 +46,14 @@ export default {
/* 初始化搜索表单信息 */
function initForm() {
Object.keys(form.value).forEach((key) => {
form.value[key] = null
const index = unref(props).info.findIndex((item) => item.key === key)
form.value[key] = (props).info[index].value || null
})
itemRefs.value.forEach((item) => {
item.clearValue()
})
}
/* 地图选择事件 */
function handleSelect(data) {
form.value = {
...form.value,
@ -65,21 +61,42 @@ export default {
}
}
const getFormOptions = computed(() => {
props.info.forEach((item) => {
// const hasInit = item.init || false
// const hasKey = Object.keys(form.value).includes(item.key)
// form.value[item.key] = hasInit ? (item.value || null) : hasKey ? form.value[item.key] : (item.value || null)
form.value[item.key] = item.value || null
})
return {
form: unref(form),
labelWidth: 'auto',
labelPlacement: 'left',
inline: true,
info: [...unref(props).info]
info: [...props.info]
}
})
watch(getFormOptions.value.form,
(value) => {
emit('change', getFormOptions.value.form)
})
function handleSearch() {
emit('search', getFormOptions.value.form)
}
function handleReset() {
initForm()
emit('reset', getFormOptions.value.form)
}
function setFormValue(params) {
Object.keys(params).forEach((key) => {
const index = unref(props).info.findIndex((item) => item.key === key)
form.value[key] = (props).info[index].value || null
})
}
function showMoreItem() {
showItemNum.value = showItemNum.value === len.value ? 3 : len.value
}
@ -89,6 +106,7 @@ export default {
getFormOptions,
handleSearch,
handleReset,
setFormValue,
handleSelect,
itemRefs,
showMoreItem
@ -111,6 +129,9 @@ export default {
.n-cascader{
width: 200px;
}
&.hidden-item{
display: none;
}
transition: all 10s;
}
.form__button{

View File

@ -0,0 +1,73 @@
<template>
<div v-if="external" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-bind="$attrs" />
<svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
import { isExternal } from '@/utils/is.js'
import { defineComponent, computed } from 'vue'
export default defineComponent({
name: 'SvgIcon',
props: {
prefix: {
type: String,
default: 'icon'
},
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
setup(props, { emit }) {
const external = computed(() => {
return isExternal(props.iconClass)
})
const iconName = computed(() => {
return `#icon-${props.iconClass}`
})
const svgClass = computed(() => {
if (props.className) {
return 'svg-icon ' + props.className
} else {
return 'svg-icon'
}
})
const styleExternalIcon = computed(() => {
return {
mask: `url(${props.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
}
})
return {
external,
iconName,
svgClass,
styleExternalIcon
}
}
})
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<div v-if="isFailed && useEmpty">
<slot name="empty" />
</div>
<div v-else :id="getPlayerId" />
</template>
<script>
import { defineComponent, reactive, ref, computed, toRefs, nextTick } from 'vue'
export default defineComponent({
name: 'VideoPlayer',
props: {
/* 播放器id */
id: {
type: String,
default: () => 'palyer'
},
/* 是有填充空白 */
useEmpty: {
type: Boolean,
default: false
}
},
emits: ['timeUpdate', 'video-status'],
setup(props, { emit }) {
const videoPlayer = Symbol.for(props.id)
const data = reactive({
[videoPlayer]: null,
seekTime: null,
canSeek: true,
isSeek: false,
isFailed: true
})
/* 获取播放器的id */
const getPlayerId = computed(() => {
return props.id
})
/* 播放器组件 */
const toolComponent = Aliplayer.Component({
/* 时间更新事件 */
timeupdate(player, e) {
data.seekTime = player.getCurrentTime()
emit('video-status', data.isSeek ? 'skip' : 'playing')
},
ready(player, e) {
emit('video-status', 'ready')
},
pause(player, e) {
emit('video-status', 'pause')
},
play(player, e) {
emit('video-status', 'play')
},
ended(player, e) {
emit('video-status', 'ended')
},
error(player, e) {
data.isFailed = true
emit('video-status', 'error')
}
})
/* 初始化播放器 */
function init(options) {
if (!options.source) {
data.isFailed = true
} else {
data.isFailed = false
nextTick(() => {
/* 实例化ali播放器 */
const player = new Aliplayer(
{
id: props.id,
width: '500px',
height: '260px',
autoplay: true,
...options,
components: [toolComponent]
},
function(player) { player.mute() }
)
/* 监听开始拖拽事件 */
player.on('startSeek', ({ paramData }) => {
/* 仅变更标识 */
data.isSeek = true
})
/* 监听完成拖拽事件 */
player.on('completeSeek', ({ paramData }) => {
/* 仅变更标识 */
data.isSeek = false
data.seekTime = paramData
/* 是否通知跳转 */
if (data.canSeek) {
emit('video-status', 'skip')
}
})
data[videoPlayer] = player
})
}
}
/* 获取当前播放器的时间 */
function getTime() {
let currentTime = 0
let duration = 0
const seekTime = data.seekTime
if (data[videoPlayer] && !data.isFailed) {
currentTime = data[videoPlayer]?.getCurrentTime()
duration = data[videoPlayer]?.getDuration()
}
return {
currentTime,
duration,
seekTime
}
}
/* 设定播放器播放时间 */
function seekTime(time) {
if (data[videoPlayer] && !data.isFailed) {
data.canSeek = false
data[videoPlayer]?.seek(time)
setTimeout(() => {
data.canSeek = true
}, 100)
}
}
/* 设定播放器开始 */
function playVideo() {
if (data[videoPlayer] && !data.isFailed) {
data[videoPlayer]?.play()
}
}
/* 设定播放器暂停 */
function pauseVideo() {
if (data[videoPlayer] && !data.isFailed) {
data[videoPlayer]?.pause()
}
}
/* 销毁播放器 */
function disposeVideo() {
if (data[videoPlayer] && !data.isFailed) {
data[videoPlayer]?.dispose()
}
data.isFailed = true
}
return {
...toRefs(data),
getPlayerId,
init,
getTime,
seekTime,
playVideo,
pauseVideo,
disposeVideo
}
}
})
</script>
<style>
.prism-player .prism-ErrorMessage .prism-error-operation{
border-bottom: none;
}
.prism-player .prism-ErrorMessage .prism-error-operation a.prism-button.prism-button-refresh{
display: none;
}
.prism-player .prism-ErrorMessage .prism-error-operation a.prism-button.prism-button-orange{
display: none;
}
/* .prism-player .prism-ErrorMessage .prism-detect-info.prism-center{
display: none;
} */
</style>

View File

@ -3,16 +3,45 @@ export default [
{
path: '/system',
component: Layout,
redirect: '/system/menu',
redirect: '/system/user',
name: 'System',
title: '系统管理',
meta: {
title: '系统管理'
},
children: [
{
path: 'user',
component: () => import('@/views/system-manage/user-manage/index.vue'),
name: 'SystemUser',
title: '用户管理',
meta: {
title: '用户管理'
}
},
{
path: 'role',
component: () => import('@/views/system-manage/role-manage/index.vue'),
name: 'SystemRole',
title: '角色管理',
meta: {
title: '角色管理'
}
},
{
path: 'dept',
component: () => import('@/views/system-manage/department-manage/index.vue'),
name: 'SystemDept',
title: '部门管理',
meta: {
title: '部门管理'
}
},
{
path: 'menu',
component: () => import('@/views/system/menu/index.vue'),
component: () => import('@/views/system-manage/menu-manage/index.vue'),
name: 'SystemMenu',
title: '菜单管理',
meta: {
title: '菜单管理'
}

View File

@ -105,7 +105,7 @@ export default {
}
/* 调用store中的方法登录以及存储token和用户信息 */
const res = await userStore.getLoginToken(this.loginForm)
const res = await userStore.getLoginToken(data.loginForm)
if (res.code === 0) {
const toPath = decodeURIComponent((route.query?.redirect || '/'))
route.name === '/login' ? router.replace('/') : router.replace(toPath)

View File

@ -0,0 +1,139 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="departmentForm"
:rules="departmentRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="departmentForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="departmentForm[item.key]" v-bind="item.props" />
<n-input-number v-if="item.type === 'number'" v-model:value="departmentForm[item.key]" v-bind="item.props" />
</n-form-item>
</template>
</n-form>
</template>
</Modal>
</template>
<script>
import { form, getDeptOptions } from '../tools/form.js'
import Modal from '@/components/Modal/index.vue'
import { addDept, editDept } from '@/api/system/dept/index.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
export default defineComponent({
name: 'UserModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建部门',
'preview': '部门详情',
'update': '编辑部门'
}
getDeptOptions()
const { departmentForm, departmentRules } = form
const formRef = ref()
const data = reactive({
departmentForm: {
...departmentForm,
...props.data,
pid: props.data?.pid === 0 ? null : props.data?.pid
},
departmentRules: {
...departmentRules
},
disabled: props.type === 'preview'
})
const getModalOptions = computed(() => {
return {
title: MODAL_TYPE[props.type],
show: props.visible,
negativeText: '取消',
positiveText: '确认'
}
})
const getFormOptions = computed(() => {
return {
...form.formItem
}
})
function handleConfirm() {
formRef.value.validate((errors) => {
if (!errors) {
const params = { ...data.departmentForm }
if (params.id) {
editDept(params).then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
}).catch(e => {
console.log(e)
})
} else {
addDept(params).then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
}
})
}
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
return {
...toRefs(data),
formRef,
getModalOptions,
getFormOptions,
handleConfirm,
handleClose
}
}
})
</script>
<style scoped lang='scss'>
.n-input-number{
width: 100%;
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<div>
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
:pagination="false"
data-type="tree"
size="large"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
</template>
</data-table>
</n-card>
</div>
<!-- 新增编辑弹窗 -->
<DepartmentModal v-if="modalShow" v-model:visible="modalShow" :data="rowData" :type="modalType" @reload="handleSearch" />
</template>
<script>
import search from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import DepartmentModal from './components/DepartmentModal.vue'
import { getDeptList } from '@/api/system/dept/index.js'
import { unref, toRefs, reactive, onUnmounted } from 'vue'
export default {
name: 'MenuPage',
components: { dataTable, DepartmentModal, headSearch },
setup() {
const data = reactive({
...toRefs(table),
search
})
const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await getDeptList(_params)
}
//
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}
onUnmounted(() => {
data.searchParams = null
})
return {
...toRefs(data),
loadDataTable,
handleModal
}
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,32 @@
import { ref, reactive } from 'vue'
import { USER_STATUS } from '@/utils/dictionary.js'
import { getDeptAll } from '@/api/system/dept/index'
import { dataToSelect } from '@/utils/handleData.js'
const departmentOptions = ref()
export const form = reactive({
departmentForm: {
code: '',
name: '',
sort: null,
pid: null,
note: ''
},
departmentRules: {
code: [{ required: true, message: '请输入部门编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入部门名称', type: 'string', trigger: 'blur' }],
sort: [{ required: true, type: 'number', message: '请输入排序号', trigger: 'blur' }]
},
formItem: [
{ type: 'input', key: 'code', label: '部门编号', props: { maxlength: '20', placeholder: '请输入部门编号', clearable: true }},
{ type: 'input', key: 'name', label: '部门名称', props: { maxlength: '20', placeholder: '请输入部门名称', clearable: true }},
{ type: 'select', key: 'pid', label: '所属部门', props: { options: departmentOptions, placeholder: '请选择所属部门' }},
{ type: 'number', key: 'sort', label: '排序号', props: { min: 0, placeholder: '请输入排序号', clearable: true }},
{ type: 'input', key: 'note', label: '备注', props: { type: 'textarea', autosize: { maxlength: '200', minRows: 3, maxRows: 3 }}}
]
})
export const getDeptOptions = async function() {
const res = await getDeptAll()
departmentOptions.value = dataToSelect(res.data, { label: 'name', value: 'id' })
}

View File

@ -1,4 +1,6 @@
const data = [
import { reactive } from 'vue'
const data = reactive([
{
label: '部门名称',
key: 'name',
@ -6,7 +8,7 @@ const data = [
placeholder: '请输入部门名称'
}
}
]
])
export default data

View File

@ -0,0 +1,124 @@
import { h, ref, reactive } from 'vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { deleteDept } from '@/api/system/dept/index.js'
/* 注册table */
const tableRef = ref()
const searchParams = ref()
function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}
/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建preview:预览edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = type === 'create' ? { pid: row.id } : row
data.modalType = type
data.modalShow = true
}
// 删除方法
function deleteData(id) {
deleteDept(id)
.then((res) => {
if (res.code === 0) {
handleSearch()
}
})
.catch((e) => {
console.log(e)
})
}
const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
handleSearch,
columns: [
{
title: '部门编号',
key: 'code',
align: 'center'
},
{
title: '部门名称',
key: 'name',
align: 'center'
},
{
title: '排序',
key: 'sort',
align: 'center'
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
minWidth: 120
},
{
title: '更新时间',
key: 'updateTime',
align: 'center',
minWidth: 120
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '添加',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'create')
},
auth: 'basic_list'
},
{
label: '修改',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})
export default data

View File

@ -0,0 +1,161 @@
<template>
<Modal
:options="getModalOptions"
:on-close="handleClose"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="menuForm"
:rules="menuRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<n-grid x-gap="12" :cols="2">
<template v-for="(item,index) in getFormOptions" :key="index">
<n-gi>
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="menuForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="menuForm[item.key]" v-bind="item.props" />
<n-input-number v-if="item.type === 'number'" v-model:value="menuForm[item.key]" v-bind="item.props" />
<n-radio-group v-if="item.type === 'radio'" v-model:value="menuForm[item.key]" :name="item.key">
<n-space>
<n-radio v-for="(cItem,cIndex) in item.options" :key="`${item.key}_${cIndex}`" :value="cItem.value"> {{ cItem.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-gi>
</template>
</n-grid>
</n-form>
</template>
</Modal>
</template>
<script>
import { form, changeMenuType, getMenuOptions } from '../tools/form.js'
import { defineComponent, ref, reactive, computed, watch, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import { addMenu, editMenu } from '@/api/system/menu/index.js'
export default defineComponent({
name: 'MenuModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建菜单',
'preview': '菜单详情',
'update': '编辑菜单'
}
getMenuOptions()
const { menuForm, menuRules } = form
const formRef = ref()
const data = reactive({
menuForm: {
...menuForm,
...props.data,
pid: props.data?.pid === 0 ? null : props.data?.pid
},
menuRules: {
...menuRules
},
disabled: props.type === 'preview'
})
watch(() => data.menuForm.type,
(val) => {
switch (val) {
case 0:
data.menuForm.permission = ''
break
case 1:
data.menuForm.path = ''
data.menuForm.component = ''
break
}
changeMenuType(val)
})
const getModalOptions = computed(() => {
return {
title: MODAL_TYPE[props.type],
show: props.visible,
negativeText: '取消',
positiveText: '确认'
}
})
const getFormOptions = computed(() => {
console.log('change')
return {
...form.formItem
}
})
function handleConfirm() {
formRef.value.validate((errors) => {
if (!errors) {
const params = { ...data.menuForm }
if (params.id) {
editMenu(params).then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
}).catch(e => {
console.log(e)
})
} else {
addMenu(params).then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
return {
...toRefs(data),
formRef,
getModalOptions,
getFormOptions,
handleConfirm,
handleClose
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -0,0 +1,69 @@
<template>
<div>
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:pagination="false"
data-type="tree"
:request="loadDataTable"
:row-key="(row) => row.id"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 添加菜单 </n-button>
</template>
</data-table>
</n-card>
</div>
<MenuModal v-if="modalShow" v-model:visible="modalShow" :data="rowData" :type="modalType" @reload="handleSearch" />
</template>
<script>
import search from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import MenuModal from './components/MenuModal.vue'
import { getMenuList } from '@/api/system/menu/index.js'
import { unref, reactive, toRefs, onUnmounted } from 'vue'
export default {
name: 'MenuPage',
components: { dataTable, MenuModal, headSearch },
setup() {
const data = reactive({
...toRefs(table),
search
})
const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await getMenuList(_params)
}
//
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}
onUnmounted(() => {
data.searchParams = null
})
return {
...toRefs(data),
loadDataTable,
handleModal
}
}
}
</script>
<style scoped lang='scss'>
</style>

View File

@ -0,0 +1,53 @@
import { ref, reactive, watch } from 'vue'
import { MENU_TYPE, MENU_OPEN, MENU_VISIBLE, MENU_STATUS } from '@/utils/dictionary.js'
import { getMenu } from '@/api/system/menu/index.js'
import { dataToSelect } from '@/utils/handleData.js'
const menuOptions = ref()
const menuType0 = ref(true)
const menuType1 = ref(false)
export const form = reactive({
menuForm: {
pid: null,
type: 0,
title: '',
target: '1',
icon: '',
permission: '',
path: '',
sort: null,
component: '',
hide: 0,
status: 1
},
menuRules: {
title: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
sort: [{ required: true, type: 'number', message: '请输入排序号', trigger: 'blur' }]
},
formItem: [
{ type: 'select', key: 'pid', label: '上级菜单', props: { options: menuOptions, placeholder: '请选择上级菜单' }},
{ type: 'radio', key: 'type', label: '菜单类型', options: MENU_TYPE },
{ type: 'input', key: 'title', label: '菜单名称', props: { maxlength: '20', placeholder: '请输入菜单名称', clearable: true }},
{ type: 'radio', key: 'target', label: '打开方式', options: MENU_OPEN },
{ type: 'input', key: 'icon', label: '菜单图标', props: { maxlength: '20', placeholder: '请选择菜单图标', clearable: true }},
{ type: 'input', key: 'permission', label: '权限标识', props: { maxlength: '20', placeholder: '请输入部门名称', disabled: menuType0, clearable: true }},
{ type: 'input', key: 'path', label: '路由地址', props: { maxlength: '20', placeholder: '请输入部门名称', disabled: menuType1, clearable: true }},
{ type: 'number', key: 'sort', label: '排序号', props: { min: 0, placeholder: '请输入排序号', clearable: true }},
{ type: 'input', key: 'component', label: '组件路径', props: { maxlength: '20', placeholder: '请输入部门名称', disabled: menuType1, clearable: true }},
{ type: 'radio', key: 'hide', label: '是否可见', options: MENU_VISIBLE },
{ type: 'radio', key: 'status', label: '菜单状态', options: MENU_STATUS }
]
})
export const getMenuOptions = async function() {
const res = await getMenu()
setTimeout(() => {
menuOptions.value = dataToSelect(res.data, { label: 'title', value: 'id' })
}, 3000)
}
export const changeMenuType = function(value) {
menuType0.value = !value
menuType1.value = !menuType0.value
}

View File

@ -0,0 +1,13 @@
import { reactive } from 'vue'
const data = reactive([
{
label: '菜单名称',
key: 'title',
props: {
placeholder: '请输入菜单名称'
}
}
])
export default data

View File

@ -0,0 +1,165 @@
import { MENU_TYPE, MENU_STATUS, MENU_VISIBLE } from '@/utils/dictionary.js'
import { h, ref, reactive } from 'vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { deleteMenu } from '@/api/system/menu/index.js'
/* 注册table */
const tableRef = ref()
const searchParams = ref()
function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}
/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建preview:预览edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = type === 'create' ? { pid: row.id } : row
data.modalType = type
data.modalShow = true
}
// 删除方法
function deleteData(id) {
deleteMenu(id)
.then((res) => {
if (res.code === 0) {
handleSearch()
}
})
.catch((e) => {
console.log(e)
})
}
const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
handleSearch,
columns: [
{
title: '菜单标题',
key: 'title',
align: 'center',
width: 200
},
{
title: '菜单类型',
key: 'type',
align: 'center',
width: 100,
render(row) {
return h(TableTags, {
data: row.type,
filters: MENU_TYPE
})
}
},
{
title: '路由地址',
key: 'path',
align: 'center',
width: 200
},
{
title: '组件路径',
key: 'component',
align: 'center',
width: 200
},
{
title: '状态',
key: 'status',
align: 'center',
width: 100,
render(row) {
return h(TableTags, {
data: row.status,
filters: MENU_STATUS
})
}
},
{
title: '排序',
key: 'sort',
align: 'center',
width: 100
},
{
title: '是否可见',
key: 'hide',
align: 'center',
width: 100,
render(row) {
return h(TableTags, {
data: row.hide,
filters: MENU_VISIBLE
})
}
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
width: 160
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '添加',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'create')
},
auth: 'basic_list'
},
{
label: '修改',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})
export default data

View File

@ -33,14 +33,14 @@ export default defineComponent({
type: Boolean,
default: false
},
row: {
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'done': null
'reload': null
},
setup(props, { emit }) {
const data = reactive({
@ -62,7 +62,7 @@ export default defineComponent({
//
async function getMenuByRoleId() {
const res = await getRolePermission(props.row.id)
const res = await getRolePermission(props.data.id)
data.allTreeData = toTreeData(res.data, 'id', 'pid', 'children')
const checkedArr = res.data
if (checkedArr.length) {
@ -80,6 +80,17 @@ export default defineComponent({
data.menuIds = keys
}
function handleConfirm() {
savePermission({ roleId: props.data.id, menuIds: data.menuIds }).then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
}).catch(e => {
console.log(e)
})
}
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
@ -88,25 +99,9 @@ export default defineComponent({
return {
...toRefs(data),
getModalOptions,
handleClose,
handleCheckTree
}
},
methods: {
//
handleConfirm() {
console.log(this.menuIds, this.checkedTreeData)
savePermission({ roleId: this.row.id, menuIds: this.menuIds }).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
handleCheckTree,
handleConfirm,
handleClose
}
}
})

View File

@ -0,0 +1,139 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="roleForm"
:rules="roleRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="roleForm[item.key]" v-bind="item.props" />
<n-radio-group v-if="item.type === 'radio'" v-model:value="roleForm[item.key]" :name="item.key">
<n-space>
<n-radio v-for="(cItem,cIndex) in item.options" :key="`${item.key}_${cIndex}`" :value="cItem.value"> {{ cItem.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</template>
</n-form>
</template>
</Modal>
</template>
<script>
import form from '../tools/form.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import { addRole, editRole } from '@/api/system/role/index'
export default defineComponent({
name: 'RoleModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建角色',
'preview': '角色详情',
'update': '编辑角色'
}
const { roleForm, roleRules } = form
const formRef = ref()
const data = reactive({
roleForm: {
...roleForm,
...props.data
},
roleRules: {
...roleRules
},
disabled: props.type === 'preview'
})
const getModalOptions = computed(() => {
return {
title: MODAL_TYPE[props.type],
show: props.visible,
negativeText: '取消',
positiveText: '确认'
}
})
const getFormOptions = computed(() => {
return {
...form.formItem
}
})
function handleConfirm() {
formRef.value?.validate((errors) => {
if (!errors) {
const params = { ...data.roleForm }
if (params.id) {
/* 编辑 */
editRole(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
addRole(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
return {
...toRefs(data),
formRef,
getModalOptions,
getFormOptions,
handleConfirm,
handleClose
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -0,0 +1,104 @@
<template>
<div>
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:row-key="(row) => row.id"
:request="loadDataTable"
size="large"
scroll-x="1200"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
<n-popconfirm
negative-text="取消"
positive-text="确认"
@positive-click="deleteComplex"
>
<template #trigger>
<n-button type="primary">删除</n-button>
</template>
确定删除选中的数据吗?
</n-popconfirm>
</template>
</data-table>
</n-card>
</div>
<!-- 新增编辑弹窗 -->
<RoleModal v-if="modalShow" v-model:visible="modalShow" :data="rowData" :type="modalType" @reload="handleSearch" />
<!-- 分配权限 -->
<ConfigModal v-if="configModalShow" v-model:visible="configModalShow" :data="rowData" @reload="handleSearch" />
</template>
<script>
import search from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import RoleModal from './components/RoleModal.vue'
import ConfigModal from './components/ConfigModal.vue'
import { getRoleList } from '@/api/system/role/index'
import { ref, unref, toRefs, reactive, onUnmounted } from 'vue'
export default {
name: 'MenuPage',
components: { headSearch, dataTable, RoleModal, ConfigModal },
setup() {
const data = reactive({
...toRefs(table),
search
})
const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await getRoleList(_params)
}
//
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}
//
const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}
//
function deleteComplex() {
if (selectedIds.value.length) {
data.deleteData(selectedIds.value)
} else {
$message.warning('请至少选中一条数据')
}
}
onUnmounted(() => {
data.searchParams = null
})
return {
...toRefs(data),
loadDataTable,
handleModal,
handleCheck,
selectedIds,
deleteComplex
}
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,24 @@
import { reactive } from 'vue'
import { USER_STATUS } from '@/utils/dictionary.js'
const data = reactive({
roleForm: {
code: '',
name: '',
status: 1,
note: ''
},
roleRules: {
code: [{ required: true, message: '请输入角色编号', trigger: 'blur' }],
name: [{ required: true, message: '请输入角色名称', type: 'string', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', type: 'number', trigger: 'blur' }]
},
formItem: [
{ type: 'input', key: 'code', label: '角色编号', props: { maxlength: '20', placeholder: '请输入角色编号', clearable: true }},
{ type: 'input', key: 'name', label: '角色名称', props: { maxlength: '20', placeholder: '请输入角色名称', clearable: true }},
{ type: 'radio', key: 'status', label: '状态', options: USER_STATUS },
{ type: 'input', key: 'note', label: '备注', props: { type: 'textarea', autosize: { maxlength: '200', minRows: 3, maxRows: 3 }}}
]
})
export default data

View File

@ -1,4 +1,6 @@
const data = [
import { reactive } from 'vue'
const data = reactive([
{
label: '角色名称',
key: 'name',
@ -6,6 +8,6 @@ const data = [
placeholder: '请输入角色名称'
}
}
]
])
export default data

View File

@ -0,0 +1,165 @@
import { h, ref, reactive } from 'vue'
import TableSwitch from '@/components/DataTable/tools/Switch.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { deleteRole, setRoleStatus } from '@/api/system/role/index'
/* 注册table */
const tableRef = ref()
const searchParams = ref()
function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}
/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建preview:预览edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}
/**
* @description: 打开分配权限窗口
* @param {*} row 单行数据
* @return {*}
*/
function configure(row) {
data.rowData = row
data.configModalShow = true
}
/**
* @description: 改变状态
* @param {*} row 选中数据
* @return {*}
*/
function handleStatusChange(row) {
setRoleStatus({ id: row.data.id, status: row.value })
.then((res) => {
if (res.code === 0) {
handleSearch()
}
})
.catch((e) => {
console.log(e)
})
}
// 删除接口
function deleteData(data) {
deleteRole(data)
.then((res) => {
if (res.code === 0) {
handleSearch()
}
})
.catch((e) => {
console.log(e)
})
}
const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
configModalShow: false,
handleSearch,
deleteData,
columns: [
{ type: 'selection' },
{
title: '角色编号',
key: 'code',
align: 'center'
},
{
title: '角色名称',
key: 'name',
align: 'center'
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
Minwidth: 160
},
{
title: '更新时间',
key: 'updateTime',
align: 'center',
Minwidth: 160
},
{
title: '状态',
key: 'status',
align: 'center',
width: 100,
render(row) {
return h(TableSwitch, {
data: { id: row.id, status: row.status },
rowKey: 'status',
checkedValue: 1,
uncheckedValue: 2,
onChange: handleStatusChange.bind(row)
})
}
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '修改',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '分配权限',
type: 'button',
props: {
type: 'primary',
ghost: true,
text: true,
onClick: configure.bind(null, row)
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})
export default data

View File

@ -0,0 +1,168 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="userForm"
:rules="userRules"
:label-width="80"
label-placement="left"
require-mark-placement="left"
:disabled="disabled"
>
<template v-for="(item,index) in getFormOptions" :key="index">
<n-form-item :label="item.label" :path="item.key">
<UploadOss v-if="item.type === 'oss'" :ref="el=>{ossRefs[item.refIndex] = el}" :default-list="userForm[item.file]" @upload-status="handleUploadStatus" />
<n-input v-if="item.type === 'input'" v-model:value="userForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="userForm[item.key]" v-bind="item.props" />
<n-radio-group v-if="item.type === 'radio'" v-model:value="userForm[item.key]" :name="item.key">
<n-space>
<n-radio v-for="(cItem,cIndex) in item.options" :key="`${item.key}_${cIndex}`" :value="cItem.value"> {{ cItem.label }}</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</template>
</n-form>
</template>
</Modal>
</template>
<script>
import { form, getDeptOptions, getRoleOptions } from '../tools/form.js'
import { defineComponent, ref, reactive, computed, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import UploadOss from '@/components/UploadOss/index.vue'
import { addUser, editUser } from '@/api/system/user/index'
export default defineComponent({
name: 'UserModal',
components: { Modal, UploadOss },
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'create'
},
data: {
type: Object,
default: () => null
}
},
emits: {
'update:visible': null,
'reload': null
},
setup(props, { emit }) {
const MODAL_TYPE = {
'create': '新建用户',
'preview': '用户详情',
'update': '编辑用户'
}
getDeptOptions()
getRoleOptions()
const { userForm, userRules } = form
const formRef = ref()
const ossRefs = ref([])
const data = reactive({
userForm: {
...userForm,
...props.data,
roleIds: props?.data?.roles.map((item) => { return item.id }) || []
},
userRules: {
...userRules
},
disabled: props.type === 'preview'
})
const getModalOptions = computed(() => {
return {
show: props.visible,
title: MODAL_TYPE[props.type],
negativeText: '取消',
positiveText: '确认'
}
})
const getFormOptions = computed(() => {
return {
...form.formItem
}
})
function handleUploadStatus(status) {
data.userForm.imageStatus = status
}
function handleConfirm() {
formRef.value?.validate((errors) => {
if (!errors) {
const uploads = ossRefs.value.map((item, index) => {
return item.startUpload()
})
Promise.all(uploads)
.then(response => {
const isError = response.map((item) => {
return item.includes('error')
})
if (!isError.includes(true)) {
const imageStr = response.join()
const params = {
...data.userForm,
// avatar: imageStr
avatar: '/imagedir/2uim2hpl94u_1655380018696.png'
}
if (params.id) {
/* 编辑 */
editUser(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
} else {
/* 新增 */
addUser(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('文件上传失败,请稍后重试')
}
})
} else {
$message.error('请完善必填信息')
}
})
}
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
return {
...toRefs(data),
formRef,
ossRefs,
getModalOptions,
getFormOptions,
handleUploadStatus,
handleConfirm,
handleClose
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -0,0 +1,105 @@
<template>
<div>
<n-card>
<headSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:row-key="(row) => row.id"
:request="loadDataTable"
size="large"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
<n-popconfirm
negative-text="取消"
positive-text="确认"
@positive-click="deleteComplex"
>
<template #trigger>
<n-button type="primary"> 删除 </n-button>
</template>
确认要删除选中数据吗?
</n-popconfirm>
</template>
</data-table>
</n-card>
</div>
<!-- 新增编辑弹窗 -->
<UserModal v-if="modalShow" v-model:visible="modalShow" :type="modalType" :data="rowData" @reload="handleSearch" />
</template>
<script>
import { search, fetchRolesOption } from './tools/search.js'
import table from './tools/table.js'
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import UserModal from './components/UserModal.vue'
import { getUserList } from '@/api/system/user/index.js'
import { unref, ref, toRefs, reactive, onUnmounted } from 'vue'
export default {
name: 'MenuPage',
components: { dataTable, UserModal, headSearch },
setup() {
fetchRolesOption()
const data = reactive({
...toRefs(table),
...toRefs(search)
})
const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await getUserList(_params)
}
//
function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}
//
const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}
//
function deleteComplex() {
if (selectedIds.value.length) {
data.deleteData(selectedIds.value)
} else {
$message.warning('请至少选中一条数据')
}
}
onUnmounted(() => {
data.searchParams = null
})
return {
...toRefs(data),
loadDataTable,
handleModal,
selectedIds,
deleteComplex,
handleCheck
}
},
methods: {
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,52 @@
import { ref, reactive } from 'vue'
import { USER_STATUS } from '@/utils/dictionary.js'
import { getDeptAll } from '@/api/system/dept/index'
import { getRoleAll } from '@/api/system/role/index'
import { dataToSelect } from '@/utils/handleData.js'
const departmentOptions = ref()
const rolesOptions = ref()
export const form = reactive({
userForm: {
avatar: '',
code: '',
deptId: null,
username: '',
realname: '',
password: '',
roleIds: [],
status: 1,
note: ''
},
userRules: {
imageStatus: [{ required: true, message: '请选择头像', type: 'string', trigger: 'blur' }],
code: [{ required: true, message: '请输入编号', trigger: 'blur' }],
realname: [{ required: true, message: '请输入用户姓名', type: 'string', trigger: 'blur' }],
deptId: [{ required: true, message: '请选择部门', type: 'number', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', type: 'number', trigger: 'blur' }],
roleIds: [{ required: true, message: '请选择角色', type: 'array', trigger: 'blur' }],
username: [{ required: true, message: '请输入用户账号', type: 'string', trigger: 'blur' }]
},
formItem: [
// { type: 'oss', key: 'imageStatus', label: '头像', file: 'avatar', refIndex: 0 },
{ type: 'oss', key: 'imageStatus', label: '头像', file: 'avatar' },
{ type: 'input', key: 'code', label: '用户编号', props: { maxlength: '20', placeholder: '请输入用户编号', clearable: true }},
{ type: 'input', key: 'username', label: '用户账号', props: { maxlength: '20', placeholder: '请输入用户账号', clearable: true }},
{ type: 'input', key: 'password', label: '登录密码', props: { type: 'password', maxlength: '20', placeholder: '请输入登录密码', clearable: true }},
{ type: 'input', key: 'realname', label: '用户姓名', props: { maxlength: '20', placeholder: '请输入用户姓名', clearable: true }},
{ type: 'select', key: 'deptId', label: '所属部门', props: { options: departmentOptions, placeholder: '请选择所属部门' }},
{ type: 'select', key: 'roleIds', label: '角色', props: { multiple: true, options: rolesOptions, placeholder: '请选择角色' }},
{ type: 'radio', key: 'status', label: '状态', options: USER_STATUS },
{ type: 'input', key: 'note', label: '备注', props: { type: 'textarea', autosize: { maxlength: '200', minRows: 3, maxRows: 3 }}}
]
})
export const getDeptOptions = async function() {
const res = await getDeptAll()
departmentOptions.value = dataToSelect(res.data, { label: 'name', value: 'id' })
}
// 获取角色列表
export const getRoleOptions = async function() {
const res = await getRoleAll()
rolesOptions.value = dataToSelect(res.data, { label: 'name', value: 'id' })
}

View File

@ -0,0 +1,36 @@
import { getRoleAll } from '@/api/system/role/index'
import { ref, reactive } from 'vue'
const rolesOptions = ref([])
export const search = reactive({
search: [
{
label: '用户账号',
key: 'username',
props: {
placeholder: '请输入用户账号'
}
},
{
label: '用户姓名',
key: 'realname',
props: {
placeholder: '请输入用户姓名'
}
},
{
label: '用户角色',
type: 'select',
key: 'roleId',
props: {
placeholder: '请选择用户角色',
options: rolesOptions
}
}
]
})
export const fetchRolesOption = async function() {
const res = await getRoleAll()
rolesOptions.value = res.data.map((item) => { return { key: item.id, label: item.name } })
}

View File

@ -0,0 +1,210 @@
import { h, ref, reactive } from 'vue'
import TableImage from '@/components/DataTable/tools/Image.vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableSwitch from '@/components/DataTable/tools/Switch.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { resetPassword, deleteUser, setUserStatus } from '@/api/system/user/index.js'
/* 注册table */
const tableRef = ref()
const searchParams = ref()
function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}
/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建preview:预览edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}
// 设置状态
function setStatus(row) {
setUserStatus({ id: row.data.id, status: row.value }).then(res => {
if (res.code === 0) {
handleSearch()
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
}
/**
* @description: 重置密码
* @param {Number} id 选中数据id
* @return {*}
*/
function handlePasswordReset(id) {
resetPassword({ id })
.then(res => {
if (res.code === 0) {
handleSearch()
}
}).catch(e => {
console.log(e)
})
}
/**
* @description: 删除用户接口
* @param {Array} ids 用户id集合
* @return {*}
*/
function deleteData(ids) {
deleteUser(ids).then((res) => {
if (res.code === 0) {
handleSearch()
}
}).catch(e => {
console.log(e)
})
}
const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
handleSearch,
deleteData,
columns: [
{
type: 'selection'
},
{
title: '用户编号',
key: 'code',
align: 'center'
},
{
title: '头像',
key: 'avatar',
align: 'center',
render(row) {
return h(TableImage, {
images: {
width: 36,
height: 36,
src: row.avatar
}
})
}
},
{
title: '用户账号',
key: 'username',
align: 'center'
},
{
title: '用户姓名',
key: 'realname',
align: 'center'
},
{
title: '角色',
key: 'roleList',
align: 'center',
render(row) {
return h(TableTags, {
data: row.roles,
rowKey: 'name'
})
}
},
{
title: '部门',
key: 'deptName',
align: 'center'
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
width: 160
},
{
title: '更新时间',
key: 'updateTime',
align: 'center',
width: 160
},
{
title: '状态',
key: 'status',
align: 'center',
width: 100,
render(row) {
return h(TableSwitch, {
data: { id: row.id, status: row.status },
rowKey: 'status',
checkedValue: 1,
uncheckedValue: 2,
onChange: setStatus.bind(row)
})
}
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '修改',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'update')
},
auth: 'basic_list'
},
{
label: '重置密码',
type: 'popconfirm',
tip: '确定要重置为123456吗?',
props: {
onPositiveClick: handlePasswordReset.bind(null, row.id)
},
ButtonProps: {
text: true,
type: 'primary'
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
onPositiveClick: deleteData.bind(null, [row.id])
},
ButtonProps: {
text: true,
type: 'primary'
}
}
],
align: 'center'
})
}
}
]
})
export default data

View File

@ -1,187 +0,0 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="form"
label-placement="left"
:rules="rules"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
>
<n-form-item label="部门编号:" path="code">
<n-input
v-model:value="form.code"
clearable
:maxlength="20"
placeholder="请输入部门编号"
/>
</n-form-item>
<n-form-item label="部门名称:" path="name">
<n-input
v-model:value="form.name"
clearable
:maxlength="20"
placeholder="请输入部门名称"
/>
</n-form-item>
<n-form-item label="所属部门:" path="pid">
<n-select
v-model:value="form.pid"
:options="getDeptList"
placeholder="请选择所属部门"
/>
</n-form-item>
<n-form-item label="排序号" path="sort">
<n-input-number v-model:value="form.sort" placeholder="请输入排序号" />
</n-form-item>
<n-form-item
label="备注:"
>
<n-input
v-model:value="form.note"
type="textarea"
:rows="3"
:maxlength="200"
placeholder="请输入备注"
/>
</n-form-item>
</n-form>
</template>
</Modal>
</template>
<script>
import { defineComponent, computed, reactive, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import { addDept, editDept } from '@/api/system/dept/index'
export default defineComponent({
name: 'UserModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
row: {
type: Object,
default: () => {}
},
deptList: {
type: Array,
default: () => []
}
},
emits: {
'update:visible': null,
'done': null
},
setup(props, { emit }) {
const data = reactive({
form: {
avatar: '/images/user/20211011/20211011151447698.jpg',
code: '',
deptId: null,
username: '',
realname: '',
password: '',
roleIds: [],
status: 1,
note: ''
},
deptOptions: [],
rolesOptions: [],
rules: {
code: [
{ required: true, message: '请输入部门编号', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入部门名称', type: 'string', trigger: 'blur' }
],
sort: [
{ required: true, type: 'number', message: '请输入排序号', trigger: 'blur' }
]
}
})
//
const getDeptList = computed(() => {
const list = props.deptList.map((item) => {
const menu = {
label: item.name,
value: item.id
}
return menu
})
return list
})
const getModalOptions = computed(() => {
const row = props.row
if (props.row.pid === 0) {
row.pid = null
}
console.log('====部门计算属性触发了====', row)
return {
title: props.row.name ? '修改部门' : '添加部门',
show: props.visible,
form: Object.assign(data.form, row),
negativeText: '取消',
positiveText: '确认'
}
})
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
return {
...toRefs(data),
getDeptList,
getModalOptions,
handleClose
}
},
methods: {
//
handleConfirm() {
const type = this.row.name ? 'edit' : 'add'
this.$refs.formRef.validate((errors) => {
if (!errors) {
if (type === 'add') {
addDept(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
} else if (type === 'edit') {
editDept(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
}
}
})
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -1,188 +0,0 @@
<template>
<div>
<n-card>
<headSearch :info="info" @search="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:pagination="false"
data-type="tree"
size="large"
scroll-x="1200"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
</template>
</data-table>
</n-card>
</div>
<!-- 新增编辑弹窗 -->
<dept-modal
v-if="modalShow"
v-model:visible="modalShow"
:row="rowData"
:dept-list="deptList"
@done="handleSearch"
/>
</template>
<script>
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import {
getDeptList,
getDeptAll,
deleteDept
} from '@/api/system/dept/index.js'
import { h, unref, toRefs, ref, reactive, onMounted } from 'vue'
import DeptModal from './components/DeptModal.vue'
import info from './info.js'
import table from './table.js'
export default {
name: 'MenuPage',
components: { dataTable, DeptModal, headSearch },
setup() {
const data = reactive({
columns: [
...table.columns,
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '添加',
type: 'button',
props: {
type: 'primary',
onClick: add.bind(null, row)
},
auth: 'basic_list'
},
{
label: '修改',
type: 'button',
props: {
type: 'primary',
onClick: play.bind(null, row)
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
negativeText: '取消',
positiveText: '确认',
onPositiveClick: deleteSingle.bind(null, row.id)
}
}
],
align: 'center'
})
}
}
],
info: ref(info),
modalShow: false,
rowData: {
status: 1,
type: 0,
hide: 0
},
deptList: []
})
//
async function getDept() {
const list = await getDeptAll()
data.deptList = list.data
}
onMounted(() => {
getDept()
})
/**
* @description: 获取部门数据并做树形结构处理
* @return {*}
*/
const params = ref({})
const tableRef = ref()
function handleSearch(data) {
params.value = {
...data
}
tableRef.value.reFetch({ ...unref(params) })
}
const loadDataTable = async(res) => {
const _params = {
...unref(params),
...res
}
return await getDeptList(_params)
}
//
function handleModal() {
data.modalShow = true
data.rowData = {}
}
//
function add(row) {
data.modalShow = true
data.rowData = {}
data.rowData.pid = row.id
}
//
function play(row) {
data.rowData = row
data.modalShow = true
}
//
function deleteSingle(id) {
deleteData(id)
}
//
function deleteData(id) {
deleteDept(id)
.then((res) => {
if (res.code === 0) {
handleSearch({})
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
.catch((e) => {
console.log(e)
})
}
return {
...toRefs(data),
tableRef,
loadDataTable,
handleSearch,
handleModal,
deleteSingle
}
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

View File

@ -1,30 +0,0 @@
const table = {
columns: [{
title: '部门编号',
key: 'code',
align: 'center'
},
{
title: '部门名称',
key: 'name',
align: 'center'
},
{
title: '排序',
key: 'sort',
align: 'center'
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
minWidth: 120
},
{
title: '更新时间',
key: 'updateTime',
align: 'center',
minWidth: 120
}]
}
export default table

View File

@ -1,238 +0,0 @@
<template>
<Modal
:options="getModalOptions"
:on-close="handleClose"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
>
<template #Context>
<n-form ref="formRef" :model="form" :rules="rules" require-mark-placement="left" :label-width="80" label-placement="left">
<n-grid x-gap="12" :cols="2">
<n-gi>
<n-form-item label="上级菜单" path="pid">
<n-select
v-model:value="form.pid"
:options="getMenuList"
placeholder="请选择上级菜单"
/>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="菜单类型" path="type">
<n-radio-group v-model:value="form.type" name="type">
<n-radio v-for="item in typeOptions" :key="item.key" :value="item.key">
{{ item.label }}
</n-radio>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="菜单名称" path="title">
<n-input v-model:value="form.title" placeholder="请输入菜单名称" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="打开方式" path="target">
<n-radio-group v-model:value="form.target" name="target">
<n-radio v-for="item in openOptions" :key="item.key" :value="item.key">
{{ item.label }}
</n-radio>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="菜单图标" path="icon">
<n-input v-model:value="form.icon" :disabled="form.type === 1" placeholder="请选择菜单图标" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="权限标识" path="permission">
<n-input v-model:value="form.permission" :disabled="form.type === 0" placeholder="请输入权限标识" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="路由地址" path="path">
<n-input v-model:value="form.path" :disabled="form.type === 1" placeholder="请输入路由地址" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="排序号" path="sort">
<n-input-number v-model:value="form.sort" placeholder="请输入排序号" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="组件路径" path="component">
<n-input v-model:value="form.component" :disabled="form.type === 1" placeholder="请输入组件路径" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="是否可见" path="hide">
<n-radio-group v-model:value="form.hide" name="hide">
<n-radio v-for="item in visibleOptions" :key="item.key" :value="item.key">
{{ item.label }}
</n-radio>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="菜单状态" path="status">
<n-radio-group v-model:value="form.status" name="status">
<n-radio v-for="item in statusOptions" :key="item.key" :value="item.key">
{{ item.label }}
</n-radio>
</n-radio-group>
</n-form-item>
</n-gi>
</n-grid>
</n-form>
</template>
</Modal>
</template>
<script>
import { defineComponent, computed, reactive, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import { addMenu, editMenu } from '@/api/system/menu/index.js'
export default defineComponent({
name: 'MenuModal',
components: { Modal },
props: {
/* 可见 */
visible: {
type: Boolean,
default: false
},
/* 选中的数据 */
data: {
type: Object,
default: () => {}
},
menuList: {
type: Array,
default: () => []
}
},
emits: {
'update:visible': null,
'done': null
},
setup(props, { emit }) {
const data = reactive({
form: {
title: '',
icon: '',
permission: '',
path: '',
component: '',
hide: 0,
status: 1
},
rules: {
title: [{
required: true,
message: '请输入菜单名称',
trigger: 'blur'
}],
sort: [{
required: true,
type: 'number',
message: '请输入排序号',
trigger: 'blur'
}]
},
typeOptions: [
{ key: 0, label: '菜单' },
{ key: 1, label: '按钮' }
],
openOptions: [
{ key: '1', label: '内部' },
{ key: '2', label: '外部' }
],
visibleOptions: [
{ key: 0, label: '可见' },
{ key: 1, label: '不可见' }
],
statusOptions: [
{ key: 1, label: '在用' },
{ key: 2, label: '停用' }
]
})
const getMenuList = computed(() => {
const list = props.menuList.map((item) => {
const menu = {
label: item.title,
value: item.id
}
return menu
})
return list
})
const getModalOptions = computed(() => {
const row = props.data
if (props.data.pid === 0) {
row.pid = null
}
console.log('菜单页面计算属性触发:', row)
return {
title: props.data.title ? '修改菜单' : '添加菜单',
show: props.visible,
form: Object.assign(data.form, row),
negativeText: '取消',
positiveText: '确认'
}
})
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
return {
...toRefs(data),
getMenuList,
getModalOptions,
handleClose
}
},
methods: {
//
handleConfirm() {
const type = this.data.title ? 'edit' : 'add'
this.$refs.formRef.validate((errors) => {
if (!errors) {
if (type === 'add') {
addMenu(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
} else if (type === 'edit') {
editMenu(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -1,177 +0,0 @@
<template>
<div>
<n-card>
<headSearch :info="info" />
<!-- <data-table
ref="tableRef"
:columns="columns"
:pagination="false"
data-type="tree"
:request="loadDataTable"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 添加菜单 </n-button>
</template>
</data-table> -->
</n-card>
</div>
<MenuModal
v-if="modalShow"
v-model:visible="modalShow"
:data="rowData"
:menu-list="menuList"
@done="handleSearch"
/>
</template>
<script>
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import MenuModal from './components/MenuModal.vue'
import Action from '@/components/DataTable/tools/Action.vue'
import { getMenu, getMenuList, deleteMenu } from '@/api/system/menu/index.js'
import { h, ref, unref, reactive, toRefs, onMounted } from 'vue'
import info from './info.js'
import table from './table.js'
export default {
name: 'MenuPage',
components: { dataTable, MenuModal, headSearch },
setup() {
const data = reactive({
columns: [
...table.columns,
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(Action, {
actions: [
{
label: '添加',
type: 'button',
props: {
type: 'primary',
onClick: add.bind(null, row)
},
auth: 'basic_list'
},
{
label: '修改',
type: 'button',
props: {
type: 'primary',
onClick: play.bind(null, row)
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
negativeText: '取消',
positiveText: '确认',
onPositiveClick: deleteSingle.bind(null, row.id)
}
}
],
align: 'center'
})
}
}
],
info: ref(info),
modalShow: false,
menuList: [],
rowData: {
status: 1,
type: 0,
hide: 0
}
})
//
async function getMenuAll() {
const list = await getMenu()
data.menuList = list.data
}
onMounted(() => {
getMenuAll()
})
/**
* @description: 获取菜单数据
* @return {*}
*/
const params = ref({})
const tableRef = ref()
function handleSearch(data) {
params.value = {
...data
}
tableRef.value.reFetch({ ...unref(params) })
}
const loadDataTable = async(res) => {
const _params = {
...unref(params),
...res
}
return await getMenuList(_params)
}
//
function handleModal() {
data.modalShow = true
data.rowData = {}
}
//
function add(row) {
data.modalShow = true
data.rowData = {}
data.rowData.pid = row.id
}
//
function play(row) {
data.rowData = row
data.modalShow = true
}
//
function deleteSingle(id) {
deleteData(id)
}
//
function deleteData(id) {
deleteMenu(id)
.then((res) => {
if (res.code === 0) {
handleSearch({})
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
.catch((e) => {
console.log(e)
})
}
return {
...toRefs(data),
tableRef,
loadDataTable,
handleSearch,
handleModal,
deleteSingle
}
}
}
</script>
<style scoped lang='scss'>
</style>

View File

@ -1,15 +0,0 @@
const data = [
{
label: '菜单名称',
type: 'area',
key: 'name',
refIndex: 0,
props: {
placeholder: '请输入菜单名称',
options: [{ label: 1, value: 1, children: [{ label: '1-1', value: '1-1' }] }]
}
}
]
export default data

View File

@ -1,101 +0,0 @@
import { h } from 'vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
const table = {
columns: [
{
title: '菜单标题',
key: 'title',
align: 'center',
width: 200
},
{
title: '菜单类型',
key: 'type',
align: 'center',
width: 100,
render(row) {
return h(TableTags, {
data: row.type,
filters: [
{
key: 0,
label: '菜单'
},
{
key: 1,
label: '节点'
}
]
})
}
},
{
title: '路由地址',
key: 'path',
align: 'center',
width: 200
},
{
title: '组件路径',
key: 'component',
align: 'center',
width: 200
},
{
title: '状态',
key: 'status',
align: 'center',
width: 100,
render(row) {
return h(TableTags, {
data: row.status,
filters: [
{
key: 1,
label: '在用'
},
{
key: 2,
label: '停用'
}
]
})
}
},
{
title: '排序',
key: 'sort',
align: 'center',
width: 100
},
{
title: '是否可见',
key: 'hide',
align: 'center',
width: 100,
render(row) {
return h(TableTags, {
data: row.hide,
filters: [
{
key: 1,
label: '可见'
},
{
key: 2,
label: '不可见'
}
]
})
}
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
width: 160
}
]
}
export default table

View File

@ -1,166 +0,0 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="form"
label-placement="left"
:rules="rules"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
>
<n-form-item label="角色编号:" path="code">
<n-input
v-model:value="form.code"
clearable
:maxlength="20"
placeholder="请输入角色编号"
/>
</n-form-item>
<n-form-item label="角色名称:" path="name">
<n-input
v-model:value="form.name"
clearable
:maxlength="20"
placeholder="请输入角色名称"
/>
</n-form-item>
<n-form-item label="状态" path="status">
<n-radio-group
v-model:value="form.status"
>
<n-radio :value="1">正常</n-radio>
<n-radio :value="2">禁用</n-radio>
</n-radio-group>
</n-form-item>
<n-form-item
label="备注:"
>
<n-input
v-model:value="form.note"
type="textarea"
:rows="3"
:maxlength="200"
placeholder="请输入备注"
/>
</n-form-item>
</n-form>
</template>
</Modal>
</template>
<script>
import { defineComponent, computed, reactive, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import { addRole, editRole } from '@/api/system/role/index'
export default defineComponent({
name: 'RoleModal',
components: { Modal },
props: {
visible: {
type: Boolean,
default: false
},
row: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'done': null
},
setup(props, { emit }) {
const data = reactive({
form: {
code: '',
name: '',
status: 1,
note: ''
},
rules: {
code: [
{ required: true, message: '请输入角色编号', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入角色名称', type: 'string', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', type: 'number', trigger: 'blur' }
]
}
})
const getModalOptions = computed(() => {
return {
title: Object.keys(props.row).length === 0 ? '添加角色' : '编辑角色',
show: props.visible,
form: Object.assign(data.form, props.row),
negativeText: '取消',
positiveText: '确认'
}
})
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
return {
getModalOptions,
handleClose,
...toRefs(data),
rules: reactive({
code: [
{ required: true, message: '请输入角色编号', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入角色名称', type: 'string', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', type: 'number', trigger: 'blur' }
]
})
}
},
methods: {
//
handleConfirm() {
const type = Object.keys(this.row).length === 0 ? 'add' : 'edit'
this.$refs.formRef.validate((errors) => {
if (!errors) {
if (type === 'add') {
addRole(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
} else if (type === 'edit') {
editRole(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
}
}
})
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -1,239 +0,0 @@
<template>
<div>
<n-card>
<headSearch :info="info" @search="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:row-key="(row) => row.id"
:request="loadDataTable"
size="large"
scroll-x="1200"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
<n-popconfirm
negative-text="取消"
positive-text="确认"
@positive-click="deleteComplex"
>
<template #trigger>
<n-button type="primary">删除</n-button>
</template>
确定删除选中的数据吗?
</n-popconfirm>
</template>
</data-table>
</n-card>
</div>
<!-- 新增编辑弹窗 -->
<role-modal
v-if="modalShow"
v-model:visible="modalShow"
:row="rowData"
@done="handleSearch"
/>
<!-- 分配权限 -->
<config-modal
v-if="configModalShow"
v-model:visible="configModalShow"
:row="rowData"
@done="handleSearch"
/>
</template>
<script>
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import TableSwitch from '@/components/DataTable/tools/Switch.vue'
import {
getRoleList,
deleteRole,
setRoleStatus
} from '@/api/system/role/index'
import { h, ref, unref, toRefs, reactive } from 'vue'
import table from './table.js'
import info from './info.js'
import RoleModal from './components/RoleModal.vue'
import ConfigModal from './components/ConfigModal.vue'
export default {
name: 'MenuPage',
components: { headSearch, dataTable, RoleModal, ConfigModal },
setup() {
const data = reactive({
columns: [
...table.columns,
{
title: '状态',
key: 'status',
align: 'center',
width: 100,
render(row) {
return h(TableSwitch, {
data: { id: row.id, status: row.status },
rowKey: 'status',
checkedValue: 1,
uncheckedValue: 2,
onChange: setStatus.bind(row)
})
}
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '修改',
type: 'button',
props: {
type: 'primary',
onClick: play.bind(null, row)
},
auth: 'basic_list'
},
{
label: '分配权限',
type: 'button',
props: {
type: 'primary',
ghost: true,
onClick: configure.bind(null, row)
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
negativeText: '取消',
positiveText: '确认',
onPositiveClick: deleteSingle.bind(null, row.id)
}
}
],
align: 'center'
})
}
}
],
info: ref(info),
modalShow: false,
configModalShow: false,
rowData: {}
})
//
function play(row) {
data.rowData = row
data.modalShow = true
}
//
function configure(row) {
data.rowData = row
data.configModalShow = true
}
const params = ref({})
const tableRef = ref()
function handleSearch(data) {
params.value = {
...data
}
tableRef.value.reFetch({ ...unref(params) })
}
const loadDataTable = async(res) => {
const _params = {
...unref(params),
...res
}
return await getRoleList(_params)
}
//
function handleModal() {
data.rowData = {}
data.modalShow = true
}
//
const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}
//
function deleteComplex() {
if (selectedIds.value.length) {
deleteData(selectedIds.value)
} else {
$message.warning('请至少选中一条数据')
}
}
//
function deleteSingle(id) {
deleteData([id])
}
//
function deleteData(data) {
deleteRole(data)
.then((res) => {
if (res.code === 0) {
handleSearch()
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
.catch((e) => {
console.log(e)
})
}
//
function setStatus(row) {
console.log(row)
setRoleStatus({ id: row.data.id, status: row.value })
.then((res) => {
if (res.code === 0) {
handleSearch()
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
.catch((e) => {
console.log(e)
})
}
return {
...toRefs(data),
tableRef,
loadDataTable,
handleSearch,
handleModal,
handleCheck,
selectedIds,
deleteSingle,
deleteComplex,
deleteData,
setStatus
}
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

View File

@ -1,30 +0,0 @@
const data = {
columns: [
{ type: 'selection' },
{
title: '角色编号',
key: 'code',
align: 'center'
},
{
title: '角色名称',
key: 'name',
align: 'center'
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
Minwidth: 160
},
{
title: '更新时间',
key: 'updateTime',
align: 'center',
Minwidth: 160
}
]
}
export default data

View File

@ -1,251 +0,0 @@
<template>
<Modal
:options="getModalOptions"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
:on-close="handleClose"
>
<template #Context>
<n-form
ref="formRef"
:model="form"
label-placement="left"
:rules="rules"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
>
<n-form-item
label="头像:"
path="avatar"
>
<uploadImage
:options="{
max:1,
action: '/upload/uploadImage/demo',
}"
/>
</n-form-item>
<n-form-item label="用户编号:" path="code">
<n-input
v-model:value="form.code"
clearable
:maxlength="20"
placeholder="请输入用户编号"
/>
</n-form-item>
<n-form-item label="用户账号:" path="username">
<n-input
v-model:value="form.username"
clearable
:maxlength="20"
placeholder="请输入用户账号"
/>
</n-form-item>
<n-form-item
label="登录密码:"
path="password"
>
<n-input
v-model:value="form.password"
type="password"
:maxlength="20"
placeholder="请输入登录密码"
/>
</n-form-item>
<n-form-item label="用户姓名:" path="realname">
<n-input
v-model:value="form.realname"
clearable
:maxlength="20"
placeholder="请输入用户姓名"
/>
</n-form-item>
<n-form-item label="所属部门:" path="deptId">
<n-select
v-model:value="form.deptId"
:options="deptOptions"
placeholder="请选择所属部门"
/>
</n-form-item>
<n-form-item label="角色:" path="roleIds">
<n-select
v-model:value="form.roleIds"
clearable
:multiple="true"
:options="rolesOptions"
placeholder="请选择角色"
/>
</n-form-item>
<n-form-item label="状态" path="status">
<n-radio-group
v-model:value="form.status"
>
<n-radio :value="1">正常</n-radio>
<n-radio :value="2">禁用</n-radio>
</n-radio-group>
</n-form-item>
<n-form-item
label="备注:"
>
<n-input
v-model:value="form.note"
type="textarea"
:rows="3"
:maxlength="200"
placeholder="请输入备注"
/>
</n-form-item>
</n-form>
</template>
</Modal>
</template>
<script>
import { defineComponent, computed, onMounted, reactive, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import uploadImage from '@/components/ImgUpload/index.vue'
import { getDeptAll } from '@/api/system/dept/index'
import { getRoleAll } from '@/api/system/role/index'
import { addUser, editUser } from '@/api/system/user/index'
import { dataToSelect } from '@/utils/handleData.js'
export default defineComponent({
name: 'UserModal',
components: { Modal, uploadImage },
props: {
visible: {
type: Boolean,
default: false
},
row: {
type: Object,
default: () => {}
}
},
emits: {
'update:visible': null,
'done': null
},
setup(props, { emit }) {
const data = reactive({
form: {
avatar: '/images/user/20211011/20211011151447698.jpg',
code: '',
deptId: null,
username: '',
realname: '',
password: '',
roleIds: [],
status: 1,
note: ''
},
deptOptions: [],
rolesOptions: [],
rules: {
avatar: [
{ required: true, message: '请选择头像', type: 'string', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入编号', trigger: 'blur' }
],
realname: [
{ required: true, message: '请输入用户姓名', type: 'string', trigger: 'blur' }
],
deptId: [
{ required: true, message: '请选择部门', type: 'number', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', type: 'number', trigger: 'blur' }
],
roleIds: [
{ required: true, message: '请选择角色', type: 'array', trigger: 'blur' }
],
username: [
{ required: true, message: '请输入用户账号', type: 'string', trigger: 'blur' }
]
}
})
const getModalOptions = computed(() => {
return {
title: Object.keys(props.row).length === 0 ? '添加用户' : '编辑用户',
show: props.visible,
form: Object.assign(data.form, props.row),
negativeText: '取消',
positiveText: '确认'
}
})
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
//
async function getDeptOptions() {
const res = await getDeptAll()
data.deptOptions = dataToSelect(res.data, { label: 'name', value: 'id' })
}
//
async function getRoleOptions() {
const res = await getRoleAll()
data.rolesOptions = dataToSelect(res.data, { label: 'name', value: 'id' })
}
onMounted(() => {
getDeptOptions()
getRoleOptions()
})
//
const handleUpload = ({ file }) => {
console.log(file)
// data.form.avatar = file.url
}
return {
...toRefs(data),
getModalOptions,
handleClose,
handleUpload
}
},
methods: {
//
handleConfirm() {
const type = Object.keys(this.row).length === 0 ? 'add' : 'edit'
this.$refs.formRef.validate((errors) => {
if (!errors) {
if (type === 'add') {
addUser(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
} else if (type === 'edit') {
editUser(this.form).then(res => {
if (res.code === 0) {
this.handleClose()
this.$emit('done')
$message.success(res.msg)
} else {
$message.error(res.msg)
}
})
}
} else {
$message.error('请完善必填信息')
}
})
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -1,233 +0,0 @@
<template>
<div>
<n-card>
<headSearch :info="info" @search="handleSearch" />
<data-table
ref="tableRef"
:columns="columns"
:row-key="(row) => row.id"
:request="loadDataTable"
size="large"
@update:checked-row-keys="handleCheck"
>
<template #tableTitle>
<n-button type="primary" @click="handleModal"> 新建 </n-button>
<n-popconfirm
negative-text="取消"
positive-text="确认"
@positive-click="deleteComplex"
>
<template #trigger>
<n-button type="primary"> 删除 </n-button>
</template>
确认要删除选中数据吗?
</n-popconfirm>
</template>
</data-table>
</n-card>
</div>
<!-- 新增编辑弹窗 -->
<user-modal
v-if="modalShow"
v-model:visible="modalShow"
:row="rowData"
@done="handleSearch"
/>
</template>
<script>
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import TableSwitch from '@/components/DataTable/tools/Switch.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { getUserList, resetPassword, deleteUser, setUserStatus } from '@/api/system/user/index.js'
import { h, unref, ref, toRefs, reactive } from 'vue'
import UserModal from './components/UserModal.vue'
import info from './info.js'
import table from './table.js'
export default {
name: 'MenuPage',
components: { dataTable, UserModal, headSearch },
setup() {
const data = reactive({
columns: [
...table.columns,
{
title: '状态',
key: 'status',
align: 'center',
width: 100,
render(row) {
return h(TableSwitch, {
data: { id: row.id, status: row.status },
rowKey: 'status',
checkedValue: 1,
uncheckedValue: 2,
onChange: setStatus.bind(row)
})
}
},
{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '修改',
type: 'button',
props: {
type: 'primary',
onClick: play.bind(null, row)
},
auth: 'basic_list'
},
{
label: '重置密码',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定要重置为123456吗?',
props: {
negativeText: '取消',
positiveText: '确认',
onPositiveClick: resetPsw.bind(null, row.id)
}
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list',
tip: '确定删除这条数据吗?',
props: {
negativeText: '取消',
positiveText: '确认',
onPositiveClick: deleteSingle.bind(null, row.id)
}
}
],
align: 'center'
})
}
}
],
info: ref(info),
modalShow: false,
rowData: {}
})
//
function play(row) {
data.rowData = row
data.modalShow = true
}
const params = ref({})
const tableRef = ref()
function handleSearch(data) {
params.value = {
...data
}
tableRef.value.reFetch({ ...unref(params) })
}
const loadDataTable = async(res) => {
const _params = {
...unref(params),
...res
}
return await getUserList(_params)
}
//
function handleModal() {
data.rowData = {}
data.modalShow = true
}
//
const selectedIds = ref([])
function handleCheck(rowKeys) {
selectedIds.value = rowKeys
}
//
function deleteComplex() {
if (selectedIds.value.length) {
deleteData(selectedIds.value)
} else {
$message.warning('请至少选中一条数据')
}
}
//
function deleteSingle(id) {
deleteData([id])
}
//
function deleteData(data) {
deleteUser(data).then((res) => {
if (res.code === 0) {
handleSearch()
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
}
//
function resetPsw(id) {
resetPassword({ id }).then(res => {
if (res.code === 0) {
handleSearch()
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
}
//
function setStatus(row) {
setUserStatus({ id: row.data.id, status: row.value }).then(res => {
if (res.code === 0) {
handleSearch()
$message.success(res.msg)
} else {
$message.error(res.msg)
}
}).catch(e => {
console.log(e)
})
}
return {
loadDataTable,
handleModal,
...toRefs(data),
tableRef,
handleSearch,
selectedIds,
deleteComplex,
handleCheck,
deleteData,
resetPsw,
setStatus
}
},
methods: {
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

View File

@ -1,45 +0,0 @@
import { getRoleAll } from '@/api/system/role/index'
import { dataToSelect } from '@/utils/handleData.js'
import { ref } from 'vue'
const rolesOptions = ref([])
const getRolesOption = async function() {
const res = await getRoleAll()
rolesOptions.value = dataToSelect(res.data, { label: 'name', value: 'id' })
}
// getRolesOption()
const data = [
{
label: '用户账号',
key: 'username',
props: {
placeholder: '请输入用户账号'
}
},
{
label: '用户姓名',
key: 'realname',
props: {
placeholder: '请输入用户姓名'
}
},
{
label: '用户角色',
type: 'select',
key: 'roleId',
props: {
placeholder: '请选择用户角色',
options: rolesOptions
}
}
// {
// label: '角色类型',
// type: 'date',
// key: 'date',
// props: {
// type: 'date'
// }
// }
]
export default data

View File

@ -1,69 +0,0 @@
import { h } from 'vue'
import TableImage from '@/components/DataTable/tools/Image.vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
const table = {
columns: [
{
type: 'selection'
},
{
title: '用户编号',
key: 'code',
align: 'center'
},
{
title: '头像',
key: 'avatar',
align: 'center',
render(row) {
return h(TableImage, {
images: {
width: 36,
height: 36,
src: row.avatar
}
})
}
},
{
title: '用户账号',
key: 'username',
align: 'center'
},
{
title: '用户姓名',
key: 'realname',
align: 'center'
},
{
title: '角色',
key: 'roleList',
align: 'center',
render(row) {
return h(TableTags, {
data: row.roleList,
rowKey: 'name'
})
}
},
{
title: '部门',
key: 'deptName',
align: 'center'
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
width: 160
},
{
title: '更新时间',
key: 'updateTime',
align: 'center',
width: 160
}
]
}
export default table