fix: 修复工作台快捷入口错误路由 + 打通 Drawer 重构遗留的编译错误

- workbench 快捷入口「操作日志/登录日志」指向不存在的 /system/log/*,
  按后端菜单真实路由改为 /log/operation 与 /log/login
- 6 个页面(admin/role/menu/community/listing/push)Drawer 重构误用
  内部包路径 @vben-core/popup-ui(app 未直接依赖),改从 @vben/common-ui
  导入 useVbenDrawer 并合并同源 import
- 清理未使用变量:push 页 ref、preferences.vue loadLocaleMessages、
  user-dropdown.vue ref
- preferences 默认配置补缺失字段 languageToggleButtonPosition:'none'
- menu 页 submit 改三元、log 页嵌套三元抽 methodTheme 辅助函数
- vue-tsc 与全部 lint 钩子通过
This commit is contained in:
夏犀麟 2026-08-27 12:24:43 +08:00
parent e069a823b3
commit 7dc9bc6c2a
36 changed files with 247 additions and 924 deletions

View File

@ -1,6 +1,7 @@
# admin.xpcool.com 变更记录 # admin.xpcool.com 变更记录
> 倒序最新在上。格式YYYY-MM-DD | 类型 | 摘要 > 倒序最新在上。格式YYYY-MM-DD | 类型 | 摘要
2026-08-27 | CHG | 三大改造①移除国际化多语言仅留中文——preferences.ts 固定 locale=zh-CN删除 header/toolbar/user-dropdown 三处语言切换入口、偏好面板语言选择项(general.vue/appLocale/widget.vue languageToggle 位置配置)config.ts 禁 languageToggle 且 order 移除;删除主应用与 packages/locales 的 en-US 语言包locales/index.ts loadDayjsLocale 仅留 zh-CN。②新增主题色管理——theme.css 品牌色改为引用 --primary 变量(不再写死 #4f5bff),背景/主按钮渐变用 color-mix 跟随主题色,配合 useTDesignDesignTokens 使偏好面板 BuiltinTheme 切换主题色真正生效constants.ts default 主题色与 config.ts colorPrimary 统一为靛蓝 hsl(235 100% 65%)。③业务弹窗统一为 vben 抽屉——6 页(system/{role,menu,admin}、recruitment/push、house/{listing,community})的 t-dialog 全部替换为 useVbenDrawer(<Drawer>)drawerApi.open/close/setState(标题)/lock(提交中)community/listing 抽屉加宽(class w-160/w-150)
2026-08-27 | CHG | 服务器安全日志页挂在系统监控菜单下id=96views/monitor/security/index.vue——顶部 4 统计卡片SSH爆破/fail2ban封禁/成功登录/总数)+ TOP 攻击源标签区 + 筛选来源IP/事件类型/时间范围 t-date-range-picker+ Vben VxeTable 列表(时间/来源IP/来源端口/目标端口/事件类型标签/详情 tooltip事件标签按类型配色api/serverSecurity.tslist/stats 全 POST无 URL 参数typecheck 通过 2026-08-27 | CHG | 服务器安全日志页挂在系统监控菜单下id=96views/monitor/security/index.vue——顶部 4 统计卡片SSH爆破/fail2ban封禁/成功登录/总数)+ TOP 攻击源标签区 + 筛选来源IP/事件类型/时间范围 t-date-range-picker+ Vben VxeTable 列表(时间/来源IP/来源端口/目标端口/事件类型标签/详情 tooltip事件标签按类型配色api/serverSecurity.tslist/stats 全 POST无 URL 参数typecheck 通过
2026-08-27 | CHG | 全量表格重构views 下全部 11 个 t-table 页面统一改为 Vben Vxe TableuseVbenVxeGrid#/adapter/vxe-table+ Vben TableAction 行操作——system/{log(范式),login-log,admin,role}、recruitment/{info,crawler,push}、house/{data,listing,community,presale}、dashboard/workbench两处本地小表 proxyConfig.enabled=false + gridApi.setGridOptions 写数据。要点gridOptions 需注解 VxeTableGridOptions否则 type:'seq' 推断 string 报错);单元格自定义用 columns[].slots.default + h(TDesign组件);远程分页走 proxyConfig.ajax.query 返回 {items,total};筛选区保留手写 TDesign 组件不动admin/role/listing/community 行操作 ActionItem 带 auth 权限码、删除 danger 标红listing 多选批量标记改 vxe checkboxConfig + gridApi.grid.getCheckboxRecords()confirm 弹窗交互(删除/重置密码)保留 DialogPlugin 不变typecheck 通过 2026-08-27 | CHG | 全量表格重构views 下全部 11 个 t-table 页面统一改为 Vben Vxe TableuseVbenVxeGrid#/adapter/vxe-table+ Vben TableAction 行操作——system/{log(范式),login-log,admin,role}、recruitment/{info,crawler,push}、house/{data,listing,community,presale}、dashboard/workbench两处本地小表 proxyConfig.enabled=false + gridApi.setGridOptions 写数据。要点gridOptions 需注解 VxeTableGridOptions否则 type:'seq' 推断 string 报错);单元格自定义用 columns[].slots.default + h(TDesign组件);远程分页走 proxyConfig.ajax.query 返回 {items,total};筛选区保留手写 TDesign 组件不动admin/role/listing/community 行操作 ActionItem 带 auth 权限码、删除 danger 标红listing 多选批量标记改 vxe checkboxConfig + gridApi.grid.getCheckboxRecords()confirm 弹窗交互(删除/重置密码)保留 DialogPlugin 不变typecheck 通过

View File

@ -39,29 +39,13 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
} }
/** /**
* dayjs的语言包 * dayjs的语言包
* @param lang * @param lang
*/ */
async function loadDayjsLocale(lang: SupportedLanguagesType) { async function loadDayjsLocale(lang: SupportedLanguagesType) {
let locale; if (lang === 'zh-CN') {
switch (lang) { const locale = await import('dayjs/locale/zh-cn');
case 'en-US': {
locale = await import('dayjs/locale/en');
break;
}
case 'zh-CN': {
locale = await import('dayjs/locale/zh-cn');
break;
}
// 默认使用英语
default: {
locale = await import('dayjs/locale/en');
}
}
if (locale) {
dayjs.locale(locale); dayjs.locale(locale);
} else {
console.error(`Failed to load dayjs locale for ${lang}`);
} }
} }

View File

@ -1,13 +0,0 @@
{
"title": "Demos",
"tdesign": "TDesign Vue",
"vben": {
"title": "Project",
"about": "About",
"document": "Document",
"antdv": "Ant Design Vue Version",
"antdv-next": "Antdv Next Version",
"naive-ui": "Naive UI Version",
"element-plus": "Element Plus Version"
}
}

View File

@ -1,15 +0,0 @@
{
"auth": {
"login": "Login",
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password",
"profile": "Profile"
},
"dashboard": {
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
}
}

View File

@ -16,6 +16,8 @@ export const overridesPreferences = defineOverridesPreferences({
accessMode: 'backend', accessMode: 'backend',
// 后端暂未实现 refresh 接口,关闭 token 自动刷新 // 后端暂未实现 refresh 接口,关闭 token 自动刷新
enableRefreshToken: false, enableRefreshToken: false,
// 固定中文:移除国际化多语言,仅保留中文
locale: 'zh-CN',
}, },
copyright: appCopyrightPreferences, copyright: appCopyrightPreferences,
}); });

View File

