diff --git a/mpuniapp/pages/owner/profile/index.uvue b/mpuniapp/pages/owner/profile/index.uvue
index 92be888..e0f6eed 100644
--- a/mpuniapp/pages/owner/profile/index.uvue
+++ b/mpuniapp/pages/owner/profile/index.uvue
@@ -1,76 +1,209 @@
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
@@ -152,8 +312,9 @@ import {
resolveProfileText,
previewAccountAvatar,
removeAccountAvatar,
+ calculateQuotaPercent,
} from './useProfile.uts';
-import { OmniLocale } from '../../../common/locale.uts';
+import { OmniLocale, isRtl } from '../../../common/locale.uts';
import { fetchCardQuota } from '../../../common/api.uts';
import { getTenantId, clearAuthToken } from '../../../common/request.uts';
@@ -165,6 +326,34 @@ type LangOption = {
const state = reactive(loadProfileSettings());
const quotaLine = ref('--');
const quotaExceeded = ref(false);
+const quotaPercent = ref(20);
+const languageDrawerVisible = ref(false);
+
+const pageRtlClass = computed((): string => {
+ return isRtl(state.locale) ? 'page-rtl' : '';
+});
+
+const currentLanguageLabel = computed((): string => {
+ for (let i = 0; i < languageList.length; i++) {
+ if (languageList[i].code === state.locale) {
+ return languageList[i].label;
+ }
+ }
+ return 'English';
+});
+
+function openLanguageDrawer(): void {
+ languageDrawerVisible.value = true;
+}
+
+function closeLanguageDrawer(): void {
+ languageDrawerVisible.value = false;
+}
+
+function onSelectLocaleFromDrawer(code: OmniLocale): void {
+ onSelectLocale(code);
+ closeLanguageDrawer();
+}
function onAvatarClick(): void {
if (state.account.avatar !== '') {
@@ -214,15 +403,64 @@ function onLeadNoticeChange(e: UniSwitchChangeEvent): void {
toggleLeadNotice(state, e.detail.value);
}
-function onSelectLocale(code: OmniLocale): void {
- setPreferredLocale(state, code);
- saveProfileSettings(state);
+const myCardsSubText = computed((): string => {
+ return state.locale === 'zh-CN' ? '查看名片与在线出示' : 'View & share';
+});
+
+const cardEditSubText = computed((): string => {
+ return state.locale === 'zh-CN' ? '修改联系与社交矩阵' : 'Edit profile';
+});
+
+const productCasesSubText = computed((): string => {
+ return state.locale === 'zh-CN' ? '画廊相册与量化成效' : 'Showcases & metrics';
+});
+
+const articleDynamicsSubText = computed((): string => {
+ return state.locale === 'zh-CN' ? '发布行业博文与动态' : 'Publish insights';
+});
+
+const sharePosterSubText = computed((): string => {
+ return state.locale === 'zh-CN' ? '展会专属扫码展台' : 'Digital QR poster';
+});
+
+const exchangeHallSubText = computed((): string => {
+ return state.locale === 'zh-CN' ? '面对面扫码碰卡互换' : 'Peer exchange';
+});
+
+function onEditProfileClick(): void {
+ openPage('/pages/owner/card/edit');
+}
+
+function onQuotaClick(): void {
+ const isZh = state.locale === 'zh-CN';
+ uni.showModal({
+ title: isZh ? '企业商业配额' : 'Commercial Quota',
+ content: isZh
+ ? `当前用量:${quotaLine.value} 张名片。\n如需扩容或升级企业旗舰版,请前往管理后台或联系您的企业管理员。`
+ : `Current Usage: ${quotaLine.value} cards.\nTo upgrade or expand quota, please visit the admin console.`,
+ showCancel: false,
+ confirmText: isZh ? '知道了' : 'OK',
+ });
}
function openPage(url: string): void {
- uni.navigateTo({
- url,
- });
+ const tabPages = [
+ '/pages/portal/index',
+ '/pages/owner/card/index',
+ '/pages/owner/radar',
+ '/pages/owner/leads',
+ '/pages/owner/profile/index',
+ ];
+ const isTab = tabPages.some((tab: string): boolean => url.startsWith(tab));
+ if (isTab) {
+ uni.switchTab({
+ url,
+ });
+ } else {
+ uni.navigateTo({
+ url,
+ });
+ }
}
async function loadQuota(): Promise {
@@ -231,9 +469,11 @@ async function loadQuota(): Promise {
const quota = await fetchCardQuota(tenantId !== '' ? tenantId : 1);
quotaLine.value = `${quota.used} / ${quota.limit}`;
quotaExceeded.value = quota.exceeded === true;
+ quotaPercent.value = calculateQuotaPercent(quota.used, quota.limit);
} catch (_e) {
quotaLine.value = '1 / 5';
quotaExceeded.value = false;
+ quotaPercent.value = calculateQuotaPercent(1, 5);
}
}
@@ -272,11 +512,25 @@ onShow(() => {
diff --git a/mpuniapp/pages/owner/profile/useProfile.uts b/mpuniapp/pages/owner/profile/useProfile.uts
index d2c65e9..bdd4f28 100644
--- a/mpuniapp/pages/owner/profile/useProfile.uts
+++ b/mpuniapp/pages/owner/profile/useProfile.uts
@@ -392,7 +392,7 @@ export function resolveProfileText(key: string): string {
case 'tierBadge':
return 'PRO';
case 'previewPortal':
- return isZh ? '预览官网' : 'Preview Site';
+ return isZh ? '预览微官网' : 'Preview site';
case 'metricsCards':
return isZh ? '活跃名片' : 'Active Cards';
case 'metricsVisitors':
@@ -416,7 +416,23 @@ export function resolveProfileText(key: string): string {
case 'logout':
return isZh ? '退出登录' : 'Log out';
case 'workspaceSection':
- return isZh ? '出海工作台' : 'Workspace';
+ return isZh ? '智能硬件与团队' : 'Hardware & Team';
+ case 'cardStudioSection':
+ return isZh ? '名片与微官网工作室' : 'Digital Card Studio';
+ case 'myCards':
+ return isZh ? '我的数字名片' : 'Digital Cards';
+ case 'cardEdit':
+ return isZh ? '极速资料维护' : 'Quick Profile';
+ case 'productCases':
+ return isZh ? '产品相册与案例' : 'Portfolio & Cases';
+ case 'articleDynamics':
+ return isZh ? '文章与商务动态' : 'Insights & News';
+ case 'sharePoster':
+ return isZh ? '分享海报' : 'Share Poster';
+ case 'exchangeHall':
+ return isZh ? '互换大厅' : 'Exchange Hall';
+ case 'editProfile':
+ return isZh ? '编辑资料' : 'Edit';
case 'nfcSection':
return isZh ? 'NFC 智能硬件' : 'NFC Fleet';
case 'nfcScan':
diff --git a/mpuniapp/tests/owner-profile.test.ts b/mpuniapp/tests/owner-profile.test.ts
new file mode 100644
index 0000000..0b72d90
--- /dev/null
+++ b/mpuniapp/tests/owner-profile.test.ts
@@ -0,0 +1,54 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import path from 'node:path';
+
+const MP_ROOT = path.resolve('m:/2026项目/CRMmp/mpuniapp');
+const profileUtsPath = path.join(MP_ROOT, 'pages/owner/profile/useProfile.uts');
+const profileUvuePath = path.join(MP_ROOT, 'pages/owner/profile/index.uvue');
+
+test('1. useProfile.uts 必须包含名片工作室所有核心文案键', () => {
+ assert.ok(fs.existsSync(profileUtsPath), '必须存在 useProfile.uts');
+ const content = fs.readFileSync(profileUtsPath, 'utf-8');
+
+ assert.ok(content.includes('cardStudioSection'), '必须包含 cardStudioSection');
+ assert.ok(content.includes('myCards'), '必须包含 myCards');
+ assert.ok(content.includes('cardEdit'), '必须包含 cardEdit');
+ assert.ok(content.includes('productCases'), '必须包含 productCases');
+ assert.ok(content.includes('articleDynamics'), '必须包含 articleDynamics');
+ assert.ok(content.includes('sharePoster'), '必须包含 sharePoster');
+ assert.ok(content.includes('exchangeHall'), '必须包含 exchangeHall');
+ assert.ok(content.includes('editProfile'), '必须包含 editProfile');
+});
+
+test('2. profile/index.uvue 零 Emoji 违规审查', () => {
+ const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
+ const content = fs.readFileSync(profileUvuePath, 'utf-8');
+ assert.equal(emojiRegex.test(content), false, 'profile/index.uvue 严禁含有 Emoji');
+});
+
+test('3. profile/index.uvue 必须完整挂载数字名片工作室 6 大业务入口', () => {
+ const content = fs.readFileSync(profileUvuePath, 'utf-8');
+
+ assert.ok(content.includes('/pages/owner/card/index'), '必须包含我的名片列表入口');
+ assert.ok(content.includes('/pages/owner/card/edit'), '必须包含名片极速维护入口');
+ assert.ok(content.includes('/pages/owner/card/cases'), '必须包含产品相册与案例入口');
+ assert.ok(content.includes('/pages/owner/card/articles'), '必须包含文章与商务动态入口');
+ assert.ok(content.includes('/pages/owner/card/qrcode'), '必须包含分享海报入口');
+ assert.ok(content.includes('/pages/owner/hall/index'), '必须包含名片互换大厅入口');
+});
+
+test('4. openPage 跳转逻辑必须支持 TabBar 页面使用 uni.switchTab 防止死链', () => {
+ const content = fs.readFileSync(profileUvuePath, 'utf-8');
+
+ assert.ok(content.includes('uni.switchTab'), 'TabBar 页面必须调用 uni.switchTab 跳转');
+ assert.ok(content.includes('uni.navigateTo'), '常规页面调用 uni.navigateTo 跳转');
+ assert.ok(content.includes('/pages/owner/card/index'), 'TabBar 判断列表必须包含 /pages/owner/card/index');
+});
+
+test('5. 个人资料顶部身份区域与配额进度条具备点击交互响应', () => {
+ const content = fs.readFileSync(profileUvuePath, 'utf-8');
+
+ assert.ok(content.includes('onEditProfileClick'), '顶部身份卡片必须具备 onEditProfileClick');
+ assert.ok(content.includes('onQuotaClick'), '配额进度条必须具备 onQuotaClick 交互反馈');
+});