feat(mobile): redesign profile page with identity card and deduped entries

This commit is contained in:
2026-07-19 22:39:57 +08:00
parent c17d83fc59
commit a20ca50b87
2 changed files with 160 additions and 149 deletions
+157 -148
View File
@@ -2,68 +2,41 @@
<view class="page profile-page employee-page-shell"> <view class="page profile-page employee-page-shell">
<view class="profile-head"> <view class="profile-head">
<text class="profile-eyebrow">银城物业 · 员工成长</text> <text class="profile-eyebrow">银城物业 · 员工成长</text>
<text class="page-title">我的成长</text> <text class="page-title">我</text>
<text class="page-subtitle">{{ phone || '登录后查看训练记录和能力画像' }}</text> <text class="page-subtitle">训练记录、积分与个人能力画像</text>
</view> </view>
<view v-if="message" class="status-message">{{ message }}</view> <view v-if="message" class="status-message">{{ message }}</view>
<view class="panel account-card"> <view class="panel identity-card">
<view> <view class="identity-avatar">
<text class="panel-title">账号</text> <uni-icons type="person-filled" size="26" color="#e60012" />
<text class="account-phone">{{ phone || '未登录' }}</text>
</view> </view>
<button v-if="!loggedIn" class="primary-button account-action" @click="goLogin">去登录</button> <view class="identity-info">
<button v-else class="secondary-button account-action" @click="logout">退出</button> <text class="identity-phone">{{ phone || '未登录' }}</text>
</view> <text v-if="loggedIn" class="identity-position">{{ positionProfile.value }} · {{ positionProfile.heroTitle }}</text>
<text v-if="loggedIn" class="identity-source">岗位来自人力系统组织同步,如需调整请联系 HR</text>
<view v-if="loggedIn" class="panel position-panel"> <text v-else class="identity-source">登录后查看训练记录和能力画像</text>
<view class="position-current">
<view>
<text class="panel-title">岗位身份</text>
<text class="position-title">{{ positionProfile.value }} · {{ positionProfile.heroTitle }}</text>
<text class="position-source">岗位来自人力系统组织同步,如需调整请联系 HR。</text>
</view>
</view> </view>
<button v-if="!loggedIn" class="primary-button identity-action" @click="goLogin">去登录</button>
<text v-else class="identity-logout" role="button" @click="logout">退出</text>
</view> </view>
<view v-if="supervisorLearning" class="panel supervisor-mode-panel"> <view v-if="supervisorLearning" class="panel supervisor-mode-panel">
<view> <view>
<text class="panel-title">当前为学员模式</text> <text class="panel-title">当前为学员模式</text>
<text class="position-source">主管组织身份未改变,仅切换到个人学习与训练界面。</text> <text class="identity-source">主管组织身份未改变,仅切换到个人学习与训练界面。</text>
</view> </view>
<button class="secondary-button account-action" @click="returnToSupervisor">返回主管工作台</button> <button class="secondary-button account-action" @click="returnToSupervisor">返回主管工作台</button>
</view> </view>
<view class="panel display-panel">
<view class="display-heading">
<view>
<text class="panel-title">显示与辅助</text>
<text class="display-current">字体大小 · {{ currentFontSizeLabel }}</text>
</view>
</view>
<view class="font-size-options" role="group" aria-label="字体大小">
<button
v-for="option in appFontSizeOptions"
:key="option.value"
class="font-size-option"
:class="{ active: fontSize === option.value }"
:aria-pressed="fontSize === option.value"
@click="changeFontSize(option.value)"
>
{{ option.label }}
</button>
</view>
<text class="display-note">选择后会同步到今日、练、问、我等全部页面,并保留你的设置。</text>
</view>
<view v-if="loggedIn && profile" class="panel profile-summary"> <view v-if="loggedIn && profile" class="panel profile-summary">
<text class="panel-title">成长摘要</text> <text class="panel-title">成长摘要</text>
<text class="ai-score-disclaimer">AI训练综合分仅作训练参考,不等于BRD五维绩效总分,人可复核/否决</text> <text class="ai-score-disclaimer">AI训练综合分仅作训练参考,不等于BRD五维绩效总分,人可复核/否决</text>
<view class="score-row"> <view class="score-row">
<view class="score-main"> <view class="score-main">
<text class="score-value">{{ profile.score }}</text> <text class="score-value">{{ profile.score }}</text>
<text class="score-label">训练综合分</text> <text class="score-label">训练综合分</text>
</view> </view>
<view class="metric-grid"> <view class="metric-grid">
<view class="metric"> <view class="metric">
@@ -124,39 +97,27 @@
</button> </button>
<view v-if="loggedIn" class="panel feature-entries"> <view v-if="loggedIn" class="panel feature-entries">
<text class="panel-title">常用功能</text> <text class="panel-title">我的助手</text>
<button class="feature-entry" @click="openMemories"> <button class="feature-entry" @click="openMemories">
<view class="feature-entry-icon memory">记</view> <view class="feature-entry-icon">
<uni-icons type="auth" size="20" color="#e60012" />
</view>
<view class="feature-entry-copy"> <view class="feature-entry-copy">
<text class="feature-entry-title">我的资料与记忆</text> <text class="feature-entry-title">我的资料与记忆</text>
<text class="feature-entry-desc">确认待保存事项 · 查看项目跟进</text> <text class="feature-entry-desc">确认待保存事项 · 查看项目跟进</text>
</view> </view>
<text class="points-entry-arrow">›</text> <text class="points-entry-arrow">›</text>
</button> </button>
<button class="feature-entry" @click="openLearning">
<view class="feature-entry-icon learn">学</view>
<view class="feature-entry-copy">
<text class="feature-entry-title">学习中心</text>
<text class="feature-entry-desc">课程学习 · 错题本 · 岗位 SOP</text>
</view>
<text class="points-entry-arrow">›</text>
</button>
<button class="feature-entry" @click="openPracticeSummary"> <button class="feature-entry" @click="openPracticeSummary">
<view class="feature-entry-icon summary">训</view> <view class="feature-entry-icon">
<uni-icons type="list" size="20" color="#e60012" />
</view>
<view class="feature-entry-copy"> <view class="feature-entry-copy">
<text class="feature-entry-title">我的训练概况</text> <text class="feature-entry-title">我的训练概况</text>
<text class="feature-entry-desc">问师傅帮你汇总近期训练情况</text> <text class="feature-entry-desc">问师傅帮你汇总近期训练情况</text>
</view> </view>
<text class="points-entry-arrow">›</text> <text class="points-entry-arrow">›</text>
</button> </button>
<button class="feature-entry" @click="openReport">
<view class="feature-entry-icon report">报</view>
<view class="feature-entry-copy">
<text class="feature-entry-title">工作上报</text>
<text class="feature-entry-desc">上报案例 / 视频 / SOP / 知识</text>
</view>
<text class="points-entry-arrow">›</text>
</button>
</view> </view>
<view v-if="loggedIn && evidence" class="panel evidence-panel"> <view v-if="loggedIn && evidence" class="panel evidence-panel">
@@ -226,6 +187,31 @@
</view> </view>
<view v-else class="empty">暂无训练记录</view> <view v-else class="empty">暂无训练记录</view>
</view> </view>
<view class="panel display-panel">
<button class="display-heading" @click="showFontOptions = !showFontOptions">
<view class="display-heading-copy">
<text class="panel-title">显示与辅助</text>
<text class="display-current">字体大小 · {{ currentFontSizeLabel }}</text>
</view>
<text class="display-toggle">{{ showFontOptions ? '收起' : '调整' }}</text>
</button>
<template v-if="showFontOptions">
<view class="font-size-options" role="group" aria-label="字体大小">
<button
v-for="option in appFontSizeOptions"
:key="option.value"
class="font-size-option"
:class="{ active: fontSize === option.value }"
:aria-pressed="fontSize === option.value"
@click="changeFontSize(option.value)"
>
{{ option.label }}
</button>
</view>
<text class="display-note">切换后同步到今日、练、问、我全部页面,并自动保存。</text>
</template>
</view>
</view> </view>
</template> </template>
@@ -261,6 +247,7 @@ const history = ref<PracticeRecord[]>([]);
const showGrowthDetail = ref(false); const showGrowthDetail = ref(false);
const showEvidence = ref(false); const showEvidence = ref(false);
const showAllHistory = ref(false); const showAllHistory = ref(false);
const showFontOptions = ref(false);
const visibleHistory = computed(() => (showAllHistory.value ? history.value : history.value.slice(0, 3))); const visibleHistory = computed(() => (showAllHistory.value ? history.value : history.value.slice(0, 3)));
const evidence = ref<PromotionEvidence | null>(null); const evidence = ref<PromotionEvidence | null>(null);
const positionProfile = ref(getSelectedPositionProfile()); const positionProfile = ref(getSelectedPositionProfile());
@@ -296,10 +283,6 @@ const openPoints = () => {
uni.navigateTo({ url: '/pages/user/points/index' }); uni.navigateTo({ url: '/pages/user/points/index' });
}; };
const openLearning = () => {
uni.navigateTo({ url: '/pages/user/learning/index' });
};
const openMemories = () => { const openMemories = () => {
uni.navigateTo({ url: '/pages/user/assistant/memories' }); uni.navigateTo({ url: '/pages/user/assistant/memories' });
}; };
@@ -309,10 +292,6 @@ const openPracticeSummary = () => {
uni.switchTab({ url: '/pages/user/sop/index' }); uni.switchTab({ url: '/pages/user/sop/index' });
}; };
const openReport = () => {
uni.navigateTo({ url: '/pages/user/report/index' });
};
const clearProfileData = () => { const clearProfileData = () => {
profile.value = null; profile.value = null;
history.value = []; history.value = [];
@@ -415,17 +394,6 @@ onShow(() => {
background: linear-gradient(180deg, #f8fbff 0%, #edf4ff 100%); background: linear-gradient(180deg, #f8fbff 0%, #edf4ff 100%);
} }
.profile-head::after {
position: absolute;
right: 20px;
bottom: 20px;
width: 42px;
height: 4px;
border-radius: 4px;
background: var(--employee-primary);
content: '';
}
.profile-eyebrow { .profile-eyebrow {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 10px;
@@ -448,22 +416,115 @@ onShow(() => {
font-size: 14px; font-size: 14px;
} }
.account-card, .identity-card {
.history-head, display: flex;
.supervisor-mode-panel, align-items: center;
.display-heading { gap: 14px;
margin-top: 16px;
}
.identity-avatar {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
border-radius: 50%;
background: var(--employee-primary-soft);
}
.identity-info {
flex: 1;
display: flex;
min-width: 0;
flex-direction: column;
}
.identity-phone {
color: var(--employee-text);
font-size: 17px;
font-weight: 900;
}
.identity-position {
margin-top: 4px;
color: var(--employee-primary);
font-size: 13px;
font-weight: 700;
}
.identity-source {
margin-top: 4px;
color: var(--employee-muted);
font-size: 12px;
line-height: 1.45;
}
.identity-action {
min-width: 84px;
padding: 0 12px;
}
.identity-logout {
flex-shrink: 0;
padding: 6px 4px;
color: var(--employee-muted);
font-size: 13px;
}
.supervisor-mode-panel {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
} }
.account-action {
min-width: 84px;
padding: 0 12px;
}
.display-panel { .display-panel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 14px; gap: 14px;
} }
.display-heading {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: 0;
padding: 0;
border: none;
background: transparent;
line-height: 1.3;
text-align: left;
}
.display-heading::after {
border: none;
}
.display-heading-copy {
display: flex;
min-width: 0;
flex-direction: column;
}
.display-toggle {
flex-shrink: 0;
padding: 4px 12px;
border: 1px solid #e3e6ea;
border-radius: 999px;
background: #f7f8fa;
color: #6b7480;
font-size: 12px;
}
.display-current, .display-current,
.display-note { .display-note {
display: block; display: block;
@@ -501,49 +562,8 @@ onShow(() => {
box-shadow: 0 4px 12px rgba(24, 34, 48, 0.08); box-shadow: 0 4px 12px rgba(24, 34, 48, 0.08);
} }
.account-card {
margin-top: 16px;
border-left: 4px solid var(--employee-primary);
}
.account-phone {
display: block;
margin-top: 8px;
color: var(--employee-muted);
font-size: 14px;
}
.account-action {
min-width: 84px;
padding: 0 12px;
}
.position-current {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.position-title {
display: block;
margin-top: 8px;
color: var(--employee-primary);
font-size: 16px;
font-weight: 900;
}
.position-source {
display: block;
margin-top: 7px;
color: var(--employee-muted);
font-size: 12px;
line-height: 1.5;
}
.profile-summary { .profile-summary {
border-color: var(--employee-line); border-color: var(--employee-line);
border-top: 3px solid var(--employee-primary);
} }
.score-row { .score-row {
@@ -568,11 +588,11 @@ onShow(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 18px; border-radius: 18px;
background: var(--employee-primary-soft); background: #f5f7fa;
} }
.score-value { .score-value {
color: var(--employee-primary); color: var(--employee-text);
font-size: 42px; font-size: 42px;
font-weight: 900; font-weight: 900;
line-height: 1; line-height: 1;
@@ -783,7 +803,7 @@ onShow(() => {
} }
.dimension-score { .dimension-score {
color: var(--employee-primary); color: var(--employee-text);
font-size: 20px; font-size: 20px;
font-weight: 900; font-weight: 900;
} }
@@ -803,6 +823,13 @@ onShow(() => {
font-size: 13px; font-size: 13px;
} }
.history-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.history-panel { .history-panel {
border-color: var(--employee-line); border-color: var(--employee-line);
} }
@@ -825,7 +852,7 @@ onShow(() => {
} }
.history-score text:first-child { .history-score text:first-child {
color: var(--employee-primary); color: var(--employee-text);
font-size: 20px; font-size: 20px;
font-weight: 900; font-weight: 900;
} }
@@ -871,25 +898,7 @@ onShow(() => {
width: 38px; width: 38px;
height: 38px; height: 38px;
border-radius: 10px; border-radius: 10px;
color: #ffffff; background: var(--employee-primary-soft);
font-size: 16px;
font-weight: 700;
}
.feature-entry-icon.learn {
background: #1f5c9d;
}
.feature-entry-icon.memory {
background: #5d6878;
}
.feature-entry-icon.summary {
background: #e70014;
}
.feature-entry-icon.report {
background: #d98200;
} }
.feature-entry-copy { .feature-entry-copy {
+3 -1
View File
@@ -5,6 +5,8 @@ const uni = typeof uniPlugin === 'function' ? uniPlugin : uniPlugin.default;
const devPort = Number(process.env.PORT) || 5175; const devPort = Number(process.env.PORT) || 5175;
const devHost = process.env.HOST || '0.0.0.0'; const devHost = process.env.HOST || '0.0.0.0';
const proxyTarget = process.env.VITE_PROXY_TARGET || 'https://wygj-api.localhost'; const proxyTarget = process.env.VITE_PROXY_TARGET || 'https://wygj-api.localhost';
// 代理到线上等已带 /dev-api 前缀的网关时,设 VITE_PROXY_KEEP_PREFIX=1 保留前缀
const keepProxyPrefix = process.env.VITE_PROXY_KEEP_PREFIX === '1';
export default defineConfig({ export default defineConfig({
plugins: [uni()], plugins: [uni()],
@@ -17,7 +19,7 @@ export default defineConfig({
target: proxyTarget, target: proxyTarget,
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
rewrite: (path) => path.replace(/^\/dev-api/, '') rewrite: (path) => (keepProxyPrefix ? path : path.replace(/^\/dev-api/, ''))
} }
} }
} }