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'; import { request } from '../request';
/** 获取角色列表 /** 获取角色列表
* *
*/ */
export function roleListApi() { export function roleListApi() {
return request({ url: '/api/role' }); return request({ url: '/api/role' });
} }
/** /**
* 登录 * 编辑角色
* *
* @param username User name */
* @param password Password export function editRoleApi(params: { id: number, intro: string, name: string, perms: string[] }) {
*/ return request({
export function fetchLogin(username: string, password: string) { url: '/api/role',
return request({ method: 'post',
url: '/api/auth/login', data: params
method: 'post', });
data: { }
username,
password
}
});
}
+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"> <script setup lang="ts">
import { ref, computed, h, onMounted } from 'vue'; import { ref, computed, h, onMounted } from 'vue';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { NButton, NPopconfirm } from 'naive-ui'; import { NButton } from 'naive-ui';
import { roleListApi } from '@/service/api/role'; import { roleListApi, editRoleApi } from '@/service/api/role';
const router = useRouter(); const router = useRouter();
const appStore = useAppStore(); const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16)); const gap = computed(() => (appStore.isMobile ? 0 : 16));
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const btnLoading = ref<boolean>(false); const btnLoading = ref<boolean>(false);
const currentType = ref<string>('add'); const formRef = ref<any>(null);
const formRef = ref<any>(null); const currentRole = ref<{ id: number, intro: string, name: string, perms: string[] }>({
const currentRole = ref<{ id: number, name: string }>({ id: 0,
id: 0, intro: '',
name: '', name: '',
}); perms: [],
const rules = ref({ });
name: { required: true, message: '角色名称不能为空', trigger: ['blur'] } const rules = ref({
}) name: { required: true, message: '角色名称不能为空', trigger: ['blur'] }
const roleModal = ref<boolean>(false); })
const permissionModal = ref<boolean>(false); const roleModal = ref<boolean>(false);
const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({ const permissionModal = ref<boolean>(false);
page: 1, const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({
pageSize: 20, page: 1,
pageCount: 0, pageSize: 20,
}) pageCount: 0,
const columns = ref([ })
{ const columns = ref([
title: '序号', {
key: 'index', title: '序号',
width: 5, key: 'index',
align: 'center', width: 5,
render: (_row: any, index: number) => { align: 'center',
return h('span', {}, { default: () => (pagination.value.page - 1) * pagination.value.pageSize + index + 1 }) render: (_row: any, index: number) => {
} return h('span', {}, { default: () => (pagination.value.page - 1) * pagination.value.pageSize + index + 1 })
}, }
{ },
title: '角色名称', {
key: 'name', title: '角色名称',
width: 44, key: 'name',
align: 'center', width: 44,
}, align: 'center',
{ },
title: '操作', {
key: 'action', title: '操作',
width: 8, key: 'action',
align: 'center', width: 8,
render: (row: any) => { align: 'center',
return h('div', { class: 'flex items-center justify-center gap-3' }, { render: (row: any) => {
default: () => [ return h('div', { class: 'flex items-center justify-center gap-3' }, {
h(NButton, { default: () => [
text: true, h(NButton, {
size: 'tiny', text: true,
type: 'primary', size: 'tiny',
onClick: () => handleSetPermission(row) type: 'primary',
}, { onClick: () => handleSetPermission(row)
// 渲染按钮文本 }, {
default: () => '设置权限' // 渲染按钮文本
}), default: () => '设置权限'
h(NButton, { }),
text: true, h(NButton, {
size: 'tiny', text: true,
type: 'primary', size: 'tiny',
onClick: () => handleEdit(row) type: 'primary',
}, { onClick: () => handleEdit(row)
// 渲染按钮文本 }, {
default: () => '编辑' // 渲染按钮文本
}), default: () => '编辑'
h(NPopconfirm, { }),
// 组件 Props ]
positiveText: '确定', })
negativeText: '取消', }
onPositiveClick: () => { },
handleDelete(row) ])
}, const list = ref<{ id: number, name: string, intro: string, perms: string[] }[]>([])
// 可选:自定义确认/取消按钮的样式
positiveButtonProps: { size: 'tiny', type: 'primary' }, // 获取列表
negativeButtonProps: { size: 'tiny' } const getList = async () => {
}, { loading.value = true;
// 插槽 (Slots) try {
trigger: () => h(NButton, const res = await roleListApi();
{ list.value = res.data;
text: true, } finally {
size: 'tiny', loading.value = false;
type: 'error', }
}, }
{
default: () => '删除' // 按钮文字 // 设置权限
} const handleSetPermission = (row: any) => {
), currentRole.value = JSON.parse(JSON.stringify(row));
default: () => '确定要删除吗?' // 弹出框的提示内容 permissionModal.value = true;
}), }
] // 编辑角色
}) const handleEdit = (row: any) => {
} currentRole.value = JSON.parse(JSON.stringify(row));
}, roleModal.value = true;
]) }
const list = ref<{ id: number, name: string, intro: string, perms: string[] }[]>([]) // 保存角色
const saveRole = (e: MouseEvent) => {
// 获取列表 e.preventDefault()
const getList = async () => { formRef.value?.validate(async (errors: any) => {
loading.value = true; if (!errors) {
try { btnLoading.value = true;
const res = await roleListApi(); try {
list.value = res.data; await editRoleApi(currentRole.value);
} finally { const target = list.value.find((item:any) => item.id === currentRole.value.id);
loading.value = false; if (target) {
} target.name = currentRole.value.name;
} }
window.$message?.success('操作成功')
// 打开添加角色弹框 btnLoading.value = false;
const addRole = () => { roleModal.value = false;
currentType.value = 'add'; } finally {
currentRole.value = { btnLoading.value = false;
id: 0, }
name: '', }
}; })
roleModal.value = true; }
} // 保存权限
// 设置权限 const savePermission = async () => {
const handleSetPermission = (row: any) => { btnLoading.value = true;
currentRole.value = { setTimeout(() => {
id: row.id, window.$message?.success('操作成功')
name: row.name, btnLoading.value = false;
}; permissionModal.value = false;
permissionModal.value = true; }, 1000)
} }
// 编辑角色
const handleEdit = (row: any) => { onMounted(() => {
currentType.value = 'edit'; const allRoutes = router.getRoutes();
currentRole.value = { getList()
id: row.id, })
name: row.name, </script>
};
roleModal.value = true; <template>
} <NSpace vertical :size="16">
// 删除角色 <!-- 角色管理 -->
const handleDelete = (row: any) => { <NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
loading.value = true; <div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
setTimeout(() => { <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
loading.value = false; <NGi span="24 s:24 m:24">
list.value = list.value.filter((item: any) => item.id !== row.id); <div class="h-100% flex items-center gap-2">
window.$message?.success('删除成功') <Icon icon="lucide:user" class="size-16px" :style="{ color: themeStore.themeColor }" />
}, 1000) <p class="text-[16px] font-600">角色管理</p>
} </div>
// 保存角色 </NGi>
const saveRole = async (e: MouseEvent) => { </NGrid>
e.preventDefault() </div>
formRef.value?.validate((errors: any) => { <div class="scroll">
if (!errors) { <NDataTable
btnLoading.value = true; size="small"
setTimeout(() => { :loading="loading"
// 新增 :single-line="false"
if(currentType.value === 'add'){ :columns="columns"
list.value.push({id:list.value.length+1,name:currentRole.value?.name}) :data="list"
} :pagination="pagination"
// 编辑 max-height="calc(100vh - 290px)"
if(currentType.value === 'edit'){ />
const target = list.value.find((item:any) => item.id === currentRole.value.id); </div>
if (target) { <!-- 新增或编辑弹框 -->
target.name = currentRole.value?.name; <NModal
} v-model:show="roleModal"
} preset="card"
window.$message?.success('操作成功') title="编辑角色"
btnLoading.value = false; :auto-focus="false"
roleModal.value = false; :style="{ width: '450px', height: 'auto' }"
}, 1000) :segmented="{ content: true, footer: true }"
} >
}) <template #default>
} <NForm ref="formRef" :model="currentRole" :rules="rules" label-placement="left" label-width="auto">
<NFormItem label="角色名称" path="name">
onMounted(() => { <NInput
const allRoutes = router.getRoutes(); v-model:value="currentRole.name"
getList() placeholder="请输入角色名称"
}) />
</script> </NFormItem>
</NForm>
<template> </template>
<NSpace vertical :size="16"> <template #footer>
<!-- 角色管理 --> <div class="flex justify-end gap-[10px]">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }"> <NButton @click="roleModal = false">取消</NButton>
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }"> <NButton type="primary" :loading="btnLoading" @click="saveRole">确定</NButton>
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive> </div>
<NGi span="24 s:12 m:12"> </template>
<div class="h-100% flex items-center gap-2"> </NModal>
<Icon icon="lucide:user" class="size-16px" :style="{ color: themeStore.themeColor }" /> <!-- 设置权限抽屉 -->
<p class="text-[16px] font-600">角色管理</p> <NDrawer v-model:show="permissionModal" :width="502" placement="right">
</div> <NDrawerContent :title="`设置权限-${currentRole.name}`" closable>
</NGi> <template #footer>
<NGi span="24 s:12 m:12" class="flex items-center justify-end gap-12px"> <div class="flex justify-end gap-[10px]">
<NButton type="primary" size="small" @click="addRole"> <NButton @click="permissionModal = false">取消</NButton>
<Icon icon="ic:round-plus" class="size-16px" /> <NButton type="primary" :loading="btnLoading" @click="savePermission">保存</NButton>
添加角色 </div>
</NButton> </template>
</NGi> </NDrawerContent>
</NGrid> </NDrawer>
</div> </NCard>
<div class="scroll"> </NSpace>
<NDataTable </template>
size="small"
:loading="loading" <style scoped lang="scss">
:single-line="false" .scroll {
:columns="columns" height: calc(100vh - 205px);
:data="list" overflow-y: auto;
:pagination="pagination" }
max-height="calc(100vh - 290px)" </style>
/>
</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>
File diff suppressed because it is too large Load Diff