i18n: 客户端管理新增多语言配置示例

This commit is contained in:
xlsea 2025-05-12 21:34:30 +08:00 committed by 马铃薯头
parent f461f94c2a
commit e7af01f084
7 changed files with 275 additions and 54 deletions

View File

@ -77,7 +77,7 @@ function handleExport() {
<template #icon> <template #icon>
<icon-material-symbols:download-rounded class="text-icon" /> <icon-material-symbols:download-rounded class="text-icon" />
</template> </template>
导出 {{ $t('common.export') }}
</NButton> </NButton>
</slot> </slot>
<slot name="after"></slot> <slot name="after"></slot>

View File

@ -12,6 +12,8 @@ const local: App.I18n.Schema = {
addSuccess: 'Add Success', addSuccess: 'Add Success',
backToHome: 'Back to home', backToHome: 'Back to home',
batchDelete: 'Batch Delete', batchDelete: 'Batch Delete',
import: 'Import',
export: 'Export',
cancel: 'Cancel', cancel: 'Cancel',
close: 'Close', close: 'Close',
check: 'Check', check: 'Check',
@ -45,12 +47,14 @@ const local: App.I18n.Schema = {
tip: 'Tip', tip: 'Tip',
trigger: 'Trigger', trigger: 'Trigger',
update: 'Update', update: 'Update',
saveSuccess: 'Save Success',
updateSuccess: 'Update Success', updateSuccess: 'Update Success',
userCenter: 'User Center', userCenter: 'User Center',
yesOrNo: { yesOrNo: {
yes: 'Yes', yes: 'Yes',
no: 'No' no: 'No'
} },
second: 'Second'
}, },
request: { request: {
logout: 'Logout user after request failed', logout: 'Logout user after request failed',
@ -269,6 +273,71 @@ const local: App.I18n.Schema = {
desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!' desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!'
}, },
creativity: 'Creativity' creativity: 'Creativity'
},
common: {
id: 'ID',
createBy: 'Creator',
createTime: 'Create Time',
updateBy: 'Updater',
updateTime: 'Update Time',
remark: 'Remark',
form: {
remark: {
required: 'Please enter remark',
invalid: 'Remark cannot be empty'
}
}
},
system: {
client: {
title: 'Client List',
clientId: 'Client ID',
clientKey: 'Client Key',
clientSecret: 'Client Secret',
grantTypeList: 'Grant Type',
deviceType: 'Device Type',
activeTimeout: 'Token Active Timeout',
timeout: 'Token Timeout',
status: 'Status',
form: {
clientId: {
required: 'Please enter Client ID',
invalid: 'Client ID cannot be empty'
},
clientKey: {
required: 'Please enter Client Key',
invalid: 'Client Key cannot be empty'
},
clientSecret: {
required: 'Please enter Client Secret',
invalid: 'Client Secret cannot be empty'
},
grantTypeList: {
required: 'Please select Grant Type',
invalid: 'Grant Type cannot be empty'
},
deviceType: {
required: 'Please select Device Type',
invalid: 'Device Type cannot be empty'
},
activeTimeout: {
required: 'Please enter Active Timeout',
invalid: 'Active Timeout cannot be empty',
tooltip: 'Specify time without operation will expire (unit: second), default 30 minutes (1800 seconds)'
},
timeout: {
required: 'Please enter Timeout',
invalid: 'Timeout cannot be empty',
tooltip: 'Specify time will expire (unit: second), default 7 days (604800 seconds)'
},
status: {
required: 'Please select Status',
invalid: 'Status cannot be empty'
}
},
addClient: 'Add Client',
editClient: 'Edit Client'
}
} }
}, },
form: { form: {

View File

@ -12,6 +12,8 @@ const local: App.I18n.Schema = {
addSuccess: '添加成功', addSuccess: '添加成功',
backToHome: '返回首页', backToHome: '返回首页',
batchDelete: '批量删除', batchDelete: '批量删除',
import: '导入',
export: '导出',
cancel: '取消', cancel: '取消',
close: '关闭', close: '关闭',
check: '勾选', check: '勾选',
@ -45,12 +47,14 @@ const local: App.I18n.Schema = {
tip: '提示', tip: '提示',
trigger: '触发', trigger: '触发',
update: '更新', update: '更新',
saveSuccess: '保存成功',
updateSuccess: '更新成功', updateSuccess: '更新成功',
userCenter: '个人中心', userCenter: '个人中心',
yesOrNo: { yesOrNo: {
yes: '是', yes: '是',
no: '否' no: '否'
} },
second: '秒'
}, },
request: { request: {
logout: '请求失败后登出用户', logout: '请求失败后登出用户',
@ -269,6 +273,71 @@ const local: App.I18n.Schema = {
desc5: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!' desc5: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!'
}, },
creativity: '创意' creativity: '创意'
},
common: {
id: 'ID',
createBy: '创建者',
createTime: '创建时间',
updateBy: '更新者',
updateTime: '更新时间',
remark: '备注',
form: {
remark: {
required: '请输入备注',
invalid: '备注不能为空'
}
}
},
system: {
client: {
title: '客户端列表',
clientId: '客户端 ID',
clientKey: '客户端 Key',
clientSecret: '客户端秘钥',
grantTypeList: '授权类型',
deviceType: '设备类型',
activeTimeout: 'Token 活跃超时时间',
timeout: 'Token 固定超时',
status: '状态',
form: {
clientId: {
required: '请输入客户端 ID',
invalid: '客户端 ID 不能为空'
},
clientKey: {
required: '请输入客户端 Key',
invalid: '客户端 Key 不能为空'
},
clientSecret: {
required: '请输入客户端秘钥',
invalid: '客户端秘钥不能为空'
},
grantTypeList: {
required: '请选择授权类型',
invalid: '授权类型不能为空'
},
deviceType: {
required: '请选择设备类型',
invalid: '设备类型不能为空'
},
activeTimeout: {
required: '请输入 Token 活跃超时时间',
invalid: 'Token 活跃超时时间不能为空',
tooltip: '指定时间无操作则过期(单位:秒), 默认30分钟(1800秒)'
},
timeout: {
required: '请输入 Token 固定超时',
invalid: 'Token 固定超时不能为空',
tooltip: '指定时间必定过期(单位:秒),默认七天(604800秒)'
},
status: {
required: '请选择状态',
invalid: '状态不能为空'
}
},
addClient: '新增客户端',
editClient: '编辑客户端'
}
} }
}, },
form: { form: {

41
src/typings/app.d.ts vendored
View File

@ -290,6 +290,7 @@ declare namespace App {
type FormMsg = { type FormMsg = {
required: string; required: string;
invalid: string; invalid: string;
tooltip?: string;
}; };
type Schema = { type Schema = {
@ -306,6 +307,8 @@ declare namespace App {
addSuccess: string; addSuccess: string;
backToHome: string; backToHome: string;
batchDelete: string; batchDelete: string;
import: string;
export: string;
cancel: string; cancel: string;
close: string; close: string;
check: string; check: string;
@ -340,11 +343,13 @@ declare namespace App {
trigger: string; trigger: string;
update: string; update: string;
updateSuccess: string; updateSuccess: string;
saveSuccess: string;
userCenter: string; userCenter: string;
yesOrNo: { yesOrNo: {
yes: string; yes: string;
no: string; no: string;
}; };
second: string;
}; };
request: { request: {
logout: string; logout: string;
@ -417,6 +422,17 @@ declare namespace App {
}; };
route: Record<I18nRouteKey, string>; route: Record<I18nRouteKey, string>;
page: { page: {
common: {
id: string;
createBy: string;
createTime: string;
updateBy: string;
updateTime: string;
remark: string;
form: {
remark: FormMsg;
};
};
login: { login: {
common: { common: {
loginOrRegister: string; loginOrRegister: string;
@ -492,6 +508,31 @@ declare namespace App {
}; };
creativity: string; creativity: string;
}; };
system: {
client: {
title: string;
clientId: string;
clientKey: string;
clientSecret: string;
grantTypeList: string;
deviceType: string;
activeTimeout: string;
timeout: string;
status: string;
form: {
clientId: FormMsg;
clientKey: FormMsg;
clientSecret: FormMsg;
grantTypeList: FormMsg;
deviceType: FormMsg;
activeTimeout: FormMsg;
timeout: FormMsg;
status: FormMsg;
};
addClient: string;
editClient: string;
};
};
}; };
form: { form: {
required: string; required: string;

View File

@ -15,9 +15,11 @@ import ClientSearch from './modules/client-search.vue';
defineOptions({ defineOptions({
name: 'ClientList' name: 'ClientList'
}); });
useDict('sys_grant_type'); useDict('sys_grant_type');
useDict('sys_device_type'); useDict('sys_device_type');
useDict('sys_normal_disable'); useDict('sys_normal_disable');
const appStore = useAppStore(); const appStore = useAppStore();
const { download } = useDownload(); const { download } = useDownload();
const { hasAuth } = useAuth(); const { hasAuth } = useAuth();
@ -58,25 +60,25 @@ const {
}, },
{ {
key: 'clientId', key: 'clientId',
title: '客户端id', title: $t('page.system.client.clientId'),
align: 'center', align: 'center',
minWidth: 120 minWidth: 120
}, },
{ {
key: 'clientKey', key: 'clientKey',
title: '客户端key', title: $t('page.system.client.clientKey'),
align: 'center', align: 'center',
minWidth: 120 minWidth: 120
}, },
{ {
key: 'clientSecret', key: 'clientSecret',
title: '客户端秘钥', title: $t('page.system.client.clientSecret'),
align: 'center', align: 'center',
minWidth: 120 minWidth: 120
}, },
{ {
key: 'grantTypeList', key: 'grantTypeList',
title: '授权类型', title: $t('page.system.client.grantTypeList'),
align: 'center', align: 'center',
minWidth: 120, minWidth: 120,
render: row => { render: row => {
@ -85,7 +87,7 @@ const {
}, },
{ {
key: 'deviceType', key: 'deviceType',
title: '设备类型', title: $t('page.system.client.deviceType'),
align: 'center', align: 'center',
minWidth: 120, minWidth: 120,
render: row => { render: row => {
@ -94,25 +96,25 @@ const {
}, },
{ {
key: 'activeTimeout', key: 'activeTimeout',
title: 'token活跃超时时间', title: $t('page.system.client.activeTimeout'),
align: 'center', align: 'center',
minWidth: 120, minWidth: 120,
render: row => { render: row => {
return `${row.activeTimeout}`; return `${row.activeTimeout}${$t('common.second')}`;
} }
}, },
{ {
key: 'timeout', key: 'timeout',
title: 'token固定超时', title: $t('page.system.client.timeout'),
align: 'center', align: 'center',
minWidth: 120, minWidth: 120,
render: row => { render: row => {
return `${row.timeout}`; return `${row.timeout}${$t('common.second')}`;
} }
}, },
{ {
key: 'status', key: 'status',
title: '状态', title: $t('page.system.client.status'),
align: 'center', align: 'center',
minWidth: 120, minWidth: 120,
render: row => { render: row => {
@ -205,7 +207,7 @@ async function handleExport() {
<template> <template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto"> <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<ClientSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" /> <ClientSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
<NCard title="客户端列表" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper"> <NCard :title="$t('page.system.client.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra> <template #header-extra>
<TableHeaderOperation <TableHeaderOperation
v-model:columns="columnChecks" v-model:columns="columnChecks"

View File

@ -3,6 +3,7 @@ import { computed, reactive, watch } from 'vue';
import { fetchCreateClient, fetchUpdateClient } from '@/service/api/system/client'; import { fetchCreateClient, fetchUpdateClient } from '@/service/api/system/client';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales'; import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'ClientOperateDrawer' name: 'ClientOperateDrawer'
}); });
@ -31,8 +32,8 @@ const { createRequiredRule } = useFormRules();
const title = computed(() => { const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = { const titles: Record<NaiveUI.TableOperateType, string> = {
add: '新增客户端', add: $t('page.system.client.addClient'),
edit: '编辑客户端' edit: $t('page.system.client.editClient')
}; };
return titles[props.operateType]; return titles[props.operateType];
}); });
@ -46,7 +47,7 @@ function createDefaultModel(): Model {
clientKey: '', clientKey: '',
clientSecret: '', clientSecret: '',
grantTypeList: [], grantTypeList: [],
deviceType: '', deviceType: undefined,
activeTimeout: 1800, activeTimeout: 1800,
timeout: 604800, timeout: 604800,
status: '0' status: '0'
@ -59,13 +60,13 @@ type RuleKey = Extract<
>; >;
const rules: Record<RuleKey, App.Global.FormRule> = { const rules: Record<RuleKey, App.Global.FormRule> = {
id: createRequiredRule('id不能为空'), id: createRequiredRule($t('page.system.client.form.clientId.required')),
clientKey: createRequiredRule('客户端key不能为空'), clientKey: createRequiredRule($t('page.system.client.form.clientKey.required')),
clientSecret: createRequiredRule('客户端秘钥不能为空'), clientSecret: createRequiredRule($t('page.system.client.form.clientSecret.required')),
grantTypeList: createRequiredRule('授权类型不能为空'), grantTypeList: createRequiredRule($t('page.system.client.form.grantTypeList.required')),
deviceType: createRequiredRule('设备类型不能为空'), deviceType: createRequiredRule($t('page.system.client.form.deviceType.required')),
activeTimeout: createRequiredRule('token活跃超时时间不能为空'), activeTimeout: createRequiredRule($t('page.system.client.form.activeTimeout.required')),
timeout: createRequiredRule('token固定超时不能为空') timeout: createRequiredRule($t('page.system.client.form.timeout.required'))
}; };
function handleUpdateModelWhenEdit() { function handleUpdateModelWhenEdit() {
@ -117,7 +118,7 @@ async function handleSubmit() {
if (error) return; if (error) return;
} }
window.$message?.success($t('common.updateSuccess')); window.$message?.success($t('common.saveSuccess'));
closeDrawer(); closeDrawer();
emit('submitted'); emit('submitted');
} }
@ -134,59 +135,84 @@ watch(visible, () => {
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%"> <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
<NDrawerContent :title="title" :native-scrollbar="false" closable> <NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules"> <NForm ref="formRef" :model="model" :rules="rules">
<NFormItem v-if="operateType === 'edit'" label="客户端 ID" path="clientId"> <NFormItem v-if="operateType === 'edit'" :label="$t('page.system.client.clientId')" path="clientId">
<NInput v-model:value="model.clientId" disabled placeholder="请输入客户端 ID" /> <NInput
v-model:value="model.clientId"
disabled
:placeholder="$t('page.system.client.form.clientId.required')"
/>
</NFormItem> </NFormItem>
<NFormItem label="客户端 key" path="clientKey"> <NFormItem :label="$t('page.system.client.clientKey')" path="clientKey">
<NInput v-model:value="model.clientKey" :disabled="operateType === 'edit'" placeholder="请输入客户端 key" /> <NInput
v-model:value="model.clientKey"
:disabled="operateType === 'edit'"
:placeholder="$t('page.system.client.form.clientKey.required')"
/>
</NFormItem> </NFormItem>
<NFormItem label="客户端秘钥" path="clientSecret"> <NFormItem :label="$t('page.system.client.clientSecret')" path="clientSecret">
<NInput <NInput
v-model:value="model.clientSecret" v-model:value="model.clientSecret"
:disabled="operateType === 'edit'" :disabled="operateType === 'edit'"
placeholder="请输入客户端秘钥" :placeholder="$t('page.system.client.form.clientSecret.required')"
/> />
</NFormItem> </NFormItem>
<NFormItem label="授权类型" path="grantType"> <NFormItem :label="$t('page.system.client.grantTypeList')" path="grantTypeList">
<DictSelect v-model:value="model.grantTypeList" dict-code="sys_grant_type" multiple /> <DictSelect
v-model:value="model.grantTypeList"
:placeholder="$t('page.system.client.form.grantTypeList.required')"
dict-code="sys_grant_type"
multiple
/>
</NFormItem> </NFormItem>
<NFormItem label="设备类型" path="deviceType"> <NFormItem :label="$t('page.system.client.deviceType')" path="deviceType">
<DictSelect v-model:value="model.deviceType" dict-code="sys_device_type" /> <DictSelect
v-model:value="model.deviceType"
:placeholder="$t('page.system.client.form.deviceType.required')"
dict-code="sys_device_type"
/>
</NFormItem> </NFormItem>
<NFormItem label="token活跃超时时间" path="activeTimeout"> <NFormItem :label="$t('page.system.client.activeTimeout')" path="activeTimeout">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="指定时间无操作则过期(单位:秒), 默认30分钟(1800秒)" /> <FormTip :content="$t('page.system.client.form.activeTimeout.tooltip')" />
<span class="pl-3px">token活跃超时时间</span> <span class="pl-3px">{{ $t('page.system.client.activeTimeout') }}</span>
</div> </div>
</template> </template>
<NInputNumber v-model:value="model.activeTimeout" placeholder="请输入token活跃超时时间"> <NInputNumber
v-model:value="model.activeTimeout"
:placeholder="$t('page.system.client.form.activeTimeout.required')"
>
<template #suffix> <template #suffix>
<span class="text-sm"></span> <span class="text-sm">{{ $t('common.second') }}</span>
</template> </template>
</NInputNumber> </NInputNumber>
</NFormItem> </NFormItem>
<NFormItem label="token固定超时" path="timeout"> <NFormItem :label="$t('page.system.client.timeout')" path="timeout">
<template #label> <template #label>
<div class="flex-center"> <div class="flex-center">
<FormTip content="指定时间必定过期(单位:秒),默认七天(604800秒)" /> <FormTip :content="$t('page.system.client.form.timeout.tooltip')" />
<span class="pl-3px">token固定超时</span> <span class="pl-3px">{{ $t('page.system.client.timeout') }}</span>
</div> </div>
</template> </template>
<NInputNumber v-model:value="model.timeout" placeholder="请输入token固定超时"> <NInputNumber v-model:value="model.timeout" :placeholder="$t('page.system.client.form.timeout.required')">
<template #suffix> <template #suffix>
<span class="text-sm"></span> <span class="text-sm">{{ $t('common.second') }}</span>
</template> </template>
</NInputNumber> </NInputNumber>
</NFormItem> </NFormItem>
<NFormItem label="状态" path="status"> <NFormItem :label="$t('page.system.client.status')" path="status">
<DictRadio v-model:value="model.status" :disabled="model.id == 1" dict-code="sys_normal_disable" /> <DictRadio
v-model:value="model.status"
:placeholder="$t('page.system.client.form.status.required')"
:disabled="model.id == 1"
dict-code="sys_normal_disable"
/>
</NFormItem> </NFormItem>
</NForm> </NForm>
<template #footer> <template #footer>
<NSpace :size="16"> <NSpace :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton> <NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton> <NButton type="primary" @click="handleSubmit">{{ $t('common.save') }}</NButton>
</NSpace> </NSpace>
</template> </template>
</NDrawerContent> </NDrawerContent>

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useNaiveForm } from '@/hooks/common/form'; import { useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales'; import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'ClientSearch' name: 'ClientSearch'
}); });
@ -33,16 +34,29 @@ async function search() {
<NCollapseItem :title="$t('common.search')" name="user-search"> <NCollapseItem :title="$t('common.search')" name="user-search">
<NForm ref="formRef" :model="model" label-placement="left" :label-width="90"> <NForm ref="formRef" :model="model" label-placement="left" :label-width="90">
<NGrid responsive="screen" item-responsive> <NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:6" label="客户端key" path="clientKey" class="pr-24px"> <NFormItemGi
<NInput v-model:value="model.clientKey" placeholder="请输入客户端key" /> span="24 s:12 m:6"
:label="$t('page.system.client.clientKey')"
path="clientKey"
class="pr-24px"
>
<NInput v-model:value="model.clientKey" :placeholder="$t('page.system.client.form.clientKey.required')" />
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="客户端秘钥" path="clientSecret" class="pr-24px"> <NFormItemGi
<NInput v-model:value="model.clientSecret" placeholder="请输入客户端秘钥" /> span="24 s:12 m:6"
:label="$t('page.system.client.clientSecret')"
path="clientSecret"
class="pr-24px"
>
<NInput
v-model:value="model.clientSecret"
:placeholder="$t('page.system.client.form.clientSecret.required')"
/>
</NFormItemGi> </NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="状态" path="status" class="pr-24px"> <NFormItemGi span="24 s:12 m:6" :label="$t('page.system.client.status')" path="status" class="pr-24px">
<DictSelect <DictSelect
v-model:value="model.status" v-model:value="model.status"
placeholder="请选择状态" :placeholder="$t('page.system.client.form.status.required')"
dict-code="sys_normal_disable" dict-code="sys_normal_disable"
clearable clearable
/> />