feat(projects): make branch main tiny & modify request retry times to 0

This commit is contained in:
青菜白玉汤
2024-07-12 10:55:23 +08:00
committed by Soybean
parent cf9fc7d31f
commit 793b16e9ba
40 changed files with 19 additions and 3544 deletions

View File

@@ -1,85 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import pkg from '~/package.json';
const appStore = useAppStore();
const column = computed(() => (appStore.isMobile ? 1 : 2));
interface PkgJson {
name: string;
version: string;
dependencies: PkgVersionInfo[];
devDependencies: PkgVersionInfo[];
}
interface PkgVersionInfo {
name: string;
version: string;
}
const { name, version, dependencies, devDependencies } = pkg;
function transformVersionData(tuple: [string, string]): PkgVersionInfo {
const [$name, $version] = tuple;
return {
name: $name,
version: $version
};
}
const pkgJson: PkgJson = {
name,
version,
dependencies: Object.entries(dependencies).map(item => transformVersionData(item)),
devDependencies: Object.entries(devDependencies).map(item => transformVersionData(item))
};
const latestBuildTime = BUILD_TIME;
</script>
<template>
<NSpace vertical :size="16">
<NCard :title="$t('page.about.title')" :bordered="false" size="small" segmented class="card-wrapper">
<p>{{ $t('page.about.introduction') }}</p>
</NCard>
<NCard :title="$t('page.about.projectInfo.title')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions label-placement="left" bordered size="small" :column="column">
<NDescriptionsItem :label="$t('page.about.projectInfo.version')">
<NTag type="primary">{{ pkgJson.version }}</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.about.projectInfo.latestBuildTime')">
<NTag type="primary">{{ latestBuildTime }}</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.about.projectInfo.githubLink')">
<a class="text-primary" :href="pkg.homepage" target="_blank" rel="noopener noreferrer">
{{ $t('page.about.projectInfo.githubLink') }}
</a>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.about.projectInfo.previewLink')">
<a class="text-primary" :href="pkg.website" target="_blank" rel="noopener noreferrer">
{{ $t('page.about.projectInfo.previewLink') }}
</a>
</NDescriptionsItem>
</NDescriptions>
</NCard>
<NCard :title="$t('page.about.prdDep')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions label-placement="left" bordered size="small" :column="column">
<NDescriptionsItem v-for="item in pkgJson.dependencies" :key="item.name" :label="item.name">
{{ item.version }}
</NDescriptionsItem>
</NDescriptions>
</NCard>
<NCard :title="$t('page.about.devDep')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions label-placement="left" bordered size="small" :column="column">
<NDescriptionsItem v-for="item in pkgJson.devDependencies" :key="item.name" :label="item.name">
{{ item.version }}
</NDescriptionsItem>
</NDescriptions>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,24 +0,0 @@
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales';
const route = useRoute();
const { routerPushByKey } = useRouterPush();
const routeQuery = computed(() => JSON.stringify(route.query));
</script>
<template>
<div>
<LookForward>
<div>
<NButton @click="routerPushByKey('function_tab')">{{ $t('page.function.multiTab.backTab') }}</NButton>
<div class="py-24px">{{ $t('page.function.multiTab.routeParam') }}: {{ routeQuery }}</div>
</div>
</LookForward>
</div>
</template>
<style scoped></style>

View File

