合并代码冲突

This commit is contained in:
余菲 2022-06-09 10:51:32 +08:00
commit f8a3e2ebde
42 changed files with 1822 additions and 460 deletions

View File

@ -5,7 +5,7 @@ VITE_PUBLIC_PATH = '/'
VITE_APP_USE_MOCK = false
# proxy
VITE_PROXY = [["/api-dev","http://127.0.0.1:8002/api"]]
VITE_PROXY = [["/api-dev","http://192.168.11.11:9070/api"]]
# base api
VITE_APP_GLOB_BASE_API = '/api-dev'

View File

@ -21,6 +21,7 @@ function deepClone(source) {
const menuList = []
const userList = []
// const deptList = []
const count = 100
for (let i = 0; i < count; i++) {
@ -48,7 +49,84 @@ for (let i = 0; i < count; i++) {
createTime: '@datetime',
updateTime: '@datetime'
}))
// deptList.push(Mock.mock({
// code: '',
// id: 1,
// name: '@name',
// fullname: '@cname',
// 'pid|1': [1, 2, 3, 4, 5],
// 'sort|+1': 0,
// type: 1,
// 'note|1-8': '好',
// createTime: '@datetime',
// updateTime: '@datetime'
// }))
}
const deptList = Mock.mock({
code: 0,
message: '获取成功',
type: 'success',
data: [{
code: 'XH212313',
id: 1,
name: '@name',
fullname: '@cname',
pid: 0,
sort: 0,
type: 1,
'note|1-8': '好',
createTime: '@datetime',
updateTime: '@datetime'
},
{
code: 'XH212312',
id: 2,
name: '@name',
fullname: '@cname',
pid: 1,
sort: 0,
type: 2,
'note|1-8': '好',
createTime: '@datetime',
updateTime: '@datetime'
},
{
code: 'XH212323',
id: 3,
name: '@name',
fullname: '@cname',
pid: 2,
sort: 0,
type: 2,
'note|1-8': '好',
createTime: '@datetime',
updateTime: '@datetime'
},
{
code: 'XH212319',
id: 4,
name: '@name',
fullname: '@cname',
pid: 0,
sort: 1,
type: 1,
'note|1-8': '好',
createTime: '@datetime',
updateTime: '@datetime'
},
{
code: 'XH212398',
id: 5,
name: '@name',
fullname: '@cname',
pid: 0,
sort: 2,
type: 2,
'note|1-8': '好',
createTime: '@datetime',
updateTime: '@datetime'
}]
})
export default [
{
@ -90,5 +168,20 @@ export default [
}
return resultSuccess(data)
}
},
{
url: '/api-mock/dept/apiIndex',
timeout: 1000,
method: 'get',
response: config => {
const { page = 1, limit = 10 } = config.query
const data = {
list: deptList,
page: Number(page),
limit: Number(limit),
total: 5
}
return resultSuccess(data)
}
}
]

View File

@ -31,6 +31,14 @@ const asyncRoutes = [
meta: {
title: '角色管理'
}
},
{
path: 'dept',
component: 'views/system/dept/index',
name: 'SystemDept',
meta: {
title: '部门管理'
}
}
]
}

66
package-lock.json generated
View File

@ -11,7 +11,7 @@
"@vicons/antd": "^0.10.0",
"@vicons/ionicons5": "^0.10.0",
"axios": "^0.26.1",
"dayjs": "^1.11.0",
"dayjs": "^1.11.2",
"mockjs": "^1.1.0",
"pinia": "^2.0.13",
"vue": "^3.2.16",
@ -3049,7 +3049,11 @@
"node_modules/connect/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
<<<<<<< HEAD
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
=======
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true
},
"node_modules/consola": {
@ -3535,9 +3539,15 @@
}
},
"node_modules/dayjs": {
<<<<<<< HEAD
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
"integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
=======
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz",
"integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw=="
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
},
"node_modules/debug": {
"version": "4.3.4",
@ -4352,9 +4362,15 @@
}
},
"node_modules/esbuild-register": {
<<<<<<< HEAD
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.3.3.tgz",
"integrity": "sha512-eFHOkutgIMJY5gc8LUp/7c+LLlDqzNi9T6AwCZ2WKKl3HmT+5ef3ZRyPPxDOynInML0fgaC50yszPKfPnjC0NQ==",
=======
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.3.2.tgz",
"integrity": "sha512-jceAtTO6zxPmCfSD5cBb3rgIK1vmuqCKYwgylHiS1BF4pq0jJiJb4K2QMuqF4BEw7XDBRatYzip0upyTzfkgsQ==",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true,
"peerDependencies": {
"esbuild": ">=0.12 <1"
@ -6082,7 +6098,11 @@
"node_modules/is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
<<<<<<< HEAD
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
=======
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true
},
"node_modules/is-number": {
@ -6232,9 +6252,15 @@
}
},
"node_modules/jake/node_modules/async": {
<<<<<<< HEAD
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
=======
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true
},
"node_modules/jake/node_modules/chalk": {
@ -7854,9 +7880,15 @@
}
},
"node_modules/pinia/node_modules/vue-demi": {
<<<<<<< HEAD
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.1.tgz",
"integrity": "sha512-xmkJ56koG3ptpLnpgmIzk9/4nFf4CqduSJbUM0OdPoU87NwRuZ6x49OLhjSa/fC15fV+5CbEnrxU4oyE022svg==",
=======
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@ -13694,7 +13726,11 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
<<<<<<< HEAD
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
=======
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true
}
}
@ -14049,9 +14085,15 @@
"requires": {}
},
"dayjs": {
<<<<<<< HEAD
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
"integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
=======
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz",
"integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw=="
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
},
"debug": {
"version": "4.3.4",
@ -14603,9 +14645,15 @@
"optional": true
},
"esbuild-register": {
<<<<<<< HEAD
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.3.3.tgz",
"integrity": "sha512-eFHOkutgIMJY5gc8LUp/7c+LLlDqzNi9T6AwCZ2WKKl3HmT+5ef3ZRyPPxDOynInML0fgaC50yszPKfPnjC0NQ==",
=======
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.3.2.tgz",
"integrity": "sha512-jceAtTO6zxPmCfSD5cBb3rgIK1vmuqCKYwgylHiS1BF4pq0jJiJb4K2QMuqF4BEw7XDBRatYzip0upyTzfkgsQ==",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true,
"requires": {}
},
@ -15884,7 +15932,11 @@
"is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
<<<<<<< HEAD
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
=======
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true
},
"is-number": {
@ -15989,9 +16041,15 @@
},
"dependencies": {
"async": {
<<<<<<< HEAD
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
=======
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"dev": true
},
"chalk": {
@ -17254,9 +17312,15 @@
},
"dependencies": {
"vue-demi": {
<<<<<<< HEAD
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.1.tgz",
"integrity": "sha512-xmkJ56koG3ptpLnpgmIzk9/4nFf4CqduSJbUM0OdPoU87NwRuZ6x49OLhjSa/fC15fV+5CbEnrxU4oyE022svg==",
=======
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
>>>>>>> 40d50ccf36aef63b9c0df852b2171d9185ea2c1f
"requires": {}
}
}

View File

@ -12,7 +12,7 @@
"@vicons/antd": "^0.10.0",
"@vicons/ionicons5": "^0.10.0",
"axios": "^0.26.1",
"dayjs": "^1.11.0",
"dayjs": "^1.11.2",
"mockjs": "^1.1.0",
"pinia": "^2.0.13",
"vue": "^3.2.16",

View File

@ -2,16 +2,25 @@
<n-config-provider inline-theme-disabled :theme-overrides="themeOverrides">
<n-loading-bar-provider>
<loading-bar />
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
<n-dialog-provider>
<dialog-content />
<n-message-provider>
<message-content />
</n-message-provider>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</n-dialog-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<script setup>
import themeOverrides from '@/utils/ui/theme.js'
import LoadingBar from '@/components/LoadingBar/index.vue'
import loadingBar from '@/components/LoadingBar/index.vue'
import messageContent from '@/components/Message/index.vue'
import dialogContent from '@/components/Dialog/index.vue'
</script>
<style lang="scss">

View File

@ -4,13 +4,20 @@ export const login = (data) => {
return request({
url: '/auth/login',
method: 'post',
data,
data
})
}
export const refreshToken = () => {
return request({
url: '/auth/refreshToken',
method: 'post',
method: 'post'
})
}
export function getMenu() {
return request({
url: '/index/getMenuList',
method: 'GET'
})
}

View File

@ -1,5 +1,10 @@
import { defAxios as request } from '@/utils/http'
/**
* 登录接口
* @param {Object} 用户名以及密码
* @returns 返回token信息
*/
export function userLogin(data = {}) {
return request({
url: '/login/login',
@ -7,9 +12,25 @@ export function userLogin(data = {}) {
data
})
}
/**
* 获取验证码
* @returns 验证码图片
*/
export function userCaptcha() {
return request({
url: '/login/captcha',
method: 'get'
})
}
/**
* 退出登录
* @returns
*/
export function loginOut() {
return request({
url: '/login/logout',
method: 'GET'
})
}

View File

@ -0,0 +1,59 @@
import { defAxios as request } from '@/utils/http'
/**
* 获取部门分页数据接口
* @returns 部门分页
*/
export function getDeptList(params) {
return request({
url: '/dept/index',
method: 'GET',
params
})
}
/**
* 获取部门全部数据接口
* @returns 部门全部数据
*/
export function getDeptAll(params) {
return request({
url: '/dept/getDeptList',
method: 'GET',
params
})
}
/**
* 添加部门
* params
*/
export function addDept(data) {
return request({
url: '/dept/add',
method: 'POST',
data
})
}
/**
* 编辑部门
* params
*/
export function editDept(data) {
return request({
url: '/dept/edit',
method: 'PUT',
data
})
}
/**
* 删除部门
* params
*/
export function deleteDept(id) {
return request({
url: `/dept/delete${[id]}`,
method: 'DELETE'
})
}

View File

@ -21,3 +21,11 @@ export function getUserList(params) {
params
})
}
export function getDeptList(params) {
return request({
url: '/dept/apiIndex',
method: 'GET',
params
})
}