@ -1,19 +1,20 @@
/** /**
* 全局主题美化 * 全局主题美化
* 设计语言靛蓝(indigo)主色 + (violet)渐变点缀柔和圆角轻投影有纵深的浅色背景 * 设计语言跟随系统主题色可在偏好设置中切换主题色柔和圆角轻投影有纵深的浅色背景
* 说明Vben useTDesignDesignTokens 会用 JS 把品牌色写到 documentElement * 说明Vben useTDesignDesignTokens 会用 JS 根据主题色动态把 --td-* 品牌色写到
* 普通 :root 规则会被内联样式压过因此对关键 --td-* 变量加 !important 强制生效 * documentElement 因此这里不再用固定色值覆盖品牌色改为引用 --primary
* 变量保证切换主题色时整个系统颜色同步变化
*/ */
:root { :root {
/* —— 品牌主色(靛蓝 #4f5bff → 紫 #7c3aed 渐变体系) —— */ /* —— 品牌主色:跟随系统主题色(--primary支持在系统内切换 —— */
--td-brand-color: #4f5bff !important; --td-brand-color: var(--primary);
--td-brand-color-hover: #6b75ff !important; --td-brand-color-hover: var(--primary-600);
--td-brand-color-active: #3a45e0 !important; --td-brand-color-active: var(--primary-700);
--td-brand-color-disabled: #c2c7ff !important; --td-brand-color-disabled: var(--primary-200);
--td-brand-color-light: #eef0ff !important; --td-brand-color-light: var(--primary-50);
--td-brand-color-light-hover: #e0e3ff !important; --td-brand-color-light-hover: var(--primary-100);
--td-brand-color-light-active: #d6daff !important; --td-brand-color-light-active: var(--primary-200);
/* —— 圆角:更现代、更柔 —— */ /* —— 圆角:更现代、更柔 —— */
--td-radius-small: 6px !important; --td-radius-small: 6px !important;
@ -49,12 +50,12 @@ body {
background-color: #f5f7fb; background-color: #f5f7fb;
background-image: radial-gradient( background-image: radial-gradient(
1200px 600px at 100% -10%, 1200px 600px at 100% -10%,
rgb(79 91 255 / 6%), color-mix(in srgb, var(--primary) 8%, transparent),
transparent 60% transparent 60%
), ),
radial-gradient( radial-gradient(
900px 500px at -10% 110%, 900px 500px at -10% 110%,
rgb(124 58 237 / 5%), color-mix(in srgb, var(--primary) 7%, transparent),
transparent 55% transparent 55%
); );
background-attachment: fixed; background-attachment: fixed;
@ -94,20 +95,24 @@ body {
background-color: #fafbff !important; background-color: #fafbff !important;
} }
/* 主按钮:带品牌渐变,更出彩 */ /* 主按钮:带品牌渐变,更出彩(跟随主题色) */
.t-button--theme-primary:not(.t-button--variant-outline):not( .t-button--theme-primary:not(.t-button--variant-outline):not(
.t-button--variant-text .t-button--variant-text
):not(.t-button--variant-dashed) { ):not(.t-button--variant-dashed) {
background: linear-gradient(135deg, #4f5bff 0%, #7c3aed 100%); background: linear-gradient(
135deg,
var(--primary) 0%,
var(--primary-700) 100%
);
border: none; border: none;
box-shadow: 0 4px 12px rgb(79 91 255 / 28%); box-shadow: 0 4px 12px color-mix(in srgb, var(--primary) 28%, transparent);
transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease; transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
} }
.t-button--theme-primary:not(.t-button--variant-outline):not( .t-button--theme-primary:not(.t-button--variant-outline):not(
.t-button--variant-text .t-button--variant-text
):not(.t-button--variant-dashed):hover { ):not(.t-button--variant-dashed):hover {
box-shadow: 0 6px 16px rgb(79 91 255 / 36%); box-shadow: 0 6px 16px color-mix(in srgb, var(--primary) 36%, transparent);
filter: brightness(1.05); filter: brightness(1.05);
transform: translateY(-1px); transform: translateY(-1px);
} }
@ -142,12 +147,12 @@ html[theme-mode='dark'] body {
background-color: #16171a; background-color: #16171a;
background-image: radial-gradient( background-image: radial-gradient(
1200px 600px at 100% -10%, 1200px 600px at 100% -10%,
rgb(79 91 255 / 12%), color-mix(in srgb, var(--primary) 14%, transparent),
transparent 60% transparent 60%
), ),
radial-gradient( radial-gradient(
900px 500px at -10% 110%, 900px 500px at -10% 110%,
rgb(124 58 237 / 10%), color-mix(in srgb, var(--primary) 12%, transparent),
transparent 55% transparent 55%
); );
} }

View File

@ -93,13 +93,13 @@ const quickNavs = [
}, },
{ {
title: '操作日志', title: '操作日志',
path: '/system/log/operation', path: '/log/operation',
icon: ICONS.log, icon: ICONS.log,
gradient: 'linear-gradient(135deg,#8b5cf6,#a855f7)', gradient: 'linear-gradient(135deg,#8b5cf6,#a855f7)',
}, },
{ {
title: '登录日志', title: '登录日志',
path: '/system/log/login', path: '/log/login',
icon: ICONS.login, icon: ICONS.login,
gradient: 'linear-gradient(135deg,#f59e0b,#fbbf24)', gradient: 'linear-gradient(135deg,#f59e0b,#fbbf24)',
}, },

View File