@@ -1,63 +0,0 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { fetchCustomBackendError } from '@/service/api';
async function logout() {
await fetchCustomBackendError('8888', $t('request.logoutMsg'));
}
async function logoutWithModal() {
await fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'));
}
async function refreshToken() {
await fetchCustomBackendError('9999', $t('request.tokenExpired'));
}
async function handleRepeatedMessageError() {
await Promise.all([
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2'))
]);
}
async function handleRepeatedModalError() {
await Promise.all([
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'))
]);
}
</script>
<template>
<NSpace vertical :size="16">
<NCard :title="$t('request.logout')" :bordered="false" size="small" segmented class="card-wrapper">
<NButton @click="logout">{{ $t('common.trigger') }}</NButton>
</NCard>
<NCard :title="$t('request.logoutWithModal')" :bordered="false" size="small" segmented class="card-wrapper">
<NButton @click="logoutWithModal">{{ $t('common.trigger') }}</NButton>
</NCard>
<NCard :title="$t('request.refreshToken')" :bordered="false" size="small" segmented class="card-wrapper">
<NButton @click="refreshToken">{{ $t('common.trigger') }}</NButton>
</NCard>
<NCard
:title="$t('page.function.request.repeatedErrorOccurOnce')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NButton @click="handleRepeatedMessageError">{{ $t('page.function.request.repeatedError') }}(Message)</NButton>
<NButton class="ml-12px" @click="handleRepeatedModalError">
{{ $t('page.function.request.repeatedError') }}(Modal)
</NButton>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,71 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales';
import { useTabStore } from '@/store/modules/tab';
const tabStore = useTabStore();
const { routerPushByKey } = useRouterPush();
const tabLabel = ref('');
function changeTabLabel() {
tabStore.setTabLabel(tabLabel.value);
}
function resetTabLabel() {
tabStore.resetTabLabel();
}
</script>
<template>
<NSpace vertical :size="16">
<NCard
:title="$t('page.function.tab.tabOperate.title')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NDivider title-placement="left">{{ $t('page.function.tab.tabOperate.addTab') }}</NDivider>
<NButton @click="routerPushByKey('about')">{{ $t('page.function.tab.tabOperate.addTabDesc') }}</NButton>
<NDivider title-placement="left">{{ $t('page.function.tab.tabOperate.closeTab') }}</NDivider>
<NSpace>
<NButton @click="tabStore.removeActiveTab">
{{ $t('page.function.tab.tabOperate.closeCurrentTab') }}
</NButton>
<NButton @click="tabStore.removeTabByRouteName('about')">
{{ $t('page.function.tab.tabOperate.closeAboutTab') }}
</NButton>
</NSpace>
<NDivider title-placement="left">{{ $t('page.function.tab.tabOperate.addMultiTab') }}</NDivider>
<NSpace>
<NButton @click="routerPushByKey('function_multi-tab')">
{{ $t('page.function.tab.tabOperate.addMultiTabDesc1') }}
</NButton>
<NButton @click="routerPushByKey('function_multi-tab', { query: { a: '1' } })">
{{ $t('page.function.tab.tabOperate.addMultiTabDesc2') }}
</NButton>
</NSpace>
</NCard>
<NCard
:title="$t('page.function.tab.tabTitle.title')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NDivider title-placement="left">{{ $t('page.function.tab.tabTitle.changeTitle') }}</NDivider>
<NInputGroup class="max-w-240px">
<NInput v-model:value="tabLabel" />
<NButton type="primary" @click="changeTabLabel">{{ $t('page.function.tab.tabTitle.change') }}</NButton>
</NInputGroup>
<NDivider title-placement="left">{{ $t('page.function.tab.tabTitle.resetTitle') }}</NDivider>
<NButton type="error" ghost class="w-80px" @click="resetTabLabel">
{{ $t('page.function.tab.tabTitle.reset') }}
</NButton>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -1,103 +0,0 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useLoading } from '@sa/hooks';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useAuthStore } from '@/store/modules/auth';
import { useTabStore } from '@/store/modules/tab';
import { useAuth } from '@/hooks/business/auth';
const route = useRoute();
const appStore = useAppStore();
const authStore = useAuthStore();
const tabStore = useTabStore();
const { hasAuth } = useAuth();
const { loading, startLoading, endLoading } = useLoading();
type AccountKey = 'super' | 'admin' | 'user';
interface Account {
key: AccountKey;
label: string;
userName: string;
password: string;
}
const accounts = computed<Account[]>(() => [
{
key: 'super',
label: $t('page.login.pwdLogin.superAdmin'),
userName: 'Super',
password: '123456'
},
{
key: 'admin',
label: $t('page.login.pwdLogin.admin'),
userName: 'Admin',
password: '123456'
},
{
key: 'user',
label: $t('page.login.pwdLogin.user'),
userName: 'User',
password: '123456'
}
]);
const loginAccount = ref<AccountKey>('super');
async function handleToggleAccount(account: Account) {
loginAccount.value = account.key;
startLoading();
await authStore.login(account.userName, account.password, false);
tabStore.initTabStore(route);
endLoading();
appStore.reloadPage();
}
</script>
<template>
<NSpace vertical :size="16">
<NCard :title="$t('route.function_toggle-auth')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions bordered :column="1">
<NDescriptionsItem :label="$t('page.manage.user.userRole')">
<NSpace>
<NTag v-for="role in authStore.userInfo.roles" :key="role">{{ role }}</NTag>
</NSpace>
</NDescriptionsItem>
<NDescriptionsItem ions-item :label="$t('page.function.toggleAuth.toggleAccount')">
<NSpace>
<NButton
v-for="account in accounts"
:key="account.key"
:loading="loading && loginAccount === account.key"
:disabled="loading && loginAccount !== account.key"
@click="handleToggleAccount(account)"
>
{{ account.label }}
</NButton>
</NSpace>
</NDescriptionsItem>
</NDescriptions>
</NCard>
<NCard
:title="$t('page.function.toggleAuth.authHook')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NSpace>
<NButton v-if="hasAuth('B_CODE1')">{{ $t('page.function.toggleAuth.superAdminVisible') }}</NButton>
<NButton v-if="hasAuth('B_CODE2')">{{ $t('page.function.toggleAuth.adminVisible') }}</NButton>
<NButton v-if="hasAuth('B_CODE3')">
{{ $t('page.function.toggleAuth.adminOrUserVisible') }}
</NButton>
</NSpace>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -15,6 +15,9 @@ const gap = computed(() => (appStore.isMobile ? 0 : 16));
<template>
<NSpace vertical :size="16">
<NAlert :title="$t('common.warning')" type="warning" closable>
{{ $t('page.home.branchDesc') }}
</NAlert>
<HeaderBanner />
<CardData />
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>

View File

