diff --git a/niucloud/app/api/controller/apiController/Course.php b/niucloud/app/api/controller/apiController/Course.php
index 77787823..d8b8f06e 100644
--- a/niucloud/app/api/controller/apiController/Course.php
+++ b/niucloud/app/api/controller/apiController/Course.php
@@ -90,7 +90,12 @@ class Course extends BaseApiService
public function courseAllList(Request $request){
$data = $this->request->params([
- ["schedule_date",0]
+ ["schedule_date",0],
+ ["start_date",""], // 开始日期
+ ["end_date",""], // 结束日期
+ ["teacher_name",""], // 教练姓名
+ ["venue_number",""], // 场地编号
+ ["time_hour",""] // 时间段
]);
return success((new CourseService())->listAll($data));
}
diff --git a/niucloud/app/model/course_schedule/CourseSchedule.php b/niucloud/app/model/course_schedule/CourseSchedule.php
index 943e5c23..1ab2c188 100644
--- a/niucloud/app/model/course_schedule/CourseSchedule.php
+++ b/niucloud/app/model/course_schedule/CourseSchedule.php
@@ -109,9 +109,5 @@ class CourseSchedule extends BaseModel
return $this->hasOne(Campus::class, 'id', 'campus_id')->joinType('left')->withField('campus_name,id')->bind(['campus_name'=>'campus_name']);
}
- public function studentCourses()
- {
- return $this->hasOne(StudentCourses::class, 'course_id', 'course_id');
- }
}
diff --git a/niucloud/app/service/api/apiService/CourseService.php b/niucloud/app/service/api/apiService/CourseService.php
index 2e1c6eed..e024297b 100644
--- a/niucloud/app/service/api/apiService/CourseService.php
+++ b/niucloud/app/service/api/apiService/CourseService.php
@@ -319,18 +319,81 @@ class CourseService extends BaseApiService
public function listAll($data)
{
$where = [];
+
+ // 基础日期查询
if ($data['schedule_date']) {
$where[] = ['course_date','=', $data['schedule_date']];
}
+
+ // 日期范围查询
+ if (!empty($data['start_date'])) {
+ $where[] = ['course_date','>=', $data['start_date']];
+ }
+ if (!empty($data['end_date'])) {
+ $where[] = ['course_date','<=', $data['end_date']];
+ }
+
+ // 场地编号筛选 - 根据venue表的id或venue_name进行筛选
+ if (!empty($data['venue_number'])) {
+ // 先查询匹配的场地ID
+ $venue_ids = Db::name('venue')->where('id', $data['venue_number'])->column('id');
+ if (empty($venue_ids)) {
+ // 如果根据ID查不到,可能是根据场地名称查询
+ $venue_ids = Db::name('venue')->where('venue_name', 'like', '%' . $data['venue_number'] . '%')->column('id');
+ }
+
+ if (empty($venue_ids)) {
+ // 如果没有找到匹配的场地,返回空结果
+ return [];
+ }
+
+ $where[] = ['venue_id', 'in', $venue_ids];
+ }
+
+ // 教练姓名筛选 - 需要关联personnel表
+ if (!empty($data['teacher_name'])) {
+ // 先查询匹配的教练ID
+ $coach_ids = Db::name('personnel')->where('name', 'like', '%' . $data['teacher_name'] . '%')->column('id');
+ if (!empty($coach_ids)) {
+ $where[] = ['coach_id', 'in', $coach_ids];
+ } else {
+ // 如果找不到匹配的教练,返回空结果
+ return [];
+ }
+ }
+
$CourseSchedule = new CourseSchedule();
- $list = $CourseSchedule
- ->where($where)
- ->with(['course' => function($query) {
- $query->select();
- },'venue' => function($query) {
- $query->select();
- },'campus','studentCourses'])
+ $query = $CourseSchedule->where($where);
+
+ // 时间段筛选 - 根据time_slot字段和传入的时间范围
+ if (!empty($data['time_hour']) && $data['time_hour'] !== '全部时间') {
+ $time_condition = $this->getTimeSlotCondition($data['time_hour']);
+ if ($time_condition) {
+ [$start_time, $end_time] = $time_condition;
+ // 查询time_slot字段中包含在指定时间范围内的课程
+ // time_slot格式为"HH:MM-HH:MM",我们需要检查时间段是否有重叠
+ $query = $query->where(function ($subQuery) use ($start_time, $end_time) {
+ $subQuery->where('time_slot', 'like', $start_time . '%')
+ ->whereOr('time_slot', 'like', '%' . $start_time . '%')
+ ->whereOr('time_slot', 'like', '%' . $end_time . '%')
+ ->whereOr(function ($subQuery2) use ($start_time, $end_time) {
+ // 检查开始时间是否在范围内
+ $subQuery2->whereRaw("TIME(SUBSTRING_INDEX(time_slot, '-', 1)) >= ?", [$start_time])
+ ->whereRaw("TIME(SUBSTRING_INDEX(time_slot, '-', 1)) < ?", [$end_time]);
+ })
+ ->whereOr(function ($subQuery3) use ($start_time, $end_time) {
+ // 检查结束时间是否在范围内
+ $subQuery3->whereRaw("TIME(SUBSTRING_INDEX(time_slot, '-', -1)) > ?", [$start_time])
+ ->whereRaw("TIME(SUBSTRING_INDEX(time_slot, '-', -1)) <= ?", [$end_time]);
+ });
+ });
+ }
+ }
+
+ $list = $query
+ ->with(['course','venue','campus','coach'])
->select()->toArray();
+
foreach ($list as $k => $v) {
$student = Db::name('person_course_schedule')
->alias('pcs')
@@ -344,6 +407,25 @@ class CourseService extends BaseApiService
}
return $list;
}
+
+ /**
+ * 根据时间选择器的值获取时间段查询条件
+ * @param string $timeHour
+ * @return array|null 返回时间范围数组 [start_hour, end_hour] 或 null
+ */
+ private function getTimeSlotCondition($timeHour)
+ {
+ switch ($timeHour) {
+ case '上午(8:00-12:00)':
+ return ['08:00', '12:00']; // 上午时间范围
+ case '下午(12:00-18:00)':
+ return ['12:00', '18:00']; // 下午时间范围
+ case '晚上(18:00-22:00)':
+ return ['18:00', '22:00']; // 晚上时间范围
+ default:
+ return null;
+ }
+ }
public function addSchedule(array $data){
diff --git a/niucloud/app/service/api/apiService/ResourceSharingService.php b/niucloud/app/service/api/apiService/ResourceSharingService.php
index cb9f714e..ac791073 100644
--- a/niucloud/app/service/api/apiService/ResourceSharingService.php
+++ b/niucloud/app/service/api/apiService/ResourceSharingService.php
@@ -160,7 +160,7 @@ class ResourceSharingService extends BaseApiService
}
}
}
-
+
// 共享人查询 - 如果指定了shared_by,优先处理这个条件
if (isset($where['shared_by']) && $where['shared_by']) {
$model = $model->where(function ($query) use ($where) {
@@ -192,7 +192,7 @@ class ResourceSharingService extends BaseApiService
});
}
}
-
+
// 处理查询条件 - CustomerResources模型的字段
$resource_conditions = [];
@@ -248,10 +248,14 @@ class ResourceSharingService extends BaseApiService
$model = $model->where('shared_at', '>=', $where['shared_at_arr'][0])
->where('shared_at', '<=', $where['shared_at_arr'][1]);
}
-
+
// 过滤已分配的资源(只显示可再分配的资源)
- $model = $model->where(function ($query) {
- $query->where('shared_by', 0)->whereOr('shared_by', null);
+ $model = $model->when($where['shared_by'] > 0, function ($query) use ($where) {
+ $query->where('shared_by', $where['shared_by'])->whereOr('user_id', $where['shared_by']);
+ }, function ($query) {
+ $query->where(function ($query) {
+ $query->where('shared_by', 0)->whereOr('shared_by', null);
+ });
});
// 查询数据
@@ -283,11 +287,11 @@ class ResourceSharingService extends BaseApiService
// 获取资源ID列表用于查询关联信息
$resource_ids = array_column($list['data'], 'resource_id');
$resource_ids = array_unique(array_filter($resource_ids));
-
+
// 获取分配人员ID列表
$shared_by_ids = array_column($list['data'], 'shared_by');
$shared_by_ids = array_unique(array_filter($shared_by_ids));
-
+
// 查询分配人员信息
$shared_by_names = [];
if (!empty($shared_by_ids)) {
@@ -326,7 +330,7 @@ class ResourceSharingService extends BaseApiService
// 处理到访信息
foreach ($visit_records as $record) {
$resource_id = $record['person_id'];
-
+
if (!isset($visit_info[$resource_id])) {
$visit_info[$resource_id] = [
'first_visit_status' => '未到',
@@ -334,10 +338,10 @@ class ResourceSharingService extends BaseApiService
'visit_count' => 0
];
}
-
+
if ($record['status'] == 1) { // 假设status=1表示已到
$visit_info[$resource_id]['visit_count']++;
-
+
if ($visit_info[$resource_id]['visit_count'] == 1) {
$visit_info[$resource_id]['first_visit_status'] = '已到';
} elseif ($visit_info[$resource_id]['visit_count'] == 2) {
@@ -369,22 +373,22 @@ class ResourceSharingService extends BaseApiService
$item['customerResource']['source'] = get_dict_value('source', $item['customerResource']['source']);
$item['customerResource']['source_channel'] = get_dict_value('SourceChannel', $item['customerResource']['source_channel']);
$item['customerResource']['campus_name'] = $campus_names[$item['customerResource']['campus']] ?? '';
-
+
// 修复沟通时间字段
$item['customerResource']['communication_time'] = $communication_times[$item['resource_id']] ?? '';
-
+
// 添加到访信息
$resource_id = $item['resource_id'];
$item['customerResource']['first_visit_status'] = $visit_info[$resource_id]['first_visit_status'] ?? '未到';
$item['customerResource']['second_visit_status'] = $visit_info[$resource_id]['second_visit_status'] ?? '未到';
-
+
// 添加开单状态
$item['customerResource']['order_status'] = $order_status[$resource_id] ?? '未开单';
}
-
+
// 添加分配人员信息
$item['shared_by_name'] = $shared_by_names[$item['shared_by']] ?? '未分配';
-
+
// 判断资源是否可再分配(如果已有shared_by且不为0,说明已分配)
$item['can_reassign'] = empty($item['shared_by']) || $item['shared_by'] == 0;
}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..0a246124
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,71 @@
+{
+ "name": "zhjwxt",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@playwright/test": "^1.54.1"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.54.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
+ "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.54.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.54.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
+ "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.54.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.54.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
+ "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..5846ca99
--- /dev/null
+++ b/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "@playwright/test": "^1.54.1"
+ }
+}
diff --git a/uniapp/common/util.js b/uniapp/common/util.js
index 401acbe5..27497607 100644
--- a/uniapp/common/util.js
+++ b/uniapp/common/util.js
@@ -387,6 +387,163 @@ function getResourceUrl(resource) {
//如果没有 http 协议,则加上 http 协议+服务域名
return resource.indexOf('http') === -1 ? 'https://' + img_domian + resource : resource;
}
+
+/**
+ * 安全访问对象属性的方法,优化性能
+ * @param {Object} obj 目标对象
+ * @param {String} path 属性路径,如 'a.b.c'
+ * @param {*} defaultValue 默认值
+ * @returns {*} 属性值或默认值
+ */
+function safeGet(obj, path, defaultValue = '') {
+ if (!obj) return defaultValue
+
+ // 使用缓存来提高性能
+ if (!safeGet._pathCache) safeGet._pathCache = {}
+
+ // 使用路径作为缓存键
+ const cacheKey = path
+
+ // 如果这个路径没有缓存过分割结果,则计算并缓存
+ if (!safeGet._pathCache[cacheKey]) {
+ safeGet._pathCache[cacheKey] = path.split('.')
+ }
+
+ const keys = safeGet._pathCache[cacheKey]
+ let result = obj
+
+ // 使用for循环而不是for...of,更高效
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i]
+ if (result === null || result === undefined || !result.hasOwnProperty(key)) {
+ return defaultValue
+ }
+ result = result[key]
+ }
+
+ return result || defaultValue
+}
+
+/**
+ * 格式化文件大小
+ * @param {Number} bytes 字节数
+ * @returns {String} 格式化后的大小
+ */
+function formatFileSize(bytes) {
+ if (bytes === 0) return '0 B'
+ const k = 1024
+ const sizes = ['B', 'KB', 'MB', 'GB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+/**
+ * 获取当前日期 YYYY-MM-DD 格式
+ * @returns {String} 当前日期字符串
+ */
+function getCurrentDate() {
+ const now = new Date()
+ const year = now.getFullYear()
+ const month = String(now.getMonth() + 1).padStart(2, '0')
+ const day = String(now.getDate()).padStart(2, '0')
+ return `${year}-${month}-${day}`
+}
+
+/**
+ * 格式化年龄显示
+ * @param {Number} age 年龄(小数格式,如9.05表示9岁5个月)
+ * @returns {String} 格式化后的年龄字符串
+ */
+function formatAge(age) {
+ if (!age) return '未知年龄'
+ const years = Math.floor(age)
+ const months = Math.round((age - years) * 12)
+ if (months === 0) {
+ return `${years}岁`
+ }
+ return `${years}岁${months}个月`
+}
+
+/**
+ * 格式化性别显示
+ * @param {Number} gender 性别值(1男,2女)
+ * @returns {String} 性别字符串
+ */
+function formatGender(gender) {
+ switch (gender) {
+ case 1: return '男'
+ case 2: return '女'
+ default: return '未知'
+ }
+}
+
+/**
+ * 拨打电话的通用方法
+ * @param {String} phoneNumber 电话号码
+ * @param {Function} successCallback 成功回调
+ * @param {Function} failCallback 失败回调
+ */
+function makePhoneCall(phoneNumber, successCallback, failCallback) {
+ if (!phoneNumber) {
+ uni.showToast({
+ title: '电话号码为空',
+ icon: 'none'
+ })
+ return
+ }
+
+ uni.makePhoneCall({
+ phoneNumber: phoneNumber,
+ success: (res) => {
+ console.log('拨打电话成功')
+ if (successCallback) successCallback(res)
+ },
+ fail: (err) => {
+ console.error('拨打电话失败:', err)
+ uni.showToast({
+ title: '拨打电话失败',
+ icon: 'none'
+ })
+ if (failCallback) failCallback(err)
+ }
+ })
+}
+
+/**
+ * 页面跳转的通用方法
+ * @param {String} url 跳转路径
+ * @param {Object} params 跳转参数对象
+ */
+function navigateToPage(url, params = {}) {
+ let queryString = ''
+
+ // 将参数对象转换为查询字符串
+ if (Object.keys(params).length > 0) {
+ const paramArray = []
+ for (const key in params) {
+ if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
+ paramArray.push(`${key}=${encodeURIComponent(params[key])}`)
+ }
+ }
+ if (paramArray.length > 0) {
+ queryString = '?' + paramArray.join('&')
+ }
+ }
+
+ const fullUrl = url + queryString
+
+ uni.navigateTo({
+ url: fullUrl,
+ fail: (err) => {
+ console.error('页面跳转失败:', err)
+ uni.showToast({
+ title: '页面跳转失败',
+ icon: 'none'
+ })
+ }
+ })
+}
+
module.exports = {
loginOut,
openHomeView,
@@ -398,5 +555,13 @@ module.exports = {
img,
formatToDateTime,
getDict,
- uploadFile
+ uploadFile,
+ getResourceUrl,
+ safeGet,
+ formatFileSize,
+ getCurrentDate,
+ formatAge,
+ formatGender,
+ makePhoneCall,
+ navigateToPage
}
diff --git a/uniapp/common/utils-index.js b/uniapp/common/utils-index.js
new file mode 100644
index 00000000..5d28348d
--- /dev/null
+++ b/uniapp/common/utils-index.js
@@ -0,0 +1,155 @@
+/**
+ * 工具函数索引文件
+ * 统一管理项目中的可复用工具函数
+ */
+
+import util from './util.js'
+
+// 导出所有工具函数
+export const {
+ // 登录退出相关
+ loginOut,
+ openHomeView,
+
+ // 时间格式化
+ formatTime,
+ formatDateTime,
+ formatToDateTime,
+ dateUtils,
+ getCurrentDate,
+
+ // 通用格式化
+ formatLocation,
+ formatFileSize,
+ formatAge,
+ formatGender,
+
+ // 对象处理
+ safeGet,
+
+ // 样式相关
+ hexToRgba,
+ img,
+ getResourceUrl,
+
+ // 数据获取
+ getDict,
+
+ // 文件上传
+ uploadFile,
+
+ // 通信相关
+ makePhoneCall,
+
+ // 页面跳转
+ navigateToPage
+} = util
+
+// 工具函数使用说明
+export const UtilsUsage = {
+ // 对象安全访问
+ safeGet: {
+ description: '安全访问对象属性,避免undefined错误',
+ params: {
+ obj: 'Object - 目标对象',
+ path: 'String - 属性路径,如 "a.b.c"',
+ defaultValue: 'Any - 默认值'
+ },
+ returns: 'Any - 属性值或默认值',
+ example: `
+ const name = safeGet(clientInfo, 'customerResource.name', '未知客户')
+ `
+ },
+
+ // 文件大小格式化
+ formatFileSize: {
+ description: '格式化文件大小为可读格式',
+ params: {
+ bytes: 'Number - 字节数'
+ },
+ returns: 'String - 格式化后的大小',
+ example: `
+ formatFileSize(1024000) // "1 MB"
+ `
+ },
+
+ // 年龄格式化
+ formatAge: {
+ description: '格式化年龄显示',
+ params: {
+ age: 'Number - 年龄(小数格式,如9.05表示9岁5个月)'
+ },
+ returns: 'String - 格式化后的年龄',
+ example: `
+ formatAge(9.05) // "9岁5个月"
+ `
+ },
+
+ // 性别格式化
+ formatGender: {
+ description: '格式化性别显示',
+ params: {
+ gender: 'Number - 性别值(1男,2女)'
+ },
+ returns: 'String - 性别字符串',
+ example: `
+ formatGender(1) // "男"
+ `
+ },
+
+ // 拨打电话
+ makePhoneCall: {
+ description: '拨打电话的通用方法',
+ params: {
+ phoneNumber: 'String - 电话号码',
+ successCallback: 'Function - 成功回调(可选)',
+ failCallback: 'Function - 失败回调(可选)'
+ },
+ example: `
+ makePhoneCall('13800138000',
+ () => console.log('拨打成功'),
+ (err) => console.error('拨打失败', err)
+ )
+ `
+ },
+
+ // 页面跳转
+ navigateToPage: {
+ description: '页面跳转的通用方法',
+ params: {
+ url: 'String - 跳转路径',
+ params: 'Object - 跳转参数对象(可选)'
+ },
+ example: `
+ navigateToPage('/pages/detail/index', {
+ id: 123,
+ name: '测试'
+ })
+ // 结果: /pages/detail/index?id=123&name=测试
+ `
+ },
+
+ // 获取当前日期
+ getCurrentDate: {
+ description: '获取当前日期 YYYY-MM-DD 格式',
+ returns: 'String - 当前日期字符串',
+ example: `
+ getCurrentDate() // "2024-01-15"
+ `
+ },
+
+ // 时间格式化
+ formatToDateTime: {
+ description: '时间格式转换',
+ params: {
+ dateTime: 'String - 时间字符串,如 "2024-05-01 01:10:21"',
+ fmt: 'String - 格式模板,默认 "Y-m-d H:i:s"'
+ },
+ returns: 'String - 格式化后的时间',
+ example: `
+ formatToDateTime('2024-05-01 01:10:21', 'Y-m-d H:i') // "2024-05-01 01:10"
+ `
+ }
+}
+
+export default util
\ No newline at end of file
diff --git a/uniapp/components/README.md b/uniapp/components/README.md
new file mode 100644
index 00000000..9779f419
--- /dev/null
+++ b/uniapp/components/README.md
@@ -0,0 +1,266 @@
+# 组件和工具函数库
+
+这是从 `clue_info.vue` 页面重构提取出来的可复用组件和工具函数集合。
+
+## 🧩 组件列表
+
+### 1. ClientInfoCard - 客户信息卡片
+显示客户基本信息的卡片组件,包含客户头像、姓名、电话等信息。
+
+**位置**: `components/client-info-card/client-info-card.vue`
+
+**Props**:
+- `clientInfo`: Object - 客户信息对象
+
+**Events**:
+- `call`: 拨打电话事件,参数: phoneNumber
+
+**使用示例**:
+```vue
+
+```
+
+### 2. StudentInfoCard - 学生信息卡片
+显示学生信息的卡片组件,支持展开/收起操作按钮。
+
+**位置**: `components/student-info-card/student-info-card.vue`
+
+**Props**:
+- `student`: Object - 学生信息对象
+- `actions`: Array - 操作按钮配置(可选)
+- `showDetails`: Boolean - 是否显示详细信息(默认true)
+
+**Events**:
+- `toggle-actions`: 切换操作面板事件
+- `action`: 操作按钮点击事件,参数: { action, student }
+
+**使用示例**:
+```vue
+
+```
+
+### 3. TabSwitcher - 标签切换组件
+通用的标签页切换组件。
+
+**位置**: `components/tab-switcher/tab-switcher.vue`
+
+**Props**:
+- `tabs`: Array - 标签配置数组 [{ id, name }]
+- `activeTabId`: String|Number - 当前激活标签ID
+
+**Events**:
+- `tab-change`: 标签切换事件,参数: { tabId, index, tab }
+
+**使用示例**:
+```vue
+
+```
+
+### 4. FitnessRecordCard - 体测记录卡片
+显示体测记录数据和相关PDF报告的卡片组件。
+
+**位置**: `components/fitness-record-card/fitness-record-card.vue`
+
+**Props**:
+- `record`: Object - 体测记录对象
+
+**Events**:
+- `file-click`: 文件点击事件,参数: { file, record }
+
+**使用示例**:
+```vue
+
+```
+
+### 5. CallRecordCard - 通话记录卡片
+显示通话记录详情的卡片组件。
+
+**位置**: `components/call-record-card/call-record-card.vue`
+
+**Props**:
+- `record`: Object - 通话记录对象
+
+**使用示例**:
+```vue
+
+```
+
+## 🛠️ 工具函数库
+
+### 核心工具函数
+
+以下函数已添加到 `common/util.js` 中:
+
+#### 1. safeGet(obj, path, defaultValue)
+安全访问对象属性,避免undefined错误。
+
+```javascript
+const name = this.$util.safeGet(clientInfo, 'customerResource.name', '未知客户')
+```
+
+#### 2. formatFileSize(bytes)
+格式化文件大小为可读格式。
+
+```javascript
+this.$util.formatFileSize(1024000) // "1 MB"
+```
+
+#### 3. formatAge(age)
+格式化年龄显示(小数转年龄+月份)。
+
+```javascript
+this.$util.formatAge(9.05) // "9岁5个月"
+```
+
+#### 4. formatGender(gender)
+格式化性别显示。
+
+```javascript
+this.$util.formatGender(1) // "男"
+this.$util.formatGender(2) // "女"
+```
+
+#### 5. makePhoneCall(phoneNumber, successCallback, failCallback)
+拨打电话的通用方法。
+
+```javascript
+this.$util.makePhoneCall('13800138000')
+```
+
+#### 6. navigateToPage(url, params)
+页面跳转的通用方法,自动处理参数拼接。
+
+```javascript
+this.$util.navigateToPage('/pages/detail/index', {
+ id: 123,
+ name: '测试'
+})
+// 结果: /pages/detail/index?id=123&name=测试
+```
+
+#### 7. getCurrentDate()
+获取当前日期 YYYY-MM-DD 格式。
+
+```javascript
+const today = this.$util.getCurrentDate() // "2024-01-15"
+```
+
+## 📁 文件结构
+
+```
+components/
+├── client-info-card/
+│ └── client-info-card.vue
+├── student-info-card/
+│ └── student-info-card.vue
+├── tab-switcher/
+│ └── tab-switcher.vue
+├── fitness-record-card/
+│ └── fitness-record-card.vue
+├── call-record-card/
+│ └── call-record-card.vue
+├── index.js # 组件导出索引
+└── README.md # 本文档
+
+common/
+├── util.js # 扩展后的工具函数库
+└── utils-index.js # 工具函数导出索引
+```
+
+## 🚀 使用方式
+
+### 在页面中使用组件
+
+1. **直接导入使用**:
+```vue
+
+
+
+
+
+```
+
+2. **批量导入**:
+```javascript
+// 从索引文件导入
+import { ClientInfoCard, StudentInfoCard } from '@/components/index.js'
+```
+
+### 使用工具函数
+
+工具函数已集成到全局 `$util` 对象中,可直接使用:
+
+```vue
+
+ {{ $util.safeGet(data, 'user.name', '未知') }}
+
+
+
+```
+
+## 💡 重构优势
+
+1. **代码复用**: 组件和工具函数可在多个页面中复用
+2. **维护性**: 集中管理,修改一处影响全局
+3. **可测试性**: 独立组件易于单元测试
+4. **性能优化**: 减少重复代码,提升应用性能
+5. **开发效率**: 标准化组件提升开发速度
+
+## 📋 迁移指南
+
+原 `clue_info.vue` 中的代码可按以下方式迁移:
+
+1. 替换工具函数调用:
+ - `this.safeGet()` → `this.$util.safeGet()`
+ - `this.formatAge()` → `this.$util.formatAge()`
+ - `this.makeCall()` → `this.$util.makePhoneCall()`
+
+2. 替换组件代码:
+ - 客户信息展示区域 → ``
+ - 学生信息展示区域 → ``
+ - 标签切换区域 → ``
+ - 体测记录展示 → ``
+ - 通话记录展示 → ``
+
+3. 事件处理:
+ - 保持原有事件处理逻辑
+ - 通过组件events获取用户操作
\ No newline at end of file
diff --git a/uniapp/components/call-record-card/call-record-card.vue b/uniapp/components/call-record-card/call-record-card.vue
new file mode 100644
index 00000000..b80af449
--- /dev/null
+++ b/uniapp/components/call-record-card/call-record-card.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+ 通话类型:
+ {{ getCallType(record.call_type) }}
+
+
+ 拨打人员:
+ {{ record.caller_name }}
+
+
+ 通话号码:
+ {{ record.phone_number }}
+
+
+
+
+ 通话备注:
+ {{ record.notes }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/components/client-info-card/client-info-card.vue b/uniapp/components/client-info-card/client-info-card.vue
new file mode 100644
index 00000000..b71133db
--- /dev/null
+++ b/uniapp/components/client-info-card/client-info-card.vue
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
+
+ 渠道来源:
+ {{ $util.safeGet(clientInfo, 'customerResource.source_channel_name', '未知渠道') }}
+
+
+ 来源类型:
+ {{ $util.safeGet(clientInfo, 'customerResource.source_name', '未知来源') }}
+
+
+ 分配顾问:
+ {{ $util.safeGet(clientInfo, 'customerResource.consultant_name', '未知顾问') }}
+
+
+ 性别:
+ {{ $util.safeGet(clientInfo, 'customerResource.gender_name', '未知性别') }}
+
+
+
+
+
+
+ {{ action.text }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/components/course-edit-popup/course-edit-popup.less b/uniapp/components/course-edit-popup/course-edit-popup.less
new file mode 100644
index 00000000..0c99ea83
--- /dev/null
+++ b/uniapp/components/course-edit-popup/course-edit-popup.less
@@ -0,0 +1,153 @@
+// 弹窗容器样式
+.popup-container {
+ background: #fff;
+ border-radius: 15rpx;
+ padding: 30rpx;
+ margin: 20rpx;
+ max-height: 80vh;
+ overflow-y: auto;
+ width: 90%;
+ max-width: 600rpx;
+}
+
+// 弹窗头部
+.popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 30rpx;
+ padding-bottom: 20rpx;
+ border-bottom: 1px solid #eee;
+}
+
+.popup-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+}
+
+.popup-close {
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 36rpx;
+ color: #999;
+ cursor: pointer;
+
+ &:hover {
+ color: #666;
+ }
+}
+
+// 课程编辑容器
+.course-edit-container {
+ .edit-section {
+ margin-bottom: 30rpx;
+
+ .section-title {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 20rpx;
+ }
+
+ .empty-tip {
+ color: #999;
+ font-size: 26rpx;
+ text-align: center;
+ padding: 40rpx 0;
+ }
+
+ .coach-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15rpx;
+
+ .coach-item {
+ position: relative;
+ background: #f5f5f5;
+ border: 2rpx solid #f5f5f5;
+ border-radius: 10rpx;
+ padding: 20rpx 30rpx;
+ min-width: 120rpx;
+ text-align: center;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &.selected {
+ background: #e8f5ff;
+ border-color: #29d3b4;
+ color: #29d3b4;
+ }
+
+ &:hover {
+ background: #e8f5ff;
+ border-color: #29d3b4;
+ }
+
+ .coach-name {
+ font-size: 26rpx;
+ color: inherit;
+ }
+
+ .coach-check {
+ position: absolute;
+ top: -5rpx;
+ right: -5rpx;
+ width: 30rpx;
+ height: 30rpx;
+ background: #29d3b4;
+ color: #fff;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20rpx;
+ font-weight: bold;
+ }
+ }
+ }
+ }
+}
+
+// 弹窗底部
+.popup-footer {
+ display: flex;
+ justify-content: space-between;
+ gap: 20rpx;
+ margin-top: 40rpx;
+ padding-top: 30rpx;
+ border-top: 1px solid #eee;
+}
+
+.popup-btn {
+ flex: 1;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 10rpx;
+ font-size: 28rpx;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &.cancel-btn {
+ background: #f5f5f5;
+ color: #666;
+
+ &:hover {
+ background: #e8e8e8;
+ }
+ }
+
+ &.confirm-btn {
+ background: #29d3b4;
+ color: #fff;
+
+ &:hover {
+ background: #25c4a8;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uniapp/components/course-edit-popup/course-edit-popup.vue b/uniapp/components/course-edit-popup/course-edit-popup.vue
new file mode 100644
index 00000000..66b3d280
--- /dev/null
+++ b/uniapp/components/course-edit-popup/course-edit-popup.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/components/fitness-record-card/fitness-record-card.vue b/uniapp/components/fitness-record-card/fitness-record-card.vue
new file mode 100644
index 00000000..19770502
--- /dev/null
+++ b/uniapp/components/fitness-record-card/fitness-record-card.vue
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+ 身高
+ {{ record.height }}cm
+
+
+ 体重
+ {{ record.weight }}kg
+
+
+
+
+ 体测报告
+
+
+ 📄
+
+ {{ pdf.name }}
+ {{ $util.formatFileSize(pdf.size) }}
+
+
+ 查看
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/components/fitness-record-popup/fitness-record-popup.less b/uniapp/components/fitness-record-popup/fitness-record-popup.less
new file mode 100644
index 00000000..8b3e97f7
--- /dev/null
+++ b/uniapp/components/fitness-record-popup/fitness-record-popup.less
@@ -0,0 +1,215 @@
+// 体测记录弹窗样式
+.popup-container {
+ width: 90vw;
+ max-width: 600rpx;
+ background: #fff;
+ border-radius: 20rpx;
+ overflow: hidden;
+ box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.2);
+}
+
+.popup-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 30rpx 40rpx;
+ background: #29d3b4;
+ color: #fff;
+}
+
+.popup-title {
+ font-size: 32rpx;
+ font-weight: bold;
+}
+
+.popup-close {
+ width: 50rpx;
+ height: 50rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ font-size: 28rpx;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.popup-close:active {
+ background: rgba(255, 255, 255, 0.3);
+ transform: scale(0.9);
+}
+
+.popup-footer {
+ display: flex;
+ padding: 30rpx 40rpx;
+ background: #f8f9fa;
+ gap: 20rpx;
+}
+
+.popup-btn {
+ flex: 1;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12rpx;
+ font-size: 30rpx;
+ font-weight: bold;
+ transition: all 0.3s ease;
+}
+
+.cancel-btn {
+ background: #e9ecef;
+ color: #666;
+}
+
+.cancel-btn:active {
+ background: #dee2e6;
+ transform: scale(0.95);
+}
+
+.confirm-btn {
+ background: #29d3b4;
+ color: #fff;
+}
+
+.confirm-btn:active {
+ background: #1ea08e;
+ transform: scale(0.95);
+}
+
+// 体测记录表单样式
+.fitness-record-form {
+ width: 100%;
+ max-height: 60vh;
+ overflow-y: auto;
+ padding: 20rpx;
+ box-sizing: border-box;
+}
+
+.form-section {
+ width: 100%;
+}
+
+.form-item {
+ margin-bottom: 30rpx;
+}
+
+.form-label {
+ font-size: 30rpx;
+ color: #333;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+}
+
+.form-input {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 0 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+}
+
+.form-input:focus-within {
+ border-color: #29d3b4;
+}
+
+.form-input input {
+ width: 100%;
+ height: 80rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+}
+
+.file-upload-area {
+ width: 100%;
+}
+
+.upload-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f8f9fa;
+ border: 2px dashed #ddd;
+ border-radius: 12rpx;
+ padding: 40rpx;
+ margin-bottom: 20rpx;
+ color: #666;
+ transition: all 0.3s ease;
+}
+
+.upload-btn:active {
+ background: #e9ecef;
+ border-color: #29d3b4;
+}
+
+.upload-icon {
+ font-size: 40rpx;
+ margin-right: 15rpx;
+}
+
+.upload-text {
+ font-size: 28rpx;
+}
+
+.selected-files {
+ width: 100%;
+}
+
+.selected-file-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 15rpx 20rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ margin-bottom: 10rpx;
+ border: 1px solid #eee;
+}
+
+.file-info {
+ display: flex;
+ align-items: center;
+ flex: 1;
+}
+
+.file-icon {
+ font-size: 28rpx;
+ margin-right: 12rpx;
+ color: #666;
+}
+
+.file-name {
+ flex: 1;
+ font-size: 26rpx;
+ color: #333;
+ margin-right: 10rpx;
+}
+
+.file-size {
+ font-size: 22rpx;
+ color: #999;
+}
+
+.file-remove {
+ width: 40rpx;
+ height: 40rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #ff4757;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 24rpx;
+ font-weight: bold;
+ margin-left: 15rpx;
+}
+
+.file-remove:active {
+ background: #ff3838;
+}
\ No newline at end of file
diff --git a/uniapp/components/fitness-record-popup/fitness-record-popup.vue b/uniapp/components/fitness-record-popup/fitness-record-popup.vue
new file mode 100644
index 00000000..c645cc84
--- /dev/null
+++ b/uniapp/components/fitness-record-popup/fitness-record-popup.vue
@@ -0,0 +1,276 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/components/index.js b/uniapp/components/index.js
new file mode 100644
index 00000000..51dd5feb
--- /dev/null
+++ b/uniapp/components/index.js
@@ -0,0 +1,105 @@
+/**
+ * 组件索引文件
+ * 统一管理项目中的可复用组件
+ */
+
+// 客户信息相关组件
+export { default as ClientInfoCard } from './client-info-card/client-info-card.vue'
+export { default as StudentInfoCard } from './student-info-card/student-info-card.vue'
+
+// 通用UI组件
+export { default as TabSwitcher } from './tab-switcher/tab-switcher.vue'
+
+// 记录卡片组件
+export { default as FitnessRecordCard } from './fitness-record-card/fitness-record-card.vue'
+export { default as CallRecordCard } from './call-record-card/call-record-card.vue'
+
+// 组件使用说明
+export const ComponentUsage = {
+ // 客户信息卡片
+ ClientInfoCard: {
+ description: '显示客户基本信息的卡片组件',
+ props: {
+ clientInfo: 'Object - 客户信息对象'
+ },
+ events: {
+ call: '拨打电话事件,参数: phoneNumber'
+ },
+ example: `
+
+ `
+ },
+
+ // 学生信息卡片
+ StudentInfoCard: {
+ description: '显示学生信息的卡片组件,支持操作按钮',
+ props: {
+ student: 'Object - 学生信息对象',
+ actions: 'Array - 操作按钮配置',
+ showDetails: 'Boolean - 是否显示详细信息'
+ },
+ events: {
+ 'toggle-actions': '切换操作面板事件',
+ 'action': '操作按钮点击事件,参数: { action, student }'
+ },
+ example: `
+
+ `
+ },
+
+ // 标签切换组件
+ TabSwitcher: {
+ description: '标签切换组件,支持多标签页面切换',
+ props: {
+ tabs: 'Array - 标签配置数组 [{ id, name }]',
+ activeTabId: 'String|Number - 当前激活标签ID'
+ },
+ events: {
+ 'tab-change': '标签切换事件,参数: { tabId, index, tab }'
+ },
+ example: `
+
+ `
+ },
+
+ // 体测记录卡片
+ FitnessRecordCard: {
+ description: '体测记录卡片组件,显示体测数据和报告',
+ props: {
+ record: 'Object - 体测记录对象'
+ },
+ events: {
+ 'file-click': '文件点击事件,参数: { file, record }'
+ },
+ example: `
+
+ `
+ },
+
+ // 通话记录卡片
+ CallRecordCard: {
+ description: '通话记录卡片组件,显示通话详情',
+ props: {
+ record: 'Object - 通话记录对象'
+ },
+ example: `
+
+ `
+ }
+}
\ No newline at end of file
diff --git a/uniapp/components/student-edit-popup/student-edit-popup.less b/uniapp/components/student-edit-popup/student-edit-popup.less
new file mode 100644
index 00000000..da23a452
--- /dev/null
+++ b/uniapp/components/student-edit-popup/student-edit-popup.less
@@ -0,0 +1,228 @@
+// 学生信息编辑弹窗样式
+.student-edit-popup {
+ width: 90vw;
+ max-width: 600rpx;
+ max-height: 80vh;
+ background: #fff;
+ border-radius: 20rpx;
+ overflow: hidden;
+
+ .popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 30rpx 40rpx;
+ background: #f8f9fa;
+ border-bottom: 1rpx solid #eee;
+
+ .popup-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #333;
+ }
+
+ .popup-close {
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 36rpx;
+ color: #999;
+ cursor: pointer;
+
+ &:hover {
+ color: #666;
+ }
+ }
+ }
+
+ .student-form-container {
+ max-height: 60vh;
+ padding: 0 40rpx;
+
+ .form-section {
+ padding: 20rpx 0;
+
+ .form-group {
+ margin-bottom: 40rpx;
+
+ .form-group-title {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 20rpx;
+ padding-bottom: 10rpx;
+ border-bottom: 2rpx solid #29d3b4;
+ }
+
+ .form-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 30rpx;
+
+ .form-label {
+ width: 160rpx;
+ font-size: 28rpx;
+ color: #666;
+ flex-shrink: 0;
+
+ &.required::after {
+ content: '*';
+ color: #ff4757;
+ margin-left: 4rpx;
+ }
+ }
+
+ .form-input {
+ flex: 1;
+
+ input {
+ width: 100%;
+ height: 80rpx;
+ padding: 0 20rpx;
+ border: 1rpx solid #ddd;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ background: #fff;
+
+ &:focus {
+ border-color: #29d3b4;
+ outline: none;
+ }
+
+ &::placeholder {
+ color: #ccc;
+ }
+ }
+
+ .picker-display {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 80rpx;
+ padding: 0 20rpx;
+ border: 1rpx solid #ddd;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ background: #fff;
+ color: #333;
+
+ .picker-arrow {
+ font-size: 32rpx;
+ color: #999;
+ transform: rotate(90deg);
+ }
+ }
+ }
+
+ .form-textarea {
+ flex: 1;
+
+ textarea {
+ width: 100%;
+ min-height: 120rpx;
+ padding: 20rpx;
+ border: 1rpx solid #ddd;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ background: #fff;
+ resize: none;
+
+ &:focus {
+ border-color: #29d3b4;
+ outline: none;
+ }
+
+ &::placeholder {
+ color: #ccc;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .popup-footer {
+ display: flex;
+ padding: 30rpx 40rpx;
+ background: #f8f9fa;
+ border-top: 1rpx solid #eee;
+ gap: 20rpx;
+
+ .popup-btn {
+ flex: 1;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &.cancel-btn {
+ background: #f5f5f5;
+ color: #666;
+ border: 1rpx solid #ddd;
+
+ &:hover {
+ background: #e9e9e9;
+ }
+ }
+
+ &.confirm-btn {
+ background: #29d3b4;
+ color: #fff;
+ border: 1rpx solid #29d3b4;
+
+ &:hover {
+ background: #26c4a6;
+ }
+ }
+ }
+ }
+}
+
+// 响应式适配
+@media (max-width: 750rpx) {
+ .student-edit-popup {
+ width: 95vw;
+
+ .popup-header {
+ padding: 25rpx 30rpx;
+
+ .popup-title {
+ font-size: 30rpx;
+ }
+ }
+
+ .student-form-container {
+ padding: 0 30rpx;
+
+ .form-section {
+ .form-group {
+ .form-item {
+ flex-direction: column;
+ align-items: flex-start;
+
+ .form-label {
+ width: 100%;
+ margin-bottom: 10rpx;
+ }
+
+ .form-input,
+ .form-textarea {
+ width: 100%;
+ }
+ }
+ }
+ }
+ }
+
+ .popup-footer {
+ padding: 25rpx 30rpx;
+ }
+ }
+}
\ No newline at end of file
diff --git a/uniapp/components/student-edit-popup/student-edit-popup.vue b/uniapp/components/student-edit-popup/student-edit-popup.vue
new file mode 100644
index 00000000..5a756a26
--- /dev/null
+++ b/uniapp/components/student-edit-popup/student-edit-popup.vue
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/components/student-info-card/student-info-card.vue b/uniapp/components/student-info-card/student-info-card.vue
new file mode 100644
index 00000000..f3229c19
--- /dev/null
+++ b/uniapp/components/student-info-card/student-info-card.vue
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+ 生日:
+ {{ student.birthday || '未知' }}
+
+
+ 紧急联系人:
+ {{ student.emergency_contact }}
+
+
+ 联系电话:
+ {{ student.contact_phone }}
+
+
+ 备注:
+ {{ student.note }}
+
+
+
+
+
+
+ {{ action.text }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/components/tab-switcher/tab-switcher.vue b/uniapp/components/tab-switcher/tab-switcher.vue
new file mode 100644
index 00000000..4ab9c059
--- /dev/null
+++ b/uniapp/components/tab-switcher/tab-switcher.vue
@@ -0,0 +1,70 @@
+
+
+
+
+ {{ tab.name }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/pages/market/clue/class_arrangement.vue b/uniapp/pages/market/clue/class_arrangement.vue
index 318baa5f..17c25340 100644
--- a/uniapp/pages/market/clue/class_arrangement.vue
+++ b/uniapp/pages/market/clue/class_arrangement.vue
@@ -7,10 +7,10 @@
{{ item.day }}
-
+
-
- 查看更多
+
+ 查询
@@ -19,6 +19,71 @@
+
+
+
+
@@ -50,7 +115,21 @@
selectedDayIndex: 4,
date: '',
courseList: [],
- resource_id:''
+ resource_id: '',
+
+ // 查询弹窗相关
+ showSearchPopup: false,
+ searchForm: {
+ time_hour: '',
+ start_date: '',
+ end_date: '',
+ teacher_name: '',
+ venue_number: ''
+ },
+
+ // 时间选择器相关
+ timeIndex: 0,
+ timeOptions: ['全部时间', '上午(8:00-12:00)', '下午(12:00-18:00)', '晚上(18:00-22:00)']
};
},
onLoad(options) {
@@ -113,6 +192,97 @@
completed: '已结束'
};
return statusMap[status] || status;
+ },
+
+ // 查询弹窗相关方法
+ openSearchPopup() {
+ this.showSearchPopup = true;
+ },
+
+ closeSearchPopup() {
+ this.showSearchPopup = false;
+ },
+
+ // 时间选择器变化
+ onTimeChange(e) {
+ this.timeIndex = e.detail.value;
+ this.searchForm.time_hour = this.timeOptions[this.timeIndex];
+ },
+
+ // 开始日期选择变化
+ onStartDateChange(e) {
+ this.searchForm.start_date = e.detail.value;
+ console.log('开始日期选择:', e.detail.value);
+ },
+
+ // 结束日期选择变化
+ onEndDateChange(e) {
+ this.searchForm.end_date = e.detail.value;
+ console.log('结束日期选择:', e.detail.value);
+ },
+
+ // 搜索并关闭弹窗
+ searchDataAndClose() {
+ console.log('执行搜索,表单数据:', this.searchForm);
+ this.searchCourseData();
+ this.showSearchPopup = false;
+ },
+
+ // 搜索课程数据
+ async searchCourseData() {
+ try {
+ let searchParams = {
+ schedule_date: this.date
+ };
+
+ // 添加搜索条件
+ if (this.searchForm.time_hour && this.searchForm.time_hour !== '全部时间') {
+ searchParams.time_hour = this.searchForm.time_hour;
+ }
+ if (this.searchForm.start_date) {
+ searchParams.start_date = this.searchForm.start_date;
+ }
+ if (this.searchForm.end_date) {
+ searchParams.end_date = this.searchForm.end_date;
+ }
+ if (this.searchForm.teacher_name) {
+ searchParams.teacher_name = this.searchForm.teacher_name;
+ }
+ if (this.searchForm.venue_number) {
+ searchParams.venue_number = this.searchForm.venue_number;
+ }
+
+ console.log('搜索参数:', searchParams);
+
+ let data = await apiRoute.courseAllList(searchParams);
+ this.courseList = data.data;
+
+ uni.showToast({
+ title: '查询完成',
+ icon: 'success'
+ });
+ } catch (error) {
+ console.error('搜索失败:', error);
+ uni.showToast({
+ title: '搜索失败',
+ icon: 'none'
+ });
+ }
+ },
+
+ // 重置搜索条件
+ resetSearchOnly() {
+ this.searchForm = {
+ time_hour: '',
+ start_date: '',
+ end_date: '',
+ teacher_name: '',
+ venue_number: ''
+ };
+ this.timeIndex = 0;
+
+ // 重新加载默认数据
+ this.getDate();
}
},
};
@@ -236,4 +406,191 @@
}
}
}
+
+ // 搜索弹窗样式
+ .search_popup_mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.6);
+ z-index: 999;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .search_popup_content {
+ background: #fff;
+ border-bottom-left-radius: 24rpx;
+ border-bottom-right-radius: 24rpx;
+ animation: slideDown 0.3s ease-out;
+ width: 100%;
+ max-height: 80vh;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ }
+
+ @keyframes slideDown {
+ from {
+ transform: translateY(-100%);
+ }
+ to {
+ transform: translateY(0);
+ }
+ }
+
+ // 弹窗搜索内容样式
+ .popup_search_content {
+ padding: 0;
+ background: #fff;
+ min-height: 60vh;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ border-bottom-left-radius: 24rpx;
+ border-bottom-right-radius: 24rpx;
+ overflow: hidden;
+ }
+
+ .popup_header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 32rpx;
+ border-bottom: 1px solid #f0f0f0;
+ }
+
+ .popup_title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #333;
+ }
+
+ .popup_close {
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .close_text {
+ font-size: 32rpx;
+ color: #999;
+ }
+ }
+
+ .popup_scroll_view {
+ flex: 1;
+ padding: 32rpx;
+ overflow-y: auto;
+ }
+
+ .popup_filter_section {
+ margin-bottom: 32rpx;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .popup_filter_row {
+ display: flex;
+ gap: 20rpx;
+ margin-bottom: 24rpx;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .popup_filter_item {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+
+ &.full_width {
+ flex: 1;
+ }
+
+ .popup_filter_label {
+ font-size: 26rpx;
+ color: #666;
+ font-weight: 500;
+ }
+
+ .popup_filter_input {
+ height: 72rpx;
+ line-height: 72rpx;
+ padding: 0 16rpx;
+ border: 1px solid #ddd;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ color: #333;
+ background: #fff;
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+
+ .popup_filter_picker {
+ height: 72rpx;
+ line-height: 72rpx;
+ padding: 0 16rpx;
+ border: 1px solid #ddd;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ color: #333;
+ background: #fff;
+ position: relative;
+
+ &::after {
+ content: '▼';
+ position: absolute;
+ right: 16rpx;
+ font-size: 20rpx;
+ color: #999;
+ }
+ }
+ }
+
+ .popup_filter_buttons {
+ display: flex;
+ gap: 20rpx;
+ padding: 32rpx;
+ margin-top: auto;
+ border-top: 1px solid #f0f0f0;
+ background: #fff;
+ border-bottom-left-radius: 24rpx;
+ border-bottom-right-radius: 24rpx;
+ }
+
+ .popup_filter_btn {
+ flex: 1;
+ height: 72rpx;
+ line-height: 72rpx;
+ text-align: center;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ font-weight: 600;
+
+ &.search_btn {
+ background: #29d3b4;
+ color: #fff;
+ }
+
+ &.reset_btn {
+ background: #f5f5f5;
+ color: #666;
+ border: 1px solid #ddd;
+ }
+
+ &.close_btn {
+ background: #666;
+ color: #fff;
+ }
+ }
\ No newline at end of file
diff --git a/uniapp/pages/market/clue/clue_info.less b/uniapp/pages/market/clue/clue_info.less
new file mode 100644
index 00000000..85badc61
--- /dev/null
+++ b/uniapp/pages/market/clue/clue_info.less
@@ -0,0 +1,2204 @@
+.assemble {
+ width: 100%;
+ height: 100vh;
+ overflow: auto;
+ background-color: #292929;
+}
+
+.main_box {
+ background: #292929;
+ min-height: 20vh;
+}
+
+// 操作按钮样式
+.action-buttons {
+ display: flex;
+ align-items: center;
+
+ .btn-item {
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: 20rpx;
+
+ .btn-icon {
+ width: 40rpx;
+ height: 40rpx;
+ }
+ }
+}
+
+// 标签切换器容器样式
+.tab-switcher-container {
+ margin: 20rpx;
+ background: #fff;
+ border-radius: 20rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+//统计信息
+.count_section {
+ width: 100%;
+ position: relative;
+
+ .main {
+ width: 100%;
+ position: absolute;
+ z-index: 2;
+ padding: 0rpx 24rpx;
+ display: flex;
+ justify-content: center;
+ }
+
+ .bg_top {
+ height: 180rpx;
+ background-color: #29D3B4;
+ }
+
+ .bg_bottom {
+ height: 80rpx;
+ background-color: #292929;
+ }
+}
+
+.selected-text {
+ color: #1CD188;
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+ font-size: 28rpx;
+ padding: 0 6rpx;
+}
+
+.text {
+ color: #333333;
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+ font-size: 28rpx;
+ padding: 0 6rpx;
+}
+
+.basic-message {
+ color: #fff;
+ padding: 16rpx 60rpx;
+ display: flex;
+ justify-content: space-between;
+}
+
+.basic-message-div {
+ width: 92%;
+ margin: auto;
+ background: #555555;
+ border-radius: 16rpx;
+ padding: 16rpx 0;
+}
+
+.basic-message-div-txt {
+ display: flex;
+ justify-content: space-between;
+ color: #FFFFFF;
+ padding: 14rpx 30rpx;
+}
+
+.bottom-label {
+ width: 100%;
+ height: 150rpx;
+ background: #fff;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 30rpx;
+}
+
+// 自定义弹窗样式
+.custom-popup-dialog {
+ width: 650rpx;
+ border-radius: 16rpx;
+ background-color: #fff;
+ overflow: hidden;
+}
+
+.dialog-header {
+ padding: 30rpx;
+ text-align: center;
+ border-bottom: 1px solid #eee;
+}
+
+.dialog-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+}
+
+.dialog-content {
+ padding: 30rpx;
+}
+
+.dialog-footer {
+ display: flex;
+ border-top: 1px solid #eee;
+}
+
+.dialog-btn {
+ flex: 1;
+ padding: 25rpx 0;
+ text-align: center;
+ font-size: 30rpx;
+}
+
+.cancel-btn {
+ color: #666;
+ border-right: 1px solid #eee;
+}
+
+.confirm-btn {
+ color: #29D3B4;
+ font-weight: bold;
+}
+
+// 备注文本框样式
+.remark-textarea-container {
+ width: 100%;
+ position: relative;
+ padding: 10rpx 0;
+}
+
+.remark-textarea {
+ width: 100%;
+ height: 240rpx;
+ border: 1px solid #ddd;
+ border-radius: 8rpx;
+ padding: 20rpx;
+ font-size: 28rpx;
+ background-color: #f8f8f8;
+ box-sizing: border-box;
+}
+
+.remark-count {
+ position: absolute;
+ bottom: 20rpx;
+ right: 20rpx;
+ font-size: 24rpx;
+ color: #999;
+}
+
+// 备注按钮样式
+.remark-btn {
+ background-color: #29D3B4 !important;
+ color: white !important;
+ margin: 0 !important;
+ padding: 0 20rpx !important;
+ height: 60rpx !important;
+ line-height: 60rpx !important;
+ font-size: 24rpx !important;
+ border-radius: 30rpx !important;
+}
+
+.follow-records-list {
+ width: 92%;
+ background: #434544;
+ border-radius: 16rpx;
+ margin: auto;
+ padding: 16rpx;
+ color: #fff;
+ position: relative;
+}
+
+.same-community {
+ width: 120rpx;
+ background: #3d4a55;
+ color: #1e62a9;
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 8rpx 16rpx;
+ text-align: center;
+ border-radius: 40rpx 70rpx 70rpx 12rpx;
+}
+
+.coeducation {
+ width: 120rpx;
+ background: #3d4a55;
+ color: #2e9380;
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 8rpx 16rpx;
+ text-align: center;
+ border-radius: 40rpx 70rpx 70rpx 12rpx;
+}
+
+.same-community {
+ width: 120rpx;
+ background: #40504c;
+ color: #1e62a9;
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 8rpx 16rpx;
+ text-align: center;
+ border-radius: 40rpx 70rpx 70rpx 12rpx;
+}
+
+.search {
+ width: 92%;
+ margin: auto;
+ display: flex;
+ margin-top: 20rpx;
+}
+
+.selected {
+ background: #294e48;
+ border-radius: 16rpx;
+ padding: 12rpx 8rpx;
+ text-align: center;
+ color: #29d3b4;
+ margin-left: 5rpx;
+}
+
+.not-selected {
+ margin-left: 5rpx;
+ background: #ffffff;
+ border: 4rpx #959595 solid;
+ border-radius: 16rpx;
+ padding: 12rpx 8rpx;
+ text-align: center;
+}
+
+.call-record-item {
+ width: 100%;
+ background: #434544;
+ border-radius: 16rpx;
+ margin: auto;
+ padding: 16rpx;
+ color: #fff;
+ position: relative;
+}
+
+.call-record-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.call-name {
+ font-size: 28rpx;
+ font-weight: bold;
+}
+
+.call-time {
+ font-size: 24rpx;
+ color: #999;
+}
+
+.call-record-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.call-detail {
+ display: flex;
+ align-items: center;
+}
+
+.call-label {
+ font-size: 24rpx;
+ color: #999;
+ margin-right: 10rpx;
+}
+
+.call-remark {
+ margin-top: 10rpx;
+}
+
+.call-remark-content {
+ font-size: 24rpx;
+ color: #fff;
+}
+
+// 通话记录样式
+.call-log {
+ width: 100%;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+}
+
+.call-record-wrapper {
+ margin-bottom: 20rpx;
+}
+
+.call-record-item {
+ width: 100%;
+ background: #3D3D3D;
+ border-radius: 16rpx;
+ padding: 20rpx;
+ color: #fff;
+ box-sizing: border-box;
+}
+
+.call-record-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15rpx;
+ border-bottom: 1px solid #4A4A4A;
+ padding-bottom: 10rpx;
+}
+
+.call-name {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #fff;
+}
+
+.call-time {
+ font-size: 24rpx;
+ color: #999;
+}
+
+.call-record-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10rpx;
+}
+
+.call-detail {
+ display: flex;
+ align-items: center;
+}
+
+.call-label {
+ font-size: 24rpx;
+ color: #999;
+ margin-right: 10rpx;
+}
+
+.call-remark {
+ margin-top: 10rpx;
+ padding-top: 10rpx;
+ border-top: 1px solid #4A4A4A;
+}
+
+.call-remark-content {
+ font-size: 24rpx;
+ color: #fff;
+ word-break: break-all;
+ display: inline-block;
+ width: 90%;
+ vertical-align: top;
+}
+
+// 课程信息样式
+.course-info {
+ padding: 20rpx;
+}
+
+.course-item {
+ background: #3D3D3D;
+ border-radius: 16rpx;
+ padding: 30rpx;
+ margin-bottom: 20rpx;
+ color: #fff;
+}
+
+.course-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 1px solid #4A4A4A;
+}
+
+.course-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #fff;
+}
+
+.course-status {
+ padding: 8rpx 16rpx;
+ border-radius: 12rpx;
+ font-size: 24rpx;
+ color: #fff;
+}
+
+.status-active {
+ background: #29d3b4;
+}
+
+.status-expired {
+ background: #ff6b6b;
+}
+
+.status-completed {
+ background: #999;
+}
+
+.status-default {
+ background: #666;
+}
+
+.course-progress {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+}
+
+.progress-bar {
+ flex: 1;
+ height: 12rpx;
+ background: #555;
+ border-radius: 6rpx;
+ overflow: hidden;
+ margin-right: 20rpx;
+}
+
+.progress-fill {
+ height: 100%;
+ background: linear-gradient(to right, #29d3b4, #1ea08e);
+ border-radius: 6rpx;
+ transition: width 0.3s ease;
+}
+
+.progress-text {
+ font-size: 26rpx;
+ color: #29d3b4;
+ min-width: 120rpx;
+ text-align: right;
+}
+
+.course-details {
+ margin-bottom: 20rpx;
+}
+
+.detail-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15rpx;
+ font-size: 28rpx;
+}
+
+.detail-label {
+ color: #999;
+}
+
+.detail-value {
+ color: #fff;
+}
+
+.course-actions {
+ text-align: center;
+ padding-top: 15rpx;
+ border-top: 1px solid #4A4A4A;
+}
+
+.action-btn {
+ color: #29d3b4;
+ font-size: 28rpx;
+}
+
+.empty-course {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 0;
+}
+
+.empty-img {
+ width: 200rpx;
+ height: 200rpx;
+ opacity: 0.5;
+ margin-bottom: 30rpx;
+}
+
+.empty-text {
+ color: #999;
+ font-size: 28rpx;
+}
+
+// 教练配置编辑弹窗样式
+.popup-container {
+ width: 90vw;
+ max-width: 600rpx;
+ background: #fff;
+ border-radius: 20rpx;
+ box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.3);
+ overflow: hidden;
+}
+
+.popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 30rpx 40rpx;
+ background: #29d3b4;
+ color: #fff;
+}
+
+.popup-title {
+ font-size: 36rpx;
+ font-weight: bold;
+}
+
+.popup-close {
+ font-size: 40rpx;
+ width: 60rpx;
+ height: 60rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+}
+
+.popup-footer {
+ display: flex;
+ border-top: 1px solid #eee;
+}
+
+.popup-btn {
+ flex: 1;
+ padding: 30rpx;
+ text-align: center;
+ font-size: 32rpx;
+ font-weight: bold;
+}
+
+.cancel-btn {
+ color: #666;
+ border-right: 1px solid #eee;
+}
+
+.confirm-btn {
+ color: #29d3b4;
+}
+
+.popup-btn:active {
+ background: #f5f5f5;
+}
+
+.course-edit-container {
+ width: 100%;
+ max-height: 60vh;
+ overflow-y: auto;
+ padding: 30rpx 20rpx;
+ box-sizing: border-box;
+}
+
+.edit-section {
+ margin-bottom: 40rpx;
+ width: 100%;
+}
+
+.section-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 20rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 2px solid #29d3b4;
+}
+
+.coach-list {
+ width: 100%;
+ max-height: 300rpx;
+ overflow-y: auto;
+ border: 1px solid #eee;
+ border-radius: 12rpx;
+ background: #fff;
+}
+
+.coach-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 25rpx 30rpx;
+ border-bottom: 1px solid #f0f0f0;
+ background: #fff;
+ transition: all 0.3s ease;
+ min-height: 80rpx;
+ box-sizing: border-box;
+}
+
+.coach-item:last-child {
+ border-bottom: none;
+}
+
+.coach-item.selected {
+ background: #f0f9ff;
+ border-left: 4rpx solid #29d3b4;
+}
+
+.coach-item:active {
+ background: #f5f5f5;
+}
+
+.coach-name {
+ font-size: 30rpx;
+ color: #333;
+ flex: 1;
+}
+
+.coach-check {
+ color: #29d3b4;
+ font-size: 36rpx;
+ font-weight: bold;
+ width: 40rpx;
+ text-align: center;
+}
+
+.empty-tip {
+ color: #999;
+ font-size: 28rpx;
+ text-align: center;
+ padding: 40rpx 0;
+}
+
+// 体测记录样式
+.fitness-test-records {
+ padding: 20rpx;
+}
+
+.add-record-btn-container {
+ margin-bottom: 30rpx;
+ display: flex;
+ justify-content: center;
+}
+
+.add-record-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #29d3b4;
+ color: #fff;
+ border-radius: 30rpx;
+ padding: 20rpx 40rpx;
+ box-shadow: 0 4rpx 12rpx rgba(41, 211, 180, 0.3);
+
+ .add-icon {
+ font-size: 32rpx;
+ margin-right: 10rpx;
+ font-weight: bold;
+ }
+
+ .add-text {
+ font-size: 28rpx;
+ }
+}
+
+.add-record-btn:active {
+ background: #1ea08e;
+ transform: scale(0.98);
+}
+
+.fitness-record-list {
+ width: 100%;
+}
+
+.empty-records {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 0;
+
+ .empty-img {
+ width: 200rpx;
+ height: 200rpx;
+ opacity: 0.5;
+ margin-bottom: 30rpx;
+ }
+
+ .empty-text {
+ color: #999;
+ font-size: 28rpx;
+ }
+}
+
+.fitness-record-item {
+ background: #3D3D3D;
+ border-radius: 16rpx;
+ padding: 25rpx;
+ margin-bottom: 20rpx;
+ color: #fff;
+ transition: background 0.3s ease;
+}
+
+.fitness-record-item:active {
+ background: #454545;
+}
+
+.record-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 1px solid #4A4A4A;
+}
+
+.record-date {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #29d3b4;
+}
+
+.record-actions {
+ display: flex;
+ align-items: center;
+}
+
+.edit-btn {
+ background: rgba(41, 211, 180, 0.2);
+ color: #29d3b4;
+ padding: 8rpx 16rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ border: 1px solid #29d3b4;
+}
+
+.edit-btn:active {
+ background: rgba(41, 211, 180, 0.3);
+}
+
+.record-content {
+ width: 100%;
+}
+
+.record-data {
+ display: flex;
+ justify-content: space-around;
+ margin-bottom: 20rpx;
+}
+
+.data-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 15rpx;
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 12rpx;
+ min-width: 120rpx;
+}
+
+.data-label {
+ font-size: 24rpx;
+ color: #999;
+ margin-bottom: 8rpx;
+}
+
+.data-value {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #fff;
+}
+
+.pdf-attachments {
+ margin-top: 20rpx;
+ padding-top: 20rpx;
+ border-top: 1px solid #4A4A4A;
+}
+
+.attachment-title {
+ font-size: 26rpx;
+ color: #999;
+ margin-bottom: 15rpx;
+}
+
+.pdf-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10rpx;
+}
+
+.pdf-item {
+ display: flex;
+ align-items: center;
+ padding: 15rpx;
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 12rpx;
+ transition: background 0.3s ease;
+}
+
+.pdf-item:active {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.pdf-icon {
+ font-size: 28rpx;
+ margin-right: 12rpx;
+}
+
+.pdf-name {
+ flex: 1;
+ font-size: 26rpx;
+ color: #fff;
+ margin-right: 10rpx;
+}
+
+.pdf-size {
+ font-size: 22rpx;
+ color: #999;
+}
+
+// 体测记录表单样式
+.fitness-record-form {
+ width: 100%;
+ max-height: 60vh;
+ overflow-y: auto;
+ padding: 20rpx;
+ box-sizing: border-box;
+}
+
+.form-section {
+ width: 100%;
+}
+
+.form-item {
+ margin-bottom: 30rpx;
+}
+
+.form-label {
+ font-size: 30rpx;
+ color: #333;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+}
+
+.form-input {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 0 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+}
+
+.form-input:focus-within {
+ border-color: #29d3b4;
+}
+
+.form-input input {
+ width: 100%;
+ height: 80rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+}
+
+.file-upload-area {
+ width: 100%;
+}
+
+.upload-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f8f9fa;
+ border: 2px dashed #ddd;
+ border-radius: 12rpx;
+ padding: 40rpx;
+ margin-bottom: 20rpx;
+ color: #666;
+ transition: all 0.3s ease;
+}
+
+.upload-btn:active {
+ background: #e9ecef;
+ border-color: #29d3b4;
+}
+
+.upload-icon {
+ font-size: 40rpx;
+ margin-right: 15rpx;
+}
+
+.upload-text {
+ font-size: 28rpx;
+}
+
+.selected-files {
+ width: 100%;
+}
+
+.selected-file-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 15rpx 20rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ margin-bottom: 10rpx;
+ border: 1px solid #eee;
+}
+
+.file-info {
+ display: flex;
+ align-items: center;
+ flex: 1;
+}
+
+.file-icon {
+ font-size: 28rpx;
+ margin-right: 12rpx;
+ color: #666;
+}
+
+.file-name {
+ flex: 1;
+ font-size: 26rpx;
+ color: #333;
+ margin-right: 10rpx;
+}
+
+.file-size {
+ font-size: 22rpx;
+ color: #999;
+}
+
+.file-remove {
+ width: 40rpx;
+ height: 40rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #ff4757;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 24rpx;
+ font-weight: bold;
+ margin-left: 15rpx;
+}
+
+.file-remove:active {
+ background: #ff3838;
+}
+
+// 学生信息相关样式
+.student-info-section {
+ margin-top: 20rpx;
+}
+
+.add-student-btn {
+ display: flex;
+ align-items: center;
+ background: #29d3b4;
+ color: #fff;
+ border-radius: 20rpx;
+ padding: 8rpx 16rpx;
+ font-size: 24rpx;
+
+ .add-icon {
+ margin-right: 6rpx;
+ font-weight: bold;
+ }
+}
+
+.add-student-btn:active {
+ background: #1ea08e;
+ transform: scale(0.95);
+}
+
+.student-cards-container {
+ width: 92%;
+ margin: 20rpx auto;
+}
+
+.student-swiper {
+ height: 400rpx;
+
+ &::v-deep .uni-swiper-dots {
+ bottom: 20rpx;
+ }
+
+ &::v-deep .uni-swiper-dot {
+ width: 12rpx;
+ height: 12rpx;
+ background: rgba(255, 255, 255, 0.5);
+ }
+
+ &::v-deep .uni-swiper-dot-active {
+ background: #29d3b4;
+ }
+}
+
+.student-swiper-item {
+ padding: 0 10rpx;
+}
+
+.student-card {
+ background: #3D3D3D;
+ border-radius: 20rpx;
+ padding: 30rpx;
+ min-height: 340rpx;
+ display: flex;
+ flex-direction: column;
+ color: #fff;
+}
+
+.student-card-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 1px solid #4A4A4A;
+}
+
+.student-avatar {
+ width: 80rpx;
+ height: 80rpx;
+ margin-right: 20rpx;
+
+ .avatar-img {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+
+ .avatar-placeholder {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background: #29d3b4;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #fff;
+ }
+}
+
+.student-basic-info {
+ flex: 1;
+
+ .student-name {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #fff;
+ margin-bottom: 8rpx;
+ }
+
+ .student-meta {
+ display: flex;
+ align-items: center;
+ font-size: 24rpx;
+ color: #999;
+
+ .student-age {
+ margin-right: 20rpx;
+ }
+ }
+}
+
+.student-actions {
+ display: flex;
+ align-items: center;
+
+ .edit-btn {
+ background: rgba(41, 211, 180, 0.2);
+ color: #29d3b4;
+ padding: 8rpx 16rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ border: 1px solid #29d3b4;
+
+ .btn-text {
+ font-size: 24rpx;
+ }
+ }
+
+ .edit-btn:active {
+ background: rgba(41, 211, 180, 0.3);
+ }
+}
+
+.student-card-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.student-details {
+ margin-bottom: 20rpx;
+
+ .detail-item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 12rpx;
+ font-size: 26rpx;
+
+ .detail-label {
+ color: #999;
+ min-width: 120rpx;
+ }
+
+ .detail-value {
+ color: #fff;
+ flex: 1;
+ text-align: right;
+ }
+ }
+}
+
+.student-stats {
+ display: flex;
+ justify-content: space-around;
+ margin-top: auto;
+ padding-top: 15rpx;
+ border-top: 1px solid #4A4A4A;
+
+ .stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .stat-value {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #29d3b4;
+ margin-bottom: 5rpx;
+ }
+
+ .stat-label {
+ font-size: 22rpx;
+ color: #999;
+ }
+ }
+}
+
+.empty-students {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 340rpx;
+
+ .empty-img {
+ width: 160rpx;
+ height: 160rpx;
+ opacity: 0.5;
+ margin-bottom: 20rpx;
+ }
+
+ .empty-text {
+ color: #999;
+ font-size: 28rpx;
+ margin-bottom: 30rpx;
+ }
+
+ .empty-add-btn {
+ background: #29d3b4;
+ color: #fff;
+ padding: 15rpx 30rpx;
+ border-radius: 25rpx;
+ font-size: 26rpx;
+ }
+
+ .empty-add-btn:active {
+ background: #1ea08e;
+ transform: scale(0.95);
+ }
+}
+
+// 操作面板样式
+.student-actions-panel {
+ margin-top: 20rpx;
+ border-top: 1px solid #4A4A4A;
+ padding-top: 15rpx;
+
+ .panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 15rpx;
+
+ .panel-title {
+ font-size: 26rpx;
+ color: #495057;
+ font-weight: 600;
+ }
+
+ .collapse-icon {
+ width: 30rpx;
+ height: 30rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: #29d3b4;
+ color: #fff;
+ font-size: 20rpx;
+ font-weight: bold;
+ transition: transform 0.3s ease;
+
+ &.expanded {
+ transform: rotate(180deg);
+ }
+ }
+ }
+
+ .actions-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12rpx;
+ margin-top: 15rpx;
+
+ .action-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 15rpx 8rpx;
+ background: #fff;
+ border-radius: 8rpx;
+ border: 1px solid #dee2e6;
+ transition: all 0.3s ease;
+ position: relative;
+
+ &:active {
+ transform: scale(0.95);
+ background: #e3f2fd;
+ }
+
+ .action-icon {
+ font-size: 32rpx;
+ margin-bottom: 5rpx;
+ display: block;
+ line-height: 1;
+ }
+
+ .action-text {
+ font-size: 22rpx;
+ color: #495057;
+ text-align: center;
+ line-height: 1.2;
+ font-weight: 500;
+ }
+
+ &:hover {
+ background: #f8f9fa;
+ border-color: #29d3b4;
+
+ .action-text {
+ color: #29d3b4;
+ }
+ }
+ }
+ }
+
+ &.collapsed {
+ .actions-grid {
+ display: none;
+ }
+ }
+}
+
+.actions-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10rpx 0;
+ cursor: pointer;
+
+ .toggle-text {
+ font-size: 28rpx;
+ color: #29d3b4;
+ font-weight: bold;
+ }
+
+ .toggle-icon {
+ font-size: 24rpx;
+ color: #29d3b4;
+ transition: transform 0.3s ease;
+
+ &.expanded {
+ transform: rotate(180deg);
+ }
+ }
+}
+
+.actions-toggle:active {
+ background: rgba(41, 211, 180, 0.1);
+ border-radius: 8rpx;
+}
+
+.actions-content {
+ margin-top: 15rpx;
+ animation: slideDown 0.3s ease;
+}
+
+@keyframes slideDown {
+ from {
+ opacity: 0;
+ max-height: 0;
+ }
+ to {
+ opacity: 1;
+ max-height: 200rpx;
+ }
+}
+
+.actions-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15rpx;
+}
+
+.action-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(41, 211, 180, 0.3);
+ border-radius: 12rpx;
+ padding: 20rpx 15rpx;
+ min-width: 120rpx;
+ flex: 1;
+ transition: all 0.3s ease;
+
+ .action-icon {
+ font-size: 28rpx;
+ margin-bottom: 8rpx;
+ }
+
+ .action-text {
+ font-size: 22rpx;
+ color: #fff;
+ text-align: center;
+ }
+}
+
+.action-item:active {
+ background: rgba(41, 211, 180, 0.2);
+ border-color: #29d3b4;
+ transform: scale(0.95);
+
+ .action-text {
+ color: #29d3b4;
+ }
+}
+
+// 学生信息编辑弹窗样式
+.student-edit-popup {
+ width: 95vw;
+ max-width: 700rpx;
+ height: 85vh;
+ max-height: 1400rpx;
+ display: flex;
+ flex-direction: column;
+}
+
+.student-form-container {
+ flex: 1;
+ padding: 20rpx;
+ box-sizing: border-box;
+ overflow-y: auto;
+ min-height: 0;
+}
+
+.form-group {
+ margin-bottom: 40rpx;
+
+ .form-group-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 25rpx;
+ padding-bottom: 10rpx;
+ border-bottom: 2px solid #29d3b4;
+ position: relative;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: -2rpx;
+ left: 0;
+ width: 60rpx;
+ height: 2rpx;
+ background: #29d3b4;
+ }
+ }
+}
+
+.form-item {
+ margin-bottom: 25rpx;
+
+ .form-label {
+ font-size: 28rpx;
+ color: #333;
+ margin-bottom: 12rpx;
+ font-weight: 500;
+
+ &.required {
+ position: relative;
+
+ &::before {
+ content: '*';
+ color: #ff4757;
+ margin-right: 4rpx;
+ font-weight: bold;
+ }
+ }
+ }
+
+ .form-input {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 0 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+
+ &:focus-within {
+ border-color: #29d3b4;
+ }
+
+ input {
+ width: 100%;
+ height: 80rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+
+ .picker-display {
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 28rpx;
+ color: #333;
+
+ .picker-arrow {
+ color: #999;
+ font-size: 32rpx;
+ font-weight: bold;
+ transform: rotate(90deg);
+ }
+ }
+ }
+
+ .form-textarea {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+
+ &:focus-within {
+ border-color: #29d3b4;
+ }
+
+ textarea {
+ width: 100%;
+ height: 120rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+ resize: none;
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+ }
+}
+
+// 整合后的基本信息卡片样式
+.basic-info-card {
+ background: #fff;
+ border-radius: 15rpx;
+ margin: 20rpx;
+ padding: 25rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+ .basic-info-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 25rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 2px solid #f1f3f4;
+
+ .info-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+
+ .title-accent {
+ color: #29d3b4;
+ margin-left: 10rpx;
+ }
+ }
+
+ .info-status {
+ padding: 6rpx 15rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ color: #fff;
+ background: #28a745;
+ }
+ }
+
+ .basic-info-content {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20rpx;
+
+ .info-item {
+ display: flex;
+ flex-direction: column;
+
+ .info-label {
+ font-size: 24rpx;
+ color: #6c757d;
+ margin-bottom: 5rpx;
+ }
+
+ .info-value {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+
+ &.empty {
+ color: #adb5bd;
+ font-style: italic;
+ }
+ }
+ }
+ }
+}
+
+// 整合后的学生卡片容器样式
+.integrated-cards-container {
+ margin: 20rpx;
+ min-height: 670rpx;
+ display: block;
+
+ .student-swiper {
+ height: 650rpx !important;
+ min-height: 650rpx;
+
+ // 强制设置swiper内部组件的高度
+ ::v-deep .uni-swiper-wrapper {
+ height: 650rpx !important;
+ }
+
+ ::v-deep .uni-swiper-slides {
+ height: 650rpx !important;
+ }
+
+ ::v-deep .uni-swiper-slide {
+ height: 650rpx !important;
+ }
+ }
+
+ .student-swiper-item {
+ height: 620rpx !important;
+ min-height: 620rpx;
+ display: flex;
+ flex-direction: column;
+ }
+}
+
+// 整合的学生卡片样式
+.integrated-student-card {
+ background: #fff;
+ border-radius: 15rpx;
+ overflow: hidden;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
+ min-height: 600rpx;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+
+ // 客户基本信息区域
+ .basic-info-section {
+ padding: 25rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: #fff;
+
+ .section-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 25rpx;
+
+ .customer-avatar {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 28rpx;
+ font-weight: bold;
+ margin-right: 20rpx;
+ }
+
+ .customer-info {
+ flex: 1;
+
+ .customer-name {
+ font-size: 32rpx;
+ font-weight: bold;
+ margin-bottom: 8rpx;
+ }
+
+ .customer-meta {
+ font-size: 24rpx;
+ opacity: 0.9;
+ }
+ }
+
+ .contact-actions {
+ display: flex;
+ gap: 15rpx;
+
+ .contact-btn {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.9);
+ background: rgba(255, 255, 255, 0.3);
+ }
+
+ .contact-icon {
+ font-size: 24rpx;
+ }
+ }
+ }
+ }
+
+ .basic-info-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20rpx;
+
+ .basic-info-item {
+ .info-label {
+ font-size: 24rpx;
+ opacity: 0.8;
+ margin-bottom: 5rpx;
+ }
+
+ .info-value {
+ font-size: 26rpx;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+
+ .divider-line {
+ height: 2rpx;
+ background: #f0f0f0;
+ margin: 0 25rpx;
+ }
+
+ // 学生信息区域
+ .student-info-section {
+ padding: 25rpx;
+ flex: 1;
+ min-height: 300rpx;
+
+ .student-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 25rpx;
+
+ .student-avatar {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #29d3b4 0%, #26c6da 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 32rpx;
+ font-weight: bold;
+ margin-right: 20rpx;
+ }
+
+ .student-basic-info {
+ flex: 1;
+
+ .student-name {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 8rpx;
+ }
+
+ .student-meta {
+ font-size: 24rpx;
+ color: #666;
+
+ .student-age, .student-gender {
+ margin-right: 20rpx;
+ }
+ }
+ }
+
+ .student-edit-btn {
+ padding: 8rpx 20rpx;
+ border-radius: 20rpx;
+ border: 2px solid #29d3b4;
+ color: #29d3b4;
+ font-size: 24rpx;
+ transition: all 0.3s ease;
+
+ &:active {
+ background: #29d3b4;
+ color: #fff;
+ }
+ }
+ }
+
+ .student-details-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15rpx;
+
+ .detail-item {
+ .detail-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-bottom: 5rpx;
+ }
+
+ .detail-value {
+ font-size: 26rpx;
+ color: #333;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+
+ .actions-divider {
+ height: 1rpx;
+ background: #f0f0f0;
+ margin: 0 25rpx;
+ }
+
+ // 学生操作面板
+ .student-actions-panel {
+ padding: 20rpx 25rpx;
+
+ .actions-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10rpx 0;
+
+ .toggle-icon {
+ font-size: 20rpx;
+ color: #666;
+ transition: transform 0.3s ease;
+
+ &.expanded {
+ transform: rotate(180deg);
+ }
+ }
+ }
+
+ .actions-content {
+ margin-top: 15rpx;
+
+ .actions-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 15rpx;
+
+ .action-item {
+ padding: 20rpx 10rpx;
+ border-radius: 12rpx;
+ background: #f8f9fa;
+ border: 2px solid #e9ecef;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.95);
+ background: #e9ecef;
+ border-color: #29d3b4;
+ }
+
+ .action-icon {
+ font-size: 28rpx;
+ margin-bottom: 8rpx;
+ }
+
+ .action-text {
+ font-size: 22rpx;
+ color: #495057;
+ text-align: center;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+ }
+}
+
+// 学生上下文标题样式
+.student-context-header {
+ padding: 20rpx 30rpx;
+ background-color: rgba(41, 211, 180, 0.1);
+ border-left: 4rpx solid #29d3b4;
+ margin: 20rpx;
+ border-radius: 8rpx;
+
+ .context-title {
+ color: #29d3b4;
+ font-size: 28rpx;
+ font-weight: bold;
+ }
+}
+
+// swiper容器样式
+.student-swiper {
+ .student-swiper-item {
+ padding: 0 20rpx;
+ }
+}
+
+// 表单样式
+.form-item {
+ margin-bottom: 25rpx;
+
+ .form-label {
+ font-size: 28rpx;
+ color: #333;
+ margin-bottom: 12rpx;
+ font-weight: 500;
+
+ &.required {
+ position: relative;
+
+ &::before {
+ content: '*';
+ color: #ff4757;
+ margin-right: 4rpx;
+ font-weight: bold;
+ }
+ }
+ }
+
+ .form-input {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 0 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+
+ &:focus-within {
+ border-color: #29d3b4;
+ }
+
+ input {
+ width: 100%;
+ height: 80rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+
+ .picker-display {
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 28rpx;
+ color: #333;
+
+ .picker-arrow {
+ color: #999;
+ font-size: 32rpx;
+ font-weight: bold;
+ transform: rotate(90deg);
+ }
+ }
+ }
+
+ .form-textarea {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+
+ &:focus-within {
+ border-color: #29d3b4;
+ }
+
+ textarea {
+ width: 100%;
+ height: 120rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+ resize: none;
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+ }
+}
+
+// 学生操作面板样式(额外的样式定义)
+.student-actions-panel {
+ margin-top: 15rpx;
+ padding: 20rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ border: 2px solid #e9ecef;
+ transition: all 0.3s ease;
+
+ .panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 15rpx;
+
+ .panel-title {
+ font-size: 26rpx;
+ color: #495057;
+ font-weight: 600;
+ }
+
+ .collapse-icon {
+ width: 30rpx;
+ height: 30rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: #29d3b4;
+ color: #fff;
+ font-size: 20rpx;
+ font-weight: bold;
+ transition: transform 0.3s ease;
+
+ &.expanded {
+ transform: rotate(180deg);
+ }
+ }
+ }
+
+ .actions-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12rpx;
+ margin-top: 15rpx;
+
+ .action-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 15rpx 8rpx;
+ background: #fff;
+ border-radius: 8rpx;
+ border: 1px solid #dee2e6;
+ transition: all 0.3s ease;
+ position: relative;
+
+ &:active {
+ transform: scale(0.95);
+ background: #e3f2fd;
+ }
+
+ .action-icon {
+ font-size: 32rpx;
+ margin-bottom: 5rpx;
+ display: block;
+ line-height: 1;
+ }
+
+ .action-text {
+ font-size: 22rpx;
+ color: #495057;
+ text-align: center;
+ line-height: 1.2;
+ font-weight: 500;
+ }
+
+ &:hover {
+ background: #f8f9fa;
+ border-color: #29d3b4;
+
+ .action-text {
+ color: #29d3b4;
+ }
+ }
+ }
+ }
+
+ &.collapsed {
+ .actions-grid {
+ display: none;
+ }
+ }
+}
+
+// 整合后的基本信息卡片样式
+.basic-info-card {
+ background: #fff;
+ border-radius: 15rpx;
+ margin: 20rpx;
+ padding: 25rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+ .basic-info-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 25rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 2px solid #f1f3f4;
+
+ .info-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+
+ .title-accent {
+ color: #29d3b4;
+ margin-left: 10rpx;
+ }
+ }
+
+ .info-status {
+ padding: 6rpx 15rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ color: #fff;
+ background: #28a745;
+ }
+ }
+
+ .basic-info-content {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20rpx;
+
+ .info-item {
+ display: flex;
+ flex-direction: column;
+
+ .info-label {
+ font-size: 24rpx;
+ color: #6c757d;
+ margin-bottom: 5rpx;
+ }
+
+ .info-value {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+
+ &.empty {
+ color: #adb5bd;
+ font-style: italic;
+ }
+ }
+ }
+ }
+}
+
+// 整合后的学生卡片容器样式
+.integrated-cards-container {
+ margin: 20rpx;
+ min-height: 670rpx;
+ display: block;
+
+ .student-swiper {
+ height: 650rpx !important;
+ min-height: 650rpx;
+
+ // 强制设置swiper内部组件的高度
+ ::v-deep .uni-swiper-wrapper {
+ height: 650rpx !important;
+ }
+
+ ::v-deep .uni-swiper-slides {
+ height: 650rpx !important;
+ }
+
+ ::v-deep .uni-swiper-slide {
+ height: 650rpx !important;
+ }
+ }
+
+ .student-swiper-item {
+ height: 620rpx !important;
+ min-height: 620rpx;
+ display: flex;
+ flex-direction: column;
+ }
+}
+
+// 空状态样式
+.empty-records {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 20rpx;
+
+ .empty-img {
+ width: 200rpx;
+ height: 200rpx;
+ opacity: 0.5;
+ margin-bottom: 30rpx;
+ }
+
+ .empty-text {
+ color: #999;
+ font-size: 28rpx;
+ }
+}
\ No newline at end of file
diff --git a/uniapp/pages/market/clue/clue_info.scss b/uniapp/pages/market/clue/clue_info.scss
new file mode 100644
index 00000000..14998d59
--- /dev/null
+++ b/uniapp/pages/market/clue/clue_info.scss
@@ -0,0 +1,1408 @@
+// 客户详情页样式文件
+// 从 clue_info.vue 中提取的样式
+
+.assemble {
+ background-color: #f5f5f5;
+ min-height: 100vh;
+}
+
+.main_box {
+ background-color: #fff;
+ margin: 20rpx;
+ border-radius: 15rpx;
+ padding: 30rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+.action-buttons {
+ display: flex;
+ justify-content: space-around;
+ margin: 30rpx 0;
+ padding: 20rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+
+ .action-btn {
+ flex: 1;
+ margin: 0 10rpx;
+ padding: 15rpx 20rpx;
+ border-radius: 8rpx;
+ background: #fff;
+ border: 2px solid #e9ecef;
+ color: #495057;
+ font-size: 26rpx;
+ text-align: center;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.95);
+ background: #29d3b4;
+ color: #fff;
+ border-color: #29d3b4;
+ }
+
+ &.primary {
+ background: #29d3b4;
+ color: #fff;
+ border-color: #29d3b4;
+ }
+ }
+}
+
+.tab-switcher-container {
+ display: flex;
+ justify-content: center;
+ margin: 30rpx 0;
+ padding: 8rpx;
+ background: #f1f3f4;
+ border-radius: 25rpx;
+
+ .tab-item {
+ flex: 1;
+ padding: 15rpx 20rpx;
+ text-align: center;
+ border-radius: 20rpx;
+ font-size: 28rpx;
+ color: #666;
+ transition: all 0.3s ease;
+
+ &.active {
+ background: #29d3b4;
+ color: #fff;
+ font-weight: bold;
+ }
+ }
+}
+
+.count_section {
+ display: flex;
+ justify-content: space-around;
+ margin: 30rpx 0;
+ padding: 25rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 15rpx;
+ color: #fff;
+
+ .count-item {
+ text-align: center;
+
+ .count-number {
+ font-size: 36rpx;
+ font-weight: bold;
+ margin-bottom: 8rpx;
+ }
+
+ .count-label {
+ font-size: 24rpx;
+ opacity: 0.9;
+ }
+ }
+}
+
+.selected-text {
+ color: #29d3b4;
+ font-weight: bold;
+}
+
+.text {
+ color: #333;
+}
+
+.basic-message {
+ margin: 20rpx 0;
+
+ .message-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20rpx 0;
+ border-bottom: 1rpx solid #f0f0f0;
+
+ .message-label {
+ font-size: 28rpx;
+ color: #666;
+ }
+
+ .message-value {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+ }
+ }
+}
+
+.bottom-label {
+ margin-top: 30rpx;
+ padding: 20rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ border-left: 4rpx solid #29d3b4;
+
+ .label-title {
+ font-size: 28rpx;
+ color: #29d3b4;
+ font-weight: bold;
+ margin-bottom: 10rpx;
+ }
+
+ .label-content {
+ font-size: 26rpx;
+ color: #666;
+ line-height: 1.6;
+ }
+}
+
+.custom-popup-dialog {
+ .popup-content {
+ background: #fff;
+ border-radius: 20rpx 20rpx 0 0;
+ padding: 30rpx;
+ max-height: 80vh;
+ overflow-y: auto;
+
+ .popup-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 30rpx;
+ padding-bottom: 20rpx;
+ border-bottom: 2rpx solid #f0f0f0;
+
+ .popup-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ }
+
+ .close-btn {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ background: #f5f5f5;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #666;
+ font-size: 32rpx;
+ }
+ }
+ }
+}
+
+.remark-textarea-container {
+ margin: 20rpx 0;
+
+ .remark-textarea {
+ width: 100%;
+ min-height: 200rpx;
+ padding: 20rpx;
+ border: 2rpx solid #e9ecef;
+ border-radius: 12rpx;
+ font-size: 28rpx;
+ color: #333;
+ background: #fff;
+
+ &:focus {
+ border-color: #29d3b4;
+ outline: none;
+ }
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+}
+
+.remark-btn {
+ width: 100%;
+ padding: 20rpx;
+ background: #29d3b4;
+ color: #fff;
+ border-radius: 12rpx;
+ font-size: 28rpx;
+ font-weight: bold;
+ text-align: center;
+ margin-top: 20rpx;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.98);
+ background: #26c6da;
+ }
+}
+
+.follow-records-list {
+ .record-item {
+ padding: 25rpx;
+ margin-bottom: 20rpx;
+ background: #fff;
+ border-radius: 12rpx;
+ border-left: 4rpx solid #29d3b4;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+
+ .record-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15rpx;
+
+ .record-type {
+ padding: 6rpx 15rpx;
+ border-radius: 15rpx;
+ background: #29d3b4;
+ color: #fff;
+ font-size: 24rpx;
+ }
+
+ .record-time {
+ font-size: 24rpx;
+ color: #666;
+ }
+ }
+
+ .record-content {
+ font-size: 26rpx;
+ color: #333;
+ line-height: 1.6;
+ margin-bottom: 10rpx;
+ }
+
+ .record-operator {
+ font-size: 24rpx;
+ color: #999;
+ text-align: right;
+ }
+ }
+}
+
+.same-community {
+ color: #29d3b4;
+ font-weight: bold;
+}
+
+.coeducation {
+ color: #ff6b6b;
+ font-weight: bold;
+}
+
+.search {
+ margin: 20rpx;
+ padding: 15rpx 25rpx;
+ background: #fff;
+ border-radius: 25rpx;
+ border: 2rpx solid #e9ecef;
+ font-size: 28rpx;
+
+ &:focus {
+ border-color: #29d3b4;
+ outline: none;
+ }
+
+ &::placeholder {
+ color: #999;
+ }
+}
+
+.selected {
+ background: #29d3b4;
+ color: #fff;
+}
+
+.not-selected {
+ background: #f8f9fa;
+ color: #666;
+}
+
+.call-record-item {
+ padding: 25rpx;
+ margin: 20rpx;
+ background: #fff;
+ border-radius: 12rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+
+ .call-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15rpx;
+
+ .call-type {
+ padding: 6rpx 15rpx;
+ border-radius: 15rpx;
+ font-size: 24rpx;
+
+ &.incoming {
+ background: #28a745;
+ color: #fff;
+ }
+
+ &.outgoing {
+ background: #17a2b8;
+ color: #fff;
+ }
+
+ &.missed {
+ background: #dc3545;
+ color: #fff;
+ }
+ }
+
+ .call-duration {
+ font-size: 24rpx;
+ color: #666;
+ }
+ }
+
+ .call-info {
+ font-size: 26rpx;
+ color: #333;
+ margin-bottom: 10rpx;
+ }
+
+ .call-time {
+ font-size: 24rpx;
+ color: #999;
+ text-align: right;
+ }
+}
+
+.call-log {
+ .log-item {
+ padding: 20rpx;
+ margin-bottom: 15rpx;
+ background: #f8f9fa;
+ border-radius: 8rpx;
+ border-left: 3rpx solid #29d3b4;
+
+ .log-content {
+ font-size: 26rpx;
+ color: #333;
+ line-height: 1.5;
+ margin-bottom: 8rpx;
+ }
+
+ .log-meta {
+ display: flex;
+ justify-content: space-between;
+ font-size: 24rpx;
+ color: #666;
+ }
+ }
+}
+
+.course-info {
+ .info-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20rpx;
+ margin: 20rpx 0;
+
+ .info-item {
+ padding: 20rpx;
+ background: #f8f9fa;
+ border-radius: 8rpx;
+
+ .info-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-bottom: 8rpx;
+ }
+
+ .info-value {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+ }
+ }
+ }
+}
+
+.popup-container {
+ .popup-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .popup-content {
+ background: #fff;
+ border-radius: 15rpx;
+ padding: 30rpx;
+ margin: 20rpx;
+ max-height: 80vh;
+ overflow-y: auto;
+ width: 90%;
+ max-width: 600rpx;
+ }
+}
+
+.course-edit-container {
+ .form-section {
+ margin-bottom: 30rpx;
+
+ .section-title {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 20rpx;
+ padding-bottom: 10rpx;
+ border-bottom: 2rpx solid #29d3b4;
+ }
+
+ .form-row {
+ display: flex;
+ margin-bottom: 20rpx;
+
+ .form-item {
+ flex: 1;
+ margin-right: 20rpx;
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ .form-label {
+ font-size: 26rpx;
+ color: #666;
+ margin-bottom: 10rpx;
+ }
+
+ .form-input {
+ width: 100%;
+ padding: 15rpx;
+ border: 2rpx solid #e9ecef;
+ border-radius: 8rpx;
+ font-size: 26rpx;
+
+ &:focus {
+ border-color: #29d3b4;
+ outline: none;
+ }
+ }
+ }
+ }
+ }
+
+ .action-buttons {
+ display: flex;
+ gap: 20rpx;
+ margin-top: 30rpx;
+
+ .btn {
+ flex: 1;
+ padding: 20rpx;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ text-align: center;
+
+ &.primary {
+ background: #29d3b4;
+ color: #fff;
+ }
+
+ &.secondary {
+ background: #f8f9fa;
+ color: #666;
+ border: 2rpx solid #e9ecef;
+ }
+ }
+ }
+}
+
+.fitness-test-records {
+ .record-card {
+ background: #fff;
+ border-radius: 12rpx;
+ padding: 25rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+
+ .record-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20rpx;
+
+ .record-date {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ }
+
+ .record-status {
+ padding: 6rpx 15rpx;
+ border-radius: 15rpx;
+ font-size: 24rpx;
+
+ &.completed {
+ background: #28a745;
+ color: #fff;
+ }
+
+ &.pending {
+ background: #ffc107;
+ color: #333;
+ }
+ }
+ }
+
+ .record-data {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15rpx;
+
+ .data-item {
+ .data-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-bottom: 5rpx;
+ }
+
+ .data-value {
+ font-size: 26rpx;
+ color: #333;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+}
+
+.fitness-record-form {
+ .form-group {
+ margin-bottom: 25rpx;
+
+ .group-title {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 15rpx;
+ padding-bottom: 8rpx;
+ border-bottom: 2rpx solid #29d3b4;
+ }
+
+ .form-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15rpx;
+
+ .form-field {
+ .field-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-bottom: 8rpx;
+ }
+
+ .field-input {
+ width: 100%;
+ padding: 12rpx;
+ border: 2rpx solid #e9ecef;
+ border-radius: 6rpx;
+ font-size: 26rpx;
+
+ &:focus {
+ border-color: #29d3b4;
+ outline: none;
+ }
+ }
+ }
+ }
+ }
+
+ .submit-section {
+ margin-top: 30rpx;
+ padding-top: 20rpx;
+ border-top: 2rpx solid #f0f0f0;
+
+ .submit-btn {
+ width: 100%;
+ padding: 20rpx;
+ background: #29d3b4;
+ color: #fff;
+ border-radius: 8rpx;
+ font-size: 28rpx;
+ font-weight: bold;
+ text-align: center;
+
+ &:active {
+ background: #26c6da;
+ }
+ }
+ }
+}
+
+// 分割线样式
+.divider-line {
+ height: 2rpx;
+ background: #f0f0f0;
+ margin: 20rpx 0;
+}
+
+// 学生信息区域样式
+.student-info-section {
+ background: #fff;
+ border-radius: 15rpx;
+ margin: 20rpx;
+ padding: 25rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+ .section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 25rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 2rpx solid #f1f3f4;
+
+ .section-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+
+ .title-icon {
+ color: #29d3b4;
+ margin-right: 10rpx;
+ }
+ }
+
+ .section-actions {
+ display: flex;
+ gap: 15rpx;
+
+ .action-btn {
+ padding: 8rpx 20rpx;
+ border-radius: 20rpx;
+ border: 2rpx solid #29d3b4;
+ color: #29d3b4;
+ font-size: 24rpx;
+ transition: all 0.3s ease;
+
+ &:active {
+ background: #29d3b4;
+ color: #fff;
+ }
+ }
+ }
+ }
+
+ .student-card {
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ padding: 20rpx;
+ margin-bottom: 15rpx;
+ border: 2rpx solid #e9ecef;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.98);
+ border-color: #29d3b4;
+ }
+
+ .student-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 15rpx;
+
+ .student-avatar {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #29d3b4 0%, #26c6da 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 24rpx;
+ font-weight: bold;
+ margin-right: 15rpx;
+ }
+
+ .student-info {
+ flex: 1;
+
+ .student-name {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 5rpx;
+ }
+
+ .student-meta {
+ font-size: 24rpx;
+ color: #666;
+ }
+ }
+
+ .student-status {
+ padding: 4rpx 12rpx;
+ border-radius: 12rpx;
+ font-size: 22rpx;
+
+ &.active {
+ background: #28a745;
+ color: #fff;
+ }
+
+ &.inactive {
+ background: #6c757d;
+ color: #fff;
+ }
+ }
+ }
+
+ .student-details {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 10rpx;
+
+ .detail-item {
+ .detail-label {
+ font-size: 22rpx;
+ color: #666;
+ margin-bottom: 3rpx;
+ }
+
+ .detail-value {
+ font-size: 24rpx;
+ color: #333;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+}
+
+.actions-divider {
+ height: 1rpx;
+ background: #f0f0f0;
+ margin: 15rpx 0;
+}
+
+// 学生操作面板样式
+.student-actions-panel {
+ margin-top: 15rpx;
+ padding: 20rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ border: 2rpx solid #e9ecef;
+ transition: all 0.3s ease;
+
+ .panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 15rpx;
+
+ .panel-title {
+ font-size: 26rpx;
+ color: #495057;
+ font-weight: 600;
+ }
+
+ .collapse-icon {
+ width: 30rpx;
+ height: 30rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: #29d3b4;
+ color: #fff;
+ font-size: 20rpx;
+ font-weight: bold;
+ transition: transform 0.3s ease;
+
+ &.expanded {
+ transform: rotate(180deg);
+ }
+ }
+ }
+
+ .actions-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12rpx;
+ margin-top: 15rpx;
+
+ .action-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 15rpx 8rpx;
+ background: #fff;
+ border-radius: 8rpx;
+ border: 1px solid #dee2e6;
+ transition: all 0.3s ease;
+ position: relative;
+
+ &:active {
+ transform: scale(0.95);
+ background: #e3f2fd;
+ }
+
+ .action-icon {
+ font-size: 32rpx;
+ margin-bottom: 5rpx;
+ display: block;
+ line-height: 1;
+ }
+
+ .action-text {
+ font-size: 22rpx;
+ color: #495057;
+ text-align: center;
+ line-height: 1.2;
+ font-weight: 500;
+ }
+
+ &:hover {
+ background: #f8f9fa;
+ border-color: #29d3b4;
+
+ .action-text {
+ color: #29d3b4;
+ }
+ }
+ }
+ }
+
+ &.collapsed {
+ .actions-grid {
+ display: none;
+ }
+ }
+}
+
+// 学生信息编辑弹窗样式
+.student-edit-popup {
+ width: 95vw;
+ max-width: 700rpx;
+ height: 85vh;
+ max-height: 1400rpx;
+ display: flex;
+ flex-direction: column;
+}
+
+.student-form-container {
+ flex: 1;
+ padding: 20rpx;
+ box-sizing: border-box;
+ overflow-y: auto;
+ min-height: 0;
+}
+
+.form-group {
+ margin-bottom: 40rpx;
+
+ .form-group-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 25rpx;
+ padding-bottom: 10rpx;
+ border-bottom: 2px solid #29d3b4;
+ position: relative;
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: -2rpx;
+ left: 0;
+ width: 60rpx;
+ height: 2rpx;
+ background: #29d3b4;
+ }
+ }
+}
+
+.form-item {
+ margin-bottom: 25rpx;
+
+ .form-label {
+ font-size: 28rpx;
+ color: #333;
+ margin-bottom: 12rpx;
+ font-weight: 500;
+
+ &.required {
+ position: relative;
+
+ &::before {
+ content: '*';
+ color: #ff4757;
+ margin-right: 4rpx;
+ font-weight: bold;
+ }
+ }
+ }
+
+ .form-input {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 0 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+
+ &:focus-within {
+ border-color: #29d3b4;
+ }
+
+ input {
+ width: 100%;
+ height: 80rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+
+ .picker-display {
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 28rpx;
+ color: #333;
+
+ .picker-arrow {
+ color: #999;
+ font-size: 32rpx;
+ font-weight: bold;
+ transform: rotate(90deg);
+ }
+ }
+ }
+
+ .form-textarea {
+ border: 2px solid #eee;
+ border-radius: 12rpx;
+ padding: 20rpx;
+ background: #fff;
+ transition: border-color 0.3s ease;
+
+ &:focus-within {
+ border-color: #29d3b4;
+ }
+
+ textarea {
+ width: 100%;
+ height: 120rpx;
+ font-size: 28rpx;
+ color: #333;
+ border: none;
+ outline: none;
+ background: transparent;
+ resize: none;
+
+ &::placeholder {
+ color: #999;
+ }
+ }
+ }
+}
+
+// 整合后的基本信息卡片样式
+.basic-info-card {
+ background: #fff;
+ border-radius: 15rpx;
+ margin: 20rpx;
+ padding: 25rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+ .basic-info-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 25rpx;
+ padding-bottom: 15rpx;
+ border-bottom: 2px solid #f1f3f4;
+
+ .info-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+
+ .title-accent {
+ color: #29d3b4;
+ margin-left: 10rpx;
+ }
+ }
+
+ .info-status {
+ padding: 6rpx 15rpx;
+ border-radius: 20rpx;
+ font-size: 24rpx;
+ color: #fff;
+ background: #28a745;
+ }
+ }
+
+ .basic-info-content {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20rpx;
+
+ .info-item {
+ display: flex;
+ flex-direction: column;
+
+ .info-label {
+ font-size: 24rpx;
+ color: #6c757d;
+ margin-bottom: 5rpx;
+ }
+
+ .info-value {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+
+ &.empty {
+ color: #adb5bd;
+ font-style: italic;
+ }
+ }
+ }
+ }
+}
+
+// 整合后的学生卡片容器样式
+.integrated-cards-container {
+ margin: 20rpx;
+ min-height: 670rpx;
+ display: block;
+
+ .student-swiper {
+ height: 650rpx !important;
+ min-height: 650rpx;
+
+ // 强制设置swiper内部组件的高度
+ ::v-deep .uni-swiper-wrapper {
+ height: 650rpx !important;
+ }
+
+ ::v-deep .uni-swiper-slides {
+ height: 650rpx !important;
+ }
+
+ ::v-deep .uni-swiper-slide {
+ height: 650rpx !important;
+ }
+ }
+
+ .student-swiper-item {
+ height: 620rpx !important;
+ min-height: 620rpx;
+ display: flex;
+ flex-direction: column;
+ }
+}
+
+// 整合的学生卡片样式
+.integrated-student-card {
+ background: #fff;
+ border-radius: 15rpx;
+ overflow: hidden;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
+ min-height: 600rpx;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+
+ // 客户基本信息区域
+ .basic-info-section {
+ padding: 25rpx;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: #fff;
+
+ .section-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 25rpx;
+
+ .customer-avatar {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 28rpx;
+ font-weight: bold;
+ margin-right: 20rpx;
+ }
+
+ .customer-info {
+ flex: 1;
+
+ .customer-name {
+ font-size: 32rpx;
+ font-weight: bold;
+ margin-bottom: 8rpx;
+ }
+
+ .customer-meta {
+ font-size: 24rpx;
+ opacity: 0.9;
+ }
+ }
+
+ .contact-actions {
+ display: flex;
+ gap: 15rpx;
+
+ .contact-btn {
+ width: 60rpx;
+ height: 60rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.9);
+ background: rgba(255, 255, 255, 0.3);
+ }
+
+ .contact-icon {
+ font-size: 24rpx;
+ }
+ }
+ }
+ }
+
+ .basic-info-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20rpx;
+
+ .basic-info-item {
+ .info-label {
+ font-size: 24rpx;
+ opacity: 0.8;
+ margin-bottom: 5rpx;
+ }
+
+ .info-value {
+ font-size: 26rpx;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+
+ .divider-line {
+ height: 2rpx;
+ background: #f0f0f0;
+ margin: 0 25rpx;
+ }
+
+ // 学生信息区域
+ .student-info-section {
+ padding: 25rpx;
+ flex: 1;
+ min-height: 300rpx;
+
+ .student-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 25rpx;
+
+ .student-avatar {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #29d3b4 0%, #26c6da 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 32rpx;
+ font-weight: bold;
+ margin-right: 20rpx;
+ }
+
+ .student-basic-info {
+ flex: 1;
+
+ .student-name {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 8rpx;
+ }
+
+ .student-meta {
+ font-size: 24rpx;
+ color: #666;
+
+ .student-age, .student-gender {
+ margin-right: 20rpx;
+ }
+ }
+ }
+
+ .student-edit-btn {
+ padding: 8rpx 20rpx;
+ border-radius: 20rpx;
+ border: 2px solid #29d3b4;
+ color: #29d3b4;
+ font-size: 24rpx;
+ transition: all 0.3s ease;
+
+ &:active {
+ background: #29d3b4;
+ color: #fff;
+ }
+ }
+ }
+
+ .student-details-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15rpx;
+
+ .detail-item {
+ .detail-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-bottom: 5rpx;
+ }
+
+ .detail-value {
+ font-size: 26rpx;
+ color: #333;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+
+ .actions-divider {
+ height: 1rpx;
+ background: #f0f0f0;
+ margin: 0 25rpx;
+ }
+
+ // 学生操作面板
+ .student-actions-panel {
+ padding: 20rpx 25rpx;
+
+ .actions-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10rpx 0;
+
+ .toggle-icon {
+ font-size: 20rpx;
+ color: #666;
+ transition: transform 0.3s ease;
+
+ &.expanded {
+ transform: rotate(180deg);
+ }
+ }
+ }
+
+ .actions-content {
+ margin-top: 15rpx;
+
+ .actions-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 15rpx;
+
+ .action-item {
+ padding: 20rpx 10rpx;
+ border-radius: 12rpx;
+ background: #f8f9fa;
+ border: 2px solid #e9ecef;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s ease;
+
+ &:active {
+ transform: scale(0.95);
+ background: #e9ecef;
+ border-color: #29d3b4;
+ }
+
+ .action-icon {
+ font-size: 28rpx;
+ margin-bottom: 8rpx;
+ }
+
+ .action-text {
+ font-size: 22rpx;
+ color: #495057;
+ text-align: center;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+ }
+}
+
+// 学生上下文标题样式
+.student-context-header {
+ padding: 20rpx 30rpx;
+ background-color: rgba(41, 211, 180, 0.1);
+ border-left: 4rpx solid #29d3b4;
+ margin: 20rpx;
+ border-radius: 8rpx;
+
+ .context-title {
+ color: #29d3b4;
+ font-size: 28rpx;
+ font-weight: bold;
+ }
+}
+
+// swiper容器样式
+.student-swiper {
+ .student-swiper-item {
+ padding: 0 20rpx;
+ }
+}
+
+// 空状态样式
+.empty-records {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 20rpx;
+
+ .empty-img {
+ width: 200rpx;
+ height: 200rpx;
+ opacity: 0.5;
+ margin-bottom: 30rpx;
+ }
+
+ .empty-text {
+ color: #999;
+ font-size: 28rpx;
+ }
+}
\ No newline at end of file
diff --git a/uniapp/pages/market/clue/clue_info.vue b/uniapp/pages/market/clue/clue_info.vue
index 64c0c3dd..21c27d30 100644
--- a/uniapp/pages/market/clue/clue_info.vue
+++ b/uniapp/pages/market/clue/clue_info.vue
@@ -1,64 +1,31 @@
-
+
-
-
-
-
-
-
- {{ safeGet(clientInfo, 'customerResource.name', '未知客户') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 基本资料
-
- 课程信息
-
- 通话记录
-
- 体测记录
-
- 学习计划
-
-
-
-
+
+
+
+
+
+
-
+
-
客户和学生信息
@@ -68,135 +35,35 @@
-
-
+
+
-
-
-
-
-
-
-
- 来源渠道
- {{ safeGet(clientInfo, 'customerResource.source_channel_name', '未知渠道') }}
-
-
- 来源
- {{ safeGet(clientInfo, 'customerResource.source_name', '未知来源') }}
-
-
- 顾问
- {{ safeGet(clientInfo, 'customerResource.consultant_name', '未知顾问') }}
-
-
- 性别
- {{ safeGet(clientInfo, 'customerResource.gender_name', '未知性别') }}
-
-
-
-
-
-
-
-
-
-
-
-
- 出生日期:
- {{ student.birthday || '未设置' }}
-
-
- 联系电话:
- {{ student.contact_phone || '未设置' }}
-
-
- 紧急联系人:
- {{ student.emergency_contact || '未设置' }}
-
-
- 体验课次数:
- {{ student.trial_class_count || 0 }}次
-
-
-
-
-
-
-
-
-
- ▲
-
-
-
-
-
-
- {{ action.icon }}
- {{ action.text }}
-
-
-
-
-
+
-
-
-
-
- 暂无学生信息
-
- 添加第一个学生
-
+
+
+
+
+
+ 暂无学生信息
+
+ 添加第一个学生
@@ -268,84 +135,51 @@
-
+
-
-
-
-
-
- 呼叫时间:
- {{ v.created_at }}
-
-
-
-
-
+
+
+
+
+ 暂无通话记录
+
+
-
+
+
+
-
+
+
- 新增体测记录
+ 为{{ currentStudent.name }}新增体测记录
-
-
-
-
- 暂无体测记录
-
-
-
-
-
-
-
-
- 身高
- {{ record.height }}cm
-
-
- 体重
- {{ record.weight }}kg
-
-
-
-
-
- 体测报告
-
-
- 📄
- {{ pdf.name }}
- {{ formatFileSize(pdf.size) }}
-
-
-
-
-
+
+
+ 该学生暂无体测记录
+
+
+
@@ -374,248 +208,79 @@
-
-
-
+
-
-
-
+
-
-
-
-
+
\ No newline at end of file
diff --git a/uniapp/pages/market/clue/clue_info_refactored.vue b/uniapp/pages/market/clue/clue_info_refactored.vue
new file mode 100644
index 00000000..c3ab18ed
--- /dev/null
+++ b/uniapp/pages/market/clue/clue_info_refactored.vue
@@ -0,0 +1,549 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $util.safeGet(clientInfo, 'customerResource.name', '未知客户') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 客户和学生信息
+
+ +
+ 添加学生
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 课程信息内容...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 学习计划内容...
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/pages/market/clue/index.vue b/uniapp/pages/market/clue/index.vue
index cb8ec8e2..8fd50481 100644
--- a/uniapp/pages/market/clue/index.vue
+++ b/uniapp/pages/market/clue/index.vue
@@ -44,10 +44,10 @@
-
+
一访{{ v.customerResource.first_visit_status || '未到' }}
-
+
二访{{ v.customerResource.second_visit_status || '未到' }}
diff --git a/uniapp权限管理配置文档.md b/uniapp权限管理配置文档.md
new file mode 100644
index 00000000..ef4f3367
--- /dev/null
+++ b/uniapp权限管理配置文档.md
@@ -0,0 +1,280 @@
+# UniApp 权限管理配置文档
+
+## 📋 概述
+
+本文档详细列出了UniApp教育培训管理系统中所有页面的权限配置建议,用于实现精细化的菜单权限管理。系统支持**员工端**(市场、教练、销售)和**会员端**(学员、家长)两种登录方式。
+
+## 🔐 权限等级说明
+
+| 权限等级 | 说明 | 适用场景 |
+|---------|------|---------|
+| **公开访问** | 无需登录即可访问 | 登录页、隐私协议等 |
+| **登录验证** | 需要有效Token | 大部分功能页面 |
+| **角色验证** | 需要特定角色权限 | 业务功能页面 |
+| **管理员权限** | 需要管理员级别权限 | 人员管理、系统配置 |
+| **开发权限** | 仅开发环境可访问 | 测试页面、Demo页面 |
+
+## 👥 用户角色定义
+
+### 员工端角色
+- **市场人员** (role_type=1): 负责客户线索管理、市场推广
+- **教练** (role_type=2): 负责课程教学、学员管理
+- **销售人员** (role_type=3): 负责客户转化、订单管理
+
+### 会员端角色
+- **学员** (user_type=member): 查看课程、作业、个人信息
+- **家长** (user_type=parent): 管理孩子信息、课程、服务等
+
+## 📊 页面权限配置表
+
+### 🚪 登录认证模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/student/login/login` | 登录页面 | 公开访问 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| `/pages/student/login/forgot` | 找回密码 | 公开访问 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+
+### 📚 学员模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/student/index/index` | 学员首页 | 登录验证 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/index/job_list` | 作业列表 | 登录验证 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/index/work_details` | 作业详情 | 登录验证 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/index/physical_examination` | 体测数据 | 登录验证 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/timetable/index` | 课表主页 | 登录验证 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/timetable/info` | 课表详情 | 登录验证 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/timetable/list` | 场馆列表 | 登录验证 | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/my/my` | 个人中心 | 登录验证 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
+| `/pages/student/my/my_coach` | 我的教练 | 登录验证 | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/my/my_members` | 我的成员 | 登录验证 | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/my/lesson_consumption` | 课时消耗 | 登录验证 | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| `/pages/student/my/personal_data` | 个人资料 | 登录验证 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
+| `/pages/student/my/set_up` | 设置 | 登录验证 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
+| `/pages/student/my/update_pass` | 修改密码 | 登录验证 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
+
+### 👨🏫 教练模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/coach/home/index` | 教练首页 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/job/add` | 发布作业 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/job/list` | 全部作业 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/course/list` | 课表管理 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/course/info` | 课时详情 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/course/info_list` | 课时详情列表 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/class/list` | 班级管理 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/class/info` | 班级详情 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/student/student_list` | 我的学员 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/student/student_detail` | 学员详情 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/student/info` | 学员信息 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/student/work_details` | 作业任务 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/student/physical_examination` | 体测数据 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/schedule/schedule_table` | 课程安排 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/schedule/add_schedule` | 添加课程安排 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/schedule/adjust_course` | 调整课程安排 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/schedule/sign_in` | 课程点名 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/schedule/schedule_detail` | 课程详情 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/index` | 个人中心 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| `/pages/coach/my/info` | 个人资料 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| `/pages/coach/my/set_up` | 设置 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| `/pages/coach/my/update_pass` | 修改密码 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| `/pages/coach/my/arrival_statistics` | 到课统计 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/due_soon` | 即将到期 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/schooling_statistics` | 授课统计 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/teaching_management` | 教研管理列表 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/teaching_management_info` | 文章详情 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/gotake_exam` | 考试 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/exam_results` | 考试结果 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/my_attendance` | 我的考勤 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/service_list` | 服务列表 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/coach/my/service_detail` | 服务详情 | 角色验证 | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+
+### 💼 市场模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/market/home/index` | 市场首页 | 角色验证 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/market/index/index` | 销售首页 | 角色验证 | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/index` | 线索管理 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/clue_info` | 客户详情 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/order_list` | 订单列表 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/add_clues` | 添加客户 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/edit_clues` | 编辑客户 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/edit_clues_log` | 修改记录 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/new_task` | 新增任务 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/writing_followUp` | 添加跟进 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/class_arrangement` | 课程安排 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/class_arrangement_detail` | 课程安排详情 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/clue/clue_table` | 数据统计 | 角色验证 | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/course/course_detail` | 课程详情 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/data/index` | 数据中心 | 角色验证 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/market/data/statistics` | 市场数据统计 | 角色验证 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/market/reimbursement/list` | 报销列表 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/reimbursement/add` | 新增报销 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/reimbursement/detail` | 报销详情 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/my/index` | 个人中心 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| `/pages/market/my/info` | 个人资料 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| `/pages/market/my/set_up` | 设置 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| `/pages/market/my/update_pass` | 修改密码 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| `/pages/market/my/signed_client_list` | 已签客户 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/my/my_data` | 我的数据 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/my/dept_data` | 部门数据 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/my/campus_data` | 校区数据 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/market/my/firm_info` | 企业信息 | 角色验证 | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
+
+### 👥 家长模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/parent/user-info/index` | 用户信息 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/user-info/child-detail` | 孩子详情 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/courses/index` | 课程管理 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/courses/course-detail` | 课程详情 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/materials/index` | 教学资料 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/materials/material-detail` | 资料详情 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/services/index` | 服务管理 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/services/service-detail` | 服务详情 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/orders/index` | 订单管理 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/orders/order-detail` | 订单详情 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/messages/index` | 消息管理 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/messages/message-detail` | 消息详情 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/contracts/index` | 合同管理 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| `/pages/parent/contracts/contract-detail` | 合同详情 | 角色验证 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+
+### 🛠️ 公共模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/common/privacy_agreement` | 隐私协议 | 公开访问 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| `/pages/common/article_info` | 文章详情 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| `/pages/common/feedback` | 意见反馈 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| `/pages/common/my_message` | 我的消息 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| `/pages/common/im_chat_info` | 聊天详情 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| `/pages/common/sys_msg_list` | 系统消息 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| `/pages/common/contract_list` | 订单列表 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| `/pages/common/my_attendance` | 我的考勤 | 登录验证 | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
+| `/pages/common/personnel/add_personnel` | 新员工信息填写 | 管理员权限 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
+| `/pages/common/contract/my_contract` | 我的合同 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| `/pages/common/contract/contract_detail` | 合同详情 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| `/pages/common/contract/contract_sign` | 合同签订 | 登录验证 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+
+### 📋 教务模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/academic/home/index` | 教务首页 | 管理员权限 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
+
+### 🧪 测试模块
+
+| 页面路径 | 页面名称 | 权限等级 | 市场 | 教练 | 销售 | 学员 | 家长 | 建议纳入权限管理 |
+|---------|---------|---------|-----|-----|-----|-----|-----|-----------------|
+| `/pages/demo/mock-demo` | Mock数据演示 | 开发权限 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| `/pages/demo/dict_optimization` | 字典优化 | 开发权限 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+
+## 🎯 权限管理建议
+
+### 🔥 高优先级页面(建议优先纳入权限管理)
+
+#### 核心业务功能
+- **线索管理模块**: 客户信息、跟进记录、订单管理
+- **课程教学模块**: 课表管理、作业管理、学员管理
+- **数据统计模块**: 各类业务数据和报表
+- **财务模块**: 报销管理、合同管理
+
+#### 敏感数据页面
+- **个人信息查看**: 学员详情、教练信息、客户资料
+- **体测数据**: 学员体能测试结果
+- **考勤统计**: 教练和学员的考勤记录
+- **教研管理**: 教学资料和考试内容
+
+### 🔶 中优先级页面(建议选择性纳入)
+
+#### 查看类功能
+- **课表查看**: 学员课程表、教练课程安排
+- **消息通知**: 系统消息、聊天功能
+- **服务管理**: 各类服务项目
+
+### 🔷 低优先级页面(可不纳入权限管理)
+
+#### 通用功能
+- **个人设置**: 个人资料、密码修改、系统设置
+- **公共信息**: 隐私协议、文章详情、意见反馈
+
+## ⚙️ 技术实现建议
+
+### 1. 路由守卫实现
+
+```javascript
+// router/permission.js
+const routePermissions = {
+ '/pages/market/clue/index': ['market', 'sales'],
+ '/pages/coach/home/index': ['coach'],
+ '/pages/student/index/index': ['member', 'parent'],
+ // ...更多路由权限配置
+}
+
+function checkPermission(route, userRole) {
+ const requiredRoles = routePermissions[route]
+ return !requiredRoles || requiredRoles.includes(userRole)
+}
+```
+
+### 2. 菜单动态生成
+
+```javascript
+// menu/menuConfig.js
+const menuConfig = {
+ market: [
+ { path: '/pages/market/home/index', name: '首页', icon: 'home' },
+ { path: '/pages/market/clue/index', name: '线索管理', icon: 'clue' },
+ // ...市场人员菜单
+ ],
+ coach: [
+ { path: '/pages/coach/home/index', name: '首页', icon: 'home' },
+ { path: '/pages/coach/course/list', name: '课表管理', icon: 'course' },
+ // ...教练菜单
+ ],
+ // ...其他角色菜单
+}
+```
+
+### 3. API权限验证
+
+```php
+// middleware/AuthMiddleware.php
+public function handle($request, $next, $role = null) {
+ $user = $this->getUser($request);
+
+ if ($role && !$this->hasRole($user, $role)) {
+ return $this->unauthorizedResponse();
+ }
+
+ return $next($request);
+}
+```
+
+## 📝 配置建议总结
+
+### ✅ 强烈建议纳入权限管理的页面(58个)
+
+1. **线索管理**: 11个页面(客户信息、跟进、订单等)
+2. **教练功能**: 22个页面(课程、学员、作业等)
+3. **家长功能**: 14个页面(孩子管理、服务等)
+4. **数据统计**: 8个页面(各类业务报表)
+5. **其他业务**: 3个页面(教务、人员、考勤等)
+
+### 🔶 可选择纳入权限管理的页面(15个)
+
+1. **学员查看**: 7个页面(课表、作业、体测等)
+2. **公共业务**: 8个页面(合同、消息、服务等)
+
+### ❌ 建议不纳入权限管理的页面(25个)
+
+1. **登录认证**: 2个页面
+2. **个人设置**: 12个页面
+3. **公共信息**: 6个页面
+4. **开发测试**: 2个页面
+5. **基础功能**: 3个页面
+
+总计98个页面中,建议将**58个核心业务页面**纳入权限管理系统,确保数据安全和角色隔离的同时,保持系统的易用性和维护性。
\ No newline at end of file