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="profile-head">
<text class="profile-eyebrow">银城物业 · 员工成长</text>
<text class="page-title">我的成长</text>
<text class="page-subtitle">{{ phone || '登录后查看训练记录和能力画像' }}</text>
<text class="page-title">我</text>
<text class="page-subtitle">训练记录、积分与个人能力画像</text>
</view>
<view v-if="message" class="status-message">{{ message }}</view>
<view class="panel account-card">
<view>
<text class="panel-title">账号</text>
<text class="account-phone">{{ phone || '未登录' }}</text>
<view class="panel identity-card">
<view class="identity-avatar">
<uni-icons type="person-filled" size="26" color="#e60012" />
</view>
<button v-if="!loggedIn" class="primary-button account-action" @click="goLogin">去登录</button>
<button v-else class="secondary-button account-action" @click="logout">退出</button>
</view>
<view v-if="loggedIn" class="panel position-panel">
<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 class="identity-info">
<text class="identity-phone">{{ phone || '未登录' }}</text>
<text v-if="loggedIn" class="identity-position">{{ positionProfile.value }} · {{ positionProfile.heroTitle }}</text>
<text v-if="loggedIn" class="identity-source">岗位来自人力系统组织同步,如需调整请联系 HR</text>
<text v-else class="identity-source">登录后查看训练记录和能力画像</text>
</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 v-if="supervisorLearning" class="panel supervisor-mode-panel">
<view>
<text class="panel-title">当前为学员模式</text>
<text class="position-source">主管组织身份未改变,仅切换到个人学习与训练界面。</text>
<text class="identity-source">主管组织身份未改变,仅切换到个人学习与训练界面。</text>
</view>
<button class="secondary-button account-action" @click="returnToSupervisor">返回主管工作台</button>
</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">
<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-main">
<text class="score-value">{{ profile.score }}</text>
<text class="score-label">训练综合分</text>
<text class="score-label">训练综合分</text>
</view>
<view class="metric-grid">
<view class="metric">
@@ -124,39 +97,27 @@
</button>
<view v-if="loggedIn" class="panel feature-entries">
<text class="panel-title">常用功能</text>
<text class="panel-title">我的助手</text>
<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">
<text class="feature-entry-title">我的资料与记忆</text>
<text class="feature-entry-desc">确认待保存事项 · 查看项目跟进</text>
</view>
<text class="points-entry-arrow">›</text>
</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">
<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">
<text class="feature-entry-title">我的训练概况</text>
<text class="feature-entry-desc">问师傅帮你汇总近期训练情况</text>
</view>
<text class="points-entry-arrow">›</text>
</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 v-if="loggedIn && evidence" class="panel evidence-panel">
@@ -226,6 +187,31 @@
</view>
<view v-else class="empty">暂无训练记录</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>
</template>
@@ -261,6 +247,7 @@ const history = ref<PracticeRecord[]>([]);
const showGrowthDetail = ref(false);
const showEvidence = ref(false);
const showAllHistory = ref(false);
const showFontOptions = ref(false);
const visibleHistory = computed(() => (showAllHistory.value ? history.value : history.value.slice(0, 3)));
const evidence = ref<PromotionEvidence | null>(null);
const positionProfile = ref(getSelectedPositionProfile());
@@ -296,10 +283,6 @@ const openPoints = () => {
uni.navigateTo({ url: '/pages/user/points/index' });
};
const openLearning = () => {
uni.navigateTo({ url: '/pages/user/learning/index' });
};
const openMemories = () => {
uni.navigateTo({ url: '/pages/user/assistant/memories' });
};
@@ -309,10 +292,6 @@ const openPracticeSummary = () => {
uni.switchTab({ url: '/pages/user/sop/index' });
};
const openReport = () => {
uni.navigateTo({ url: '/pages/user/report/index' });
};
const clearProfileData = () => {
profile.value = null;
history.value = [];
@@ -415,17 +394,6 @@ onShow(() => {
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 {
display: block;
margin-bottom: 10px;
@@ -448,22 +416,115 @@ onShow(() => {
font-size: 14px;
}
.account-card,
.history-head,
.supervisor-mode-panel,
.display-heading {
.identity-card {
display: flex;
align-items: center;
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;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.account-action {
min-width: 84px;
padding: 0 12px;
}
.display-panel {
display: flex;
flex-direction: column;
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-note {
display: block;
@@ -501,49 +562,8 @@ onShow(() => {
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 {
border-color: var(--employee-line);
border-top: 3px solid var(--employee-primary);
}
.score-row {
@@ -568,11 +588,11 @@ onShow(() => {
align-items: center;
justify-content: center;
border-radius: 18px;
background: var(--employee-primary-soft);
background: #f5f7fa;
}
.score-value {
color: var(--employee-primary);
color: var(--employee-text);
font-size: 42px;
font-weight: 900;
line-height: 1;
@@ -783,7 +803,7 @@ onShow(() => {
}
.dimension-score {
color: var(--employee-primary);
color: var(--employee-text);
font-size: 20px;
font-weight: 900;
}
@@ -803,6 +823,13 @@ onShow(() => {
font-size: 13px;
}
.history-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.history-panel {
border-color: var(--employee-line);
}
@@ -825,7 +852,7 @@ onShow(() => {
}
.history-score text:first-child {
color: var(--employee-primary);
color: var(--employee-text);
font-size: 20px;
font-weight: 900;
}
@@ -871,25 +898,7 @@ onShow(() => {
width: 38px;
height: 38px;
border-radius: 10px;
color: #ffffff;
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;
background: var(--employee-primary-soft);
}
.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 devHost = process.env.HOST || '0.0.0.0';
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({
plugins: [uni()],
@@ -17,7 +19,7 @@ export default defineConfig({
target: proxyTarget,
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/dev-api/, '')
rewrite: (path) => (keepProxyPrefix ? path : path.replace(/^\/dev-api/, ''))
}
}
}