@@ -1,266 +0,0 @@
<script setup lang="tsx">
import { ref } from 'vue';
import type { Ref } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { useBoolean } from '@sa/hooks';
import { fetchGetAllPages, fetchGetMenuList } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import { yesOrNoRecord } from '@/constants/common';
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue';
import MenuOperateModal, { type OperateType } from './modules/menu-operate-modal.vue';
const appStore = useAppStore();
const { bool: visible, setTrue: openModal } = useBoolean();
const wrapperRef = ref<HTMLElement | null>(null);
const { columns, columnChecks, data, loading, pagination, getData, getDataByPage } = useTable({
apiFn: fetchGetMenuList,
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'id',
title: $t('page.manage.menu.id'),
align: 'center'
},
{
key: 'menuType',
title: $t('page.manage.menu.menuType'),
align: 'center',
width: 80,
render: row => {
const tagMap: Record<Api.SystemManage.MenuType, NaiveUI.ThemeColor> = {
1: 'default',
2: 'primary'
};
const label = $t(menuTypeRecord[row.menuType]);
return <NTag type={tagMap[row.menuType]}>{label}</NTag>;
}
},
{
key: 'menuName',
title: $t('page.manage.menu.menuName'),
align: 'center',
minWidth: 120,
render: row => {
const { i18nKey, menuName } = row;
const label = i18nKey ? $t(i18nKey) : menuName;
return <span>{label}</span>;
}
},
{
key: 'icon',
title: $t('page.manage.menu.icon'),
align: 'center',
width: 60,
render: row => {
const icon = row.iconType === '1' ? row.icon : undefined;
const localIcon = row.iconType === '2' ? row.icon : undefined;
return (
<div class="flex-center">
<SvgIcon icon={icon} localIcon={localIcon} class="text-icon" />
</div>
);
}
},
{
key: 'routeName',
title: $t('page.manage.menu.routeName'),
align: 'center',
minWidth: 120
},
{
key: 'routePath',
title: $t('page.manage.menu.routePath'),
align: 'center',
minWidth: 120
},
{
key: 'status',
title: $t('page.manage.menu.menuStatus'),
align: 'center',
width: 80,
render: row => {
if (row.status === null) {
return null;
}
const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
1: 'success',
2: 'warning'
};
const label = $t(enableStatusRecord[row.status]);
return <NTag type={tagMap[row.status]}>{label}</NTag>;
}
},
{
key: 'hideInMenu',
title: $t('page.manage.menu.hideInMenu'),
align: 'center',
width: 80,
render: row => {
const hide: CommonType.YesOrNo = row.hideInMenu ? 'Y' : 'N';
const tagMap: Record<CommonType.YesOrNo, NaiveUI.ThemeColor> = {
Y: 'error',
N: 'default'
};
const label = $t(yesOrNoRecord[hide]);
return <NTag type={tagMap[hide]}>{label}</NTag>;
}
},
{
key: 'parentId',
title: $t('page.manage.menu.parentId'),
width: 90,
align: 'center'
},
{
key: 'order',
title: $t('page.manage.menu.order'),
align: 'center',
width: 60
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 230,
render: row => (
<div class="flex-center justify-end gap-8px">
{row.menuType === '1' && (
<NButton type="primary" ghost size="small" onClick={() => handleAddChildMenu(row)}>
{$t('page.manage.menu.addChildMenu')}
</NButton>
)}
<NButton type="primary" ghost size="small" onClick={() => handleEdit(row)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
</div>
)
}
]
});
const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData);
const operateType = ref<OperateType>('add');
function handleAdd() {
operateType.value = 'add';
openModal();
}
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
onBatchDeleted();
}
function handleDelete(id: number) {
// request
console.log(id);
onDeleted();
}
/** the edit menu data or the parent menu data when adding a child menu */
const editingData: Ref<Api.SystemManage.Menu | null> = ref(null);
function handleEdit(item: Api.SystemManage.Menu) {
operateType.value = 'edit';
editingData.value = { ...item };
openModal();
}
function handleAddChildMenu(item: Api.SystemManage.Menu) {
operateType.value = 'addChild';
editingData.value = { ...item };
openModal();
}
const allPages = ref<string[]>([]);
async function getAllPages() {
const { data: pages } = await fetchGetAllPages();
allPages.value = pages || [];
}
function init() {
getAllPages();
}
// init
init();
</script>
<template>
<div ref="wrapperRef" class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="1088"
:loading="loading"
:row-key="row => row.id"
remote
:pagination="pagination"
class="sm:h-full"
/>
<MenuOperateModal
v-model:visible="visible"
:operate-type="operateType"
:row-data="editingData"
:all-pages="allPages"
@submitted="getDataByPage"
/>
</NCard>
</div>
</template>
<style scoped></style>

View File