@ -5,7 +5,7 @@ import type { CommunityItem } from '#/api/house';
import { h, reactive, ref } from 'vue'; import { h, reactive, ref } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { Page, VbenTableAction } from '@vben/common-ui'; import { Page, useVbenDrawer, VbenTableAction } from '@vben/common-ui';
import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next'; import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
@ -17,12 +17,17 @@ import {
updateCommunity, updateCommunity,
} from '#/api/house'; } from '#/api/house';
// + / + // + / +
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const dialogOpen = ref(false);
const editing = ref<CommunityItem | null>(null); const editing = ref<CommunityItem | null>(null);
// /vben
const [Drawer, drawerApi] = useVbenDrawer({
title: '新增小区',
onConfirm: submit,
});
const regionOptions = [ const regionOptions = [
'云岩区', '云岩区',
'南明区', '南明区',
@ -72,13 +77,15 @@ function openCreate() {
propertyFee: 0, propertyFee: 0,
developer: '', developer: '',
}); });
dialogOpen.value = true; drawerApi.setState({ title: '新增小区' });
drawerApi.open();
} }
function openEdit(row: CommunityItem) { function openEdit(row: CommunityItem) {
editing.value = row; editing.value = row;
Object.assign(form, { ...row }); Object.assign(form, { ...row });
dialogOpen.value = true; drawerApi.setState({ title: '编辑小区' });
drawerApi.open();
} }
async function submit() { async function submit() {
@ -87,7 +94,7 @@ async function submit() {
? await updateCommunity(editing.value.id, form) ? await updateCommunity(editing.value.id, form)
: await createCommunity(form); : await createCommunity(form);
MessagePlugin.success('保存成功'); MessagePlugin.success('保存成功');
dialogOpen.value = false; drawerApi.close();
gridApi.query(); gridApi.query();
} }
@ -178,7 +185,7 @@ function reset() {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mb-3 flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<t-input <t-input
v-model="query.keyword" v-model="query.keyword"
@ -209,12 +216,8 @@ function reset() {
<!-- 小区列表Vben Vxe Table + TableAction --> <!-- 小区列表Vben Vxe Table + TableAction -->
<Grid /> <Grid />
<t-dialog <!-- 新增/编辑小区抽屉vben 封装抽屉组件 -->
v-model:visible="dialogOpen" <Drawer class="w-160">
:header="editing ? '编辑小区' : '新增小区'"
width="640px"
:on-confirm="submit"
>
<t-form label-align="right" label-width="90px"> <t-form label-align="right" label-width="90px">
<div class="grid grid-cols-2 gap-x-4"> <div class="grid grid-cols-2 gap-x-4">
<t-form-item label="小区名称" required> <t-form-item label="小区名称" required>
@ -258,6 +261,6 @@ function reset() {
</t-form-item> </t-form-item>
</div> </div>
</t-form> </t-form>
</t-dialog> </Drawer>
</Page> </Page>
</template> </template>

View File

@ -75,7 +75,7 @@ function reset() {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mb-3 flex items-center gap-2"> <div class="flex items-center gap-2">
<t-input <t-input
v-model="query.keyword" v-model="query.keyword"
clearable clearable

View File

@ -5,7 +5,7 @@ import type { ListingItem } from '#/api/house';
import { h, reactive, ref } from 'vue'; import { h, reactive, ref } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { Page, VbenTableAction } from '@vben/common-ui'; import { Page, useVbenDrawer, VbenTableAction } from '@vben/common-ui';
import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next'; import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next';
@ -20,9 +20,14 @@ import {
// + //+ / // + //+ /
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const dialogOpen = ref(false);
const editing = ref<ListingItem | null>(null); const editing = ref<ListingItem | null>(null);
// vben
const [Drawer, drawerApi] = useVbenDrawer({
title: '编辑房源',
onConfirm: submit,
});
const regionOptions = [ const regionOptions = [
'云岩区', '云岩区',
'南明区', '南明区',
@ -69,7 +74,7 @@ async function onBatchMark(field: string, value: number) {
function openEdit(row: ListingItem) { function openEdit(row: ListingItem) {
editing.value = row; editing.value = row;
dialogOpen.value = true; drawerApi.open();
} }
async function submit() { async function submit() {
@ -84,7 +89,7 @@ async function submit() {
}); });
} }
MessagePlugin.success('保存成功'); MessagePlugin.success('保存成功');
dialogOpen.value = false; drawerApi.close();
gridApi.query(); gridApi.query();
} }
@ -257,7 +262,7 @@ function reset() {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mb-3 flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<t-input <t-input
v-model="query.keyword" v-model="query.keyword"
clearable clearable
@ -323,12 +328,8 @@ function reset() {
<!-- 房源列表Vben Vxe Table + TableAction --> <!-- 房源列表Vben Vxe Table + TableAction -->
<Grid /> <Grid />
<t-dialog <!-- 编辑房源抽屉vben 封装抽屉组件 -->
v-model:visible="dialogOpen" <Drawer class="w-150">
header="编辑房源"
width="560px"
:on-confirm="submit"
>
<t-form v-if="editing" label-align="right" label-width="90px"> <t-form v-if="editing" label-align="right" label-width="90px">
<div class="grid grid-cols-2 gap-x-4"> <div class="grid grid-cols-2 gap-x-4">
<t-form-item label="挂牌价(万)"> <t-form-item label="挂牌价(万)">
@ -358,6 +359,6 @@ function reset() {
</t-form-item> </t-form-item>
</div> </div>
</t-form> </t-form>
</t-dialog> </Drawer>
</Page> </Page>
</template> </template>

View File

@ -89,7 +89,7 @@ function reset() {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mb-3 flex items-center gap-2"> <div class="flex items-center gap-2">
<t-input <t-input
v-model="query.keyword" v-model="query.keyword"
clearable clearable

View File

@ -130,7 +130,7 @@ onMounted(load);
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mb-3 flex items-center gap-2"> <div class="flex items-center gap-2">
<t-button <t-button
v-if="hasAccessByCodes(['recruitment:crawler:trigger'])" v-if="hasAccessByCodes(['recruitment:crawler:trigger'])"
theme="primary" theme="primary"

View File

@ -162,7 +162,7 @@ function copyUrl() {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mb-3 flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<t-input <t-input
v-model="query.keyword" v-model="query.keyword"
clearable clearable
@ -225,7 +225,7 @@ function copyUrl() {
> >
<div v-if="detailLoading" class="p-6 text-center opacity-60">加载中</div> <div v-if="detailLoading" class="p-6 text-center opacity-60">加载中</div>
<div v-else-if="detail"> <div v-else-if="detail">
<div class="mb-3 flex flex-wrap gap-2 text-xs"> <div class="flex flex-wrap gap-2 text-xs">
<t-tag theme="primary" variant="light"> <t-tag theme="primary" variant="light">
{{ detail.categoryName }} {{ detail.categoryName }}
</t-tag> </t-tag>

View File

@ -3,10 +3,10 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
// + / + + Bark // + / + + Bark
import type { SubscriptionItem } from '#/api/recruitment'; import type { SubscriptionItem } from '#/api/recruitment';
import { h, onMounted, reactive, ref } from 'vue'; import { h, onMounted, reactive } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { Page, VbenTableAction } from '@vben/common-ui'; import { Page, useVbenDrawer, VbenTableAction } from '@vben/common-ui';
import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next'; import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next';
@ -31,9 +31,11 @@ function categoryLabels(cats: number[] | undefined): string {
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
// // vben
const dialogOpen = ref(false); const [Drawer, drawerApi] = useVbenDrawer({
const submitting = ref(false); title: '新增订阅',
onConfirm: submit,
});
const form = reactive({ const form = reactive({
id: 0, id: 0,
name: '', name: '',
@ -62,7 +64,8 @@ function openCreate() {
pushTime: '08:00', pushTime: '08:00',
enabled: 1, enabled: 1,
}); });
dialogOpen.value = true; drawerApi.setState({ title: '新增订阅' });
drawerApi.open();
} }
function openEdit(row: SubscriptionItem) { function openEdit(row: SubscriptionItem) {
@ -76,7 +79,8 @@ function openEdit(row: SubscriptionItem) {
pushTime: row.pushTime || '08:00', pushTime: row.pushTime || '08:00',
enabled: row.enabled, enabled: row.enabled,
}); });
dialogOpen.value = true; drawerApi.setState({ title: '编辑订阅' });
drawerApi.open();
} }
async function submit() { async function submit() {
@ -84,7 +88,7 @@ async function submit() {
MessagePlugin.warning('请填写名称与设备 Key'); MessagePlugin.warning('请填写名称与设备 Key');
return; return;
} }
submitting.value = true; drawerApi.lock(true);
try { try {
await saveSubscription({ await saveSubscription({
id: form.id || undefined, id: form.id || undefined,
@ -97,10 +101,10 @@ async function submit() {
enabled: form.enabled, enabled: form.enabled,
}); });
MessagePlugin.success(form.id ? '更新成功' : '新增成功'); MessagePlugin.success(form.id ? '更新成功' : '新增成功');
dialogOpen.value = false; drawerApi.close();
await load(); await load();
} finally { } finally {
submitting.value = false; drawerApi.lock(false);
} }
} }
@ -242,7 +246,7 @@ onMounted(load);
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mb-3 flex items-center gap-2"> <div class="flex items-center gap-2">
<t-button <t-button
v-if="hasAccessByCodes(['recruitment:push:save'])" v-if="hasAccessByCodes(['recruitment:push:save'])"
theme="primary" theme="primary"
@ -255,13 +259,8 @@ onMounted(load);
<!-- 订阅列表Vben Vxe Table + TableAction --> <!-- 订阅列表Vben Vxe Table + TableAction -->
<Grid /> <Grid />
<t-dialog <!-- 新增/编辑订阅抽屉vben 封装抽屉组件 -->
v-model:visible="dialogOpen" <Drawer>
:header="form.id ? '编辑订阅' : '新增订阅'"
width="560px"
:confirm-btn="{ loading: submitting }"
@confirm="submit"
>
<t-form label-align="right" label-width="90px"> <t-form label-align="right" label-width="90px">
<t-form-item label="名称"> <t-form-item label="名称">
<t-input v-model="form.name" placeholder="如:每日贵阳公务员" /> <t-input v-model="form.name" placeholder="如:每日贵阳公务员" />
@ -301,6 +300,6 @@ onMounted(load);
<t-switch v-model="form.enabled" :custom-value="[1, 0]" /> <t-switch v-model="form.enabled" :custom-value="[1, 0]" />
</t-form-item> </t-form-item>
</t-form> </t-form>
</t-dialog> </Drawer>
</Page> </Page>
</template> </template>

View File

@ -5,7 +5,7 @@ import type { AdminItem, RoleItem } from '#/api/system';
import { h, onMounted, reactive, ref } from 'vue'; import { h, onMounted, reactive, ref } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { Page, VbenTableAction } from '@vben/common-ui'; import { Page, useVbenDrawer, VbenTableAction } from '@vben/common-ui';
import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next'; import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next';
@ -23,9 +23,14 @@ import {
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const roleOptions = ref<{ label: string; value: number }[]>([]); const roleOptions = ref<{ label: string; value: number }[]>([]);
const dialogOpen = ref(false);
const editing = ref<AdminItem | null>(null); const editing = ref<AdminItem | null>(null);
// /vben
const [Drawer, drawerApi] = useVbenDrawer({
title: '新增人员',
onConfirm: submit,
});
// grid // grid
const query = reactive({ keyword: '' }); const query = reactive({ keyword: '' });
const form = reactive({ const form = reactive({
@ -53,7 +58,8 @@ function openCreate() {
status: 1, status: 1,
roleIds: [], roleIds: [],
}); });
dialogOpen.value = true; drawerApi.setState({ title: '新增人员' });
drawerApi.open();
} }
function openEdit(row: AdminItem) { function openEdit(row: AdminItem) {
@ -65,7 +71,8 @@ function openEdit(row: AdminItem) {
status: row.status, status: row.status,
roleIds: row.roleIds ?? [], roleIds: row.roleIds ?? [],
}); });
dialogOpen.value = true; drawerApi.setState({ title: '编辑人员' });
drawerApi.open();
} }
async function submit() { async function submit() {
@ -83,7 +90,7 @@ async function submit() {
roleIds: form.roleIds, roleIds: form.roleIds,
}); });
MessagePlugin.success('保存成功'); MessagePlugin.success('保存成功');
dialogOpen.value = false; drawerApi.close();
gridApi.query(); gridApi.query();
} }
@ -220,7 +227,7 @@ onMounted(loadRoles);
</div> </div>
<t-card :bordered="false" class="wb-filter-card"> <t-card :bordered="false" class="wb-filter-card">
<div class="mb-3 flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<t-input <t-input
v-model="query.keyword" v-model="query.keyword"
@ -246,11 +253,8 @@ onMounted(loadRoles);
<Grid /> <Grid />
</t-card> </t-card>
<t-dialog <!-- 新增/编辑人员抽屉vben 封装抽屉组件 -->
v-model:visible="dialogOpen" <Drawer>
:header="editing ? '编辑人员' : '新增人员'"
:on-confirm="submit"
>
<t-form label-align="right" label-width="80px"> <t-form label-align="right" label-width="80px">
<t-form-item label="用户名" required> <t-form-item label="用户名" required>
<t-input <t-input
@ -281,6 +285,6 @@ onMounted(loadRoles);
/> />
</t-form-item> </t-form-item>
</t-form> </t-form>
</t-dialog> </Drawer>
</Page> </Page>
</template> </template>

View File

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { LogItem } from '#/api/system'; import type { LogItem } from '#/api/system';
import { h, reactive, ref } from 'vue'; import { h, reactive, ref } from 'vue';
@ -8,7 +9,7 @@ import { Page } from '@vben/common-ui';
import { MessagePlugin, Tag } from 'tdesign-vue-next'; import { MessagePlugin, Tag } from 'tdesign-vue-next';
import { useVbenVxeGrid, type VxeTableGridOptions } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getSystemLogList } from '#/api/system'; import { getSystemLogList } from '#/api/system';
// Vben Vxe Table + TDesign // Vben Vxe Table + TDesign
@ -47,6 +48,14 @@ const sortMap: Record<string, { orderBy: string; orderDir: string }> = {
// HTTP // HTTP
const METHOD_OPTIONS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']; const METHOD_OPTIONS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
/** HTTP 方法 -> 标签主题色 */
function methodTheme(method: string) {
if (method === 'POST') return 'primary';
if (method === 'GET') return 'success';
if (method === 'PUT') return 'warning';
return 'danger';
}
const gridOptions: VxeTableGridOptions = { const gridOptions: VxeTableGridOptions = {
columns: [ columns: [
{ type: 'seq', width: 50, title: '#' }, { type: 'seq', width: 50, title: '#' },
@ -62,14 +71,7 @@ const gridOptions: VxeTableGridOptions = {
h( h(
Tag, Tag,
{ {
theme: theme: methodTheme(row.method),
row.method === 'POST'
? 'primary'
: row.method === 'GET'
? 'success'
: row.method === 'PUT'
? 'warning'
: 'danger',
variant: 'light', variant: 'light',
size: 'small', size: 'small',
}, },
@ -224,92 +226,92 @@ function copyParam(row: LogItem) {
</div> </div>
<t-card :bordered="false" class="wb-filter-card"> <t-card :bordered="false" class="wb-filter-card">
<div class="mb-3 flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<t-date-range-picker <t-date-range-picker
v-model="timeRange" v-model="timeRange"
value-type="YYYY-MM-DD" value-type="YYYY-MM-DD"
clearable clearable
separator="~" separator="~"
:placeholder="['操作开始日期', '操作结束日期']" :placeholder="['操作开始日期', '操作结束日期']"
style="width: 280px" style="width: 280px"
/>
<t-input
v-model="query.username"
clearable
placeholder="操作账号"
style="width: 150px"
@enter="search"
/>
<t-input
v-model="query.keyword"
clearable
placeholder="权限标识 / 路径 / IP"
style="width: 220px"
@enter="search"
/>
<t-input
v-model="query.ip"
clearable
placeholder="来源 IP"
style="width: 150px"
@enter="search"
/>
<t-input
v-model="query.ipLocation"
clearable
placeholder="IP 归属地"
style="width: 160px"
@enter="search"
/>
<t-select
v-model="query.method"
clearable
placeholder="方法"
filterable
style="width: 120px"
:options="METHOD_OPTIONS.map((m) => ({ label: m, value: m }))"
/>
<t-select
v-model="query.status"
style="width: 120px"
:options="[
{ label: '全部结果', value: 0 },
{ label: '成功(2xx)', value: 1 },
{ label: '失败(非2xx)', value: 2 },
]"
/>
<div class="flex items-center gap-1">
<t-input-number
v-model="query.minDuration"
:min="0"
theme="column"
placeholder="耗时≥"
style="width: 90px"
/> />
<span class="text-gray-400">~</span> <t-input
<t-input-number v-model="query.username"
v-model="query.maxDuration" clearable
:min="0" placeholder="操作账号"
theme="column" style="width: 150px"
placeholder="耗时≤" @enter="search"
style="width: 90px"
/> />
<span class="text-gray-400">ms</span> <t-input
v-model="query.keyword"
clearable
placeholder="权限标识 / 路径 / IP"
style="width: 220px"
@enter="search"
/>
<t-input
v-model="query.ip"
clearable
placeholder="来源 IP"
style="width: 150px"
@enter="search"
/>
<t-input
v-model="query.ipLocation"
clearable
placeholder="IP 归属地"
style="width: 160px"
@enter="search"
/>
<t-select
v-model="query.method"
clearable
placeholder="方法"
filterable
style="width: 120px"
:options="METHOD_OPTIONS.map((m) => ({ label: m, value: m }))"
/>
<t-select
v-model="query.status"
style="width: 120px"
:options="[
{ label: '全部结果', value: 0 },
{ label: '成功(2xx)', value: 1 },
{ label: '失败(非2xx)', value: 2 },
]"
/>
<div class="flex items-center gap-1">
<t-input-number
v-model="query.minDuration"
:min="0"
theme="column"
placeholder="耗时≥"
style="width: 90px"
/>
<span class="text-gray-400">~</span>
<t-input-number
v-model="query.maxDuration"
:min="0"
theme="column"
placeholder="耗时≤"
style="width: 90px"
/>
<span class="text-gray-400">ms</span>
</div>
<t-select
v-model="sortKey"
style="width: 160px"
:options="SORT_OPTIONS"
/>
<t-button
v-if="hasAccessByCodes(['system:log:list'])"
theme="primary"
@click="search"
>
查询
</t-button>
<t-button variant="outline" @click="reset">重置</t-button>
</div> </div>
<t-select
v-model="sortKey"
style="width: 160px"
:options="SORT_OPTIONS"
/>
<t-button
v-if="hasAccessByCodes(['system:log:list'])"
theme="primary"
@click="search"
>
查询
</t-button>
<t-button variant="outline" @click="reset">重置</t-button>
</div>
</t-card> </t-card>
<!-- 操作日志列表Vben Vxe Table --> <!-- 操作日志列表Vben Vxe Table -->

View File

@ -108,7 +108,7 @@ function reset() {
<t-card :bordered="false" class="wb-filter-card"> <t-card :bordered="false" class="wb-filter-card">
<!-- 顶部筛选区账号 + 结果 + 查询 / 重置 --> <!-- 顶部筛选区账号 + 结果 + 查询 / 重置 -->
<div class="mb-3 flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<t-input <t-input
v-model="query.username" v-model="query.username"

View File

@ -4,7 +4,7 @@ import type { MenuNode } from '#/api/system';
import { nextTick, onMounted, reactive, ref } from 'vue'; import { nextTick, onMounted, reactive, ref } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { Page } from '@vben/common-ui'; import { Page, useVbenDrawer } from '@vben/common-ui';
import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next'; import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
@ -17,9 +17,14 @@ const loading = ref(false);
const tree = ref<MenuNode[]>([]); const tree = ref<MenuNode[]>([]);
// t-enhanced-table // t-enhanced-table
const tableRef = ref<any>(null); const tableRef = ref<any>(null);
const dialogOpen = ref(false);
const editing = ref<MenuNode | null>(null); const editing = ref<MenuNode | null>(null);
// /vben
const [Drawer, drawerApi] = useVbenDrawer({
title: '新增菜单',
onConfirm: submit,
});
const form = reactive({ const form = reactive({
parentId: 0, parentId: 0,
name: '', name: '',
@ -60,7 +65,8 @@ function openCreate(parent?: MenuNode) {
status: 1, status: 1,
hidden: false, hidden: false,
}); });
dialogOpen.value = true; drawerApi.setState({ title: '新增菜单' });
drawerApi.open();
} }
function openEdit(row: MenuNode) { function openEdit(row: MenuNode) {
@ -77,17 +83,17 @@ function openEdit(row: MenuNode) {
status: row.status, status: row.status,
hidden: row.hidden, hidden: row.hidden,
}); });
dialogOpen.value = true; drawerApi.setState({ title: '编辑菜单' });
drawerApi.open();
} }
async function submit() { async function submit() {
if (editing.value) { //
await updateMenu(editing.value.id, { ...form }); editing.value
} else { ? await updateMenu(editing.value.id, { ...form })
await createMenu({ ...form }); : await createMenu({ ...form });
}
MessagePlugin.success('保存成功'); MessagePlugin.success('保存成功');
dialogOpen.value = false; drawerApi.close();
load(); load();
} }
@ -124,7 +130,7 @@ onMounted(load);
</div> </div>
<t-card :bordered="false" class="wb-filter-card"> <t-card :bordered="false" class="wb-filter-card">
<div class="mb-3 flex justify-end"> <div class="flex justify-end">
<t-button <t-button
v-if="hasAccessByCodes(['system:menu:create'])" v-if="hasAccessByCodes(['system:menu:create'])"
theme="primary" theme="primary"
@ -154,10 +160,8 @@ onMounted(load);
{{ row.name }} {{ row.name }}
</template> </template>
<template #typeText="{ row }"> <template #typeText="{ row }">
{{ {{ row.type === 1 ? '菜单' : '按钮' }}
row.type === 1 ? '菜单' : '按钮' </template>
}}
</template>
<template #op="{ row }"> <template #op="{ row }">
<t-button <t-button
v-if="hasAccessByCodes(['system:menu:create'])" v-if="hasAccessByCodes(['system:menu:create'])"
@ -190,11 +194,8 @@ onMounted(load);
</t-enhanced-table> </t-enhanced-table>
</t-card> </t-card>
<t-dialog <!-- 新增/编辑菜单抽屉vben 封装抽屉组件 -->
v-model:visible="dialogOpen" <Drawer>
:header="editing ? '编辑菜单' : '新增菜单'"
:on-confirm="submit"
>
<t-form label-align="right" label-width="80px"> <t-form label-align="right" label-width="80px">
<t-form-item label="上级 ID"> <t-form-item label="上级 ID">
<t-input-number v-model="form.parentId" :min="0" /> <t-input-number v-model="form.parentId" :min="0" />
@ -239,6 +240,6 @@ onMounted(load);
<t-switch v-model="form.hidden" /> <t-switch v-model="form.hidden" />
</t-form-item> </t-form-item>
</t-form> </t-form>
</t-dialog> </Drawer>
</Page> </Page>
</template> </template>

View File

@ -5,7 +5,7 @@ import type { MenuNode, RoleItem } from '#/api/system';
import { h, onMounted, reactive, ref } from 'vue'; import { h, onMounted, reactive, ref } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { Page, VbenTableAction } from '@vben/common-ui'; import { Page, useVbenDrawer, VbenTableAction } from '@vben/common-ui';
import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next'; import { DialogPlugin, MessagePlugin, Tag } from 'tdesign-vue-next';
@ -22,9 +22,14 @@ import {
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const menuTree = ref<any[]>([]); const menuTree = ref<any[]>([]);
const dialogOpen = ref(false);
const editing = ref<null | RoleItem>(null); const editing = ref<null | RoleItem>(null);
// /vben
const [Drawer, drawerApi] = useVbenDrawer({
title: '新增角色',
onConfirm: submit,
});
// grid // grid
const query = reactive({ keyword: '' }); const query = reactive({ keyword: '' });
const form = reactive({ const form = reactive({
@ -51,7 +56,8 @@ async function loadMenus() {
function openCreate() { function openCreate() {
editing.value = null; editing.value = null;
Object.assign(form, { code: '', name: '', status: 1, menuIds: [] }); Object.assign(form, { code: '', name: '', status: 1, menuIds: [] });
dialogOpen.value = true; drawerApi.setState({ title: '新增角色' });
drawerApi.open();
} }
function openEdit(row: RoleItem) { function openEdit(row: RoleItem) {
@ -62,7 +68,8 @@ function openEdit(row: RoleItem) {
status: row.status, status: row.status,
menuIds: row.menuIds ?? [], menuIds: row.menuIds ?? [],
}); });
dialogOpen.value = true; drawerApi.setState({ title: '编辑角色' });
drawerApi.open();
} }
async function submit() { async function submit() {
@ -80,7 +87,7 @@ async function submit() {
menuIds: form.menuIds, menuIds: form.menuIds,
}); });
MessagePlugin.success('保存成功'); MessagePlugin.success('保存成功');
dialogOpen.value = false; drawerApi.close();
gridApi.query(); gridApi.query();
} }
@ -185,7 +192,7 @@ onMounted(loadMenus);
</div> </div>
<t-card :bordered="false" class="wb-filter-card"> <t-card :bordered="false" class="wb-filter-card">
<div class="mb-3 flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<t-input <t-input
v-model="query.keyword" v-model="query.keyword"
@ -211,12 +218,8 @@ onMounted(loadMenus);
<Grid /> <Grid />
</t-card> </t-card>
<t-dialog <!-- 新增/编辑角色抽屉vben 封装抽屉组件 -->
v-model:visible="dialogOpen" <Drawer>
:header="editing ? '编辑角色' : '新增角色'"
width="560px"
:on-confirm="submit"
>
<t-form label-align="right" label-width="80px"> <t-form label-align="right" label-width="80px">
<t-form-item label="角色编码" required> <t-form-item label="角色编码" required>
<t-input <t-input
@ -242,6 +245,6 @@ onMounted(loadMenus);
/> />
</t-form-item> </t-form-item>
</t-form> </t-form>
</t-dialog> </Drawer>
</Page> </Page>
</template> </template>

View File

@ -1,42 +0,0 @@
$ pnpm -F @vben/web-tdesign run dev
. | [WARN] Unsupported engine: wanted: {"node":"^22.18.0 || ^24.12.0"} (current: {"node":"v23.0.0","pnpm":"11.16.0"})
$ pnpm vite --mode development
[Vben Form] BREAKING CHANGE: Named field slot control bindings moved to `slotProps.componentProps`. Replace `v-bind="slotProps"` with `v-bind="slotProps.componentProps"`. See https://doc.vben.pro/components/common-ui/vben-form.html
Port 5999 is in use, trying another one...
VITE v8.2.2 ready in 2781 ms
➜ Local: http://localhost:6000/
➜ Network: http://192.168.110.65:6000/ WLAN
➜ Network: http://172.20.192.1:6000/ vEthernet (Default …
➜ Vben Admin Docs: https://doc.vben.pro
 ➜ press h + enter to show help
17:20:13 [vite] .env.development changed, restarting server...
[Vben Form] BREAKING CHANGE: Named field slot control bindings moved to `slotProps.componentProps`. Replace `v-bind="slotProps"` with `v-bind="slotProps.componentProps"`. See https://doc.vben.pro/components/common-ui/vben-form.html
17:20:14 [vite] server restarted.
➜ Local: http://localhost:20000/
➜ Network: http://192.168.110.65:20000/ WLAN
➜ Network: http://172.20.192.1:20000/ vEthernet (Default …
➜ Vben Admin Docs: https://doc.vben.pro
17:20:53 [vite] .env.development changed, restarting server...
[Vben Form] BREAKING CHANGE: Named field slot control bindings moved to `slotProps.componentProps`. Replace `v-bind="slotProps"` with `v-bind="slotProps.componentProps"`. See https://doc.vben.pro/components/common-ui/vben-form.html
Port 20100 is in use, trying another one...
17:20:54 [vite] server restarted.
➜ Local: http://localhost:20101/
➜ Network: http://192.168.110.65:20101/ WLAN
➜ Network: http://172.20.192.1:20101/ vEthernet (Default …
➜ Vben Admin Docs: https://doc.vben.pro
18:53:59 [vite] vite.config.ts changed, restarting server...
[Vben Form] BREAKING CHANGE: Named field slot control bindings moved to `slotProps.componentProps`. Replace `v-bind="slotProps"` with `v-bind="slotProps.componentProps"`. See https://doc.vben.pro/components/common-ui/vben-form.html
18:54:00 [vite] server restarted.
18:54:09 [vite] (client) page reload src/api/core/auth.ts
18:54:13 [vite] (client) page reload src/api/core/menu.ts
18:54:16 [vite] (client) page reload src/api/core/user.ts
18:54:22 [vite] (client) page reload src/api/system.ts
18:54:25 [vite] (client) page reload src/api/log.ts
E:\xxcool\project\admin.xpcool.com\apps\web-tdesign:
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] @vben/web-tdesign@5.7.0 dev: `pnpm vite --mode development`
Exit status 4294967295
[ELIFECYCLE] Command failed with exit code 4294967295.

View File

@ -119,7 +119,7 @@ const defaultPreferences: Preferences = {
theme: { theme: {
builtinType: 'default', builtinType: 'default',
colorDestructive: 'hsl(348 100% 61%)', colorDestructive: 'hsl(348 100% 61%)',
colorPrimary: 'hsl(212 100% 45%)', colorPrimary: 'hsl(235 100% 65%)',
colorSuccess: 'hsl(144 57% 58%)', colorSuccess: 'hsl(144 57% 58%)',
colorWarning: 'hsl(42 84% 61%)', colorWarning: 'hsl(42 84% 61%)',
mode: 'dark', mode: 'dark',
@ -140,8 +140,9 @@ const defaultPreferences: Preferences = {
fullscreenButtonPosition: 'header', fullscreenButtonPosition: 'header',
globalSearch: true, globalSearch: true,
globalSearchButtonPosition: 'header', globalSearchButtonPosition: 'header',
languageToggle: true, // 移除国际化:不再显示语言切换按钮
languageToggleButtonPosition: 'header', languageToggle: false,
languageToggleButtonPosition: 'none',
lockScreen: true, lockScreen: true,
lockScreenButtonPosition: 'header', lockScreenButtonPosition: 'header',
logoutButtonPosition: 'header', logoutButtonPosition: 'header',
@ -158,7 +159,6 @@ const defaultPreferences: Preferences = {
'globalSearch', 'globalSearch',
'preferences', 'preferences',
'themeToggle', 'themeToggle',
'languageToggle',
'timezone', 'timezone',
'fullscreen', 'fullscreen',
'refresh', 'refresh',

View File

@ -9,7 +9,7 @@ interface BuiltinThemePreset {
const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
{ {
color: 'hsl(212 100% 45%)', color: 'hsl(235 100% 65%)',
type: 'default', type: 'default',
}, },
{ {

View File

@ -8,7 +8,6 @@ import { preferences } from '@vben/preferences';
import { import {
AuthenticationColorToggle, AuthenticationColorToggle,
AuthenticationLayoutToggle, AuthenticationLayoutToggle,
LanguageToggle,
ThemeToggle, ThemeToggle,
} from '../widgets'; } from '../widgets';
@ -21,12 +20,12 @@ defineOptions({
}); });
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
toolbarList: () => ['color', 'language', 'layout', 'theme'], //
toolbarList: () => ['color', 'layout', 'theme'],
}); });
const showColor = computed(() => props.toolbarList.includes('color')); const showColor = computed(() => props.toolbarList.includes('color'));
const showLayout = computed(() => props.toolbarList.includes('layout')); const showLayout = computed(() => props.toolbarList.includes('layout'));
const showLanguage = computed(() => props.toolbarList.includes('language'));
const showTheme = computed(() => props.toolbarList.includes('theme')); const showTheme = computed(() => props.toolbarList.includes('theme'));
</script> </script>
@ -42,8 +41,7 @@ const showTheme = computed(() => props.toolbarList.includes('theme'));
<AuthenticationColorToggle v-if="showColor" /> <AuthenticationColorToggle v-if="showColor" />
<AuthenticationLayoutToggle v-if="showLayout" /> <AuthenticationLayoutToggle v-if="showLayout" />
</div> </div>
<!-- Always show Language and Theme toggles --> <!-- 仅展示主题切换 -->
<LanguageToggle v-if="showLanguage && preferences.widget.languageToggle" />
<ThemeToggle v-if="showTheme && preferences.widget.themeToggle" /> <ThemeToggle v-if="showTheme && preferences.widget.themeToggle" />
</div> </div>
</template> </template>

View File

@ -18,7 +18,6 @@ import { useMagicKeys, whenever } from '@vueuse/core';
import { import {
GlobalSearch, GlobalSearch,
LanguageToggle,
LockScreenModal, LockScreenModal,
Notification, Notification,
PreferencesButton, PreferencesButton,
@ -163,12 +162,6 @@ const rightSlots = computed(() => {
preferences.widget.themeToggleButtonPosition === 'header', preferences.widget.themeToggleButtonPosition === 'header',
slotName: 'theme-toggle', slotName: 'theme-toggle',
}, },
languageToggle: {
visible:
preferences.widget.languageToggle &&
preferences.widget.languageToggleButtonPosition === 'header',
slotName: 'language-toggle',
},
timezone: { timezone: {
visible: visible:
preferences.widget.timezone && preferences.widget.timezone &&
@ -337,14 +330,6 @@ function clearPreferencesAndLogout() {
{{ $t('preferences.theme.title') }} {{ $t('preferences.theme.title') }}
</VbenTooltip> </VbenTooltip>
</template> </template>
<template v-else-if="slot.name === 'language-toggle'">
<VbenTooltip side="bottom">
<template #trigger>
<LanguageToggle class="mr-1" />
</template>
{{ $t('preferences.widget.languageToggle') }}
</VbenTooltip>
</template>
<template v-else-if="slot.name === 'fullscreen'"> <template v-else-if="slot.name === 'fullscreen'">
<VbenFullScreen <VbenFullScreen
class="mr-1" class="mr-1"

View File

@ -279,7 +279,7 @@ useEventListener('mousemove', () => {
" "
:data-index="index" :data-index="index"
:data-search-item="index" :data-search-item="index"
class="group mb-3 flex-center w-full cursor-pointer rounded-lg bg-accent p-4" class="group flex-center w-full cursor-pointer rounded-lg bg-accent p-4"
@click="handleEnter" @click="handleEnter"
@mouseenter="handleMouseenter" @mouseenter="handleMouseenter"
> >

View File

@ -2,7 +2,6 @@ export { default as Breadcrumb } from './breadcrumb.vue';
export * from './check-updates'; export * from './check-updates';
export { default as AuthenticationColorToggle } from './color-toggle.vue'; export { default as AuthenticationColorToggle } from './color-toggle.vue';
export * from './global-search'; export * from './global-search';
export { default as LanguageToggle } from './language-toggle.vue';
export { default as AuthenticationLayoutToggle } from './layout-toggle.vue'; export { default as AuthenticationLayoutToggle } from './layout-toggle.vue';
export * from './lock-screen'; export * from './lock-screen';
export * from './notification'; export * from './notification';

View File

@ -1,9 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { LanguageOption } from '@vben/constants'; import { onMounted, ref, unref } from 'vue';
import { onMounted, onUnmounted, ref, unref } from 'vue';
import { onSupportLanguagesChange } from '@vben/constants';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useTimezoneStore } from '@vben/stores'; import { useTimezoneStore } from '@vben/stores';
@ -15,7 +12,6 @@ defineOptions({
name: 'PreferenceGeneralConfig', name: 'PreferenceGeneralConfig',
}); });
const appLocale = defineModel<string>('appLocale');
const appTimezone = defineModel<string>('appTimezone'); const appTimezone = defineModel<string>('appTimezone');
const appDynamicTitle = defineModel<boolean>('appDynamicTitle'); const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
const appWatermark = defineModel<boolean>('appWatermark'); const appWatermark = defineModel<boolean>('appWatermark');
@ -26,12 +22,6 @@ const appEnableCopyPreferences = defineModel<boolean>(
); );
const timezoneStore = useTimezoneStore(); const timezoneStore = useTimezoneStore();
const languageList = ref<LanguageOption[]>([]);
const unsubscribe = onSupportLanguagesChange((langs) => {
languageList.value = [...langs];
});
onUnmounted(unsubscribe);
const timezoneOptionsRef = ref< const timezoneOptionsRef = ref<
{ {
label: string; label: string;
@ -50,9 +40,6 @@ onMounted(async () => {
</script> </script>
<template> <template>
<SelectItem v-model="appLocale" :items="languageList">
{{ $t('preferences.language') }}
</SelectItem>
<SelectItem v-model="appTimezone" :items="timezoneOptionsRef"> <SelectItem v-model="appTimezone" :items="timezoneOptionsRef">
{{ $t('preferences.timezone') }} {{ $t('preferences.timezone') }}
</SelectItem> </SelectItem>

View File

@ -19,9 +19,6 @@ const widgetGlobalSearchButtonPosition = defineModel<string>(
const widgetFullscreenButtonPosition = defineModel<string>( const widgetFullscreenButtonPosition = defineModel<string>(
'widgetFullscreenButtonPosition', 'widgetFullscreenButtonPosition',
); );
const widgetLanguageToggleButtonPosition = defineModel<string>(
'widgetLanguageToggleButtonPosition',
);
const widgetNotificationButtonPosition = defineModel<string>( const widgetNotificationButtonPosition = defineModel<string>(
'widgetNotificationButtonPosition', 'widgetNotificationButtonPosition',
); );
@ -91,7 +88,6 @@ const positionMap: Record<string, string> = {
globalSearch: 'widgetGlobalSearchButtonPosition', globalSearch: 'widgetGlobalSearchButtonPosition',
preferences: 'appPreferencesButtonPosition', preferences: 'appPreferencesButtonPosition',
themeToggle: 'widgetThemeToggleButtonPosition', themeToggle: 'widgetThemeToggleButtonPosition',
languageToggle: 'widgetLanguageToggleButtonPosition',
timezone: 'widgetTimezoneButtonPosition', timezone: 'widgetTimezoneButtonPosition',
fullscreen: 'widgetFullscreenButtonPosition', fullscreen: 'widgetFullscreenButtonPosition',
refresh: 'widgetRefreshButtonPosition', refresh: 'widgetRefreshButtonPosition',
@ -104,7 +100,6 @@ const labelMap: Record<string, string> = {
globalSearch: 'preferences.widget.globalSearch', globalSearch: 'preferences.widget.globalSearch',
preferences: 'preferences.title', preferences: 'preferences.title',
themeToggle: 'preferences.widget.themeToggle', themeToggle: 'preferences.widget.themeToggle',
languageToggle: 'preferences.widget.languageToggle',
timezone: 'preferences.widget.timezone', timezone: 'preferences.widget.timezone',
fullscreen: 'preferences.widget.fullscreen', fullscreen: 'preferences.widget.fullscreen',
refresh: 'preferences.widget.refresh', refresh: 'preferences.widget.refresh',
@ -131,7 +126,6 @@ function getPosition(
const modelMap: Record<string, any> = { const modelMap: Record<string, any> = {
widgetGlobalSearchButtonPosition, widgetGlobalSearchButtonPosition,
widgetFullscreenButtonPosition, widgetFullscreenButtonPosition,
widgetLanguageToggleButtonPosition,
widgetNotificationButtonPosition, widgetNotificationButtonPosition,
widgetThemeToggleButtonPosition, widgetThemeToggleButtonPosition,
widgetLockScreenButtonPosition, widgetLockScreenButtonPosition,
@ -156,7 +150,6 @@ function handleUpdatePosition(
const modelMap: Record<string, any> = { const modelMap: Record<string, any> = {
widgetGlobalSearchButtonPosition, widgetGlobalSearchButtonPosition,
widgetFullscreenButtonPosition, widgetFullscreenButtonPosition,
widgetLanguageToggleButtonPosition,
widgetNotificationButtonPosition, widgetNotificationButtonPosition,
widgetThemeToggleButtonPosition, widgetThemeToggleButtonPosition,
widgetLockScreenButtonPosition, widgetLockScreenButtonPosition,

View File

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SupportedLanguagesType } from '@vben/locales';
import type { CustomPreferencesRecord } from '@vben/preferences'; import type { CustomPreferencesRecord } from '@vben/preferences';
import type { import type {
BreadcrumbStyleType, BreadcrumbStyleType,
@ -64,7 +63,6 @@ const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
const message = globalShareState.getMessage(); const message = globalShareState.getMessage();
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
const appTimezone = defineModel<string>('appTimezone'); const appTimezone = defineModel<string>('appTimezone');
const appDynamicTitle = defineModel<boolean>('appDynamicTitle'); const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
const appLayout = defineModel<LayoutType>('appLayout'); const appLayout = defineModel<LayoutType>('appLayout');
@ -180,9 +178,6 @@ const widgetGlobalSearchButtonPosition = defineModel<string>(
const widgetFullscreenButtonPosition = defineModel<string>( const widgetFullscreenButtonPosition = defineModel<string>(
'widgetFullscreenButtonPosition', 'widgetFullscreenButtonPosition',
); );
const widgetLanguageToggleButtonPosition = defineModel<string>(
'widgetLanguageToggleButtonPosition',
);
const widgetNotificationButtonPosition = defineModel<string>( const widgetNotificationButtonPosition = defineModel<string>(
'widgetNotificationButtonPosition', 'widgetNotificationButtonPosition',
); );
@ -381,7 +376,6 @@ function handleCustomPreferencesUpdate(updates: CustomPreferencesRecord) {
v-model:app-dynamic-title="appDynamicTitle" v-model:app-dynamic-title="appDynamicTitle"
v-model:app-enable-check-updates="appEnableCheckUpdates" v-model:app-enable-check-updates="appEnableCheckUpdates"
v-model:app-enable-copy-preferences="appEnableCopyPreferences" v-model:app-enable-copy-preferences="appEnableCopyPreferences"
v-model:app-locale="appLocale"
v-model:app-timezone="appTimezone" v-model:app-timezone="appTimezone"
v-model:app-watermark="appWatermark" v-model:app-watermark="appWatermark"
v-model:app-watermark-content="appWatermarkContent" v-model:app-watermark-content="appWatermarkContent"
@ -508,9 +502,6 @@ function handleCustomPreferencesUpdate(updates: CustomPreferencesRecord) {
v-model:widget-global-search-button-position=" v-model:widget-global-search-button-position="
widgetGlobalSearchButtonPosition widgetGlobalSearchButtonPosition
" "
v-model:widget-language-toggle-button-position="
widgetLanguageToggleButtonPosition
"
v-model:widget-lock-screen-button-position=" v-model:widget-lock-screen-button-position="
widgetLockScreenButtonPosition widgetLockScreenButtonPosition
" "

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { Settings } from '@vben/icons'; import { Settings } from '@vben/icons';
import { $t, loadLocaleMessages } from '@vben/locales'; import { $t } from '@vben/locales';
import { preferences, updatePreferences } from '@vben/preferences'; import { preferences, updatePreferences } from '@vben/preferences';
import { capitalizeFirstLetter } from '@vben/utils'; import { capitalizeFirstLetter } from '@vben/utils';
@ -58,9 +58,6 @@ const listen = computed(() => {
val: any, val: any,
) => { ) => {
updatePreferences({ [key]: { [subKey]: val } }); updatePreferences({ [key]: { [subKey]: val } });
if (key === 'app' && subKey === 'locale') {
loadLocaleMessages(val);
}
}; };
} }
} else { } else {

View File

@ -1,23 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Component } from 'vue'; import type { Component } from 'vue';
import type { LanguageOption } from '@vben/constants';
import type { AnyFunction } from '@vben/types'; import type { AnyFunction } from '@vben/types';
import { computed, onUnmounted, ref, useTemplateRef, watch } from 'vue'; import { computed, useTemplateRef, watch } from 'vue';
import { onSupportLanguagesChange } from '@vben/constants';
import { useHoverToggle, useRefresh } from '@vben/hooks'; import { useHoverToggle, useRefresh } from '@vben/hooks';
import { import {
createIconifyIcon, createIconifyIcon,
Languages,
LockKeyhole, LockKeyhole,
LogOut, LogOut,
RotateCw, RotateCw,
Search, Search,
Settings, Settings,
} from '@vben/icons'; } from '@vben/icons';
import { $t, loadLocaleMessages } from '@vben/locales'; import { $t } from '@vben/locales';
import { import {
preferences, preferences,
updatePreferences, updatePreferences,
@ -167,12 +164,6 @@ const showThemeToggleInDropdown = computed(
preferences.widget.themeToggleButtonPosition === 'user-dropdown', preferences.widget.themeToggleButtonPosition === 'user-dropdown',
); );
const showLanguageToggleInDropdown = computed(
() =>
preferences.widget.languageToggle &&
preferences.widget.languageToggleButtonPosition === 'user-dropdown',
);
const showTimezoneInDropdown = computed( const showTimezoneInDropdown = computed(
() => () =>
preferences.widget.timezone && preferences.widget.timezone &&
@ -203,7 +194,6 @@ const hasAnyInDropdown = computed(
showLogoutInDropdown.value || showLogoutInDropdown.value ||
showGlobalSearchInDropdown.value || showGlobalSearchInDropdown.value ||
showThemeToggleInDropdown.value || showThemeToggleInDropdown.value ||
showLanguageToggleInDropdown.value ||
showTimezoneInDropdown.value || showTimezoneInDropdown.value ||
showFullscreenInDropdown.value || showFullscreenInDropdown.value ||
showNotificationInDropdown.value || showNotificationInDropdown.value ||
@ -277,26 +267,6 @@ function handleNotificationSelect(event?: Event) {
refNotification.value?.toggle(); refNotification.value?.toggle();
} }
// - Radix dropdown/ locale
const showLanguageList = ref(false);
const languageList = ref<LanguageOption[]>([]);
const unsubLang = onSupportLanguagesChange((langs) => {
languageList.value = [...langs];
});
onUnmounted(unsubLang);
function handleLanguageToggleSelect(event?: Event) {
event?.preventDefault();
showLanguageList.value = !showLanguageList.value;
}
async function handleLocaleChange(event: Event, value: string) {
//
event.preventDefault();
updatePreferences({ app: { locale: value } });
await loadLocaleMessages(value);
showLanguageList.value = false;
openPopover.value = false;
}
if (preferences.shortcutKeys.enable) { if (preferences.shortcutKeys.enable) {
const keys = useMagicKeys(); const keys = useMagicKeys();
const logoutKey = keys['Alt+KeyQ']; const logoutKey = keys['Alt+KeyQ'];
@ -448,7 +418,6 @@ if (preferences.shortcutKeys.enable) {
v-if=" v-if="
showGlobalSearchInDropdown || showGlobalSearchInDropdown ||
showThemeToggleInDropdown || showThemeToggleInDropdown ||
showLanguageToggleInDropdown ||
showTimezoneInDropdown || showTimezoneInDropdown ||
showFullscreenInDropdown || showFullscreenInDropdown ||
showNotificationInDropdown || showNotificationInDropdown ||
@ -474,32 +443,6 @@ if (preferences.shortcutKeys.enable) {
<ThemeToggle class="mr-2" /> <ThemeToggle class="mr-2" />
{{ $t('preferences.theme.title') }} {{ $t('preferences.theme.title') }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem
v-if="showLanguageToggleInDropdown"
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
@select="handleLanguageToggleSelect"
>
<VbenIconButton class="mr-2" @click="handleLanguageToggleSelect">
<Languages class="size-4" />
</VbenIconButton>
{{ $t('preferences.widget.languageToggle') }}
</DropdownMenuItem>
<template v-if="showLanguageList">
<DropdownMenuItem
v-for="lang in languageList"
:key="lang.value"
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 pl-8 leading-8"
@select="(e: Event) => handleLocaleChange(e, lang.value)"
>
<span
:class="
lang.value === preferences.app.locale ? 'bg-foreground' : ''
"
class="mr-2 size-1.5 rounded-full"
></span>
{{ lang.label }}
</DropdownMenuItem>
</template>
<DropdownMenuItem <DropdownMenuItem
v-if="showTimezoneInDropdown" v-if="showTimezoneInDropdown"
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8" class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"

View File

@ -1,65 +0,0 @@
{
"welcomeBack": "Welcome Back",
"pageTitle": "Plug-and-play Admin system",
"pageDesc": "Efficient, versatile frontend template",
"loginSuccess": "Login Successful",
"loginSuccessDesc": "Welcome Back",
"loginSubtitle": "Enter your account details to manage your projects",
"selectAccount": "Quick Select Account",
"username": "Username",
"password": "Password",
"usernameTip": "Please enter username",
"passwordErrorTip": "Password is incorrect",
"passwordTip": "Please enter password",
"verifyRequiredTip": "Please complete the verification first",
"rememberMe": "Remember Me",
"createAnAccount": "Create an Account",
"createAccount": "Create Account",
"alreadyHaveAccount": "Already have an account?",
"accountTip": "Don't have an account?",
"signUp": "Sign Up",
"signUpSubtitle": "Make managing your applications simple and fun",
"confirmPassword": "Confirm Password",
"confirmPasswordTip": "The passwords do not match",
"agree": "I agree to",
"privacyPolicy": "Privacy-policy",
"terms": "Terms",
"agreeTip": "Please agree to the Privacy Policy and Terms",
"goToLogin": "Login instead",
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols",
"forgetPassword": "Forget Password?",
"forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password",
"emailTip": "Please enter email",
"emailValidErrorTip": "The email format you entered is incorrect",
"sendResetLink": "Send Reset Link",
"email": "Email",
"qrcodeSubtitle": "Scan the QR code with your phone to login",
"qrcodePrompt": "Click 'Confirm' after scanning to complete login",
"qrcodeLogin": "QR Code Login",
"wechatLogin": "Wechat Login",
"qqLogin": "QQ Login",
"githubLogin": "Github Login",
"googleLogin": "Google Login",
"dingdingLogin": "Dingding Login",
"codeSubtitle": "Enter your phone number to start managing your project",
"code": "Security code",
"codeTip": "Security code required {0} characters",
"mobile": "Mobile",
"mobileLogin": "Mobile Login",
"mobileTip": "Please enter mobile number",
"mobileErrortip": "The phone number format is incorrect",
"sendCode": "Get Security code",
"sendText": "Resend in {0}s",
"thirdPartyLogin": "Or continue with",
"weChat": "WeChat",
"qq": "QQ",
"gitHub": "GitHub",
"google": "Google",
"loginAgainTitle": "Please Log In Again",
"loginAgainSubTitle": "Your login session has expired. Please log in again to continue.",
"layout": {
"center": "Align Center",
"alignLeft": "Align Left",
"alignRight": "Align Right"
}
}

View File

@ -1,26 +0,0 @@
{
"back": "Back",
"backToHome": "Back To Home",
"login": "Login",
"logout": "Logout",
"prompt": "Prompt",
"cancel": "Cancel",
"confirm": "Confirm",
"reset": "Reset",
"noData": "No Data",
"refresh": "Refresh",
"loadingMenu": "Loading Menu",
"query": "Search",
"search": "Search",
"enabled": "Enabled",
"disabled": "Disabled",
"edit": "Edit",
"delete": "Delete",
"detail": "Detail",
"create": "Create",
"yes": "Yes",
"no": "No",
"showSearchPanel": "Show search panel",
"hideSearchPanel": "Hide search panel",
"notShow": "Don't Show"
}

View File

@ -1,245 +0,0 @@
{
"title": "Preferences",
"subtitle": "Customize Preferences & Preview in Real Time",
"enableStickyPreferencesNavigationBar": "Enable sticky preferences navigation bar",
"disableStickyPreferencesNavigationBar": "Disable sticky preferences navigation bar",
"resetTip": "Data has changed, click to reset",
"resetTitle": "Reset Preferences",
"resetSuccess": "Preferences reset successfully",
"appearance": "Appearance",
"layout": "Layout",
"content": "Content",
"other": "Other",
"wide": "Wide",
"compact": "Fixed",
"followSystem": "Follow System",
"vertical": "Vertical",
"verticalTip": "Side vertical menu mode",
"horizontal": "Horizontal",
"horizontalTip": "Horizontal menu mode, all menus displayed at the top",
"twoColumn": "Two Column",
"twoColumnTip": "Vertical Two Column Menu Mode",
"headerSidebarNav": "Header Vertical",
"headerSidebarNavTip": "Header Full Width, Sidebar Navigation Mode",
"headerTwoColumn": "Header Two Column",
"headerTwoColumnTip": "Header Navigation & Sidebar Two Column co-exists",
"mixedMenu": "Mixed Menu",
"mixedMenuTip": "Vertical & Horizontal Menu Co-exists",
"fullContent": "Full Content",
"fullContentTip": "Only display content body, hide all menus",
"normal": "Normal",
"plain": "Plain",
"rounded": "Rounded",
"copyPreferences": "Copy Preferences",
"enableCopyPreferences": "Show copy preferences button",
"copyPreferencesSuccessTitle": "Copy successful",
"copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
"clearAndLogout": "Clear Cache & Logout",
"mode": "Mode",
"general": "General",
"language": "Language",
"timezone": "Timezone",
"dynamicTitle": "Dynamic Title",
"watermark": "Watermark",
"watermarkContent": "Please input Watermark content",
"checkUpdates": "Periodic update check",
"position": {
"title": "Preferences Postion",
"header": "Header",
"auto": "Auto",
"fixed": "Fixed",
"userDropdown": "User Dropdown"
},
"sidebar": {
"buttons": "Show Buttons",
"buttonFixed": "Fixed",
"buttonCollapsed": "Collapsed",
"title": "Sidebar",
"width": "Width",
"visible": "Show Sidebar",
"draggable": "Drag Sidebar Menu",
"collapsed": "Collpase Menu",
"collapsedShowTitle": "Show Menu Title",
"autoActivateChild": "Auto Activate SubMenu",
"autoActivateChildTip": "`Enabled` to automatically activate the submenu while click menu.",
"expandOnHover": "Expand On Hover",
"expandOnHoverTip": "When the mouse hovers over menu, \n `Enabled` to expand children menus \n `Disabled` to expand whole sidebar."
},
"tabbar": {
"title": "Tabbar",
"enable": "Enable Tab Bar",
"icon": "Show Tabbar Icon",
"showMore": "Show More Button",
"showMaximize": "Show Maximize Button",
"persist": "Persist Tabs",
"visitHistory": "Visit History",
"visitHistoryTip": "When enabled, the tab bar records tab visit history. \nClosing the current tab will automatically select the last opened tab.",
"maxCount": "Max Count of Tabs",
"maxCountTip": "When the number of tabs exceeds the maximum,\nthe oldest tab will be closed.\n Set to 0 to disable count checking.",
"draggable": "Enable Draggable Sort",
"wheelable": "Support Mouse Wheel",
"middleClickClose": "Close Tab when Mouse Middle Button Click",
"wheelableTip": "When enabled, the Tabbar area responds to vertical scrolling events of the scroll wheel.",
"styleType": {
"title": "Tabs Style",
"chrome": "Chrome",
"card": "Card",
"plain": "Plain",
"brisk": "Brisk"
},
"contextMenu": {
"reload": "Reload",
"close": "Close",
"pin": "Pin",
"unpin": "Unpin",
"closeLeft": "Close Left Tabs",
"closeRight": "Close Right Tabs",
"closeOther": "Close Other Tabs",
"closeAll": "Close All Tabs",
"openInNewWindow": "Open in New Window",
"maximize": "Maximize",
"restoreMaximize": "Restore"
}
},
"navigationMenu": {
"title": "Navigation Menu",
"style": "Navigation Menu Style",
"accordion": "Sidebar Accordion Menu",
"split": "Navigation Menu Separation",
"splitTip": "When enabled, the sidebar displays the top bar's submenu"
},
"breadcrumb": {
"title": "Breadcrumb",
"home": "Show Home Button",
"enable": "Enable Breadcrumb",
"icon": "Show Breadcrumb Icon",
"background": "background",
"style": "Breadcrumb Style",
"hideOnlyOne": "Hidden when only one"
},
"animation": {
"title": "Animation",
"loading": "Page Loading",
"transition": "Page Transition",
"progress": "Page Progress"
},
"theme": {
"title": "Theme",
"radius": "Radius",
"fontSize": "Font Size",
"fontSizeTip": "Adjust global font size with real-time preview",
"light": "Light",
"dark": "Dark",
"darkSidebar": "Semi Dark Sidebar",
"darkSidebarTip": "It can be enabled when the theme is light, and the layout is neither 'Horizontal' nor 'Full Content'.",
"darkSidebarSub": "Semi Dark Sidebar Sub",
"darkSidebarSubTip": "It can be enabled when the theme is light, the semi dark sidebar is enabled, and the layout uses 'Two-Column' menu mode.",
"darkHeader": "Semi Dark Header",
"weakMode": "Weak Mode",
"grayMode": "Gray Mode",
"builtin": {
"title": "Built-in",
"default": "Default",
"violet": "Violet",
"pink": "Pink",
"rose": "Rose",
"skyBlue": "Sky Blue",
"deepBlue": "Deep Blue",
"green": "Green",
"deepGreen": "Deep Green",
"orange": "Orange",
"yellow": "Yellow",
"zinc": "Zinc",
"neutral": "Neutral",
"slate": "Slate",
"gray": "Gray",
"custom": "Custom"
}
},
"header": {
"title": "Header",
"visible": "Show Header",
"modeStatic": "Static",
"modeFixed": "Fixed",
"modeAuto": "Auto hide & Show",
"modeAutoScroll": "Scroll to Hide & Show",
"menuAlign": "Menu Align",
"menuAlignStart": "Start",
"menuAlignEnd": "End",
"menuAlignCenter": "Center"
},
"footer": {
"title": "Footer",
"visible": "Show Footer",
"fixed": "Fixed at Bottom"
},
"copyright": {
"title": "Copyright",
"enable": "Enable Copyright",
"companyName": "Company Name",
"companySiteLink": "Company Site Link",
"date": "Date",
"icp": "ICP License Number",
"icpLink": "ICP Site Link"
},
"shortcutKeys": {
"title": "Shortcut Keys",
"global": "Global",
"search": "Global Search",
"logout": "Logout",
"escape": "Close Current Window",
"preferences": "Preferences"
},
"widget": {
"title": "Widget",
"globalSearch": "Global Search",
"globalSearchPosition": "Global Search Position",
"fullscreen": "Enable Fullscreen",
"fullscreenPosition": "Fullscreen Position",
"themeToggle": "Enable Theme Toggle",
"themeTogglePosition": "Theme Toggle Position",
"languageToggle": "Language Toggle",
"languageTogglePosition": "Language Toggle Position",
"notification": "Enable Notification",
"notificationPosition": "Notification Position",
"sidebarToggle": "Enable Sidebar Toggle",
"lockScreen": "Enable Lock Screen",
"lockScreenPosition": "Lock Screen Button Position",
"logoutButtonPosition": "Logout Button Position",
"header": "Header",
"userDropdown": "User Dropdown",
"hidden": "Hidden",
"refresh": "Enable Refresh",
"refreshPosition": "Refresh Position",
"timezone": "Enable Timezone",
"timezonePosition": "Timezone Position"
},
"antd": {
"tabLabel": "Antd Extension",
"title": "Business Preferences",
"fields": {
"enableFormFullscreen": {
"label": "Enable Form Fullscreen Mode",
"tip": "Subprojects can read this extension configuration in their own business scenarios."
},
"tenantMode": {
"label": "Tenant Mode",
"options": {
"single": {
"label": "Single Tenant"
},
"multi": {
"label": "Multi Tenant"
}
}
},
"defaultTableSize": {
"label": "Default Page Size"
},
"reportTitle": {
"label": "Default Report Title",
"placeholder": "Please enter the default report title"
}
}
}
}

View File

@ -1,4 +0,0 @@
{
"updatePassword": "Update Password",
"updateBasicProfile": "Update Basic Profile"
}

View File

@ -1,165 +0,0 @@
{
"formRules": {
"required": "Please enter {0}",
"selectRequired": "Please select {0}",
"minLength": "{0} must be at least {1} characters",
"maxLength": "{0} can be at most {1} characters",
"length": "{0} must be {1} characters long",
"alreadyExists": "{0} `{1}` already exists",
"startWith": "{0} must start with `{1}`",
"invalidURL": "Please input a valid URL",
"sizeLimit": "The file size cannot exceed {0}MB",
"previewWarning": "Unable to open the file, there is no available URL or preview address"
},
"actionTitle": {
"edit": "Modify {0}",
"create": "Create {0}",
"delete": "Delete {0}",
"view": "View {0}"
},
"actionMessage": {
"deleteConfirm": "Are you sure to delete {0}?",
"deleting": "Deleting {0} ...",
"deleteSuccess": "{0} deleted successfully",
"operationSuccess": "Operation succeeded",
"operationFailed": "Operation failed"
},
"placeholder": {
"input": "Please enter",
"select": "Please select",
"upload": "Click to upload"
},
"captcha": {
"title": "Please complete the security verification",
"sliderSuccessText": "Passed",
"sliderDefaultText": "Slider and drag",
"alt": "Supports img tag src attribute value",
"sliderRotateDefaultTip": "Click picture to refresh",
"sliderTranslateDefaultTip": "Click picture to refresh",
"sliderRotateFailTip": "Validation failed",
"sliderRotateSuccessTip": "Validation successful, time {0} seconds",
"sliderTranslateFailTip": "Validation failed",
"sliderTranslateSuccessTip": "Validation successful, time {0} seconds",
"refreshAriaLabel": "Refresh captcha",
"confirmAriaLabel": "Confirm selection",
"confirm": "Confirm",
"pointAriaLabel": "Click point",
"clickInOrder": "Please click in order"
},
"iconPicker": {
"placeholder": "Select an icon",
"search": "Search icon..."
},
"jsonViewer": {
"copy": "Copy",
"copied": "Copied"
},
"crop": {
"title": "Image Cropping",
"titleTip": "Cropping Ratio {0}",
"confirm": "Crop",
"cancel": "Cancel cropping",
"errorTip": "Cropping error"
},
"tiptap": {
"placeholder": "Please enter content...",
"prompts": {
"image": "Enter image URL",
"link": "Enter link URL"
},
"toolbar": {
"bold": "Bold",
"italic": "Italic",
"underline": "Underline",
"strike": "Strike",
"code": "Code",
"codeBlock": "Code Block",
"heading": "Heading",
"paragraph": "Paragraph",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"heading4": "H4",
"bulletList": "Bullets",
"orderedList": "Numbering",
"blockquote": "Quote",
"link": "Link",
"unlink": "Unlink",
"image": "Image",
"imageUrl": "Image URL",
"imageUpload": "Upload Image",
"textColor": "Text Color",
"highlightColor": "Highlight Color",
"alignLeft": "Left",
"alignCenter": "Center",
"alignRight": "Right",
"preview": "Preview",
"undo": "Undo",
"redo": "Redo",
"clear": "Clear"
},
"upload": {
"fileTooLarge": "File size exceeds the limit",
"fileTypeNotAllowed": "File type is not allowed",
"onlySingleImage": "Only single image upload is supported, the first one is selected",
"uploadFailed": "Upload Failed"
}
},
"fallback": {
"pageNotFound": "Oops! Page Not Found",
"pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.",
"forbidden": "Oops! Access Denied",
"forbiddenDesc": "Sorry, but you don't have permission to access this page.",
"internalError": "Oops! Something Went Wrong",
"internalErrorDesc": "Sorry, but the server encountered an error.",
"offline": "Offline Page",
"offlineError": "Oops! Network Error",
"offlineErrorDesc": "Sorry, can't connect to the internet. Check your connection.",
"comingSoon": "Coming Soon",
"http": {
"requestTimeout": "The request timed out. Please try again later.",
"networkError": "A network error occurred. Please check your internet connection and try again.",
"badRequest": "Bad Request. Please check your input and try again.",
"unauthorized": "Unauthorized. Please log in to continue.",
"forbidden": "Forbidden. You do not have permission to access this resource.",
"notFound": "Not Found. The requested resource could not be found.",
"internalServerError": "Internal Server Error. Something went wrong on our end. Please try again later."
}
},
"widgets": {
"document": "Document",
"qa": "Q&A",
"setting": "Settings",
"logoutTip": "Do you want to logout?",
"viewAll": "View All Messages",
"notifications": "Notifications",
"markAllAsRead": "Make All as Read",
"clearNotifications": "Clear",
"checkUpdatesTitle": "New Version Available",
"checkUpdatesDescription": "Click to refresh and get the latest version",
"search": {
"title": "Search",
"searchNavigate": "Search Navigation",
"select": "Select",
"navigate": "Navigate",
"close": "Close",
"noResults": "No Search Results Found",
"noRecent": "No Search History",
"recent": "Search History"
},
"lockScreen": {
"title": "Lock Screen",
"screenButton": "Lock",
"password": "Password",
"placeholder": "Please enter password",
"unlock": "Click to unlock",
"errorPasswordTip": "Password error, please re-enter",
"backToLogin": "Back to login",
"entry": "Enter the system"
},
"timezone": {
"setTimezone": "Set Timezone",
"setSuccess": "Timezone set successfully"
}
}
}