View File

@ -0,0 +1,59 @@
import { defAxios as request } from '@/utils/http'
/**
* 获取菜单分页数据接口
* @returns 菜单分页数据
*/
export function getMenuList(params) {
return request({
url: '/menu/index',
method: 'GET',
params
})
}
/**
* 获取菜单全部数据接口
* @returns 菜单全部数据
*/
export function getMenu(params) {
return request({
url: '/menu/getMenuAll',
method: 'GET',
params
})
}
/**
* 添加菜单
* params
*/
export function addMenu(data) {
return request({
url: '/menu/add',
method: 'POST',
data
})
}
/**
* 编辑菜单
* params
*/
export function editMenu(data) {
return request({
url: '/menu/edit',
method: 'PUT',
data
})
}
/**
* 删除菜单
* params
*/
export function deleteMenu(id) {
return request({
url: `/menu/delete${[id]}`,
method: 'DELETE'
})
}

View File

@ -0,0 +1,95 @@
import { defAxios as request } from '@/utils/http'
/**
* 获取角色分页数据接口
* @returns 角色分页数据
*/
export function getRoleList(params) {
return request({
url: '/role/index',
method: 'GET',
params
})
}
/**
* 获取角色全部数据接口
* @returns 角色全部数据
*/
export function getRoleAll(params) {
return request({
url: '/role/getRoleList',
method: 'GET',
params
})
}
/**
* 添加角色
* params
*/
export function addRole(data) {
return request({
url: '/role/add',
method: 'POST',
data
})
}
/**
* 编辑角色
* params
*/
export function editRole(data) {
return request({
url: '/role/edit',
method: 'PUT',
data
})
}
/**
* 设置角色状态
* params
*/
export function setRoleStatus(data) {
return request({
url: '/role/status',
method: 'PUT',
data
})
}
/**
* 删除角色
* params
*/
export function deleteRole(id) {
return request({
url: `/role/delete${[id]}`,
method: 'DELETE'
})
}
/**
* 获取角色权限数据
* @param 角色id
* @returns 角色全部权限数据
*/
export function getRolePermission(id) {
return request({
url: `/role/getPermissionList/${id}`,
method: 'GET'
})
}
/**
* 保存角色权限信息
* params
*/
export function savePermission(data) {
return request({
url: '/role/savePermission',
method: 'POST',
data
})
}

View File

@ -0,0 +1,71 @@
import { defAxios as request } from '@/utils/http'
/**
* 获取用户分页数据接口
* @returns 用户分页
*/
export function getUserList(params) {
return request({
url: '/user/index',
method: 'GET',
params
})
}
/**
* 添加用户
* params
*/
export function addUser(data) {
return request({
url: '/user/add',
method: 'POST',
data
})
}
/**
* 编辑部门
* params
*/
export function editUser(data) {
return request({
url: '/user/edit',
method: 'PUT',
data
})
}
/**
* 删除用户
* params
*/
export function deleteUser(id) {
return request({
url: `/user/delete${[id]}`,
method: 'DELETE'
})
}
/**
* 设置用户状态
* params
*/
export function setUserStatus(data) {
return request({
url: '/user/status',
method: 'PUT',
data
})
}
/**
* 重置用户密码
* params
*/
export function resetPassword(data) {
return request({
url: '/user/resetPwd',
method: 'PUT',
data
})
}

View File

@ -4,7 +4,7 @@ export function getUsers(data = {}) {
return request({
url: '/users',
method: 'get',
data,
data
})
}
@ -12,12 +12,12 @@ export function getUser(id) {
if (id) {
return request({
url: `/user/${id}`,
method: 'get',
method: 'get'
})
}
return request({
url: '/user',
method: 'get',
method: 'get'
})
}
@ -26,13 +26,13 @@ export function saveUser(data = {}, id) {
return request({
url: '/user',
method: 'put',
data,
data
})
}
return request({
url: `/user/${id}`,
method: 'put',
data,
data
})
}