@@ -1,467 +0,0 @@
<script setup lang="tsx">
import { computed, reactive, ref, watch } from 'vue';
import type { SelectOption } from 'naive-ui';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue';
import { getLocalIcons } from '@/utils/icon';
import { fetchGetAllRoles } from '@/service/api';
import {
getLayoutAndPage,
getPathParamFromRoutePath,
getRoutePathByRouteName,
getRoutePathWithParam,
transformLayoutAndPageToComponent
} from './shared';
defineOptions({
name: 'MenuOperateModal'
});
export type OperateType = NaiveUI.TableOperateType | 'addChild';
interface Props {
/** the type of operation */
operateType: OperateType;
/** the edit menu data or the parent menu data when adding a child menu */
rowData?: Api.SystemManage.Menu | null;
/** all pages */
allPages: string[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<OperateType, string> = {
add: $t('page.manage.menu.addMenu'),
addChild: $t('page.manage.menu.addChildMenu'),
edit: $t('page.manage.menu.editMenu')
};
return titles[props.operateType];
});
type Model = Pick<
Api.SystemManage.Menu,
| 'menuType'
| 'menuName'
| 'routeName'
| 'routePath'
| 'component'
| 'order'
| 'i18nKey'
| 'icon'
| 'iconType'
| 'status'
| 'parentId'
| 'keepAlive'
| 'constant'
| 'href'
| 'hideInMenu'
| 'activeMenu'
| 'multiTab'
| 'fixedIndexInTab'
> & {
query: NonNullable<Api.SystemManage.Menu['query']>;
buttons: NonNullable<Api.SystemManage.Menu['buttons']>;
layout: string;
page: string;
pathParam: string;
};
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
menuType: '1',
menuName: '',
routeName: '',
routePath: '',
pathParam: '',
component: '',
layout: '',
page: '',
i18nKey: null,
icon: '',
iconType: '1',
parentId: 0,
status: '1',
keepAlive: false,
constant: false,
order: 0,
href: null,
hideInMenu: false,
activeMenu: null,
multiTab: false,
fixedIndexInTab: null,
query: [],
buttons: []
};
}
type RuleKey = Extract<keyof Model, 'menuName' | 'status' | 'routeName' | 'routePath'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
menuName: defaultRequiredRule,
status: defaultRequiredRule,
routeName: defaultRequiredRule,
routePath: defaultRequiredRule
};
const disabledMenuType = computed(() => props.operateType === 'edit');
const localIcons = getLocalIcons();
const localIconOptions = localIcons.map<SelectOption>(item => ({
label: () => (
<div class="flex-y-center gap-16px">
<SvgIcon localIcon={item} class="text-icon" />
<span>{item}</span>
</div>
),
value: item
}));
const showLayout = computed(() => model.parentId === 0);
const showPage = computed(() => model.menuType === '2');
const pageOptions = computed(() => {
const allPages = [...props.allPages];
if (model.routeName && !allPages.includes(model.routeName)) {
allPages.unshift(model.routeName);
}
const opts: CommonType.Option[] = allPages.map(page => ({
label: page,
value: page
}));
return opts;
});
const layoutOptions: CommonType.Option[] = [
{
label: 'base',
value: 'base'
},
{
label: 'blank',
value: 'blank'
}
];
/** the enabled role options */
const roleOptions = ref<CommonType.Option<string>[]>([]);
async function getRoleOptions() {
const { error, data } = await fetchGetAllRoles();
if (!error) {
const options = data.map(item => ({
label: item.roleName,
value: item.roleCode
}));
roleOptions.value = [...options];
}
}
function handleInitModel() {
Object.assign(model, createDefaultModel());
if (!props.rowData) return;
if (props.operateType === 'addChild') {
const { id } = props.rowData;
Object.assign(model, { parentId: id });
}
if (props.operateType === 'edit') {
const { component, ...rest } = props.rowData;
const { layout, page } = getLayoutAndPage(component);
const { path, param } = getPathParamFromRoutePath(rest.routePath);
Object.assign(model, rest, { layout, page, routePath: path, pathParam: param });
}
if (!model.query) {
model.query = [];
}
if (!model.buttons) {
model.buttons = [];
}
}
function closeDrawer() {
visible.value = false;
}
function handleUpdateRoutePathByRouteName() {
if (model.routeName) {
model.routePath = getRoutePathByRouteName(model.routeName);
} else {
model.routePath = '';
}
}
function handleUpdateI18nKeyByRouteName() {
if (model.routeName) {
model.i18nKey = `route.${model.routeName}` as App.I18n.I18nKey;
} else {
model.i18nKey = null;
}
}
function handleCreateButton() {
const buttonItem: Api.SystemManage.MenuButton = {
code: '',
desc: ''
};
return buttonItem;
}
function getSubmitParams() {
const { layout, page, pathParam, ...params } = model;
const component = transformLayoutAndPageToComponent(layout, page);
const routePath = getRoutePathWithParam(model.routePath, pathParam);
params.component = component;
params.routePath = routePath;
return params;
}
async function handleSubmit() {
await validate();
const params = getSubmitParams();
console.log('params: ', params);
// request
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
}
watch(visible, () => {
if (visible.value) {
handleInitModel();
restoreValidation();
getRoleOptions();
}
});
watch(
() => model.routeName,
() => {
handleUpdateRoutePathByRouteName();
handleUpdateI18nKeyByRouteName();
}
);
</script>
<template>
<NModal v-model:show="visible" :title="title" preset="card" class="w-800px">
<NScrollbar class="h-480px pr-20px">
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="100">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuType')" path="menuType">
<NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuName')" path="menuName">
<NInput v-model:value="model.menuName" :placeholder="$t('page.manage.menu.form.menuName')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routeName')" path="routeName">
<NInput v-model:value="model.routeName" :placeholder="$t('page.manage.menu.form.routeName')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routePath')" path="routePath">
<NInput v-model:value="model.routePath" disabled :placeholder="$t('page.manage.menu.form.routePath')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.pathParam')" path="pathParam">
<NInput v-model:value="model.pathParam" :placeholder="$t('page.manage.menu.form.pathParam')" />
</NFormItemGi>
<NFormItemGi v-if="showLayout" span="24 m:12" :label="$t('page.manage.menu.layout')" path="layout">
<NSelect
v-model:value="model.layout"
:options="layoutOptions"
:placeholder="$t('page.manage.menu.form.layout')"
/>
</NFormItemGi>
<NFormItemGi v-if="showPage" span="24 m:12" :label="$t('page.manage.menu.page')" path="page">
<NSelect
v-model:value="model.page"
:options="pageOptions"
:placeholder="$t('page.manage.menu.form.page')"
/>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.i18nKey')" path="i18nKey">
<NInput v-model:value="model.i18nKey" :placeholder="$t('page.manage.menu.form.i18nKey')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.order')" path="order">
<NInputNumber v-model:value="model.order" class="w-full" :placeholder="$t('page.manage.menu.form.order')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.iconTypeTitle')" path="iconType">
<NRadioGroup v-model:value="model.iconType">
<NRadio
v-for="item in menuIconTypeOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.icon')" path="icon">
<template v-if="model.iconType === '1'">
<NInput v-model:value="model.icon" :placeholder="$t('page.manage.menu.form.icon')" class="flex-1">
<template #suffix>
<SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" />
</template>
</NInput>
</template>
<template v-if="model.iconType === '2'">
<NSelect
v-model:value="model.icon"
:placeholder="$t('page.manage.menu.form.localIcon')"
:options="localIconOptions"
/>
</template>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuStatus')" path="status">
<NRadioGroup v-model:value="model.status">
<NRadio
v-for="item in enableStatusOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.keepAlive')" path="keepAlive">
<NRadioGroup v-model:value="model.keepAlive">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.constant')" path="constant">
<NRadioGroup v-model:value="model.constant">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.href')" path="href">
<NInput v-model:value="model.href" :placeholder="$t('page.manage.menu.form.href')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.hideInMenu')" path="hideInMenu">
<NRadioGroup v-model:value="model.hideInMenu">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi
v-if="model.hideInMenu"
span="24 m:12"
:label="$t('page.manage.menu.activeMenu')"
path="activeMenu"
>
<NSelect
v-model:value="model.activeMenu"
:options="pageOptions"
clearable
:placeholder="$t('page.manage.menu.form.activeMenu')"
/>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.multiTab')" path="multiTab">
<NRadioGroup v-model:value="model.multiTab">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.fixedIndexInTab')" path="fixedIndexInTab">
<NInputNumber
v-model:value="model.fixedIndexInTab"
class="w-full"
clearable
:placeholder="$t('page.manage.menu.form.fixedIndexInTab')"
/>
</NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.query')">
<NDynamicInput
v-model:value="model.query"
preset="pair"
:key-placeholder="$t('page.manage.menu.form.queryKey')"
:value-placeholder="$t('page.manage.menu.form.queryValue')"
>
<template #action="{ index, create, remove }">
<NSpace class="ml-12px">
<NButton size="medium" @click="() => create(index)">
<icon-ic:round-plus class="text-icon" />
</NButton>
<NButton size="medium" @click="() => remove(index)">
<icon-ic-round-remove class="text-icon" />
</NButton>
</NSpace>
</template>
</NDynamicInput>
</NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.button')">
<NDynamicInput v-model:value="model.buttons" :on-create="handleCreateButton">
<template #default="{ value }">
<div class="ml-8px flex-y-center flex-1 gap-12px">
<NInput
v-model:value="value.code"
:placeholder="$t('page.manage.menu.form.buttonCode')"
class="flex-1"
/>
<NInput
v-model:value="value.desc"
:placeholder="$t('page.manage.menu.form.buttonDesc')"
class="flex-1"
/>
</div>
</template>
<template #action="{ index, create, remove }">
<NSpace class="ml-12px">
<NButton size="medium" @click="() => create(index)">
<icon-ic:round-plus class="text-icon" />
</NButton>
<NButton size="medium" @click="() => remove(index)">
<icon-ic-round-remove class="text-icon" />
</NButton>
</NSpace>
</template>
</NDynamicInput>
</NFormItemGi>
</NGrid>
</NForm>
</NScrollbar>
<template #footer>
<NSpace justify="end" :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NModal>
</template>
<style scoped></style>

