Compare commits

...
2 Commits
Author SHA1 Message Date
bestlee 2908a10ee5 Merge branch 'master' of https://git.djxinxi.net/bestlee/MOC
# Conflicts:
#	src/service/api/role.ts
#	src/views/my/initiateChange/index.vue
#	src/views/systemManage/role/index.vue
#	src/views/systemManage/user/index.vue
2026-08-26 10:21:08 +08:00
bestlee 4433cf521b 角色编辑接口完成 2026-08-26 10:14:55 +08:00
5 changed files with 2603 additions and 2633 deletions
+19 -24
View File
@@ -1,24 +1,19 @@
import { request } from '../request';
/** 获取角色列表
*
*/
export function roleListApi() {
return request({ url: '/api/role' });
}
/**
* 登录
*
* @param username User name
* @param password Password
*/
export function fetchLogin(username: string, password: string) {
return request({
url: '/api/auth/login',
method: 'post',
data: {
username,
password
}
});
}
import { request } from '../request';
/** 获取角色列表
*
*/
export function roleListApi() {
return request({ url: '/api/role' });
}
/**
* 编辑角色
*
*/
export function editRoleApi(params: { id: number, intro: string, name: string, perms: string[] }) {
return request({
url: '/api/role',
method: 'post',
data: params
});
}
+19
View File
@@ -0,0 +1,19 @@
<script setup lang="ts">
const props = defineProps<{ status: any }>();
const BIG_STAGES = ["申请审批", "实施与关闭", "变更关闭"];
const bigStageOf = (s: any) => (s === "审批中" ? 0 : s === "已关闭" ? 2 : 1);
const cur = bigStageOf(props.status);
const pct = ((cur + 1) / BIG_STAGES.length) * 100;
const size = 34, stroke = 4, r = (size - stroke) / 2, c = 2 * Math.PI * r;
const title = `阶段进度 ${cur + 1}/${BIG_STAGES.length}${BIG_STAGES.slice(0, cur + 1).join(" → ")}${cur < BIG_STAGES.length - 1 ? " → …" : ""}`;
</script>
<template>
<span class="relative inline-flex shrink-0 items-center justify-center" :style="{ width: size + 'px', height: size + 'px' }" :title="title">
<svg :width="size" :height="size" class="-rotate-90">
<circle :cx="size / 2" :cy="size / 2" :r="r" stroke="#E2E8F0" :stroke-width="stroke" fill="none" />
<circle :cx="size / 2" :cy="size / 2" :r="r" stroke="#2080f0" :stroke-width="stroke" fill="none"
:stroke-dasharray="c" :stroke-dashoffset="c * (1 - pct / 100)" stroke-linecap="round" />
</svg>
<span class="absolute inset-0 flex items-center justify-center text-[9px] font-bold text-[#2080f0] dark:text-white">{{ cur + 1 }}/3</span>
</span>
</template>
File diff suppressed because it is too large Load Diff
+212 -256
View File
@@ -1,256 +1,212 @@
<script setup lang="ts">
import { ref, computed, h, onMounted } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouter } from 'vue-router';
import { NButton, NPopconfirm } from 'naive-ui';
import { roleListApi } from '@/service/api/role';
const router = useRouter();
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const loading = ref<boolean>(false);
const btnLoading = ref<boolean>(false);
const currentType = ref<string>('add');
const formRef = ref<any>(null);
const currentRole = ref<{ id: number, name: string }>({
id: 0,
name: '',
});
const rules = ref({
name: { required: true, message: '角色名称不能为空', trigger: ['blur'] }
})
const roleModal = ref<boolean>(false);
const permissionModal = ref<boolean>(false);
const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({
page: 1,
pageSize: 20,
pageCount: 0,
})
const columns = ref([
{
title: '序号',
key: 'index',
width: 5,
align: 'center',
render: (_row: any, index: number) => {
return h('span', {}, { default: () => (pagination.value.page - 1) * pagination.value.pageSize + index + 1 })
}
},
{
title: '角色名称',
key: 'name',
width: 44,
align: 'center',
},
{
title: '操作',
key: 'action',
width: 8,
align: 'center',
render: (row: any) => {
return h('div', { class: 'flex items-center justify-center gap-3' }, {
default: () => [
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleSetPermission(row)
}, {
// 渲染按钮文本
default: () => '设置权限'
}),
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleEdit(row)
}, {
// 渲染按钮文本
default: () => '编辑'
}),
h(NPopconfirm, {
// 组件 Props
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
handleDelete(row)
},
// 可选:自定义确认/取消按钮的样式
positiveButtonProps: { size: 'tiny', type: 'primary' },
negativeButtonProps: { size: 'tiny' }
}, {
// 插槽 (Slots)
trigger: () => h(NButton,
{
text: true,
size: 'tiny',
type: 'error',
},
{
default: () => '删除' // 按钮文字
}
),
default: () => '确定要删除吗?' // 弹出框的提示内容
}),
]
})
}
},
])
const list = ref<{ id: number, name: string, intro: string, perms: string[] }[]>([])
// 获取列表
const getList = async () => {
loading.value = true;
try {
const res = await roleListApi();
list.value = res.data;
} finally {
loading.value = false;
}
}
// 打开添加角色弹框
const addRole = () => {
currentType.value = 'add';
currentRole.value = {
id: 0,
name: '',
};
roleModal.value = true;
}
// 设置权限
const handleSetPermission = (row: any) => {
currentRole.value = {
id: row.id,
name: row.name,
};
permissionModal.value = true;
}
// 编辑角色
const handleEdit = (row: any) => {
currentType.value = 'edit';
currentRole.value = {
id: row.id,
name: row.name,
};
roleModal.value = true;
}
// 删除角色
const handleDelete = (row: any) => {
loading.value = true;
setTimeout(() => {
loading.value = false;
list.value = list.value.filter((item: any) => item.id !== row.id);
window.$message?.success('删除成功')
}, 1000)
}
// 保存角色
const saveRole = async (e: MouseEvent) => {
e.preventDefault()
formRef.value?.validate((errors: any) => {
if (!errors) {
btnLoading.value = true;
setTimeout(() => {
// 新增
if(currentType.value === 'add'){
list.value.push({id:list.value.length+1,name:currentRole.value?.name})
}
// 编辑
if(currentType.value === 'edit'){
const target = list.value.find((item:any) => item.id === currentRole.value.id);
if (target) {
target.name = currentRole.value?.name;
}
}
window.$message?.success('操作成功')
btnLoading.value = false;
roleModal.value = false;
}, 1000)
}
})
}
onMounted(() => {
const allRoutes = router.getRoutes();
getList()
})
</script>
<template>
<NSpace vertical :size="16">
<!-- 角色管理 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:12 m:12">
<div class="h-100% flex items-center gap-2">
<Icon icon="lucide:user" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-[16px] font-600">角色管理</p>
</div>
</NGi>
<NGi span="24 s:12 m:12" class="flex items-center justify-end gap-12px">
<NButton type="primary" size="small" @click="addRole">
<Icon icon="ic:round-plus" class="size-16px" />
添加角色
</NButton>
</NGi>
</NGrid>
</div>
<div class="scroll">
<NDataTable
size="small"
:loading="loading"
:single-line="false"
:columns="columns"
:data="list"
:pagination="pagination"
max-height="calc(100vh - 290px)"
/>
</div>
<!-- 新增或编辑弹框 -->
<NModal
v-model:show="roleModal"
preset="card"
:title="currentType === 'add' ? '新增角色' : '编辑角色'"
:auto-focus="false"
:style="{ width: '450px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<template #default>
<NForm ref="formRef" :model="currentRole" :rules="rules" label-placement="left" label-width="auto">
<NFormItem label="角色名称" path="name">
<NInput
v-model:value="currentRole.name"
placeholder="请输入角色名称"
/>
</NFormItem>
</NForm>
</template>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton @click="roleModal = false">取消</NButton>
<NButton type="primary" :loading="btnLoading" @click="saveRole">确定</NButton>
</div>
</template>
</NModal>
<!-- 设置权限抽屉 -->
<NDrawer v-model:show="permissionModal" :width="502" placement="right">
<NDrawerContent :title="`设置权限-${currentRole.name}`" closable>
</NDrawerContent>
</NDrawer>
</NCard>
</NSpace>
</template>
<style scoped lang="scss">
.scroll {
height: calc(100vh - 205px);
overflow-y: auto;
}
</style>
<script setup lang="ts">
import { ref, computed, h, onMounted } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouter } from 'vue-router';
import { NButton } from 'naive-ui';
import { roleListApi, editRoleApi } from '@/service/api/role';
const router = useRouter();
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const loading = ref<boolean>(false);
const btnLoading = ref<boolean>(false);
const formRef = ref<any>(null);
const currentRole = ref<{ id: number, intro: string, name: string, perms: string[] }>({
id: 0,
intro: '',
name: '',
perms: [],
});
const rules = ref({
name: { required: true, message: '角色名称不能为空', trigger: ['blur'] }
})
const roleModal = ref<boolean>(false);
const permissionModal = ref<boolean>(false);
const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({
page: 1,
pageSize: 20,
pageCount: 0,
})
const columns = ref([
{
title: '序号',
key: 'index',
width: 5,
align: 'center',
render: (_row: any, index: number) => {
return h('span', {}, { default: () => (pagination.value.page - 1) * pagination.value.pageSize + index + 1 })
}
},
{
title: '角色名称',
key: 'name',
width: 44,
align: 'center',
},
{
title: '操作',
key: 'action',
width: 8,
align: 'center',
render: (row: any) => {
return h('div', { class: 'flex items-center justify-center gap-3' }, {
default: () => [
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleSetPermission(row)
}, {
// 渲染按钮文本
default: () => '设置权限'
}),
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleEdit(row)
}, {
// 渲染按钮文本
default: () => '编辑'
}),
]
})
}
},
])
const list = ref<{ id: number, name: string, intro: string, perms: string[] }[]>([])
// 获取列表
const getList = async () => {
loading.value = true;
try {
const res = await roleListApi();
list.value = res.data;
} finally {
loading.value = false;
}
}
// 设置权限
const handleSetPermission = (row: any) => {
currentRole.value = JSON.parse(JSON.stringify(row));
permissionModal.value = true;
}
// 编辑角色
const handleEdit = (row: any) => {
currentRole.value = JSON.parse(JSON.stringify(row));
roleModal.value = true;
}
// 保存角色
const saveRole = (e: MouseEvent) => {
e.preventDefault()
formRef.value?.validate(async (errors: any) => {
if (!errors) {
btnLoading.value = true;
try {
await editRoleApi(currentRole.value);
const target = list.value.find((item:any) => item.id === currentRole.value.id);
if (target) {
target.name = currentRole.value.name;
}
window.$message?.success('操作成功')
btnLoading.value = false;
roleModal.value = false;
} finally {
btnLoading.value = false;
}
}
})
}
// 保存权限
const savePermission = async () => {
btnLoading.value = true;
setTimeout(() => {
window.$message?.success('操作成功')
btnLoading.value = false;
permissionModal.value = false;
}, 1000)
}
onMounted(() => {
const allRoutes = router.getRoutes();
getList()
})
</script>
<template>
<NSpace vertical :size="16">
<!-- 角色管理 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24">
<div class="h-100% flex items-center gap-2">
<Icon icon="lucide:user" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-[16px] font-600">角色管理</p>
</div>
</NGi>
</NGrid>
</div>
<div class="scroll">
<NDataTable
size="small"
:loading="loading"
:single-line="false"
:columns="columns"
:data="list"
:pagination="pagination"
max-height="calc(100vh - 290px)"
/>
</div>
<!-- 新增或编辑弹框 -->
<NModal
v-model:show="roleModal"
preset="card"
title="编辑角色"
:auto-focus="false"
:style="{ width: '450px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<template #default>
<NForm ref="formRef" :model="currentRole" :rules="rules" label-placement="left" label-width="auto">
<NFormItem label="角色名称" path="name">
<NInput
v-model:value="currentRole.name"
placeholder="请输入角色名称"
/>
</NFormItem>
</NForm>
</template>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton @click="roleModal = false">取消</NButton>
<NButton type="primary" :loading="btnLoading" @click="saveRole">确定</NButton>
</div>
</template>
</NModal>
<!-- 设置权限抽屉 -->
<NDrawer v-model:show="permissionModal" :width="502" placement="right">
<NDrawerContent :title="`设置权限-${currentRole.name}`" closable>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton @click="permissionModal = false">取消</NButton>
<NButton type="primary" :loading="btnLoading" @click="savePermission">保存</NButton>
</div>
</template>
</NDrawerContent>
</NDrawer>
</NCard>
</NSpace>
</template>
<style scoped lang="scss">
.scroll {
height: calc(100vh - 205px);
overflow-y: auto;
}
</style>
File diff suppressed because it is too large Load Diff