View File

@ -1,24 +0,0 @@
<template>
<n-config-provider inline-theme-disabled :theme-overrides="themeOverrides">
<n-loading-bar-provider>
<!-- <loading-bar /> -->
<n-dialog-provider>
<dialog-content />
<n-message-provider>
<message-content />
<slot />
</n-message-provider>
</n-dialog-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<script setup>
import themeOverrides from '@/utils/ui/theme.js'
// import MessageContent from './MessageContent.vue'
// import DialogContent from './DialogContent.vue'
// import LoadingBar from './LoadingBar.vue'
// import { useAppStore } from '@/store/modules/app'
// const appStore = useAppStore()
</script>

View File

@ -44,6 +44,15 @@ export default {
props: {
...tableProps
},
emits: [
'fetch-success',
'fetch-error',
'update:checked-row-keys',
'edit-end',
'edit-cancel',
'edit-row-end',
'edit-change'
],
setup(props, { emit }) {
const getProps = computed(() => {
return { ...props }
@ -88,6 +97,8 @@ export default {
}
})
emit('fetch-success', isRequest ? unref(getDataSourceRef) : unref(getProps).data)
const key = Symbol('s-table')
provide(key, { getBindProps })
/* tableData-end */

View File

@ -0,0 +1,44 @@
<template>
<n-switch v-bind="getSwitchProps" v-model:value="switchVlue" @update:value="changeValue" />
</template>
<script>
import { defineComponent, ref, unref, computed } from 'vue'
export default defineComponent({
name: 'TableSwitch',
props: {
data: {
type: [Object, String, Number, Boolean],
required: true
},
rowKey: {
type: String,
default: ''
}
},
emits: ['change'],
setup(props, { emit }) {
const switchVlue = ref()
const { data, rowKey } = unref(props)
switchVlue.value = rowKey ? data[rowKey] : data
const getSwitchProps = computed(() => {
return {
...unref(props)
}
})
function changeValue(value) {
const { data } = props
const params = {
data,
value
}
emit('change', params)
}
return {
switchVlue,
getSwitchProps,
changeValue
}
}
})
</script>

View File

@ -0,0 +1,99 @@
<template>
<template v-if="isFilter">
<n-tag
v-for="(item,index) in getData.data"
:key="`tag_${index}`"
v-bind="getProps"
:color="getFilter(item[getData.rowKey])?.color || getProps?.color"
>
{{ getFilter(item[getData.rowKey]).label }}
</n-tag>
</template>
<template v-else>
<n-tag v-for="(item,index) in getData.data" :key="`tag_${index}`" v-bind="getProps">
{{ item[getData.rowKey] }}
</n-tag>
</template>
</template>
<script>
import { defineComponent, computed, unref } from 'vue'
import { isArray } from '@/utils/is.js'
export default defineComponent({
name: 'TableTags',
props: {
/* 展示的数据 */
data: {
type: [Array, String, Number],
required: true
},
/* 展示数据取的字段 */
rowKey: {
type: String,
default: 'name'
},
/* 过滤的数据 */
// filters: [
// {
// key: '',
// label: '',
// color: {}
// }
// ],
filters: {
type: Array,
default: null
},
/* tag标签的属性 */
tags: {
type: Object,
default: null
}
},
setup(props, { emit }) {
const isFilter = computed(() => {
return !!(props.filters)
})
const { filters } = unref(props)
function getFilter(value) {
const data = filters.find(item => {
return item.key === value
})
return data || {
key: value,
label: value
}
}
/* 获取传递的数据 */
const getData = computed(() => {
return {
rowKey: unref(props.rowKey),
data: isArray(props.data) ? { ...unref(props.data) } : [{ [props.rowKey]: props.data }],
filters: { ...unref(props.filters) }
}
})
/* 获取tags的属性 */
const getProps = computed(() => {
return {
...unref(props.tags),
closable: false,
bordered: props.tags?.bordered || false
}
})
return {
isFilter,
getFilter,
getData,
getProps
}
}
})
</script>
<style scoped lang='scss'>
.n-tag{
background: transparent;
}
</style>

View File

@ -30,7 +30,7 @@ export const tableProps = {
// 每页数量字段名
sizeField: 'limit',
// 接口返回的数据字段名
listField: 'list',
listField: 'records',
// 接口返回总页数字段名
totalField: 'total',
// 默认分页数量

View File

@ -0,0 +1,69 @@
/**
* pid形式数据转children形式
* @param data 需要转换的数组
* @param idKey id字段名
* @param pidKey pid字段名
* @param childKey 生成的children字段名
* @param pid 顶级的pid
* @param addPIds 是否添加所有父级id的字段
* @param parentsKey 所有父级id的字段名称默认parentIds
* @param parentIds 所有父级id
* @returns {[]}
*/
export function toTreeData(data, idKey, pidKey, childKey, pid, addPIds, parentsKey, parentIds) {
if (typeof data === 'object' && !Array.isArray(data)) {
idKey = data.idKey
pidKey = data.pidKey
childKey = data.childKey
pid = data.pid
addPIds = data.addPIds
parentsKey = data.parentsKey
parentIds = data.parentIds
data = data.data
}
if (!childKey) {
childKey = 'children'
}
if (typeof pid === 'undefined') {
pid = []
data.forEach((d) => {
let flag = true
for (let i = 0; i < data.length; i++) {
if (d[pidKey] === data[i][idKey]) {
flag = false
break
}
}
if (flag) {
pid.push(d[pidKey])
}
})
}
const result = []
data.forEach((d) => {
if (d[idKey] === d[pidKey]) {
console.error('data error: ', d)
return
}
if (Array.isArray(pid) ? (pid.indexOf(d[pidKey]) !== -1) : (d[pidKey] === pid)) {
const children = toTreeData({
data: data,
idKey: idKey,
pidKey: pidKey,
childKey: childKey,
pid: d[idKey],
addPIds: addPIds,
parentsKey: parentsKey,
parentIds: (parentIds || []).concat([d[idKey]])
})
if (children.length > 0) {
d[childKey] = children
}
if (addPIds) {
d[parentsKey || 'parentIds'] = parentIds || []
}
result.push(d)
}
})
return result
}

View File

@ -1,5 +1,6 @@
import { ref, unref, computed, onMounted } from 'vue'
import { isBoolean } from '@/utils/is'
import { toTreeData } from './toTree'
export function useDataSource(propsRef, { getPaginationInfo, setPagination, setLoading, tableData }, emit) {
const dataSourceRef = ref([])
@ -42,6 +43,7 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
fetch(opt)
}
}
// 处理数据结构
const resultInfo = res[listField] ? res[listField] : []
dataSourceRef.value = dataType === 'tree' ? dealTree(resultInfo) : resultInfo
setPagination({
@ -67,8 +69,12 @@ export function useDataSource(propsRef, { getPaginationInfo, setPagination, setL
}
}
function dealTree() {
const tree = []
/**
* 递归遍历数据处理成树形结构
* @returns 返回树形结构数据
*/
function dealTree(info) {
const tree = toTreeData(info.data, 'id', 'pid', 'children')
return tree
}

View File

@ -1,134 +0,0 @@
<template>
<!-- <n-data-table>
n-data-table
</n-data-table> -->
<div class="table-toolbar">
<!--顶部左侧区域-->
<div class="flex items-center table-toolbar-left">
<slot name="tableTitle" />
</div>
<div class="flex items-center table-toolbar-right">
<!--顶部右侧区域-->
<slot name="toolbar" />
<!--刷新-->
<!-- <n-tooltip trigger="hover">
<template #trigger>
<div class="table-toolbar-right-icon" @click="reload">
<n-icon size="18">
<ReloadOutlined />
</n-icon>
</div>
</template> -->
<span @click="reload">刷新</span>
<!-- </n-tooltip> -->
<!--表格设置单独抽离成组件-->
<!-- <ColumnSetting /> -->
</div>
</div>
<div class="s-table">
<n-data-table
ref="tableElRef"
v-bind="getBindValues"
:pagination="pagination"
>
<template v-for="item in Object.keys($slots)" #[item]="data" :key="item">
<slot :name="item" v-bind="data" />
</template>
</n-data-table>
</div>
</template>
<script>
import { tableProps } from './tools/props.js'
import { useDataSource } from './tools/useDataSource.js'
import { usePagination } from './tools/usePagination.js'
import { unref, ref, computed, toRaw } from 'vue'
export default {
name: 'DataTable',
props: {
...tableProps
},
emits: [
'fetch-success',
'fetch-error',
'update:checked-row-keys',
'edit-end',
'edit-cancel',
'edit-row-end',
'edit-change'
],
setup(props, { emit }) {
const loadingRef = ref(unref(props).loading)
const getLoading = computed(() => unref(loadingRef))
function setLoading(loading) {
loadingRef.value = loading
}
/* pagination-start */
const pagination = computed(() => toRaw(unref(getPaginationInfo)))
const { getPaginationInfo, setPagination } = usePagination(props)
/* pagination-end */
/* tableData-start */
const tableData = ref([])
const { getDataSourceRef, reload } = useDataSource(props, { getPaginationInfo, setPagination, tableData, setLoading }, emit)
//
const getBindValues = computed(() => {
const tableData = unref(getDataSourceRef)
return {
...unref(props),
loading: unref(getLoading),
// columns: toRaw(unref(getPageColumns)),
// rowKey: unref(getRowKey),
data: tableData,
// size: unref(getTableSize),
remote: true,
'max-height': 'auto'
}
})
/* tableData-end */
return {
pagination,
fetch,
reload,
getBindValues
}
}
}
</script>
<style scoped lang='scss'>
.table-toolbar {
display: flex;
justify-content: space-between;
padding: 0 0 16px 0;
.table-toolbar-left {
display: flex;
align-items: center;
justify-content: flex-start;
flex: 1;
}
.table-toolbar-right {
display: flex;
justify-content: flex-end;
flex: 1;
.table-toolbar-icon {
margin-left: 12px;
font-size: 16px;
cursor: pointer;
color: var(--text-color);
&:hover {
color: #1890ff;
}
}
}
}
.table-toolbar-inner-popover-title {
padding: 2px 0;
}
</style>

View File

@ -1,35 +0,0 @@
import { NDataTable } from 'naive-ui'
export const tableProps = {
...NDataTable.props,
/* 初始化接口请求 */
request: {
type: Function,
default: null
},
/* 分页信息 */
pagination: {
type: [Object, Boolean],
default: () => {}
},
/* 分页设置信息 */
paginationSetting: {
type: Object,
default: () => {
return {
// 当前页的字段名
pageField: 'page',
// 每页数量字段名
sizeField: 'pageSize',
// 接口返回的数据字段名
listField: 'list',
// 接口返回总页数字段名
totalField: 'pageCount',
// 默认分页数量
defaultPageSize: 10,
// 可切换每页数量集合
pageSizes: [10, 20, 30, 40, 50]
}
}
}
}

View File

@ -1,99 +0,0 @@
import { ref, unref, computed, onMounted } from 'vue'
import { isBoolean } from '@/utils/is'
export function useDataSource(propsRef, { getPaginationInfo, setPagination, setLoading, tableData }, emit) {
const dataSourceRef = ref([])
async function fetch(opt) {
try {
// setLoading(true)
const { request, pagination } = unref(propsRef)
/* 无接口请求中断 */
if (!request) return
/* 获取分页信息 */
const paginationSetting = propsRef.paginationSetting
const pageField = paginationSetting.pageField
const sizeField = paginationSetting.sizeField
const totalField = paginationSetting.totalField
const listField = paginationSetting.listField
let pageParams = {}
const { page = 1, pageSize = 10 } = unref(getPaginationInfo)
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
pageParams = {}
} else {
pageParams[pageField] = (opt && opt[pageField]) || page
pageParams[sizeField] = pageSize
}
const params = {
...pageParams
}
const res = await request(params)
console.log('res', res)
const resultTotal = res[totalField] || 0
const currentPage = res[pageField]
// // 如果数据异常,需获取正确的页码再次执行
// if (resultTotal) {
// if (page > resultTotal) {
// setPagination({
// [pageField]: resultTotal
// })
// fetch(opt)
// }
// }
const resultInfo = res[listField] ? res[listField] : []
dataSourceRef.value = resultInfo
setPagination({
[pageField]: currentPage,
[totalField]: resultTotal
})
// if (opt && opt[pageField]) {
// setPagination({
// [pageField]: opt[pageField] || 1
// })
// }
emit('fetch-success', {
items: unref(resultInfo),
resultTotal
})
} catch (error) {
console.error(error)
// emit('fetch-error', error)
// dataSourceRef.value = []
} finally {
setLoading(false)
}
}
const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef)
if (!dataSource || dataSource.length === 0) {
return unref(dataSourceRef)
}
return unref(dataSourceRef)
})
function getDataSource() {
console.log(getDataSourceRef.value)
return getDataSourceRef.value
}
function setTableData(values) {
dataSourceRef.value = values
}
onMounted(() => {
setTimeout(() => {
fetch()
}, 15)
})
return {
fetch,
getDataSourceRef,
getDataSource,
setTableData
}
}

View File

@ -1,34 +0,0 @@
import { computed, unref, ref } from 'vue'
import { isBoolean } from '@/utils/is'
export function usePagination(refProps) {
const configRef = ref({})
const show = ref(true)
console.log('configRef', configRef)
console.log('refProps', refProps)
const getPaginationInfo = computed(() => {
const { pagination, paginationSetting } = unref(refProps)
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
return false
}
return {
pageSize: paginationSetting.defaultPageSize,
pageSizes: paginationSetting.pageSizes,
showSizePicker: true,
showQuickJumper: true,
...(isBoolean(pagination) ? {} : pagination),
...unref(configRef),
pageCount: unref(configRef)[paginationSetting.totalField]
}
})
function setPagination(info) {
const paginationInfo = unref(getPaginationInfo)
configRef.value = {
...(!isBoolean(paginationInfo) ? paginationInfo : {}),
...info
}
}
return { getPaginationInfo, setPagination }
}