View File

@@ -1,79 +0,0 @@
const LAYOUT_PREFIX = 'layout.';
const VIEW_PREFIX = 'view.';
const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$';
export function getLayoutAndPage(component?: string | null) {
let layout = '';
let page = '';
const [layoutOrPage = '', pageItem = ''] = component?.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT) || [];
layout = getLayout(layoutOrPage);
page = getPage(pageItem || layoutOrPage);
return { layout, page };
}
function getLayout(layout: string) {
return layout.startsWith(LAYOUT_PREFIX) ? layout.replace(LAYOUT_PREFIX, '') : '';
}
function getPage(page: string) {
return page.startsWith(VIEW_PREFIX) ? page.replace(VIEW_PREFIX, '') : '';
}
export function transformLayoutAndPageToComponent(layout: string, page: string) {
const hasLayout = Boolean(layout);
const hasPage = Boolean(page);
if (hasLayout && hasPage) {
return `${LAYOUT_PREFIX}${layout}${FIRST_LEVEL_ROUTE_COMPONENT_SPLIT}${VIEW_PREFIX}${page}`;
}
if (hasLayout) {
return `${LAYOUT_PREFIX}${layout}`;
}
if (hasPage) {
return `${VIEW_PREFIX}${page}`;
}
return '';
}
/**
* Get route name by route path
*
* @param routeName
*/
export function getRoutePathByRouteName(routeName: string) {
return `/${routeName.replace(/_/g, '/')}`;
}
/**
* Get path param from route path
*
* @param routePath route path
*/
export function getPathParamFromRoutePath(routePath: string) {
const [path, param = ''] = routePath.split('/:');
return {
path,
param
};
}
/**
* Get route path with param
*
* @param routePath route path
* @param param path param
*/
export function getRoutePathWithParam(routePath: string, param: string) {
if (param.trim()) {
return `${routePath}/:${param}`;
}
return routePath;
}

View File

