创建菜单

This commit is contained in:
Leo_Ding 2025-06-17 11:33:03 +08:00
parent 4d84501dbd
commit a32354d7fd
19 changed files with 968 additions and 66 deletions

View File

@ -2,7 +2,7 @@
NODE_ENV=development NODE_ENV=development
# app # app
VITE_TITLE=GuXuan-Admin VITE_TITLE=海邻后台管理系统
VITE_PUBLIC_PATH=/ VITE_PUBLIC_PATH=/
VITE_OUT_DIR=dist VITE_OUT_DIR=dist
VITE_PERMISSION=false VITE_PERMISSION=false
@ -12,7 +12,7 @@ VITE_ROUTER_BASE=/
VITE_ROUTER_HISTORY=hash VITE_ROUTER_HISTORY=hash
# api # api
VITE_API_BASIC=/ VITE_API_BASIC=http://10.10.1.6:8060
VITE_API_HTTP=/api/v1/ VITE_API_HTTP=/api/v1/
# storage # storage
VITE_STORAGE_NAMESPACE = gin-admin_local_ VITE_STORAGE_NAMESPACE = gin-admin_local_

View File

@ -2,7 +2,7 @@
NODE_ENV=production NODE_ENV=production
# app # app
VITE_TITLE=GuXuan-Admin VITE_TITLE=海邻后台管理系统
VITE_PUBLIC_PATH=/ VITE_PUBLIC_PATH=/
VITE_OUT_DIR=dist VITE_OUT_DIR=dist
VITE_PERMISSION=true VITE_PERMISSION=true

View File

@ -6,5 +6,5 @@
"singleQuote": true, "singleQuote": true,
"printWidth": 120, "printWidth": 120,
"endOfLine": "auto", "endOfLine": "auto",
"singleAttributePerLine": true "singleAttributePerLine": false
} }

View File

@ -3,7 +3,7 @@ export default () => ({
port: 8080, port: 8080,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://101.42.232.163:8080/api', target: 'https://mock.apifox.cn/m1/3156808-0-default',
// target: 'http://127.0.0.1:8045/api', // target: 'http://127.0.0.1:8045/api',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace('/api', ''), rewrite: (path) => path.replace('/api', ''),

26
package-lock.json generated
View File

@ -1,11 +1,11 @@
{ {
"name": "gin-admin", "name": "guxuan-admin",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "gin-admin", "name": "guxuan-admin",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@ant-design/colors": "^7.0.0", "@ant-design/colors": "^7.0.0",
@ -49,6 +49,7 @@
"husky": "^8.0.3", "husky": "^8.0.3",
"less": "^4.2.0", "less": "^4.2.0",
"lint-staged": "^14.0.0", "lint-staged": "^14.0.0",
"mockjs": "^1.1.0",
"rollup-plugin-visualizer": "^5.9.2", "rollup-plugin-visualizer": "^5.9.2",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
@ -3385,6 +3386,18 @@
"integrity": "sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==", "integrity": "sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==",
"dev": true "dev": true
}, },
"node_modules/mockjs": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/mockjs/-/mockjs-1.1.0.tgz",
"integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
"dev": true,
"dependencies": {
"commander": "*"
},
"bin": {
"random": "bin/random"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -7431,6 +7444,15 @@
"integrity": "sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==", "integrity": "sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==",
"dev": true "dev": true
}, },
"mockjs": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/mockjs/-/mockjs-1.1.0.tgz",
"integrity": "sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==",
"dev": true,
"requires": {
"commander": "*"
}
},
"ms": { "ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@ -58,6 +58,7 @@
"husky": "^8.0.3", "husky": "^8.0.3",
"less": "^4.2.0", "less": "^4.2.0",
"lint-staged": "^14.0.0", "lint-staged": "^14.0.0",
"mockjs": "^1.1.0",
"rollup-plugin-visualizer": "^5.9.2", "rollup-plugin-visualizer": "^5.9.2",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",

View File

@ -32,4 +32,6 @@ export default {
account: '个人页', account: '个人页',
'account.trigger': '触发报错', 'account.trigger': '触发报错',
'account.logout': '退出登录', 'account.logout': '退出登录',
imgMgt:'图片管理',
homeBanner:'首页轮播图'
} }

View File

