From e8675dcca468be0c2d2debc857456091f3c05bd1 Mon Sep 17 00:00:00 2001 From: zeyan <258785420@qq.com> Date: Thu, 24 Jul 2025 11:26:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/apiController/Course.php | 7 +- .../model/course_schedule/CourseSchedule.php | 4 - .../service/api/apiService/CourseService.php | 96 +- .../api/apiService/ResourceSharingService.php | 34 +- package-lock.json | 71 + package.json | 5 + uniapp/common/util.js | 167 +- uniapp/common/utils-index.js | 155 + uniapp/components/README.md | 266 ++ .../call-record-card/call-record-card.vue | 140 + .../client-info-card/client-info-card.vue | 217 ++ .../course-edit-popup/course-edit-popup.less | 153 + .../course-edit-popup/course-edit-popup.vue | 159 + .../fitness-record-card.vue | 163 + .../fitness-record-popup.less | 215 + .../fitness-record-popup.vue | 276 ++ uniapp/components/index.js | 105 + .../student-edit-popup.less | 228 ++ .../student-edit-popup/student-edit-popup.vue | 324 ++ .../student-info-card/student-info-card.vue | 229 ++ .../components/tab-switcher/tab-switcher.vue | 70 + .../pages/market/clue/class_arrangement.vue | 365 +- uniapp/pages/market/clue/clue_info.less | 2204 +++++++++++ uniapp/pages/market/clue/clue_info.scss | 1408 +++++++ uniapp/pages/market/clue/clue_info.vue | 3443 ++--------------- .../market/clue/clue_info_refactored.vue | 549 +++ uniapp/pages/market/clue/index.vue | 4 +- uniapp权限管理配置文档.md | 280 ++ 28 files changed, 8275 insertions(+), 3062 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 uniapp/common/utils-index.js create mode 100644 uniapp/components/README.md create mode 100644 uniapp/components/call-record-card/call-record-card.vue create mode 100644 uniapp/components/client-info-card/client-info-card.vue create mode 100644 uniapp/components/course-edit-popup/course-edit-popup.less create mode 100644 uniapp/components/course-edit-popup/course-edit-popup.vue create mode 100644 uniapp/components/fitness-record-card/fitness-record-card.vue create mode 100644 uniapp/components/fitness-record-popup/fitness-record-popup.less create mode 100644 uniapp/components/fitness-record-popup/fitness-record-popup.vue create mode 100644 uniapp/components/index.js create mode 100644 uniapp/components/student-edit-popup/student-edit-popup.less create mode 100644 uniapp/components/student-edit-popup/student-edit-popup.vue create mode 100644 uniapp/components/student-info-card/student-info-card.vue create mode 100644 uniapp/components/tab-switcher/tab-switcher.vue create mode 100644 uniapp/pages/market/clue/clue_info.less create mode 100644 uniapp/pages/market/clue/clue_info.scss create mode 100644 uniapp/pages/market/clue/clue_info_refactored.vue create mode 100644 uniapp权限管理配置文档.md 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 + + + +``` + +## 💡 重构优势 + +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 @@ + + + + + + \ 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 @@ + + + + + + \ 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 @@ + + + + + + \ 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 @@ + + + + + + \ 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 @@ + + + + + + \ 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 @@ + + + + + + 筛选 + + + + + + + + + + + 时间查询 + + {{ timeOptions[timeIndex] }} + + + + 班主任筛选 + + + + + + + 开始日期 + + + {{ searchForm.start_date || '选择开始日期' }} + + + + + 结束日期 + + + {{ searchForm.end_date || '选择结束日期' }} + + + + + + + + 固定位筛选 + + + + + + + + 重置 + 搜索 + 关闭 + + + + + + @@ -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 @@ - + \ 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 @@ + + + + + + \ 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