@@ -1,177 +0,0 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { fetchGetRoleList } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import { enableStatusRecord } from '@/constants/business';
import RoleOperateDrawer from './modules/role-operate-drawer.vue';
import RoleSearch from './modules/role-search.vue';
const appStore = useAppStore();
const {
columns,
columnChecks,
data,
loading,
getData,
getDataByPage,
mobilePagination,
searchParams,
resetSearchParams
} = useTable({
apiFn: fetchGetRoleList,
apiParams: {
current: 1,
size: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
status: null,
roleName: null,
roleCode: null
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'index',
title: $t('common.index'),
width: 64,
align: 'center'
},
{
key: 'roleName',
title: $t('page.manage.role.roleName'),
align: 'center',
minWidth: 120
},
{
key: 'roleCode',
title: $t('page.manage.role.roleCode'),
align: 'center',
minWidth: 120
},
{
key: 'roleDesc',
title: $t('page.manage.role.roleDesc'),
minWidth: 120
},
{
key: 'status',
title: $t('page.manage.role.roleStatus'),
align: 'center',
width: 100,
render: row => {
if (row.status === null) {
return null;
}
const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
1: 'success',
2: 'warning'
};
const label = $t(enableStatusRecord[row.status]);
return <NTag type={tagMap[row.status]}>{label}</NTag>;
}
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 130,
render: row => (
<div class="flex-center gap-8px">
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
</div>
)
}
]
});
const {
drawerVisible,
operateType,
editingData,
handleAdd,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
onBatchDeleted();
}
function handleDelete(id: number) {
// request
console.log(id);
onDeleted();
}
function edit(id: number) {
handleEdit(id);
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<RoleSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
<NCard :title="$t('page.manage.role.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="702"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
<RoleOperateDrawer
v-model:visible="drawerVisible"
:operate-type="operateType"
:row-data="editingData"
@submitted="getDataByPage"
/>
</NCard>
</div>
</template>
<style scoped></style>

View File

@@ -1,101 +0,0 @@
<script setup lang="ts">
import { computed, shallowRef } from 'vue';
import { $t } from '@/locales';
defineOptions({
name: 'ButtonAuthModal'
});
interface Props {
/** the roleId */
roleId: number;
}
const props = defineProps<Props>();
const visible = defineModel<boolean>('visible', {
default: false
});
function closeModal() {
visible.value = false;
}
const title = computed(() => $t('common.edit') + $t('page.manage.role.buttonAuth'));
type ButtonConfig = {
id: number;
label: string;
code: string;
};
const tree = shallowRef<ButtonConfig[]>([]);
async function getAllButtons() {
// request
tree.value = [
{ id: 1, label: 'button1', code: 'code1' },
{ id: 2, label: 'button2', code: 'code2' },
{ id: 3, label: 'button3', code: 'code3' },
{ id: 4, label: 'button4', code: 'code4' },
{ id: 5, label: 'button5', code: 'code5' },
{ id: 6, label: 'button6', code: 'code6' },
{ id: 7, label: 'button7', code: 'code7' },
{ id: 8, label: 'button8', code: 'code8' },
{ id: 9, label: 'button9', code: 'code9' },
{ id: 10, label: 'button10', code: 'code10' }
];
}
const checks = shallowRef<number[]>([]);
async function getChecks() {
console.log(props.roleId);
// request
checks.value = [1, 2, 3, 4, 5];
}
function handleSubmit() {
console.log(checks.value, props.roleId);
// request
window.$message?.success?.($t('common.modifySuccess'));
closeModal();
}
function init() {
getAllButtons();
getChecks();
}
// init
init();
</script>
<template>
<NModal v-model:show="visible" :title="title" preset="card" class="w-480px">
<NTree
v-model:checked-keys="checks"
:data="tree"
key-field="id"
block-line
checkable
expand-on-click
virtual-scroll
class="h-280px"
/>
<template #footer>
<NSpace justify="end">
<NButton size="small" class="mt-16px" @click="closeModal">
{{ $t('common.cancel') }}
</NButton>
<NButton type="primary" size="small" class="mt-16px" @click="handleSubmit">
{{ $t('common.confirm') }}
</NButton>
</NSpace>
</template>
</NModal>
</template>
<style scoped></style>

View File

@@ -1,130 +0,0 @@
<script setup lang="ts">
import { computed, shallowRef, watch } from 'vue';
import { $t } from '@/locales';
import { fetchGetAllPages, fetchGetMenuTree } from '@/service/api';
defineOptions({
name: 'MenuAuthModal'
});
interface Props {
/** the roleId */
roleId: number;
}
const props = defineProps<Props>();
const visible = defineModel<boolean>('visible', {
default: false
});
function closeModal() {
visible.value = false;
}
const title = computed(() => $t('common.edit') + $t('page.manage.role.menuAuth'));
const home = shallowRef('');
async function getHome() {
console.log(props.roleId);
home.value = 'home';
}
async function updateHome(val: string) {
// request
home.value = val;
}
const pages = shallowRef<string[]>([]);
async function getPages() {
const { error, data } = await fetchGetAllPages();
if (!error) {
pages.value = data;
}
}
const pageSelectOptions = computed(() => {
const opts: CommonType.Option[] = pages.value.map(page => ({
label: page,
value: page
}));
return opts;
});
const tree = shallowRef<Api.SystemManage.MenuTree[]>([]);
async function getTree() {
const { error, data } = await fetchGetMenuTree();
if (!error) {
tree.value = data;
}
}
const checks = shallowRef<number[]>([]);
async function getChecks() {
console.log(props.roleId);
// request
checks.value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
}
function handleSubmit() {
console.log(checks.value, props.roleId);
// request
window.$message?.success?.($t('common.modifySuccess'));
closeModal();
}
function init() {
getHome();
getPages();
getTree();
getChecks();
}
watch(visible, val => {
if (val) {
init();
}
});
</script>
<template>
<NModal v-model:show="visible" :title="title" preset="card" class="w-480px">
<div class="flex-y-center gap-16px pb-12px">
<div>{{ $t('page.manage.menu.home') }}</div>
<NSelect :value="home" :options="pageSelectOptions" size="small" class="w-160px" @update:value="updateHome" />
</div>
<NTree
v-model:checked-keys="checks"
:data="tree"
key-field="id"
checkable
expand-on-click
virtual-scroll
block-line
class="h-280px"
/>
<template #footer>
<NSpace justify="end">
<NButton size="small" class="mt-16px" @click="closeModal">
{{ $t('common.cancel') }}
</NButton>
<NButton type="primary" size="small" class="mt-16px" @click="handleSubmit">
{{ $t('common.confirm') }}
</NButton>
</NSpace>
</template>
</NModal>
</template>
<style scoped></style>

View File

@@ -1,134 +0,0 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { useBoolean } from '@sa/hooks';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import { enableStatusOptions } from '@/constants/business';
import MenuAuthModal from './menu-auth-modal.vue';
import ButtonAuthModal from './button-auth-modal.vue';
defineOptions({
name: 'RoleOperateDrawer'
});
interface Props {
/** the type of operation */
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.SystemManage.Role | null;
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const { bool: menuAuthVisible, setTrue: openMenuAuthModal } = useBoolean();
const { bool: buttonAuthVisible, setTrue: openButtonAuthModal } = useBoolean();
const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: $t('page.manage.role.addRole'),
edit: $t('page.manage.role.editRole')
};
return titles[props.operateType];
});
type Model = Pick<Api.SystemManage.Role, 'roleName' | 'roleCode' | 'roleDesc' | 'status'>;
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
roleName: '',
roleCode: '',
roleDesc: '',
status: null
};
}
type RuleKey = Exclude<keyof Model, 'roleDesc'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
roleName: defaultRequiredRule,
roleCode: defaultRequiredRule,
status: defaultRequiredRule
};
const roleId = computed(() => props.rowData?.id || -1);
const isEdit = computed(() => props.operateType === 'edit');
function handleInitModel() {
Object.assign(model, createDefaultModel());
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData);
}
}
function closeDrawer() {
visible.value = false;
}
async function handleSubmit() {
await validate();
// request
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
}
watch(visible, () => {
if (visible.value) {
handleInitModel();
restoreValidation();
}
});
</script>
<template>
<NDrawer v-model:show="visible" display-directive="show" :width="360">
<NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem :label="$t('page.manage.role.roleName')" path="roleName">
<NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
</NFormItem>
<NFormItem :label="$t('page.manage.role.roleCode')" path="roleCode">
<NInput v-model:value="model.roleCode" :placeholder="$t('page.manage.role.form.roleCode')" />
</NFormItem>
<NFormItem :label="$t('page.manage.role.roleStatus')" path="status">
<NRadioGroup v-model:value="model.status">
<NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
</NRadioGroup>
</NFormItem>
<NFormItem :label="$t('page.manage.role.roleDesc')" path="roleDesc">
<NInput v-model:value="model.roleDesc" :placeholder="$t('page.manage.role.form.roleDesc')" />
</NFormItem>
</NForm>
<NSpace v-if="isEdit">
<NButton @click="openMenuAuthModal">{{ $t('page.manage.role.menuAuth') }}</NButton>
<MenuAuthModal v-model:visible="menuAuthVisible" :role-id="roleId" />
<NButton @click="openButtonAuthModal">{{ $t('page.manage.role.buttonAuth') }}</NButton>
<ButtonAuthModal v-model:visible="buttonAuthVisible" :role-id="roleId" />
</NSpace>
<template #footer>
<NSpace :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NDrawerContent>
</NDrawer>
</template>
<style scoped></style>