View File

@ -1,6 +1,54 @@
<template />
<template>
<div />
</template>
<script setup>
import { isNullOrUndef } from '@/utils/is'
import { useDialog } from 'naive-ui'
window['$dialog'] = useDialog()
const NDialog = useDialog()
class Dialog {
success(title, option) {
this.showDialog('success', { title, ...option })
}
warning(title, option) {
this.showDialog('warning', { title, ...option })
}
error(title, option) {
this.showDialog('error', { title, ...option })
}
showDialog(type = 'success', option) {
if (isNullOrUndef(option.title)) {
option.showIcon = false
}
NDialog[type]({
positiveText: 'OK',
closable: false,
...option
})
}
confirm(option = {}) {
this.showDialog(option.type || 'error', {
positiveText: '确定',
negativeText: '取消',
onPositiveClick: option.confirm,
onNegativeClick: option.cancel,
onMaskClick: option.cancel,
...option
})
}
}
window['$dialog'] = new Dialog()
Object.freeze(window.$dialog)
Object.defineProperty(window, '$dialog', {
configurable: false,
writable: false
})
</script>

View File

@ -0,0 +1,83 @@
<template>
<div>
<n-upload
:default-file-list="fileList"
v-bind="getImgOptions"
:on-change="handleChange"
:on-before-upload="handleBeforeUpload"
>
点击上传
</n-upload>
<n-modal
v-model:show="showModal"
preset="card"
style="width: 600px"
>
<img :src="previewImageUrl" style="width: 100%">
</n-modal>
</div>
</template>
<script>
import { defineComponent, reactive, toRefs, computed, unref } from 'vue'
import { getToken } from '@/utils/token'
export default defineComponent({
name: 'ImgUpload',
props: {
options: {
type: Object,
default: null
},
size: {
type: Number,
default: null
}
},
setup(props, { emit }) {
const data = reactive({
fileList: []
})
const BaseURL = window.__APP__GLOB__CONF__?.VITE_APP_GLOB_BASE_API || import.meta.env.VITE_APP_GLOB_BASE_API
const getImgOptions = computed(() => {
return {
...unref(props.options),
listType: 'image-card',
defaultUpload: props.options.defaultUpload || false,
action: `${BaseURL}${props.options.action}`,
headers: {
'Authorization': getToken(),
...props.options.headers
}
}
})
function handleChange(file) {
console.log(222, file)
}
/**
* @description: 上传前判断文件是否符合条件
* @param {*} options
* @return {*}
*/
function handleBeforeUpload(options) {
const { file, fileList } = options
console.log(11, file, fileList)
if (props.size) {
const size = file.file.size
if (size < props.size * 1024 * 1024) {
fileList.splice(fileList.length - 1)
$message.error('选择的文件大小不满足条件!')
}
}
}
return {
...toRefs(data),
getImgOptions,
handleChange,
handleBeforeUpload
}
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -1,4 +1,6 @@
<template />
<template>
<div />
</template>
<script setup>
import { useMessage } from 'naive-ui'
@ -8,12 +10,6 @@ const NMessage = useMessage()
let loadingMessage = null
class Message {
/**
* 规则
* * loading message只显示一个新的message会替换正在显示的loading message
* * loading message不会自动清除除非被替换成非loading message非loading message默认2秒后自动清除
*/
removeMessage(message, duration = 2000) {
setTimeout(() => {
if (message) {
@ -25,16 +21,13 @@ class Message {
showMessage(type, content, option = {}) {
if (loadingMessage && loadingMessage.type === 'loading') {
// loading message
loadingMessage.type = type
loadingMessage.content = content
if (type !== 'loading') {
// loading message
this.removeMessage(loadingMessage, option.duration)
}
} else {
// loadingmessage,messageloading messagemessage
const message = NMessage[type](content, option)
if (type === 'loading') {
loadingMessage = message

View File

@ -2,8 +2,7 @@
<n-modal
v-bind="getModalOptions"
:style="`width:${getModalOptions.width}px`"
preset="card"
@update:show="handleClose"
:title="options.title"
>
<n-card :bordered="false">
<slot name="Context" />
@ -13,9 +12,9 @@
<script>
import { defineComponent, computed, unref } from 'vue'
import { defineComponent, computed } from 'vue'
export default defineComponent({
name: 'CardModal',
name: 'CardDialogModal',
props: {
options: {
type: Object,
@ -23,7 +22,7 @@ export default defineComponent({
}
},
emits: {
click: null, // click
onConfirm: null,
onClose: (value) => {
return value
}
@ -32,18 +31,21 @@ export default defineComponent({
const getModalOptions = computed(() => {
return {
...props.options,
width: props.options.width || 600
width: props.options.width || 600,
preset: props.options.preset || 'dialog',
showIcon: !!props.options.showIcon
}
})
const handleClick = function() {
emit('click')
const handleConfirm = function() {
emit('onConfirm')
return false
}
const handleClose = function() {
emit('onClose', true)
}
return {
getModalOptions,
handleClick,
handleConfirm,
handleClose
}
}

View File

@ -29,8 +29,6 @@ const getMenuOptions = computed(() => {
return generateOptions(permissionStore.routes, '')
})
console.log('getMenuOptions', getMenuOptions)
function resolvePath(basePath, path) {
if (isExternal(path)) return path
return (

View File

@ -20,7 +20,9 @@ export function createPermissionGuard(router) {
try {
// await userStore.getUserInfo()
const routes = await permissionStore.generateRoutes()
router.addRoute(routes[0])
routes.forEach((item) => {
router.addRoute(item)
})
router.addRoute(NOT_FOUND_ROUTE)
router.addRoute(REDIRECT_ROUTE)
next({ ...to, replace: true })

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { asyncRoutes, basicRoutes } from '@/router/routes'
import { getMenu } from '@/api/system'
import { getMenu } from '@/api/auth/index'
import Layout from '@/layout/index.vue'
import modules from '@/utils/module.js'
@ -114,7 +114,6 @@ export const usePermissionStore = defineStore('permission', {
const res = await getMenu()
if (res.code === 0) {
const result = dataArrayToRoutes(res.data)
console.log(result)
this.accessRoutes = result
return Promise.resolve(result)
} else {

View File

@ -9,4 +9,10 @@
.layout__content{
height: calc(100vh - 48px);
}
}
.n-data-table .n-data-table-tr{
th{
white-space:nowrap;
}
}

132
src/utils/handleData.js Normal file
View File

@ -0,0 +1,132 @@
/**
* pid形式数据转children形式
* @param data 需要转换的数组
* @param idKey id字段名
* @param pidKey pid字段名
* @param childKey 生成的children字段名
* @param pid 顶级的pid
* @param addPIds 是否添加所有父级id的字段
* @param parentsKey 所有父级id的字段名称默认parentIds
* @param parentIds 所有父级id
* @returns {[]}
*/
export function toTreeData(data, idKey, pidKey, childKey, pid, addPIds, parentsKey, parentIds) {
if (typeof data === 'object' && !Array.isArray(data)) {
idKey = data.idKey
pidKey = data.pidKey
childKey = data.childKey
pid = data.pid
addPIds = data.addPIds
parentsKey = data.parentsKey
parentIds = data.parentIds
data = data.data
}
if (!childKey) {
childKey = 'children'
}
if (typeof pid === 'undefined') {
pid = []
data.forEach((d) => {
let flag = true
for (let i = 0; i < data.length; i++) {
if (d[pidKey] === data[i][idKey]) {
flag = false
break
}
}
if (flag) {
pid.push(d[pidKey])
}
})
}
const result = []
data.forEach((d) => {
if (d[idKey] === d[pidKey]) {
console.error('data error: ', d)
return
}
if (Array.isArray(pid) ? (pid.indexOf(d[pidKey]) !== -1) : (d[pidKey] === pid)) {
const children = toTreeData({
data: data,
idKey: idKey,
pidKey: pidKey,
childKey: childKey,
pid: d[idKey],
addPIds: addPIds,
parentsKey: parentsKey,
parentIds: (parentIds || []).concat([d[idKey]])
})
if (children.length > 0) {
d[childKey] = children
}
if (addPIds) {
d[parentsKey || 'parentIds'] = parentIds || []
}
result.push(d)
}
})
return result
}
/**
* 遍历children形式数据
* @param data 需要遍历的数组
* @param callback 回调
* @param childKey children字段名
*/
export function eachTreeData(data, callback, childKey = 'children') {
if (!data || !data.length) {
return
}
data.forEach((d) => {
if (callback(d) !== false && d[childKey]) {
eachTreeData(d[childKey], callback, childKey)
}
})
}
/**
* 处理树形数据
* @param data 需要处理的数据
* @param formatter 处理器
* @param childKey children字段名
* @returns {[]} 处理后的数据
*/
export function formatTreeData(data, formatter, childKey = 'children') {
const result = []
if (data && data.length) {
data.forEach((d) => {
const item = formatter(d)
if (item !== false) {
if (item[childKey]) {
item[childKey] = formatTreeData(item[childKey], formatter, childKey)
}
result.push(item)
}
})
}
return result
}
/**
* 处理select数据
* @param data 需要处理的数据
* @params {label: XX, value: XX, children: XX} 需要处理的结构
* @returns {[]} 处理后的数据
*/
export function dataToSelect(data, optionsObj) {
const result = []
if (data && data.length) {
data.forEach((item) => {
const i = {}
i.label = item[optionsObj.label]
i.value = item[optionsObj.value]
if (item.children && item.children.length) {
dataToSelect(item.children, optionsObj)
i.children = item[optionsObj.children]
}
result.push(i)
})
}
return result
}

View File

@ -43,8 +43,8 @@ export function setupInterceptor(service) {
// 未登录可能是token过期或者无效了
removeToken()
router.replace({
path: '/login',
query: { ...currentRoute.query, redirect: currentRoute.path }
path: '/login'
// query: { ...currentRoute.query, redirect: currentRoute.path }
})
break
default:

View File

@ -1,43 +1,125 @@
<template>
<div>
<n-button @click="handleLogin">登录</n-button>
<div class="login_bg">
<n-form
ref="formRef"
:model="loginForm"
:rules="rules"
label-placement="left"
label-width="auto"
require-mark-placement="right-hanging"
:style="{
maxWidth: '640px',
backgroundColor: '#fff',
padding: '20px',
borderRadius: '10px'
}"
@keyup.enter="handleLogin"
>
<n-form-item label="用户名" path="username">
<n-input v-model:value="loginForm.username" placeholder="请输入用户名" />
</n-form-item>
<n-form-item label="密码" path="password">
<n-input v-model:value="loginForm.password" type="password" placeholder="请输入用户名" />
</n-form-item>
<n-form-item label="验证码" path="captcha">
<n-input v-model:value="loginForm.captcha" placeholder="请输入验证码" />
<img v-if="captcha" :src="captcha" alt="" @click="changeCode">
</n-form-item>
<n-form-item path="remember">
<n-checkbox v-model:checked="loginForm.remember" size="medium" label="记住密码" />
</n-form-item>
<n-form-item>
<n-button @click="handleLogin">登录</n-button>
</n-form-item>
</n-form>
</div>
</template>
<script>
import { userLogin, userCaptcha } from '@/api/login/index.js'
import { setToken } from '@/utils/token'
import { ref, reactive, onMounted } from 'vue'
export default {
name: 'LoginPage',
setup() {
async function handleLogin() {
try {
const params = {
captcha: '520',
key: 'f2d40fb6-b946-4b99-969d-978c6d8a4ea2',
password: '123456',
remember: false,
username: 'admin'
}
const res = await userLogin(params)
if (res.code === 0) {
setToken(res.data.access_token)
}
} catch (error) {
// console.log(error)
const loginForm = reactive({
username: '',
password: '',
key: '',
captcha: '',
remember: false
})
onMounted(() => {
changeCode(loginForm)
})
//
const captcha = ref('')
async function changeCode(form) {
const params = {
username: form.username,
password: form.password,
captcha: form.captcha
}
const captForm = await userCaptcha(params)
captcha.value = captForm.data.captcha
loginForm.key = captForm.data.key
}
// userCaptcha()
return {
handleLogin
loginForm,
captcha,
changeCode,
rules: reactive({
username: {
required: true,
trigger: ['blur', 'input'],
message: '请输入用户名'
},
password: {
required: true,
trigger: ['blur', 'input'],
message: '请输入密码'
},
captcha: {
required: true,
trigger: ['blur', 'input'],
message: '请输入验证码'
}
})
}
},
methods: {
handleLogin() {
console.log(this.loginForm)
userLogin(this.loginForm).then((res) => {
if (res.code === 0) {
// token
setToken(res.data.access_token)
this.goPage()
} else {
console.log(res)
}
})
},
//
goPage() {
// const query = this.$route.query
// const path = query && query.from ? query.from : '/'
// console.log(path)
// this.$router.replace(path)
this.$router.replace('/home')
}
}
}
</script>
<style lang="scss" scoped>
.login_bg {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -0,0 +1,157 @@
<template>
<div>
<n-card>
<data-table
:columns="data.columns"
:data="data.data"
:pagination="data.pagination"
:request="loadDataTable"
:row-key="(row) => row.id"
data-type="tree"
size="large"
scroll-x="1200"
>
<template #tableTitle>
<n-button type="primary"> 新建 </n-button>
<n-button type="primary"> 删除 </n-button>
</template>
</data-table>
</n-card>
</div>
</template>
<script>
import dataTable from '@/components/DataTable/index.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { getDeptList } from '@/api/system/index.js'
import { h, onMounted, unref } from 'vue'
import { reactive } from 'vue'
export default {
name: 'MenuPage',
components: { dataTable },
setup() {
const data = reactive({
columns: [
{
title: '部门编号',
key: 'code',
align: 'center'
},
{
title: '部门名称',
key: 'name',
align: 'center'
},
{
title: '部门全称',
key: 'fullname',
align: 'center'
},
{
title: '部门类型',
key: 'type',
align: 'center',
width: 100
},
{
title: '排序',
key: 'sort',
align: 'center'
},
{
title: '备注',
key: 'note',
align: 'center',
width: 100
},
{
title: '创建时间',
key: 'createTime',
align: 'center',
width: 160
},
{
title: '更新时间',
key: 'updateTime',
align: 'center',
width: 160
},
{
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: '修改',
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
auth: 'basic_list'
}
],
align: 'center'
})
}
}
],
data: [],
pagination: {
pageSize: 10
}
})
function play(row) {
console.log(row)
}
/**
* @description: 获取部门数据并做树形结构处理
* @return {*}
*/
async function fetchList() {
const params = {
page: 1,
limit: 10
}
const res = await getDeptList(params)
data.data = res.data
}
const params = reactive({
name: 'xiaoMa'
})
const loadDataTable = async(res) => {
const _params = {
...unref(params),
...res
}
return await getDeptList(_params)
}
onMounted(() => {
fetchList()
})
return { data, loadDataTable }
}
}
</script>
<style scoped lang='scss'>
.n-button + .n-button {
margin-left: 10px;
}
</style>

View File

@ -1,36 +1,185 @@
<template>
<Modal :options="getModalOptions" @on-close="handleClose">
<Modal
:options="getModalOptions"
:on-close="handleClose"
:on-positive-click="handleConfirm"
:on-negative-click="handleClose"
>
<template #Context>
111
<n-form ref="formRef" :model="menuForm" :rules="menuRules" 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="menuForm.pid"
placeholder="请选择上级菜单"
:options="getMenuList"
/>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="菜单类型" path="type">
<n-radio-group v-model:value="menuForm.type" name="type">
<n-radio v-for="item in typeOptions" :key="`type_${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="menuForm.title" placeholder="请输入菜单名称" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="打开方式" path="target">
<n-radio-group v-model:value="menuForm.target" name="target">
<n-radio v-for="item in openOptions" :key="`target_${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="menuForm.icon" placeholder="请选择菜单图标" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="权限标识" path="permission">
<n-input v-model:value="menuForm.permission" placeholder="请输入权限标识" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="路由地址" path="path">
<n-input v-model:value="menuForm.path" placeholder="请输入路由地址" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="排序号" path="sort">
<n-input v-model:value="menuForm.sort" placeholder="请输入排序号" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="组件路径" path="component">
<n-input v-model:value="menuForm.component" placeholder="请输入组件路径" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="是否可见" path="hide">
<n-radio-group v-model:value="menuForm.hide" name="hide">
<n-radio v-for="item in visibleOptions" :key="`hide_${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="menuForm.status" name="status">
<n-radio v-for="item in statusOptions" :key="`status_${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 } from 'vue'
import Modal from '@/components/CardModal/index.vue'
import { defineComponent, computed, reactive, toRefs, ref } from 'vue'
import Modal from '@/components/Modal/index.vue'
import { addMenu } from '@/api/system/menu/index.js'
export default defineComponent({
name: 'MenuModal',
components: { Modal },
props: {
/* 可见 */
visible: {
type: Boolean,
default: false
},
row: {
/* 选中的数据 */
data: {
type: Object,
default: () => {}
},
menuList: {
type: Array,
default: () => []
}
},
emits: {
'update:visible': null,
onClose: null,
done: null
'update:visible': null
},
setup(props, { emit }) {
const data = reactive({
menuForm: {
pid: null,
type: '0',
title: '',
target: '0',
icon: '',
permission: '',
path: '',
sort: '',
component: '',
hide: '0',
status: '1'
},
menuRules: {
title: {
required: true,
message: '请输入菜单名称',
trigger: ['blur']
},
sort: {
required: true,
message: '请输入排序号',
trigger: ['blur']
}
},
typeOptions: [
{ key: '0', label: '菜单' },
{ key: '1', label: '按钮' }
],
openOptions: [
{ key: '0', label: '组件' },
{ 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.pid
}
return menu
})
return list
})
const getModalOptions = computed(() => {
return {
show: props.visible
show: props.visible,
title: props.data ? '修改菜单' : '新建菜单',
width: 700,
negativeText: '取消',
positiveText: '确认'
}
})
@ -38,10 +187,31 @@ export default defineComponent({
const handleClose = () => {
emit('update:visible', false)
}
const formRef = ref()
const handleConfirm = () => {
formRef.value?.validate((errors) => {
if (!errors) {
const params = {
...data.menuForm
}
addMenu(params)
.then(res => {
handleClose()
})
} else {
$message.error('清先完成校验')
}
})
return false
}
return {
...toRefs(data),
formRef,
getMenuList,
getModalOptions,
handleClose
handleClose,
handleConfirm
}
}
})

View File

@ -1,7 +1,7 @@
<template>
<div>
<n-card>
<data-table :columns="data.columns" :pagination="false" :data="data.data" size="large">
<data-table :columns="data.columns" :pagination="false" :data="data.data" size="large" @fetch-success="getFechData">
<template #tableTitle>
<n-button type="primary" @click="handlleRoleAdd">
添加菜单
@ -11,7 +11,7 @@
</n-card>
</div>
<RoleModal v-model:visible="data.modalShow" />
<RoleModal v-model:visible="data.modalShow" :data="data.selectRow" :menu-list="data.data" />
</template>
<script>
@ -119,10 +119,9 @@ export default {
}
}
],
data: [
],
modalShow: false
data: [],
modalShow: false,
selectRow: null
})
function play(row) {
@ -141,11 +140,24 @@ export default {
data.modalShow = true
}
/**
* @description: 获取表单数据
* @param {*} data
* @return {*}
*/
function getFechData(data) {
}
onMounted(() => {
fetchList()
})
return { data, handlleRoleAdd }
return {
data,
handlleRoleAdd,
getFechData
}
}
}

View File

@ -28,7 +28,7 @@
import headSearch from '@/components/Search/index.vue'
import dataTable from '@/components/DataTable/index.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { getUserList } from '@/api/system/index.js'
import { getRoleList } from '@/api/system/role/index'
import { h, ref, unref } from 'vue'
import { reactive } from 'vue'
import table from './table.js'
@ -96,7 +96,7 @@ export default {
...unref(params),
...res
}
return await getUserList(_params)
return await getRoleList(_params)
}
return { data, tableRef, loadDataTable, handleSearch }

View File

@ -0,0 +1,260 @@
<template>
<Modal :options="getModalOptions" @on-close="handleClose">
<template #Context>
<n-form
ref="formRef"
:model="form"
label-placement="left"
:rules="rules"
>
<n-form-item
label="头像:"
path="avatar"
>
<uploadImage
:options="{
max:1,
action: '/upload/uploadImage/demo',
}"
size="2"
/>
<!-- <n-upload
action="https://www.mocky.io/v2/5e4bafc63100007100d8b70f"
:default-file-list="previewFileList"
list-type="image-card"
@preview="handlePreview"
/>
<n-modal
v-model:show="showModal"
preset="card"
style="width: 600px"
title="头像"
>
<img :src="form.avatar" style="width: 100%">
</n-modal> -->
</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="deptId">
<n-select
v-model:value="form.deptId"
:options="deptOptions"
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="roleIds">
<n-select
v-model:value="form.roleIds"
clearable
:multiple="true"
:options="rolesOptions"
placeholder="请选择角色"
/>
</n-form-item>
<n-form-item
v-if="form.type==2"
label="驾照类型:"
path="driverType"
>
<n-select
v-model:value="form.driverType"
clearable
:options="[1, 2]"
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="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="登录密码:"
path="password"
>
<n-input
v-model:value="form.password"
type="password"
:maxlength="20"
placeholder="请输入登录密码"
/>
</n-form-item>
<n-form-item label="用户类型" path="type">
<n-radio-group
v-model:value="form.type"
>
<n-radio :value="1">普通用户</n-radio>
<n-radio :value="2">飞手</n-radio>
</n-radio-group>
</n-form-item>
<n-form-item
v-if="form.type==2"
label="驾照编号:"
path="driverCode"
>
<n-input
v-model:value="form.driverCode"
clearable
:maxlength="20"
placeholder="请输入驾照编号"
/>
</n-form-item>
<n-form-item
label="备注:"
:label-col="{sm: {span: 3}, xs: {span: 6}}"
:wrapper-col="{sm: {span: 21}, xs: {span: 18}}"
>
<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 } 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 { dataToSelect } from '@/utils/handleData.js'
export default defineComponent({
name: 'UserModal',
components: { Modal, uploadImage },
props: {
visible: {
type: Boolean,
default: false
},
row: {
type: Object,
default: () => {}
},
title: {
type: String,
default: '添加'
}
},
emits: {
'update:visible': null,
onClose: null,
done: null
},
setup(props, { emit }) {
const getModalOptions = computed(() => {
return {
title: props.title,
show: props.visible
}
})
/* 关闭弹窗 */
const handleClose = () => {
emit('update:visible', false)
}
const form = reactive({
avatar: '',
code: '',
deptId: null,
username: '',
realname: '',
password: '',
roleIds: [],
driverType: null,
driverCode: '',
type: 1,
status: 1,
note: ''
})
//
let deptOptions = reactive([])
async function getDeptOptions() {
const res = await getDeptAll()
deptOptions = dataToSelect(res.data, { label: 'name', value: 'id' })
console.log('部门选项:', deptOptions)
}
//
let rolesOptions = reactive([])
async function getRoleOptions() {
const res = await getRoleAll()
rolesOptions = dataToSelect(res.data, { label: 'name', value: 'id' })
}
onMounted(() => {
getDeptOptions()
getRoleOptions()
})
//
const handleUpload = ({ file, event }) => {
console.log(event)
form.avatar = file.url
}
return {
getModalOptions,
handleClose,
form,
rules: reactive({
avatar: {
required: true,
trigger: 'blur',
message: '请上传头像'
},
code: {
required: true,
trigger: ['blur', 'input'],
message: '请输入用户编号'
},
deptId: {
required: true,
trigger: 'blur',
message: '请选择部门'
}
}),
handleUpload,
deptOptions,
rolesOptions
}
},
methods: {
}
})
</script>
<style scoped lang='scss'>
</style>

View File

@ -1,29 +1,36 @@
<template>
<div>
<data-table :columns="data.columns" :data="data.data" :pagination="data.pagination" size="large" scroll-x="1200">
<template #tableTitle>
<n-button type="primary">
新建
</n-button>
<n-button type="primary">
删除
</n-button>
</template>
</data-table>
<n-card>
<data-table
:columns="data.columns"
:row-key="(row) => row.id"
:request="loadDataTable"
size="large"
>
<template #tableTitle>
<n-button type="primary" @click="handleUser"> 新建 </n-button>
<!-- <n-button type="primary" @click="deleteUsers"> 删除 </n-button> -->
</template>
</data-table>
</n-card>
</div>
<!-- 新增编辑弹窗 -->
<user-modal v-if="data.modalShow" v-model:visible="data.modalShow" :row="rowData" :title="modalTitle" />
</template>
<script>
import dataTable from '@/components/DataTable/index.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import TableImage from '@/components/DataTable/tools/Image.vue'
import { getUserList } from '@/api/system/index.js'
import { h, onMounted } from 'vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableSwitch from '@/components/DataTable/tools/Switch.vue'
import { getUserList } from '@/api/system/user/index.js'
import { h, unref, ref } from 'vue'
import { reactive } from 'vue'
import UserModal from './components/UserModal.vue'
export default {
name: 'MenuPage',
components: { dataTable },
components: { dataTable, UserModal },
setup() {
const data = reactive({
columns: [
@ -41,7 +48,7 @@ export default {
images: {
width: 36,
height: 36,
src: row.avatar
src: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQiKrYt-fEfJ4SZhF9KfLLaX9f15KV6Ve1ptA&usqp=CAU'
}
})
}
@ -65,14 +72,27 @@ export default {
{
title: '角色',
key: 'roles',
align: 'center'
align: 'center',
render(row) {
return h(TableTags, {
data: row.roles
})
}
},
{
title: '状态',
key: 'status',
align: 'center',
width: 100
width: 100,
render(row) {
return h(TableSwitch, {
data: row,
rowKey: 'status',
checkedValue: 1,
uncheckedValue: 2,
onChange: getSwitch.bind(row)
})
}
},
{
title: '部门',
@ -100,7 +120,7 @@ export default {
return h(TableAction, {
actions: [
{
label: '添加',
label: '修改',
type: 'button',
props: {
type: 'primary',
@ -108,10 +128,6 @@ export default {
},
auth: 'basic_list'
},
{
label: '修改',
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
@ -123,40 +139,45 @@ export default {
}
}
],
data: [],
pagination: {
pageSize: 10
}
modalShow: false
})
const modalTitle = ref('添加用户')
let rowData = reactive({})
//
function play(row) {
rowData = row
modalTitle.value = '编辑用户'
console.log('编辑弹窗内容:', row)
data.modalShow = true
}
const params = reactive({
name: ''
})
function play(row) {
const loadDataTable = async(res) => {
const _params = {
...unref(params),
...res
}
return await getUserList(_params)
}
function handleUser() {
rowData = {}
modalTitle.value = '添加用户'
data.modalShow = true
}
function getSwitch(row) {
console.log(row)
}
/**
* @description: 获取用户数据
* @return {*}
*/
async function fetchList() {
const params = {
page: 1,
limit: 10
}
const res = await getUserList(params)
data.data = res.data.list
}
onMounted(() => {
fetchList()
})
return { data }
return { data, loadDataTable, handleUser, rowData, modalTitle }
}
}
</script>
<style scoped lang='scss'>
.n-button+.n-button{
margin-left: 10px;
.n-button + .n-button {
margin-left: 10px;
}
</style>