@ -1,6 +1,6 @@
export default { export default {
'pages.layouts.userLayout.title': 'pages.layouts.userLayout.title':
'基于 GIN + GORM 2.0 + Casbin 2.0 + Wire DI 的轻量级、灵活、优雅且功能齐全的 RBAC 脚手架。', '轻量级、灵活、优雅且功能齐全的脚手架。',
'pages.login.accountLogin.tab': '账户密码登录', 'pages.login.accountLogin.tab': '账户密码登录',
'pages.login.failure': '登录失败,请重试!', 'pages.login.failure': '登录失败,请重试!',
'pages.login.success': '登录成功!', 'pages.login.success': '登录成功!',

View File

@ -0,0 +1,62 @@
import { SettingOutlined } from '@ant-design/icons-vue'
export default [
{
path: 'imgMgt',
name: 'imgMgt',
component: 'RouteViewLayout',
meta: {
icon: SettingOutlined,
title: '图片管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
children: [
{
path: 'homeBanner',
name: 'homeBanner',
component: 'imgMgt/homeBanner/index.vue',
meta: {
title: '首页轮播图',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'role',
name: 'role',
component: 'system/role/index.vue',
meta: {
title: '角色管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'menu',
name: 'menu',
component: 'system/menu/index.vue',
meta: {
title: '菜单管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'logger',
name: 'logger',
component: 'system/logger/index.vue',
meta: {
title: '日志管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
],
},
]

View File

@ -9,6 +9,7 @@ import system from './system'
import link from './link' import link from './link'
import iframe from './iframe' import iframe from './iframe'
import other from './other' import other from './other'
import imgMgt from './imgMgt'
export default [ export default [
...home, ...home,
@ -22,4 +23,5 @@ export default [
...link, ...link,
...iframe, ...iframe,
...other, ...other,
...imgMgt
] ]

View File

@ -73,7 +73,9 @@ export function formatRoutes(routes = [], parent = {}) {
const modules = import.meta.glob('../views/**/*.vue') const modules = import.meta.glob('../views/**/*.vue')
return routes return routes
.map((item) => { .map((item) => {
console.log(item.name,localRoutes)
const localRoute = find(toList(localRoutes), { name: item.name }) const localRoute = find(toList(localRoutes), { name: item.name })
console.log(localRoute)
if (!localRoute) return if (!localRoute) return
const component = localRoute?.component || 'exception/404' const component = localRoute?.component || 'exception/404'
const isLink = localRoute?.meta?.type === 'link' const isLink = localRoute?.meta?.type === 'link'

View File

@ -24,17 +24,15 @@ const useRouterStore = defineStore('router', {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
;(async () => { ;(async () => {
try { try {
const { success, data } = await apis.user.getUserMenu().catch(() => { const { success, data } = await apis.user.getUserMenu().catch(() => {throw new Error()})
throw new Error()
})
if (config('http.code.success') === success) { if (config('http.code.success') === success) {
const list = formatApiData(data) const list = formatApiData(data)
console.log(list)
list.push(...addWebPage()) list.push(...addWebPage())
const validRoutes = formatRoutes(list) const validRoutes = formatRoutes(list)
console.log('validRoutes',validRoutes)
const menuList = generateMenuList(validRoutes) const menuList = generateMenuList(validRoutes)
console.log('menuList',menuList)
const routes = [...generateRoutes(validRoutes), notFoundRoute] const routes = [...generateRoutes(validRoutes), notFoundRoute]
const indexRoute = getFirstValidRoute(menuList) const indexRoute = getFirstValidRoute(menuList)
routes.forEach((route) => { routes.forEach((route) => {
@ -43,6 +41,9 @@ const useRouterStore = defineStore('router', {
this.routes = routes this.routes = routes
this.menuList = menuList this.menuList = menuList
this.indexRoute = indexRoute this.indexRoute = indexRoute
console.log('routes',routes)
console.log('menuList',routes)
console.log('indexRoute',routes)
resolve() resolve()
} }
} catch (error) { } catch (error) {

View File

@ -0,0 +1,169 @@
<template>
<a-card
:body-style="{ height: 'calc(100% - 56px - 47px)', padding: 0 }"
:style="{
position: 'sticky',
top: appStore.mainOffsetTop,
height: appStore.mainHeight,
}">
<template #title>
<a-input-search placeholder="搜索部门"></a-input-search>
</template>
<x-scrollbar class="pa-8-2">
<a-spin :spinning="loading">
<a-tree
block-node
:selected-keys="selectedKeys"
:tree-data="listData"
:field-names="{ key: 'id', children: 'children' }"
@select="onSelect">
<template #title="{ title }">
<span class="ant-tree-title__name">{{ title }}</span>
<span class="ant-tree-title__actions">
<a-dropdown
:trigger="['click']"
@click.stop>
<x-action-button>
<more-outlined></more-outlined>
</x-action-button>
<template #overlay>
<a-menu>
<a-menu-item @click="$refs.editDepartmentDialogRef.handleEdit()">
添加子部门
</a-menu-item>
<a-menu-item @click="$refs.editDepartmentDialogRef.handleEdit()">
编辑
</a-menu-item>
<a-menu-item @click="handleDelete">删除</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</span>
</template>
</a-tree>
<empty
v-if="!listData.length"
:image="Empty.PRESENTED_IMAGE_SIMPLE"></empty>
</a-spin>
</x-scrollbar>
<template #actions>
<span @click="$refs.editDepartmentDialogRef.handleCreate()">
<plus-outlined></plus-outlined>
新建部门
</span>
</template>
</a-card>
<edit-department-dialog
ref="editDepartmentDialogRef"
@ok="onOk"></edit-department-dialog>
</template>
<script setup>
import { useAppStore } from '@/store'
import { ref, watch } from 'vue'
import { usePagination } from '@/hooks'
import apis from '@/apis'
import { Empty, Modal, message } from 'ant-design-vue'
import { config } from '@/config'
import { head, get, find } from 'lodash-es'
import { MoreOutlined, PlusOutlined } from '@ant-design/icons-vue'
import EditDepartmentDialog from './EditDepartmentDialog.vue'
const props = defineProps({
value: {
type: String,
default: '',
},
})
const emit = defineEmits(['change', 'update:value'])
const appStore = useAppStore()
const { listData, loading, showLoading, hideLoading } = usePagination()
const editDepartmentDialogRef = ref()
const selectedKeys = ref([props.value])
watch(
() => props.value,
(val) => {
if (val === selectedKeys.value?.[0]) return
selectedKeys.value = [val]
}
)
getList()
/**
* 获取列表
* @returns {Promise<void>}
*/
async function getList() {
try {
showLoading()
const { code, data } = await apis.common.getPageList().catch(() => {
throw new Error()
})
hideLoading()
if (config('http.code.success') === code) {
const { records } = data
listData.value = records
if (listData.value.length) {
selectedKeys.value = [get(head(listData.value), 'id')]
trigger()
}
}
} catch (error) {
hideLoading()
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: '删除提示',
content: '确认删除?',
okText: '确认',
onOk: () => {
return new Promise((resolve, reject) => {
;(async () => {
try {
const { code } = await apis.common.del(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === code) {
resolve()
message.success('删除成功')
await getList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
function onSelect(keys) {
if (!keys.length) return
selectedKeys.value = keys
trigger()
}
async function onOk() {
await getList()
}
function trigger() {
const value = head(selectedKeys.value)
const record = find(listData.value, { id: value })
emit('update:value', value)
emit('change', record)
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,131 @@
<template>
<a-modal
:open="modal.open"
:title="modal.title"
:width="480"
:confirm-loading="modal.confirmLoading"
:after-close="onAfterClose"
:cancel-text="cancelText"
@ok="handleOk"
@cancel="handleCancel">
<a-form
ref="formRef"
:model="formData"
:rules="formRules"
:label-col="{ style: { width: '90px' } }">
<a-form-item
label="部门名称"
name="name">
<a-input v-model:value="formData.name"></a-input>
</a-form-item>
<a-form-item
label="上级部门"
name="parent_id">
<a-tree-select v-model:value="formData.parent_id"></a-tree-select>
</a-form-item>
<a-form-item
label="部门负责人"
name="name">
<a-input v-model:value="formData.name"></a-input>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
const emit = defineEmits(['ok'])
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建部门',
})
}
/**
* 编辑
*/
function handleEdit(record = {}) {
showModal({
type: 'edit',
title: '编辑部门',
})
formRecord.value = record
formData.value = cloneDeep(record)
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...values,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.common.create(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.common.update(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
cancelText.value = '取消'
hideLoading()
}
defineExpose({
handleCreate,
handleEdit,
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,293 @@
<template>
<a-modal
:open="modal.open"
:title="modal.title"
:width="640"
:confirm-loading="modal.confirmLoading"
:after-close="onAfterClose"
:cancel-text="cancelText"
:ok-text="okText"
@ok="handleOk"
@cancel="handleCancel">
<a-form
ref="formRef"
:model="formData"
:rules="formRules"
:label-col="{ style: { width: '90px' } }">
<a-card class="mb-8-2">
<a-row :gutter="12">
<a-col :span="12">
<a-form-item
:label="$t('pages.system.user.form.username')"
name="username">
<a-input
:placeholder="$t('pages.system.user.form.username.placeholder')"
v-model:value="formData.username"></a-input>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
:label="$t('pages.system.user.form.password')"
name="password">
<a-input-password
v-model:value="formData.password"
:placeholder="$t('pages.system.user.form.password.placeholder')" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="12">
<a-form-item
:label="$t('pages.system.user.form.name')"
name="name">
<a-input
:placeholder="$t('pages.system.user.form.name.placeholder')"
v-model:value="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
:label="$t('pages.system.user.form.roles')"
name="roles">
<a-select
v-model:value="formData.roles"
mode="multiple"
style="width: 100%"
:placeholder="$t('pages.system.user.form.roles.placeholder')"
:options="roles"
@change="handleChange"></a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="12">
<a-form-item
:label="$t('pages.system.user.form.phone')"
type="tel"
name="phone">
<a-input
:placeholder="$t('pages.system.user.form.phone.placeholder')"
type="tel"
v-model:value="formData.phone"></a-input>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
:label="$t('pages.system.user.form.email')"
type="email"
name="email">
<a-input
:placeholder="$t('pages.system.user.form.email.placeholder')"
type="email"
v-model:value="formData.email"></a-input>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="24">
<a-form-item
:label="$t('pages.system.user.form.remark')"
name="remark">
<a-textarea
:placeholder="$t('pages.system.user.form.remark.placeholder')"
v-model:value="formData.remark"></a-textarea>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="24">
<a-form-item
:label="$t('pages.system.user.form.status')"
name="status">
<a-radio-group
v-model:value="formData.status"
:options="[
{ label: $t('pages.system.user.form.status.activated'), value: 'activated' },
{ label: $t('pages.system.user.form.status.freezed'), value: 'freezed' },
]"></a-radio-group>
</a-form-item>
</a-col>
</a-row>
</a-card>
</a-form>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
const emit = defineEmits(['ok'])
const { t } = useI18n() // t
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref(t('button.cancel'))
const okText = ref(t('button.confirm'))
const rolesValue = ref([])
const roles = ref([])
formRules.value = {
name: { required: true, message: t('pages.system.user.form.username.placeholder') },
username: { required: true, message: t('pages.system.user.form.code.placeholder') },
status: { required: true, message: t('pages.system.user.form.status') },
roles: [{ required: true, message: t('pages.system.user.form.roles.placeholder'), trigger: 'change' }],
}
/**
* 请求角色
*/
getRole()
/**
* select 选择框
*/
const handleChange = (value) => {
rolesValue.value = value
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: t('pages.system.user.add'),
})
}
async function getRole() {
const { success, data } = await apis.role.getRoleList().catch(() => {
throw new Error()
})
if (!success) {
return message.error('当前角色信息错误')
}
let roleArr = []
if (data.length) {
data.forEach((item) => {
roleArr.push({
label: item.name,
value: item.id,
})
})
}
roles.value = roleArr
}
/**
* 编辑
*/
async function handleEdit(record = {}) {
showModal({
type: 'edit',
title: t('pages.system.user.edit'),
})
const { data, success } = await apis.users.getUsers(record.id).catch()
if (!success) {
hideModal()
return
}
let roles = []
if (data.roles) {
roles = formatArr(data.roles, 'edit')
}
data.roles = roles
formRecord.value = data
formData.value = cloneDeep(data)
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...values,
roles: formatArr(rolesValue.value),
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.users.createUsers(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.users.updateUsers(formData.value.id, params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.success) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 对权限组 过数据格式
*/
function formatArr(data, type = '') {
const rolesArr = []
data.forEach((item) => {
roles.value.forEach((r) => {
if (type === 'edit') {
if (item.role_id === r.value) {
rolesArr.push({
value: item.role_id,
label: r.label,
})
return
}
} else if (r.value === item) {
rolesArr.push({
role_id: item,
role_name: r.label,
})
return
}
})
})
return rolesArr
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate,
handleEdit,
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,234 @@
<template>
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form
:label-col="{ style: { width: '100px' } }"
:model="searchFormData"
layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item
:label="$t('pages.system.user.form.username')"
name="username">
<a-input
:placeholder="$t('pages.system.user.form.username.placeholder')"
v-model:value="searchFormData.username"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item name="name">
<template #label>
{{ $t('pages.system.user.form.name') }}
<a-tooltip :title="$t('pages.system.user.form.name')">
<question-circle-outlined class="ml-4-1 color-placeholder" />
</a-tooltip>
</template>
<a-input
:placeholder="$t('pages.system.user.form.name.placeholder')"
v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col
class="align-right"
v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button
ghost
type="primary"
@click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-row
:gutter="8"
:wrap="false">
<a-col flex="auto">
<a-card type="flex">
<x-action-bar class="mb-8-2">
<a-button
type="primary"
@click="$refs.editDialogRef.handleCreate()">
<template #icon>
<plus-outlined></plus-outlined>
</template>
{{ $t('pages.system.user.add') }}
</a-button>
</x-action-bar>
<a-table
:columns="columns"
:data-source="listData"
:loading="loading"
:pagination="paginationState"
:scroll="{ x: 1000 }"
@change="onTableChange">
<template #bodyCell="{ column, record }">
<template v-if="'statusType' === column.key">
<!--状态-->
<a-tag
v-if="statusUserTypeEnum.is('activated', record.status)"
color="processing">
{{ statusUserTypeEnum.getDesc(record.status) }}
</a-tag>
<!--状态-->
<a-tag
v-if="statusUserTypeEnum.is('freezed', record.status)"
color="processing">
{{ statusUserTypeEnum.getDesc(record.status) }}
</a-tag>
</template>
<template v-if="'createAt' === column.key">
{{ formatUtcDateTime(record.created_at) }}
</template>
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<a-tooltip>
<template #title> {{ $t('pages.system.user.edit') }}</template>
<edit-outlined /> </a-tooltip
></x-action-button>
<x-action-button @click="handleDelete(record)">
<a-tooltip>
<template #title>{{ $t('pages.system.delete') }}</template>
<delete-outlined style="color: #ff4d4f" /> </a-tooltip
></x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog
ref="editDialogRef"
@ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { formatUtcDateTime } from '@/utils/util'
import { config } from '@/config'
import { statusUserTypeEnum } from '@/enums/system'
import { usePagination } from '@/hooks'
import EditDialog from './components/EditDialog.vue'
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
import { useI18n } from 'vue-i18n'
defineOptions({
name: 'homeBanner',
})
const { t } = useI18n() // t
const columns = [
{ title: t('pages.system.user.form.username'), dataIndex: 'username', width: 120 },
{ title: t('pages.system.user.form.name'), dataIndex: 'name', key: 'name', width: 100 },
{ title: t('pages.system.user.form.phone'), dataIndex: 'phone', width: 120 },
{ title: t('pages.system.user.form.email'), dataIndex: 'email', width: 100 },
{ title: t('pages.system.user.form.status'), dataIndex: 'status', key: 'statusType', width: 60 },
{ title: t('pages.system.user.form.created_at'), key: 'createAt', fixed: 'right', width: 120 },
{ title: t('button.action'), key: 'action', fixed: 'right', width: 100 },
]
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } =
usePagination()
const editDialogRef = ref()
getPageList()
/**
* 获取用户列表
* @returns {Promise<void>}
*/
async function getPageList() {
try {
showLoading()
const { pageSize, current } = paginationState
const { success, data, total } = await apis.users
.getUsersList({
pageSize,
page: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
hideLoading()
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
hideLoading()
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
;(async () => {
try {
const { success } = await apis.users.delUsers(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
message.success(t('component.message.success.delete'))
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -2,17 +2,10 @@
<div> <div>
<a-tabs> <a-tabs>
<!-- 账号登录 --> <!-- 账号登录 -->
<a-tab-pane <a-tab-pane key="account" :tab="$t('pages.login.accountLogin.tab')">
key="account" <a-form :model="formData" :rules="formRules" ref="formRef">
:tab="$t('pages.login.accountLogin.tab')">
<a-form
:model="formData"
:rules="formRules"
ref="formRef">
<a-form-item name="username"> <a-form-item name="username">
<a-input <a-input :placeholder="$t('pages.login.username.placeholder')" v-model:value="formData.username"
:placeholder="$t('pages.login.username.placeholder')"
v-model:value="formData.username"
size="large"> size="large">
<template #prefix> <template #prefix>
<user-outlined></user-outlined> <user-outlined></user-outlined>
@ -20,12 +13,8 @@
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item name="password"> <a-form-item name="password">
<a-input <a-input v-model:value="formData.password" size="large" type="password"
v-model:value="formData.password" :placeholder="$t('pages.login.password.placeholder')" @pressEnter="handleLogin">
size="large"
type="password"
:placeholder="$t('pages.login.password.placeholder')"
@pressEnter="handleLogin">
<template #prefix> <template #prefix>
<lock-outlined></lock-outlined> <lock-outlined></lock-outlined>
</template> </template>
@ -33,32 +22,19 @@
</a-form-item> </a-form-item>
<a-form-item name="captcha_code"> <a-form-item name="captcha_code">
<a-space> <a-space>
<a-input <a-input v-model:value="formData.captcha_code" size="large" type="text"
v-model:value="formData.captcha_code" :placeholder="$t('pages.login.captcha.placeholder')" @pressEnter="handleLogin">
size="large"
type="text"
:placeholder="$t('pages.login.captcha.placeholder')"
@pressEnter="handleLogin">
<template #prefix> <template #prefix>
<safety-outlined /> <safety-outlined />
</template> </template>
</a-input> </a-input>
<a-image <a-image @click="getCaptcha" :preview="false" :width="140" :height="42"
@click="getCaptcha"
:preview="false"
:width="140"
:height="42"
:src="captcha_img" /> :src="captcha_img" />
</a-space> </a-space>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button <a-button type="primary" size="large" block :loading="loading" @click="handleLogin">{{
type="primary" $t('pages.login.submit') }}
size="large"
block
:loading="loading"
@click="handleLogin"
>{{ $t('pages.login.submit') }}
</a-button> </a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -127,8 +103,7 @@ async function handleLogin() {
if (values.password === 'abc-123') values.password = md5(values.password) if (values.password === 'abc-123') values.password = md5(values.password)
loading.value = true loading.value = true
const { success } = await userStore const { success } = await userStore.login({
.login({
...values, ...values,
}) })
.catch(() => { .catch(() => {
@ -170,17 +145,18 @@ function getFirstValidRoute() {
* 去首页 * 去首页
*/ */
function goIndex() { function goIndex() {
if (redirect.value) { router.push('/home')
location.href = redirect.value // if (redirect.value) {
} else { // location.href = redirect.value
const indexRoute = getFirstValidRoute() // } else {
if (!indexRoute) return // const indexRoute = getFirstValidRoute()
router.push(indexRoute) // if (!indexRoute) return
} // router.push(indexRoute)
notification.success({ // }
message: t('welcome'), // notification.success({
description: `${timeFix()}${t('home')}`, // message: t('welcome'),
}) // description: `${timeFix()}${t('home')}`,
// })
} }
</script> </script>

View File

@ -7,7 +7,7 @@ import useProgressPlugin from './config/useProgressPlugin'
import useVuePlugin from './config/useVuePlugin' import useVuePlugin from './config/useVuePlugin'
import useVisualizerPlugin from './config/useVisualizerPlugin' import useVisualizerPlugin from './config/useVisualizerPlugin'
import useServer from './config/useServer' import useServer from './config/useServer'
import useEslintPlugin from './config/useEslintPlugin' // import useEslintPlugin from './config/useEslintPlugin'
export default ({ mode }) => { export default ({ mode }) => {
const env = loadEnv(mode, process.cwd(), '') const env = loadEnv(mode, process.cwd(), '')
@ -53,7 +53,7 @@ export default ({ mode }) => {
version: pkg.version, version: pkg.version,
}), }),
}, },
plugins: [useVuePlugin(), useProgressPlugin(), useCompressPlugin(), useVisualizerPlugin(), useEslintPlugin()], plugins: [useVuePlugin(), useProgressPlugin(), useCompressPlugin(), useVisualizerPlugin()],
server: useServer(), server: useServer(),
resolve: { resolve: {
alias: { alias: {

View File

@ -801,7 +801,7 @@ combined-stream@^1.0.8:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
commander@11.0.0: commander@*, commander@11.0.0:
version "11.0.0" version "11.0.0"
resolved "https://registry.npmmirror.com/commander/-/commander-11.0.0.tgz" resolved "https://registry.npmmirror.com/commander/-/commander-11.0.0.tgz"
integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==
@ -1774,6 +1774,13 @@ minisearch@^6.1.0:
resolved "https://registry.npmjs.org/minisearch/-/minisearch-6.1.0.tgz" resolved "https://registry.npmjs.org/minisearch/-/minisearch-6.1.0.tgz"
integrity sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg== integrity sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==
mockjs@^1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/mockjs/-/mockjs-1.1.0.tgz"
integrity sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==
dependencies:
commander "*"
ms@^2.1.1: ms@^2.1.1:
version "2.1.3" version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"