View File

@@ -1,67 +0,0 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { enableStatusOptions } from '@/constants/business';
import { translateOptions } from '@/utils/common';
defineOptions({
name: 'RoleSearch'
});
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
const emit = defineEmits<Emits>();
const model = defineModel<Api.SystemManage.RoleSearchParams>('model', { required: true });
function reset() {
emit('reset');
}
function search() {
emit('search');
}
</script>
<template>
<NCard :title="$t('common.search')" :bordered="false" size="small" class="card-wrapper">
<NForm :model="model" label-placement="left" :label-width="80">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.role.roleName')" path="roleName" class="pr-24px">
<NInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.role.roleCode')" path="roleCode" class="pr-24px">
<NInput v-model:value="model.roleCode" :placeholder="$t('page.manage.role.form.roleCode')" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.role.roleStatus')" path="status" class="pr-24px">
<NSelect
v-model:value="model.status"
:placeholder="$t('page.manage.role.form.roleStatus')"
:options="translateOptions(enableStatusOptions)"
clearable
/>
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6">
<NSpace class="w-full" justify="end">
<NButton @click="reset">
<template #icon>
<icon-ic-round-refresh class="text-icon" />
</template>
{{ $t('common.reset') }}
</NButton>
<NButton type="primary" ghost @click="search">
<template #icon>
<icon-ic-round-search class="text-icon" />
</template>
{{ $t('common.search') }}
</NButton>
</NSpace>
</NFormItemGi>
</NGrid>
</NForm>
</NCard>
</template>
<style scoped></style>

View File

