客户 2026-07-18 语音需求落地:「报」分四种——优秀案例/操作视频/
完整SOP/有用知识,员工提交、主管或运营审核。
后端(新包 org.dromara.aihr.report):
- aihr_work_report 表(自建,utf8mb4_general_ci 显式声明,含提交人/
审核人/状态/附件 OSS 编号,提交人与状态双索引)
- POST /api/aihr/work-report/reports 员工提交(类型/标题/说明校验)
- POST /attachment 附件上传(≤100MB,OSS 落盘,ossId 字符串安全)
- GET /reports/mine 我的上报;GET /reports 审核队列(canModerate 门禁,
复用社区 Policy);POST /{id}/review APPROVE/REJECT
(仅 PENDING 可审,重复审核 400)
- 审核通过后的入库动作留给运营在案例库/知识库人工完成(不自动入库,
遵守'入知识库必须人工确认'原则)
员工端:
- 新页 pages/user/report(替换'建设中'占位):四类型卡片选择、
标题+说明(说明支持录音转写)、附件选择上传(视频类必传校验)、
我的上报列表(待审核/已通过/已驳回+审核意见)
- 「我」页常用功能入口文案更新
生产验证(端到端):
- API:员工提交→我的上报可见→员工查审核队列403→主管队列可见
→审核通过→重复审核400 全通过
- UI:我页入口→上报页→选类型→填表→提交→列表显示'待审核',
历史记录显示'已通过'+审核意见
- 后端发布(备份 ruoyi-admin.jar.bak-20260718123032,25s恢复),
H5 已发布(备份 h5-before-20260718123032)
二期待办:主管端待审列表页(现主管可用接口审核)、通过后一键转
案例库/知识库、上报采纳积分奖励(等客户定规则)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
931 lines
23 KiB
Vue
931 lines
23 KiB
Vue
<template>
|
||
<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>
|
||
</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>
|
||
<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>
|
||
</view>
|
||
|
||
<view v-if="supervisorLearning" class="panel supervisor-mode-panel">
|
||
<view>
|
||
<text class="panel-title">当前为学员模式</text>
|
||
<text class="position-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>
|
||
<view class="score-row">
|
||
<view class="score-main">
|
||
<text class="score-value">{{ profile.score }}</text>
|
||
<text class="score-label">训练综合分</text>
|
||
</view>
|
||
<view class="metric-grid">
|
||
<view class="metric">
|
||
<text>{{ profile.completed }}</text>
|
||
<text>已训练</text>
|
||
</view>
|
||
<view class="metric">
|
||
<text>{{ profile.pendingReview }}</text>
|
||
<text>待复盘</text>
|
||
</view>
|
||
<view class="metric">
|
||
<text>{{ profile.trainingMinutes || 0 }}</text>
|
||
<text>分钟</text>
|
||
</view>
|
||
<view class="metric">
|
||
<text>{{ profile.aiLevel || '未评级' }}</text>
|
||
<text>训练画像等级(参考)</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<button class="fold-toggle" @click="showGrowthDetail = !showGrowthDetail">
|
||
{{ showGrowthDetail ? '收起能力详情 ▲' : '展开能力详情(五维评分 · 成长路径)▼' }}
|
||
</button>
|
||
<view v-if="showGrowthDetail && profile.dimensions?.length" class="dimension-list">
|
||
<view v-for="item in profile.dimensions" :key="item.label" class="dimension-row">
|
||
<view>
|
||
<text class="dimension-title">{{ item.label }}</text>
|
||
<text class="dimension-note">{{ item.note }}</text>
|
||
</view>
|
||
<text class="dimension-score" :class="{ pending: item.value === null || item.value === undefined }">
|
||
{{ item.value === null || item.value === undefined ? '待采集' : item.value }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="showGrowthDetail && profile.growthPath?.length" class="growth-section">
|
||
<text class="section-label">成长路径</text>
|
||
<view v-for="stage in profile.growthPath" :key="stage.level" class="growth-row">
|
||
<view class="growth-copy">
|
||
<text class="growth-title">{{ stage.level }} · {{ stage.status }}</text>
|
||
<text class="growth-desc">{{ stage.evidence }}</text>
|
||
</view>
|
||
<view class="growth-progress">
|
||
<view class="growth-bar">
|
||
<view class="growth-fill" :style="{ width: `${stage.progress}%` }" />
|
||
</view>
|
||
<text>{{ stage.progress }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<button v-if="loggedIn" class="panel points-entry" @click="openPoints">
|
||
<view>
|
||
<text class="panel-title">我的积分</text>
|
||
<text class="points-entry-desc">查看问题榜奖励积分与学习学分明细</text>
|
||
</view>
|
||
<text class="points-entry-arrow">›</text>
|
||
</button>
|
||
|
||
<view v-if="loggedIn" class="panel feature-entries">
|
||
<text class="panel-title">常用功能</text>
|
||
<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-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">
|
||
<view class="fold-head">
|
||
<text class="panel-title">成长证据包(待人工复核)</text>
|
||
<button class="fold-toggle inline" @click="showEvidence = !showEvidence">
|
||
{{ showEvidence ? '收起 ▲' : '展开 ▼' }}
|
||
</button>
|
||
</view>
|
||
<template v-if="showEvidence">
|
||
<view class="evidence-grid">
|
||
<view class="evidence-metric">
|
||
<text>{{ evidence.currentLevel }}</text>
|
||
<text>参考等级</text>
|
||
</view>
|
||
<view class="evidence-metric">
|
||
<text>{{ evidence.assessmentScore }}</text>
|
||
<text>AI测评分</text>
|
||
</view>
|
||
<view class="evidence-metric">
|
||
<text>{{ evidence.humanAssessmentScore ?? '待采集' }}</text>
|
||
<text>人工校准分</text>
|
||
</view>
|
||
<view class="evidence-metric">
|
||
<text>{{ evidence.finalAdoptedScore ?? evidence.assessmentScore }}</text>
|
||
<text>最终采用分</text>
|
||
</view>
|
||
<view class="evidence-metric">
|
||
<text>{{ evidence.trainingMinutes }}</text>
|
||
<text>训练分钟</text>
|
||
</view>
|
||
<view class="evidence-metric">
|
||
<text>{{ evidence.contribution }}</text>
|
||
<text>人工贡献分</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="evidence.evidenceItems?.length" class="evidence-list">
|
||
<text v-for="item in evidence.evidenceItems" :key="item" class="evidence-item">{{ item }}</text>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
|
||
<view v-if="loggedIn" class="panel history-panel">
|
||
<view class="history-head">
|
||
<text class="panel-title">最近练过</text>
|
||
<button class="ghost-button refresh-button" :disabled="loading" @click="loadData">刷新</button>
|
||
</view>
|
||
<view v-if="history.length" class="history-list">
|
||
<view
|
||
v-for="item in visibleHistory"
|
||
:key="item.id || item.sessionId || item.time"
|
||
class="history-row history-row-clickable"
|
||
@click="openHistory(item.id)"
|
||
>
|
||
<view>
|
||
<text class="history-title">{{ item.scene }}</text>
|
||
<text class="history-desc">{{ item.time }} · {{ item.summary }}</text>
|
||
</view>
|
||
<view class="history-score">
|
||
<text>{{ item.score }}</text>
|
||
<text>{{ item.status }}</text>
|
||
</view>
|
||
</view>
|
||
<button v-if="history.length > 3" class="fold-toggle" @click="showAllHistory = !showAllHistory">
|
||
{{ showAllHistory ? '收起 ▲' : `展开全部 ${history.length} 条 ▼` }}
|
||
</button>
|
||
</view>
|
||
<view v-else class="empty">暂无训练记录</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import type { CompetencyProfile, PracticeRecord, PromotionEvidence } from '@/types/api';
|
||
import {
|
||
clearAuth,
|
||
getAuth,
|
||
getSelectedPositionProfile,
|
||
isLoggedIn,
|
||
isSupervisorLearnerMode,
|
||
leaveSupervisorLearnerMode,
|
||
rememberLoginRedirect
|
||
} from '@/services/auth';
|
||
import {
|
||
appFontSizeOptions,
|
||
getAppFontSize,
|
||
setAppFontSize,
|
||
type AppFontSize
|
||
} from '@/services/accessibility';
|
||
import { getCompetencyProfile, getPracticeHistory, getPromotionEvidence } from '@/services/practice';
|
||
import { resetPageScroll } from '@/services/navigation';
|
||
import { ensureEmployeePosition } from '@/services/position';
|
||
|
||
const loggedIn = ref(false);
|
||
const phone = ref('');
|
||
const loading = ref(false);
|
||
const message = ref('');
|
||
const profile = ref<CompetencyProfile | null>(null);
|
||
const history = ref<PracticeRecord[]>([]);
|
||
const showGrowthDetail = ref(false);
|
||
const showEvidence = ref(false);
|
||
const showAllHistory = ref(false);
|
||
const visibleHistory = computed(() => (showAllHistory.value ? history.value : history.value.slice(0, 3)));
|
||
const evidence = ref<PromotionEvidence | null>(null);
|
||
const positionProfile = ref(getSelectedPositionProfile());
|
||
const supervisorLearning = ref(false);
|
||
const fontSize = ref<AppFontSize>(getAppFontSize());
|
||
const currentFontSizeLabel = computed(() =>
|
||
appFontSizeOptions.find((item) => item.value === fontSize.value)?.label || '标准'
|
||
);
|
||
let profileLoadGeneration = 0;
|
||
let profileAccountPhone = '';
|
||
|
||
const refreshAuth = () => {
|
||
const auth = getAuth();
|
||
loggedIn.value = isLoggedIn();
|
||
phone.value = auth.phone;
|
||
positionProfile.value = getSelectedPositionProfile();
|
||
supervisorLearning.value = isSupervisorLearnerMode();
|
||
fontSize.value = getAppFontSize();
|
||
};
|
||
|
||
const changeFontSize = (value: AppFontSize) => {
|
||
fontSize.value = setAppFontSize(value);
|
||
uni.showToast({ title: `已切换为${currentFontSizeLabel.value}`, icon: 'none' });
|
||
};
|
||
|
||
const returnToSupervisor = () => {
|
||
leaveSupervisorLearnerMode();
|
||
supervisorLearning.value = false;
|
||
uni.redirectTo({ url: '/pages/supervisor/index/index' });
|
||
};
|
||
|
||
const openPoints = () => {
|
||
uni.navigateTo({ url: '/pages/user/points/index' });
|
||
};
|
||
|
||
const openLearning = () => {
|
||
uni.navigateTo({ url: '/pages/user/learning/index' });
|
||
};
|
||
|
||
const openPracticeSummary = () => {
|
||
uni.setStorageSync('aihr_sop_pending_tool', 'MY_PRACTICE_SUMMARY');
|
||
uni.switchTab({ url: '/pages/user/sop/index' });
|
||
};
|
||
|
||
const openReport = () => {
|
||
uni.navigateTo({ url: '/pages/user/report/index' });
|
||
};
|
||
|
||
const clearProfileData = () => {
|
||
profile.value = null;
|
||
history.value = [];
|
||
evidence.value = null;
|
||
profileAccountPhone = '';
|
||
};
|
||
|
||
const loadData = async () => {
|
||
const generation = ++profileLoadGeneration;
|
||
refreshAuth();
|
||
if (!loggedIn.value) {
|
||
clearProfileData();
|
||
return;
|
||
}
|
||
const requestedPhone = getAuth().phone;
|
||
if (profileAccountPhone && profileAccountPhone !== requestedPhone) {
|
||
clearProfileData();
|
||
}
|
||
if (!(await ensureEmployeePosition('/pages/user/profile/index'))) {
|
||
return;
|
||
}
|
||
refreshAuth();
|
||
if (generation !== profileLoadGeneration || !loggedIn.value) return;
|
||
const accountPhone = getAuth().phone;
|
||
loading.value = true;
|
||
message.value = '';
|
||
try {
|
||
const [nextHistory, nextProfile, nextEvidence] = await Promise.all([
|
||
getPracticeHistory(),
|
||
getCompetencyProfile(),
|
||
getPromotionEvidence()
|
||
]);
|
||
refreshAuth();
|
||
if (generation !== profileLoadGeneration) return;
|
||
if (!loggedIn.value || getAuth().phone !== accountPhone) {
|
||
clearProfileData();
|
||
return;
|
||
}
|
||
history.value = nextHistory || [];
|
||
profile.value = nextProfile || null;
|
||
evidence.value = nextEvidence || null;
|
||
profileAccountPhone = accountPhone;
|
||
} catch (error) {
|
||
refreshAuth();
|
||
if (generation !== profileLoadGeneration) return;
|
||
if (!loggedIn.value || getAuth().phone !== accountPhone) {
|
||
clearProfileData();
|
||
message.value = loggedIn.value ? '账号已切换,请刷新' : '登录已过期,请重新登录';
|
||
return;
|
||
}
|
||
message.value = error instanceof Error ? error.message : '我的数据加载失败';
|
||
} finally {
|
||
if (generation === profileLoadGeneration) loading.value = false;
|
||
}
|
||
};
|
||
|
||
const goLogin = () => {
|
||
rememberLoginRedirect('/pages/user/profile/index');
|
||
uni.navigateTo({ url: '/pages/auth/login/index' });
|
||
};
|
||
|
||
const logout = () => {
|
||
profileLoadGeneration += 1;
|
||
loading.value = false;
|
||
clearAuth();
|
||
clearProfileData();
|
||
refreshAuth();
|
||
uni.showToast({ title: '已退出', icon: 'none' });
|
||
};
|
||
|
||
const openHistory = (id?: number) => {
|
||
if (!id) {
|
||
message.value = '这条训练记录暂不支持查看详情';
|
||
return;
|
||
}
|
||
uni.navigateTo({ url: `/pages/user/history/index?id=${id}` });
|
||
};
|
||
|
||
onShow(() => {
|
||
resetPageScroll();
|
||
void loadData();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.profile-page {
|
||
padding: 0 16px 108px;
|
||
}
|
||
|
||
.profile-page .panel {
|
||
border-radius: var(--employee-card-radius);
|
||
box-shadow: var(--employee-card-shadow);
|
||
}
|
||
|
||
.profile-head {
|
||
position: relative;
|
||
margin: 0 -16px;
|
||
padding: 30px 20px 26px;
|
||
border-bottom: 1px solid rgba(47, 74, 117, 0.08);
|
||
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;
|
||
color: var(--employee-primary);
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
letter-spacing: 0.04em;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.profile-head .page-title {
|
||
color: var(--employee-text);
|
||
font-size: 28px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.profile-head .page-subtitle {
|
||
margin-top: 10px;
|
||
color: var(--employee-muted);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.account-card,
|
||
.history-head,
|
||
.supervisor-mode-panel,
|
||
.display-heading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.display-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14px;
|
||
}
|
||
|
||
.display-current,
|
||
.display-note {
|
||
display: block;
|
||
margin-top: 7px;
|
||
color: var(--employee-muted);
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.font-size-options {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 8px;
|
||
padding: 4px;
|
||
border-radius: 14px;
|
||
background: #f0f3f8;
|
||
}
|
||
|
||
.font-size-option {
|
||
min-width: 0;
|
||
min-height: 42px;
|
||
padding: 0 2px;
|
||
border: 1px solid transparent;
|
||
border-radius: 11px;
|
||
background: transparent;
|
||
color: var(--employee-text);
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.font-size-option.active {
|
||
border-color: #f0c8cc;
|
||
background: #ffffff;
|
||
color: var(--employee-primary);
|
||
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 {
|
||
display: grid;
|
||
margin-top: 16px;
|
||
grid-template-columns: 104px 1fr;
|
||
gap: 14px;
|
||
}
|
||
|
||
.ai-score-disclaimer {
|
||
display: block;
|
||
margin-top: 8px;
|
||
color: var(--employee-muted);
|
||
font-size: 12px;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.score-main {
|
||
display: flex;
|
||
min-height: 124px;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 18px;
|
||
background: var(--employee-primary-soft);
|
||
}
|
||
|
||
.score-value {
|
||
color: var(--employee-primary);
|
||
font-size: 42px;
|
||
font-weight: 900;
|
||
line-height: 1;
|
||
}
|
||
|
||
.score-label {
|
||
margin-top: 8px;
|
||
color: var(--employee-muted);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.metric-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
}
|
||
|
||
.metric {
|
||
display: flex;
|
||
min-height: 58px;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
padding: 8px 10px;
|
||
border: 1px solid var(--employee-line);
|
||
border-radius: 14px;
|
||
background: #fbfdff;
|
||
}
|
||
|
||
.metric text:first-child {
|
||
color: var(--employee-text);
|
||
font-size: 18px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.metric text:last-child {
|
||
margin-top: 4px;
|
||
color: var(--employee-muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.evidence-panel {
|
||
border-color: #f4e4c4;
|
||
border-left: 4px solid var(--employee-warning);
|
||
background: #fffaf2;
|
||
}
|
||
|
||
.evidence-grid {
|
||
display: grid;
|
||
margin-top: 14px;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
}
|
||
|
||
.evidence-metric {
|
||
display: flex;
|
||
min-height: 58px;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
padding: 8px 10px;
|
||
border: 1px solid #f4e7ce;
|
||
border-radius: 14px;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.evidence-metric text:first-child {
|
||
color: #b56b14;
|
||
font-size: 18px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.evidence-metric text:last-child {
|
||
margin-top: 4px;
|
||
color: var(--employee-muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.evidence-list {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.evidence-item {
|
||
display: block;
|
||
padding: 9px 0;
|
||
border-top: 1px solid #efe1bb;
|
||
color: #775000;
|
||
font-size: 13px;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.dimension-list,
|
||
.history-list {
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.growth-section {
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.section-label {
|
||
display: block;
|
||
color: #34373c;
|
||
font-size: 14px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.growth-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 96px;
|
||
gap: 12px;
|
||
padding: 12px 0;
|
||
border-top: 1px solid #e7e7e9;
|
||
}
|
||
|
||
.growth-row:first-of-type {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.growth-title,
|
||
.growth-desc {
|
||
display: block;
|
||
}
|
||
|
||
.growth-title {
|
||
color: var(--employee-text);
|
||
font-size: 14px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.growth-desc {
|
||
margin-top: 5px;
|
||
color: var(--employee-muted);
|
||
font-size: 12px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.growth-progress {
|
||
display: flex;
|
||
min-width: 0;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
color: var(--employee-primary);
|
||
font-size: 12px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.growth-bar {
|
||
width: 88px;
|
||
height: 6px;
|
||
overflow: hidden;
|
||
border-radius: 4px;
|
||
background: #dce8fb;
|
||
}
|
||
|
||
.growth-fill {
|
||
height: 100%;
|
||
border-radius: inherit;
|
||
background: var(--employee-primary);
|
||
}
|
||
|
||
.points-entry {
|
||
display: flex;
|
||
width: calc(100% - 0px);
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
color: var(--ink);
|
||
text-align: left;
|
||
}
|
||
|
||
.points-entry-desc {
|
||
display: block;
|
||
margin-top: 5px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.points-entry-arrow {
|
||
color: var(--muted);
|
||
font-size: 28px;
|
||
}
|
||
|
||
.dimension-row,
|
||
.history-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 12px 0;
|
||
border-top: 1px solid #e7e7e9;
|
||
}
|
||
|
||
.dimension-title,
|
||
.history-title {
|
||
display: block;
|
||
color: var(--employee-text);
|
||
font-weight: 900;
|
||
}
|
||
|
||
.dimension-note,
|
||
.history-desc {
|
||
display: block;
|
||
margin-top: 5px;
|
||
color: var(--employee-muted);
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.dimension-score {
|
||
color: var(--employee-primary);
|
||
font-size: 20px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.dimension-score.pending {
|
||
color: var(--employee-muted);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.refresh-button {
|
||
min-height: 36px;
|
||
padding: 0 12px;
|
||
border: 1px solid #dce5f2;
|
||
border-radius: var(--employee-chip-radius);
|
||
background: #ffffff;
|
||
color: #526176;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.history-panel {
|
||
border-color: var(--employee-line);
|
||
}
|
||
|
||
.history-row-clickable {
|
||
min-height: 68px;
|
||
padding: 14px 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.history-row-clickable:active {
|
||
opacity: 0.72;
|
||
}
|
||
|
||
.history-score {
|
||
display: flex;
|
||
min-width: 52px;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.history-score text:first-child {
|
||
color: var(--employee-primary);
|
||
font-size: 20px;
|
||
font-weight: 900;
|
||
}
|
||
|
||
.history-score text:last-child {
|
||
margin-top: 4px;
|
||
color: var(--employee-muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.feature-entries {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.feature-entry {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
width: 100%;
|
||
margin: 0;
|
||
padding: 12px 4px;
|
||
border: none;
|
||
background: transparent;
|
||
text-align: left;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.feature-entry::after {
|
||
border: none;
|
||
}
|
||
|
||
.feature-entry + .feature-entry {
|
||
border-top: 1px solid #f2f3f5;
|
||
}
|
||
|
||
.feature-entry-icon {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 38px;
|
||
height: 38px;
|
||
border-radius: 10px;
|
||
color: #ffffff;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.feature-entry-icon.learn {
|
||
background: #1f5c9d;
|
||
}
|
||
|
||
.feature-entry-icon.summary {
|
||
background: #e70014;
|
||
}
|
||
|
||
.feature-entry-icon.report {
|
||
background: #d98200;
|
||
}
|
||
|
||
.feature-entry-copy {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.feature-entry-title {
|
||
color: #18202b;
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.feature-entry-desc {
|
||
color: #8a93a0;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.fold-toggle {
|
||
display: block;
|
||
width: 100%;
|
||
margin: 10px 0 0;
|
||
padding: 8px 0;
|
||
border: none;
|
||
border-top: 1px dashed #eceff2;
|
||
background: transparent;
|
||
color: #6b7480;
|
||
font-size: 13px;
|
||
text-align: center;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.fold-toggle::after {
|
||
border: none;
|
||
}
|
||
|
||
.fold-toggle.inline {
|
||
width: auto;
|
||
margin: 0;
|
||
padding: 2px 10px;
|
||
border: 1px solid #e3e6ea;
|
||
border-radius: 999px;
|
||
background: #f7f8fa;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.fold-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
</style>
|