@@ -1,13 +0,0 @@
<script setup lang="ts">
interface Props {
id: string;
}
defineProps<Props>();
</script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,208 +0,0 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { fetchGetUserList } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import { useTable, useTableOperate } from '@/hooks/common/table';
import UserOperateDrawer from './modules/user-operate-drawer.vue';
import UserSearch from './modules/user-search.vue';
const appStore = useAppStore();
const {
columns,
columnChecks,
data,
getData,
getDataByPage,
loading,
mobilePagination,
searchParams,
resetSearchParams
} = useTable({
apiFn: fetchGetUserList,
showTotal: true,
apiParams: {
current: 1,
size: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
status: null,
userName: null,
userGender: null,
nickName: null,
userPhone: null,
userEmail: null
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64
},
{
key: 'userName',
title: $t('page.manage.user.userName'),
align: 'center',
minWidth: 100
},
{
key: 'userGender',
title: $t('page.manage.user.userGender'),
align: 'center',
width: 100,
render: row => {
if (row.userGender === null) {
return null;
}
const tagMap: Record<Api.SystemManage.UserGender, NaiveUI.ThemeColor> = {
1: 'primary',
2: 'error'
};
const label = $t(userGenderRecord[row.userGender]);
return <NTag type={tagMap[row.userGender]}>{label}</NTag>;
}
},
{
key: 'nickName',
title: $t('page.manage.user.nickName'),
align: 'center',
minWidth: 100
},
{
key: 'userPhone',
title: $t('page.manage.user.userPhone'),
align: 'center',
width: 120
},
{
key: 'userEmail',
title: $t('page.manage.user.userEmail'),
align: 'center',
minWidth: 200
},
{
key: 'status',
title: $t('page.manage.user.userStatus'),
align: 'center',
width: 100,
render: row => {
if (row.status === null) {
return null;
}
const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
1: 'success',
2: 'warning'
};
const label = $t(enableStatusRecord[row.status]);
return <NTag type={tagMap[row.status]}>{label}</NTag>;
}
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 130,
render: row => (
<div class="flex-center gap-8px">
<NButton type="primary" ghost size="small" onClick={() => edit(row.id)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
</div>
)
}
]
});
const {
drawerVisible,
operateType,
editingData,
handleAdd,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted
// closeDrawer
} = useTableOperate(data, getData);
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
onBatchDeleted();
}
function handleDelete(id: number) {
// request
console.log(id);
onDeleted();
}
function edit(id: number) {
handleEdit(id);
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
<NCard :title="$t('page.manage.user.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
/>
<UserOperateDrawer
v-model:visible="drawerVisible"
:operate-type="operateType"
:row-data="editingData"
@submitted="getDataByPage"
/>
</NCard>
</div>
</template>
<style scoped></style>

View File

@@ -1,166 +0,0 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { fetchGetAllRoles } from '@/service/api';
import { $t } from '@/locales';
import { enableStatusOptions, userGenderOptions } from '@/constants/business';
defineOptions({
name: 'UserOperateDrawer'
});
interface Props {
/** the type of operation */
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.SystemManage.User | null;
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: $t('page.manage.user.addUser'),
edit: $t('page.manage.user.editUser')
};
return titles[props.operateType];
});
type Model = Pick<
Api.SystemManage.User,
'userName' | 'userGender' | 'nickName' | 'userPhone' | 'userEmail' | 'userRoles' | 'status'
>;
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
userName: '',
userGender: null,
nickName: '',
userPhone: '',
userEmail: '',
userRoles: [],
status: null
};
}
type RuleKey = Extract<keyof Model, 'userName' | 'status'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
userName: defaultRequiredRule,
status: defaultRequiredRule
};
/** the enabled role options */
const roleOptions = ref<CommonType.Option<string>[]>([]);
async function getRoleOptions() {
const { error, data } = await fetchGetAllRoles();
if (!error) {
const options = data.map(item => ({
label: item.roleName,
value: item.roleCode
}));
// the mock data does not have the roleCode, so fill it
// if the real request, remove the following code
const userRoleOptions = model.userRoles.map(item => ({
label: item,
value: item
}));
// end
roleOptions.value = [...userRoleOptions, ...options];
}
}
function handleInitModel() {
Object.assign(model, createDefaultModel());
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData);
}
}
function closeDrawer() {
visible.value = false;
}
async function handleSubmit() {
await validate();
// request
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
}
watch(visible, () => {
if (visible.value) {
handleInitModel();
restoreValidation();
getRoleOptions();
}
});
</script>
<template>
<NDrawer v-model:show="visible" display-directive="show" :width="360">
<NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem :label="$t('page.manage.user.userName')" path="userName">
<NInput v-model:value="model.userName" :placeholder="$t('page.manage.user.form.userName')" />
</NFormItem>
<NFormItem :label="$t('page.manage.user.userGender')" path="userGender">
<NRadioGroup v-model:value="model.userGender">
<NRadio v-for="item in userGenderOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
</NRadioGroup>
</NFormItem>
<NFormItem :label="$t('page.manage.user.nickName')" path="nickName">
<NInput v-model:value="model.nickName" :placeholder="$t('page.manage.user.form.nickName')" />
</NFormItem>
<NFormItem :label="$t('page.manage.user.userPhone')" path="userPhone">
<NInput v-model:value="model.userPhone" :placeholder="$t('page.manage.user.form.userPhone')" />
</NFormItem>
<NFormItem :label="$t('page.manage.user.userEmail')" path="email">
<NInput v-model:value="model.userEmail" :placeholder="$t('page.manage.user.form.userEmail')" />
</NFormItem>
<NFormItem :label="$t('page.manage.user.userStatus')" path="status">
<NRadioGroup v-model:value="model.status">
<NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
</NRadioGroup>
</NFormItem>
<NFormItem :label="$t('page.manage.user.userRole')" path="roles">
<NSelect
v-model:value="model.userRoles"
multiple
:options="roleOptions"
:placeholder="$t('page.manage.user.form.userRole')"
/>
</NFormItem>
</NForm>
<template #footer>
<NSpace :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NDrawerContent>
</NDrawer>
</template>
<style scoped></style>

View File

@@ -1,98 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '@/locales';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { enableStatusOptions, userGenderOptions } from '@/constants/business';
import { translateOptions } from '@/utils/common';
defineOptions({
name: 'UserSearch'
});
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
const emit = defineEmits<Emits>();
const { formRef, validate, restoreValidation } = useNaiveForm();
const model = defineModel<Api.SystemManage.UserSearchParams>('model', { required: true });
type RuleKey = Extract<keyof Api.SystemManage.UserSearchParams, 'userEmail' | 'userPhone'>;
const rules = computed<Record<RuleKey, App.Global.FormRule>>(() => {
const { patternRules } = useFormRules(); // inside computed to make locale reactive
return {
userEmail: patternRules.email,
userPhone: patternRules.phone
};
});
async function reset() {
await restoreValidation();
emit('reset');
}
async function search() {
await validate();
emit('search');
}
</script>
<template>
<NCard :title="$t('common.search')" :bordered="false" size="small" class="card-wrapper">
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.user.userName')" path="userName" class="pr-24px">
<NInput v-model:value="model.userName" :placeholder="$t('page.manage.user.form.userName')" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.user.userGender')" path="userGender" class="pr-24px">
<NSelect
v-model:value="model.userGender"
:placeholder="$t('page.manage.user.form.userGender')"
:options="translateOptions(userGenderOptions)"
clearable
/>
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.user.nickName')" path="nickName" class="pr-24px">
<NInput v-model:value="model.nickName" :placeholder="$t('page.manage.user.form.nickName')" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.user.userPhone')" path="userPhone" class="pr-24px">
<NInput v-model:value="model.userPhone" :placeholder="$t('page.manage.user.form.userPhone')" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.user.userEmail')" path="userEmail" class="pr-24px">
<NInput v-model:value="model.userEmail" :placeholder="$t('page.manage.user.form.userEmail')" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.manage.user.userStatus')" path="userStatus" class="pr-24px">
<NSelect
v-model:value="model.status"
:placeholder="$t('page.manage.user.form.userStatus')"
:options="translateOptions(enableStatusOptions)"
clearable
/>
</NFormItemGi>
<NFormItemGi span="24 m:12" class="pr-24px">
<NSpace class="w-full" justify="end">
<NButton @click="reset">
<template #icon>
<icon-ic-round-refresh class="text-icon" />
</template>
{{ $t('common.reset') }}
</NButton>
<NButton type="primary" ghost @click="search">
<template #icon>
<icon-ic-round-search class="text-icon" />
</template>
{{ $t('common.search') }}
</NButton>
</NSpace>
</NFormItemGi>
</NGrid>
</NForm>
</NCard>
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>