Browse Source

修改 bug

master
王泽彦 1 year ago
parent
commit
ce7a849217
  1. 5
      niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php
  2. 15
      niucloud/app/api/controller/member/Member.php
  3. 24
      niucloud/app/api/route/route.php
  4. 1
      niucloud/app/common.php
  5. 4
      niucloud/app/service/admin/course_schedule/CourseScheduleService.php
  6. 2
      niucloud/app/service/api/apiService/CourseService.php
  7. 2
      niucloud/app/service/api/apiService/ResourceSharingService.php
  8. 53
      niucloud/app/service/api/member/MemberService.php
  9. 102
      uniapp/pages/market/clue/add_clues.vue
  10. 217
      uniapp/pages/market/clue/class_arrangement_detail.vue
  11. 163
      uniapp/pages/market/clue/clue_info.vue
  12. 190
      uniapp/pages/market/clue/edit_clues.vue

5
niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php

@ -164,7 +164,10 @@ class CourseSchedule extends BaseAdminController
["person_type",''],
["schedule_id",''],
["course_date",''],
["time_slot",'']
["time_slot",''],
["schedule_type", 1], // 1=正式位, 2=等待位
["course_type", 1], // 1=正式课, 2=体验课, 3=补课, 4=试听课
["position", ''] // 位置信息
]);
return (new CourseScheduleService())->addSchedule($data);
}

15
niucloud/app/api/controller/member/Member.php

@ -18,6 +18,7 @@ use app\service\api\member\MemberLogService;
use app\service\api\member\MemberService;
use core\base\BaseApiController;
use think\facade\Db;
use think\facade\Log;
use think\Response;
class Member extends BaseApiController
@ -127,9 +128,19 @@ class Member extends BaseApiController
public function list_call_up()
{
$data = $this->request->params([
['sales_id', ''],
['resource_id', ''],
['sales_id', ''], // 保留旧参数名称以保持兼容性
]);
return success((new MemberService())->list_call_up($data['sales_id']));
// 优先使用resource_id,如果不存在则使用sales_id
$resource_id = !empty($data['resource_id']) ? $data['resource_id'] : $data['sales_id'];
// 记录日志
Log::debug("Member/list_call_up - 请求参数: resource_id={$resource_id}");
$result = (new MemberService())->list_call_up($resource_id);
return success($result);
}
public function update_call_up()

24
niucloud/app/api/route/route.php

@ -127,18 +127,6 @@ Route::group(function () {
// 通过经纬度查询地址
Route::get('area/address_by_latlng', 'sys.Area/getAddressByLatlng');
/***************************************************** 字典批量获取 ****************************************************/
// 批量获取字典数据
Route::get('dict/batch', 'common.Dict/getBatchDict');
// 根据业务场景获取字典数据
Route::get('dict/scene/:scene', 'common.Dict/getDictByScene');
// 获取单个字典数据
Route::get('dict/single/:key', 'common.Dict/getDict');
// 获取字典映射关系
Route::get('dict/mapping', 'common.Dict/getDictMapping');
// 清除字典缓存
Route::post('dict/clear_cache', 'common.Dict/clearDictCache');
/***************************************************** 海报管理 ****************************************************/
//获取海报
Route::get('poster', 'poster.Poster/poster');
@ -465,7 +453,17 @@ Route::group(function () {
Route::post('xy/orderTable/add', 'apiController.OrderTable/add');
/***************************************************** 字典批量获取 ****************************************************/
// 批量获取字典数据
Route::get('dict/batch', 'common.Dict/getBatchDict');
// 根据业务场景获取字典数据
Route::get('dict/scene/:scene', 'common.Dict/getDictByScene');
// 获取单个字典数据
Route::get('dict/single/:key', 'common.Dict/getDict');
// 获取字典映射关系
Route::get('dict/mapping', 'common.Dict/getDictMapping');
// 清除字典缓存
Route::post('dict/clear_cache', 'common.Dict/clearDictCache');
})->middleware(ApiChannel::class)
->middleware(ApiPersonnelCheckToken::class, true)

1
niucloud/app/common.php

@ -1229,6 +1229,7 @@ function get_dict_value($key, $value)
$field = 'id,name,key,dictionary,memo,create_time,update_time';
$info = $dict->field($field)->where([['key', '=', $key]])->findOrEmpty()->toArray();
if ($info['dictionary'] == null) {
$info['dictionary'] = [];
}

4
niucloud/app/service/admin/course_schedule/CourseScheduleService.php

@ -299,7 +299,7 @@ class CourseScheduleService extends BaseAdminService
->alias('a')
->join(['school_customer_resources' => 'b'],'a.resources_id = b.id','left')
->where('a.schedule_id',$data['schedule_id'])
->field("b.name,a.status")
->field("a.id,a.resources_id,a.person_id,a.student_id,a.person_type,a.schedule_id,a.course_date,a.schedule_type,a.course_type,a.time_slot,a.status,a.remark,b.name")
->select()->toArray();
return $list;
@ -342,6 +342,8 @@ class CourseScheduleService extends BaseAdminService
'schedule_id' => $data['schedule_id'],
'course_date' => $data['course_date'],
'time_slot' => $data['time_slot'],
'schedule_type' => $data['schedule_type'] ?? 1, // 1=正式位, 2=等待位
'course_type' => $data['course_type'] ?? 1, // 1=正式课, 2=体验课, 3=补课, 4=试听课
]);
$CourseSchedule->where(['id' => $data['schedule_id']])->dec("available_capacity")->update();
return success("添加成功");

2
niucloud/app/service/api/apiService/CourseService.php

@ -327,7 +327,7 @@ class CourseService extends BaseApiService
->join(['school_customer_resources' => 'b'],'a.resources_id = b.id','left')
->join(['school_course_schedule' => 'c'],'c.id = a.schedule_id','left')
->where('a.schedule_id',$data['schedule_id'])
->field("b.name,a.status,a.person_type,c.campus_id,b.id as resources_id")
->field("b.name,a.status,a.person_type,c.campus_id,b.id as resources_id,a.schedule_type,a.course_type")
->select()
->toArray();

2
niucloud/app/service/api/apiService/ResourceSharingService.php

@ -300,7 +300,7 @@ class ResourceSharingService extends BaseApiService
if (!empty($item['customerResource'])) {
// 设置来源和渠道名称
$item['customerResource']['source'] = get_dict_value('source', $item['customerResource']['source']);
$item['customerResource']['source_channel'] = get_dict_value('source', $item['customerResource']['source_channel']);
$item['customerResource']['source_channel'] = get_dict_value('SourceChannel', $item['customerResource']['source_channel']);
$item['customerResource']['campus_name'] = $campus_name[$item['customerResource']['campus']] ?? '';
$item['customerResource']['communication_time'] = $resultdata[$item['resource_id']] ?? '';
}

53
niucloud/app/service/api/member/MemberService.php

@ -26,6 +26,7 @@ use core\base\BaseApiService;
use core\exception\ApiException;
use core\util\Barcode;
use think\facade\Db;
use think\facade\Log;
use think\Model;
/**
@ -181,10 +182,58 @@ class MemberService extends BaseApiService
}
/**
* 日志记录工具方法
* @param string $level 日志级别
* @param string $message 日志信息
* @return void
*/
private function log($level, $message) {
Log::$level('MemberService: ' . $message);
}
public function list_call_up($resource_id)
{
$campus = new CommunicationRecords();
return $campus->where('resource_id', $resource_id)->select()->toArray();
$communication = new CommunicationRecords();
// 添加日志记录以便调试
$this->log('debug', "list_call_up请求参数: resource_id={$resource_id}");
try {
// 检查resource_id是否有效
if (empty($resource_id)) {
$this->log('warning', "list_call_up: resource_id为空");
return [];
}
// 查询前打印SQL查询条件
$sqlDebug = "SELECT * FROM school_communication_records WHERE resource_id = '{$resource_id}' ORDER BY communication_time DESC";
$this->log('debug', "list_call_up对应SQL: {$sqlDebug}");
// 执行查询
$result = $communication->where('resource_id', $resource_id)
->order('communication_time DESC')
->select()->toArray();
// 如果没有结果,尝试使用原生 SQL 查询检查记录存在性
if (empty($result)) {
$this->log('debug', "list_call_up: 主要查询没有结果,尝试直接查询表");
$rawResult = $communication->query("SELECT COUNT(*) as count FROM school_communication_records WHERE resource_id = '{$resource_id}'");
$count = $rawResult[0]['count'] ?? 0;
$this->log('debug', "list_call_up: 原生SQL查询结果数量: {$count}");
// 如果原生查询有结果但模型查询没结果,尝试直接使用原生查询
if ($count > 0) {
$this->log('debug', "list_call_up: 检测到数据存在,使用原生查询获取");
$result = $communication->query("SELECT * FROM school_communication_records WHERE resource_id = '{$resource_id}' ORDER BY communication_time DESC");
}
}
$this->log('debug', "list_call_up查询结果数量: " . count($result));
return $result;
} catch (\Exception $e) {
$this->log('error', "list_call_up查询异常: " . $e->getMessage());
return [];
}
}
public function update_call_up($resource_id, $remarks)

102
uniapp/pages/market/clue/add_clues.vue

@ -428,7 +428,15 @@
<!-- 年月日-选择时间 -->
<fui-date-picker :show="date_picker_show" type="3" @change="change_date" @cancel="cancel_date"></fui-date-picker>
<fui-date-picker
:show="date_picker_show"
type="3"
:startYear="2020"
:endYear="2030"
:value="getCurrentDate()"
@change="change_date"
@cancel="cancel_date">
</fui-date-picker>
<!-- 选择器 -->
<fui-picker
@ -764,10 +772,56 @@ export default {
// 使用批量获取字典数据,提升性能
await this.getBatchDictData()
// 设置时间相关默认值
this.setDefaultTimes()
// this.getStaffList()//获取人员列表
// this.getAreaTree()//获取地区树形结构
},
// 设置时间相关默认值
setDefaultTimes() {
// 移除可选上课时间的默认值设置,让用户手动选择
this.formData.optional_class_time = ''
// 移除承诺到访时间的默认值设置,让用户手动选择
this.formData.promised_visit_time = ''
console.log('时间默认值已清空,用户需手动选择')
},
// 统一的日期格式化方法
formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
// FirstUI 日期选择器通常支持多种格式
// 根据实际测试结果可以调整以下格式:
// 格式选项(如果当前格式不工作,可以尝试其他格式):
const formats = {
standard: `${year}-${month}-${day}`, // YYYY-MM-DD
chinese: `${year}年${month}月${day}日`, // YYYY年MM月DD日
slash: `${year}/${month}/${day}`, // YYYY/MM/DD
dot: `${year}.${month}.${day}`, // YYYY.MM.DD
space: `${year} ${month} ${day}` // YYYY MM DD
}
// 优先使用标准格式,如果组件显示异常可以在这里切换格式
return formats.standard
// 如果标准格式不工作,可以尝试:
// return formats.chinese // 中文格式
// return formats.slash // 斜杠格式
},
// 获取当前日期(用于日期选择器的默认显示)
getCurrentDate() {
const today = new Date()
return this.formatDate(today)
},
// 批量获取字典数据
async getBatchDictData() {
try {
@ -1267,22 +1321,56 @@ export default {
//######-----时间选择器组件相关-----######
//打开下拉选择器
//打开日期选择器
openDate(input_name) {
this.date_picker_show = true
console.log('打开日期选择器:', input_name)
this.data_picker_input_name = input_name
// 设置日期选择器显示
this.$nextTick(() => {
this.date_picker_show = true
})
},
//选择跟进时间
change_date(e) {
//跟进时间
let val = (e.result ?? '')
if(val){
val = val
console.log('日期选择器返回数据:', e)
// 获取选择的日期值,兼容不同的返回格式
let val = ''
if (e.result) {
val = e.result
} else if (e.value) {
val = e.value
} else if (e.detail && e.detail.result) {
val = e.detail.result
} else if (e.detail && e.detail.value) {
val = e.detail.value
}
// 确保日期格式为 YYYY-MM-DD
if (val && typeof val === 'string') {
// 如果是时间戳,转换为日期字符串
if (/^\d+$/.test(val)) {
const date = new Date(parseInt(val))
val = this.formatDate(date)
}
// 如果包含时间部分,只保留日期部分
else if (val.includes(' ')) {
val = val.split(' ')[0]
}
// 统一格式为 YYYY-MM-DD
if (val.includes('/')) {
const parts = val.split('/')
if (parts.length === 3) {
val = `${parts[0]}-${parts[1].padStart(2, '0')}-${parts[2].padStart(2, '0')}`
}
}
}
let input_name = this.data_picker_input_name
this.formData[input_name] = val
console.log(`设置${input_name}为:`, val)
this.cancel_date()
},
//关闭选择跟进时间

217
uniapp/pages/market/clue/class_arrangement_detail.vue

@ -4,30 +4,24 @@
<view class="title">课程安排详情</view>
<view class="date">日期:{{ course_info.course_date }} {{course_info.time_slot}}</view>
</view>
<!-- 正式学员列表 -->
<view class="section">
<view class="section-title">学员列表</view>
<view class="student-list" v-if="course_info && course_info.available_capacity">
<!-- 显示已安排的学员 -->
<view
v-for="(stu, idx) in students"
:key="idx"
class="student-item"
@tap="viewStudent(stu)"
>
<view class="section-title">正式学员</view>
<view class="student-list" v-if="course_info">
<!-- 显示已安排的正式学员 -->
<view v-for="(stu, idx) in formalStudents" :key="idx" class="student-item"
@tap="viewStudent(stu)">
<view class="avatar">{{ stu.name && stu.name.charAt(0) }}</view>
<view class="info">
<view class="name">{{ stu.name }}</view>
<view class="desc">{{ getStatusText(stu.status) }}</view>
<view class="desc">{{ getCourseTypeText(stu.course_type) }} | {{ getStatusText(stu.status) }}
</view>
</view>
</view>
<!-- 显示空位 -->
<view
v-for="index in emptySeats"
:key="index"
class="student-item empty"
@tap="addStudent($event, index)"
>
<!-- 显示正式学员空位 -->
<view v-for="index in formalEmptySeats" :key="index" class="student-item empty"
@tap="addStudent($event, index, 'formal')">
<view class="avatar empty-avatar">+</view>
<view class="info">
<view class="name">空位</view>
@ -42,16 +36,46 @@
</view>
</view>
<!-- 等待位学员列表 -->
<view class="section">
<view class="section-title waiting-title">等待位</view>
<view class="student-list">
<!-- 显示等待位学员 -->
<view v-for="(stu, idx) in waitingStudents" :key="idx" class="student-item waiting"
@tap="viewStudent(stu)">
<view class="avatar waiting-avatar">{{ stu.name && stu.name.charAt(0) }}</view>
<view class="info">
<view class="name">{{ stu.name }}</view>
<view class="desc">{{ getCourseTypeText(stu.course_type) }} | {{ getStatusText(stu.status) }}
</view>
</view>
</view>
<!-- 等待位空位(固定2个) -->
<view v-for="index in waitingEmptySeats" :key="index"
class="student-item empty waiting" @tap="addStudent($event, index, 'waiting')">
<view class="avatar empty-avatar waiting-avatar">+</view>
<view class="info">
<view class="name">等待位</view>
<view class="desc">点击添加学员</view>
</view>
</view>
</view>
</view>
<!-- 请假原因弹窗 -->
<fui-modal ref="leaveReasonModal" :buttons="[]" width="600" title="请假申请">
<view class="leave-form">
<view class="leave-label">请假原因</view>
<view class="leave-input">
<fui-textarea v-model="leaveReason" placeholder="请输入请假原因" :isCounter="true" :maxlength="200" :minHeight="200" :isAutoHeight="true"></fui-textarea>
<fui-textarea v-model="leaveReason" placeholder="请输入请假原因" :isCounter="true" :maxlength="200"
:minHeight="200" :isAutoHeight="true"></fui-textarea>
</view>
<view class="leave-buttons">
<fui-button background="#434544" color="#fff" borderColor="#666" btnSize="medium" @tap="$refs.leaveReasonModal.close()">取消</fui-button>
<fui-button background="#29d3b4" color="#fff" btnSize="medium" @tap="submitLeaveRequest">提交</fui-button>
<fui-button background="#434544" color="#fff" borderColor="#666" btnSize="medium"
@tap="$refs.leaveReasonModal.close()">取消</fui-button>
<fui-button background="#29d3b4" color="#fff" btnSize="medium"
@tap="submitLeaveRequest">提交</fui-button>
</view>
</view>
</fui-modal>
@ -66,20 +90,14 @@
course_id: '',
course_info: [],
date: '',
students: [
// {
// name: '张三',
// desc: '已签到'
// },
// {
// name: '李四',
// desc: '未签到'
// },
],
students: [], // 所有学员数据
formalStudents: [], // 正式学员
waitingStudents: [], // 等待位学员
resource_id: '',
leaveReason: '', // 请假原因
currentStudent: null, // 当前操作的学生
emptySeats: [] // 空位数组
formalEmptySeats: [], // 正式学员空位数组
waitingEmptySeats: [1, 2] // 等待位固定2个空位
};
},
onLoad(query) {
@ -93,6 +111,12 @@
viewStudent(stu) {
console.log(stu, this.course_info);
// 显示操作选项弹窗
uni.showActionSheet({
title: `学员: ${stu.name}`,
itemList: stu.person_type === 'customer_resource' ? ['取消课程'] : ['申请请假'],
success: (res) => {
if (res.tapIndex === 0) {
// 判断学员类型
if (stu.person_type === 'customer_resource') {
// 如果是客户资源,弹出确认取消课程的弹窗
@ -146,6 +170,9 @@
this.$refs.leaveReasonModal.open();
this.currentStudent = stu; // 保存当前操作的学生信息
}
}
}
});
},
getStatusText(status) {
const statusMap = {
@ -155,6 +182,36 @@
};
return statusMap[status] || status;
},
// 获取课程类型文本
getCourseTypeText(courseType) {
const courseTypeMap = {
1: '正式课',
2: '体验课',
3: '补课',
4: '试听课'
};
return courseTypeMap[courseType] || '未知';
},
// 分类学员数据
categorizeStudents() {
// 根据schedule_type分类学员
// schedule_type: 1=正式位, 2=等待位
this.formalStudents = this.students.filter(stu => stu.schedule_type === 1 || stu.schedule_type === null);
this.waitingStudents = this.students.filter(stu => stu.schedule_type === 2);
// 确保学员名称存在,如果不存在则返回未知学员
this.formalStudents.forEach(stu => {
if (!stu.name) stu.name = '未知学员';
});
this.waitingStudents.forEach(stu => {
if (!stu.name) stu.name = '未知学员';
});
console.log('学员分类完成:', {
formal: this.formalStudents.length,
waiting: this.waitingStudents.length
});
},
async scheduleList() {
try {
console.log('开始获取学员列表, schedule_id:', this.course_id);
@ -174,8 +231,13 @@
// 否则按照ID排序
return a.id - b.id;
});
// 分类学员
this.categorizeStudents();
} else {
this.students = [];
this.formalStudents = [];
this.waitingStudents = [];
}
// 更新可用容量
@ -185,6 +247,8 @@
} catch (error) {
console.error('获取学员列表失败:', error);
this.students = [];
this.formalStudents = [];
this.waitingStudents = [];
return [];
}
},
@ -221,30 +285,39 @@
},
// 更新可用容量
updateAvailableCapacity() {
// 确保course_info和students都已加载
if (!this.course_info || !this.students) return;
// 确保course_info已加载
if (!this.course_info) return;
console.log('更新可用容量 - 课程总容量:', this.course_info.available_capacity, '学员数量:', this.students.length);
console.log('更新可用容量 - 课程总容量:', this.course_info.available_capacity, '正式学员数量:', this.formalStudents.length);
// 计算已占用的位置数
const occupiedSeats = this.students.length;
// 计算正式位已占用的位置数
const occupiedFormalSeats = this.formalStudents.length;
// 获取课程总容量
// 获取课程总容量(正式位)
const totalCapacity = parseInt(this.course_info.available_capacity) || 0;
// 计算可用容量
this.course_info.available_capacity = Math.max(0, totalCapacity - occupiedSeats);
// 计算正式位剩余空位
const remainingFormalSeats = Math.max(0, totalCapacity - occupiedFormalSeats);
console.log('计算后的正式位剩余容量:', remainingFormalSeats);
console.log('计算后的可用容量:', this.course_info.available_capacity);
// 初始化正式位空位数组
this.formalEmptySeats = [];
for (let i = 1; i <= remainingFormalSeats; i++) {
this.formalEmptySeats.push(i);
}
// 初始化空位数组
this.emptySeats = [];
for (let i = 1; i <= this.course_info.available_capacity; i++) {
this.emptySeats.push(i);
// 更新等待位空位(减去已有的等待位学员)
const remainingWaitingSeats = Math.max(0, 2 - this.waitingStudents.length);
this.waitingEmptySeats = [];
for (let i = 1; i <= remainingWaitingSeats; i++) {
this.waitingEmptySeats.push(i);
}
console.log('等待位剩余空位:', remainingWaitingSeats);
},
async addStudent(e, index) {
console.log('添加学员到位置:', index);
async addStudent(e, index, type = 'formal') {
console.log('添加学员到位置:', index, '类型:', type);
const data = {
'resources_id': this.resource_id,
@ -252,7 +325,9 @@
'schedule_id': this.course_id,
'course_date': this.course_info.course_date,
'time_slot': this.course_info.time_slot,
'position': index // 添加位置信息
'position': index, // 位置信息
'schedule_type': type === 'waiting' ? 2 : 1, // 1=正式位, 2=等待位
'course_type': type === 'waiting' ? 2 : 1 // 1=正式课, 2=体验课等
};
try {
@ -395,7 +470,9 @@
display: flex;
align-items: center;
padding: 18rpx 24rpx;
min-width: 260rpx;
width: calc(50% - 10rpx);
/* 一行两个,减去gap的一半 */
box-sizing: border-box;
.avatar {
width: 60rpx;
@ -411,15 +488,24 @@
}
.info {
flex: 1;
overflow: hidden;
.name {
color: #fff;
font-size: 28rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.desc {
color: #bdbdbd;
font-size: 22rpx;
margin-top: 4rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
@ -441,6 +527,41 @@
color: #ffd86b;
}
}
// 等待位样式
&.waiting {
background: #2a2a3a; // 不同的背景色
border: 1rpx solid #8a7fff;
.avatar,
.avatar.waiting-avatar {
background: #8a7fff;
color: #fff;
}
&.empty {
border: 2rpx dashed #8a7fff;
background: #1a1a2a;
.avatar.empty-avatar {
background: #8a7fff;
color: #1a1a2a;
}
.info .name {
color: #8a7fff;
}
.info .desc {
color: #8a7fff;
}
}
}
}
// 等待位标题样式
.waiting-title {
color: #8a7fff !important;
}
// 请假弹窗样式

163
uniapp/pages/market/clue/clue_info.vue

@ -49,7 +49,8 @@
<view :class="{'selected-text': switch_tags_type === 1, 'text': switch_tags_type !== 1}"
@click="switch_tags(1)">基本资料
</view>
<view :class="{'selected-text': switch_tags_type === 2, 'text': switch_tags_type !== 2}"
<view v-if="hasCourseInfo"
:class="{'selected-text': switch_tags_type === 2, 'text': switch_tags_type !== 2}"
@click="switch_tags(2)">课程信息
</view>
<view :class="{'selected-text': switch_tags_type === 3, 'text': switch_tags_type !== 3}"
@ -346,6 +347,12 @@
selectedAssistants: [], // 选中的助教ID数组
}
},
computed: {
// 判断是否有课程信息
hasCourseInfo() {
return this.courseInfo && Array.isArray(this.courseInfo) && this.courseInfo.length > 0;
}
},
onLoad(options) {
console.log('onLoad - 接收到参数:', options);
@ -375,33 +382,6 @@
async init(){
console.log('init - 开始初始化流程');
// 预加载常用字典数据
try {
console.log('init - 开始预加载字典数据');
const dictPromises = [
this.$util.getDict('SourceChannel'), // 来源渠道
this.$util.getDict('source'), // 来源
this.$util.getDict('preliminarycustomerintention'), // 客户初步意向度
this.$util.getDict('kh_status'), // 客户状态
];
// 使用Promise.all并行加载字典,但设置超时处理
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('字典加载超时')), 5000)
);
await Promise.race([
Promise.all(dictPromises),
timeoutPromise
]).catch(err => {
console.warn('字典加载异常或超时,继续执行流程:', err);
});
console.log('init - 字典数据预加载完成或已超时');
} catch (error) {
console.warn('init - 字典数据预加载失败,继续执行流程:', error);
}
// 恢复为串行请求处理,确保数据依赖关系正确
try {
// 先获取客户详情,因为其他操作可能依赖于客户详情
@ -409,14 +389,15 @@
await this.getInfo();
console.log('init - 客户详情获取完成');
// 获取员工信息、通话记录和教练列表可以并行
console.log('init - 开始获取员工信息、通话记录和教练列表');
// 获取员工信息、通话记录、教练列表和课程信息可以并行
console.log('init - 开始获取员工信息、通话记录、教练列表和课程信息');
await Promise.all([
this.getUserInfo(),
this.getListCallUp(),
this.getPersonnelList()
this.getPersonnelList(),
this.getCourseInfo() // 添加课程信息获取
]);
console.log('init - 员工信息、通话记录和教练列表获取完成');
console.log('init - 员工信息、通话记录、教练列表和课程信息获取完成');
} catch (error) {
console.error('init - 数据加载出错:', error);
}
@ -502,25 +483,27 @@
async getListCallUp(){
console.log('getListCallUp - 开始获取通话记录');
try {
// 检查resource_sharing_id是否存在
if (!this.resource_sharing_id) {
console.error('getListCallUp - resource_sharing_id为空,无法获取通话记录');
// 检查clientInfo.resource_id是否存在
if (!this.clientInfo || !this.clientInfo.resource_id) {
console.error('getListCallUp - resource_id为空,无法获取通话记录');
return false;
}
let data = {
sales_id:this.resource_sharing_id//
resource_id: this.clientInfo.resource_id // 使用正确的参数名和客户资源ID
}
console.log('getListCallUp - 请求参数:', data);
let res = await apiRoute.listCallUp(data)
console.log('getListCallUp - 响应:', res);
if(res.code != 1){
uni.showToast({
title: res.msg,
title: res.msg || '获取通话记录失败',
icon: 'none'
})
return false;
}
this.listCallUp = res.data
console.log('getListCallUp - 通话记录获取成功');
this.listCallUp = res.data || []
console.log('getListCallUp - 通话记录获取成功, 数量:', this.listCallUp.length);
return true;
} catch (error) {
console.error('getListCallUp - 获取通话记录失败:', error);
@ -738,11 +721,27 @@
//切换标签
async switch_tags(type){
// 如果点击课程信息标签但没有课程数据,则不允许切换
if (type === 2 && !this.hasCourseInfo) {
uni.showToast({
title: '暂无课程信息',
icon: 'none'
});
return;
}
this.switch_tags_type = type
// 当切换到课程信息时,获取课程数据
// 当切换到课程信息时,刷新课程数据
if (type === 2) {
await this.getCourseInfo();
}
// 当切换到通话记录时,刷新通话记录数据
if (type === 3) {
await this.getListCallUp();
console.log('刷新通话记录数据,当前记录数:', this.listCallUp.length);
}
},
getSelect(type){
this.select_type = type
@ -753,37 +752,40 @@
try {
if (!this.clientInfo.resource_id) {
console.error('getCourseInfo - resource_id为空,无法获取课程信息');
this.courseInfo = [];
return false;
}
// 使用新的学生课程信息接口
// 使用学生课程信息接口
const params = {
resource_id: this.clientInfo.resource_id,
member_id: this.clientInfo.customerResource.member_id || ''
};
// 调用新的API获取课程信息
console.log('getCourseInfo - 请求参数:', params);
try {
const res = await apiRoute.getStudentCourseInfo(params);
if (res.code === 1 && res.data) {
this.courseInfo = res.data;
console.log('getCourseInfo - API响应:', res);
if (res.code === 1) {
// 格式化课程数据
this.courseInfo = this.formatCourseData(res.data || []);
console.log('getCourseInfo - 课程信息获取成功:', this.courseInfo);
return true;
} else {
console.warn('API返回错误:', res.msg);
throw new Error(res.msg);
this.courseInfo = [];
return false;
}
} catch (apiError) {
console.warn('使用API获取课程信息失败,使用模拟数据:', apiError);
// 如果API调用失败,使用模拟数据进行演示
this.courseInfo = this.getMockCourseData();
console.log('getCourseInfo - 使用模拟课程数据');
return true;
console.warn('获取课程信息API调用失败:', apiError);
this.courseInfo = [];
return false;
}
} catch (error) {
console.error('getCourseInfo - 获取课程信息异常:', error);
// 降级到模拟数据
this.courseInfo = this.getMockCourseData();
this.courseInfo = [];
return false;
}
},
@ -811,41 +813,6 @@
}));
},
// 获取模拟课程数据(用于演示)
getMockCourseData() {
return [
{
id: 1,
course_name: '篮球基础课程',
total_count: 20,
used_count: 8,
leave_count: 2,
expiry_date: '2024-12-31',
status: 'active',
main_coach_id: 1,
main_coach_name: '张教练',
education_id: 2,
education_name: '李教务',
assistant_ids: '3,4',
assistant_names: '王助教, 赵助教'
},
{
id: 2,
course_name: '足球进阶训练',
total_count: 15,
used_count: 15,
leave_count: 1,
expiry_date: '2024-10-31',
status: 'completed',
main_coach_id: 5,
main_coach_name: '陈教练',
education_id: 2,
education_name: '李教务',
assistant_ids: '6',
assistant_names: '孙助教'
}
];
},
// 获取人员列表(教练、教务、助教)
async getPersonnelList() {
@ -1058,17 +1025,33 @@
},
// 安全访问对象属性的方法
// 安全访问对象属性的方法,优化性能
safeGet(obj, path, defaultValue = '') {
if (!obj) return defaultValue;
const keys = path.split('.');
// 使用缓存来提高性能
if (!this._pathCache) this._pathCache = {};
// 使用路径作为缓存键
const cacheKey = path;
// 如果这个路径没有缓存过分割结果,则计算并缓存
if (!this._pathCache[cacheKey]) {
this._pathCache[cacheKey] = path.split('.');
}
const keys = this._pathCache[cacheKey];
let result = obj;
for (const key of keys) {
// 使用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;
},
}

190
uniapp/pages/market/clue/edit_clues.vue

@ -140,14 +140,22 @@
</view>
</view>
</fui-form-item>
<!-- 上课时间 -->
<fui-form-item label="上课时间" labelSize='26' prop="optional_class_time" background='#434544' labelColor='#fff' :bottomBorder='false'>
<!-- 可选上课时间 -->
<fui-form-item label="可选上课时间" labelSize='26' prop="optional_class_time" background='#434544' labelColor='#fff' :bottomBorder='false'>
<view class="input-title" style="margin-right:14rpx;">
<view class="input-title" style="margin-right:14rpx;" @click="openDate('optional_class_time')">
{{ formData.optional_class_time ? formData.optional_class_time : '点击选择' }}
</view>
</view>
</fui-form-item>
<!-- 承诺到访时间 -->
<fui-form-item label="承诺到访时间" labelSize='26' prop="promised_visit_time" background='#434544' labelColor='#fff' :bottomBorder='false'>
<view class="input-title" style="margin-right:14rpx;">
<view class="input-title" style="margin-right:14rpx;" @click="openDate('promised_visit_time')">
{{ formData.promised_visit_time ? formData.promised_visit_time : '点击选择' }}
</view>
</view>
</fui-form-item>
<!-- 距离 -->
<fui-form-item label="距离" labelSize='26' prop="distance" background='#434544' labelColor='#fff' :bottomBorder='false'>
<view class="input-title" style="margin-right:14rpx;">
@ -577,20 +585,8 @@
console.log('init - 开始加载字典数据');
// 先加载所有字典数据
const dictPromises = [
this.getDict('source_channel'), //获取字典-来源渠道
this.getDict('source'), //获取字典-来源
this.getDict('purchasing_power'), //获取字典-购买力
this.getDict('initial_intent'), //获取字典-客户初步意向度
this.getDict('cognitive_idea'), //获取字典-认知理念
this.getDict('status'), //获取字典-客户状态
this.getDict('decision_maker'), //获取字典-决策人
this.getDict('distance'), //获取字典-距离
];
await Promise.all(dictPromises);
// 批量加载所有字典数据
await this.getBatchDictData();
console.log('init - 字典数据加载完成');
// 加载校区列表
@ -614,6 +610,139 @@
}
},
// 批量获取字典数据
async getBatchDictData() {
try {
// 定义需要的字典keys和对应的本地键名
const dictMapping = {
'SourceChannel': 'source_channel',
'source': 'source',
'customer_purchasing_power': 'purchasing_power',
'preliminarycustomerintention': 'initial_intent',
'cognitive_concept': 'cognitive_idea',
'kh_status': 'status',
'decision_maker': 'decision_maker',
'distance': 'distance'
};
// 获取缓存的字典数据
if (!window._dictCache) {
window._dictCache = {};
}
// 处理优先级,先请求用户可能立即需要的字典
const criticalDicts = ['source', 'source_channel'];
const regularDicts = Object.keys(dictMapping).filter(key => !criticalDicts.includes(dictMapping[key]));
// 先获取关键字典
for (const dictKey of criticalDicts) {
const key = Object.keys(dictMapping).find(k => dictMapping[k] === dictKey);
if (key) {
await this.loadDictData(key, dictMapping[key]);
}
}
// 异步获取其他字典
setTimeout(() => {
regularDicts.forEach(async (key) => {
const localKey = dictMapping[key];
if (!window._dictCache[key]) {
await this.loadDictData(key, localKey);
}
});
}, 100);
console.log('优化的批量字典数据加载初始化完成');
} catch (error) {
console.error('批量获取字典数据失败:', error);
// 如果批量获取失败,回退到单个获取
await this.fallbackGetDict();
}
},
// 加载单个字典数据
async loadDictData(key, localKey) {
try {
// 检查缓存
if (window._dictCache[key]) {
// 使用缓存数据
const dictData = window._dictCache[key];
this.processDictData(localKey, dictData);
return dictData;
}
// 加载字典数据
const dictData = await this.$util.getDict(key);
// 缓存数据
if (Array.isArray(dictData) && dictData.length > 0) {
window._dictCache[key] = dictData;
this.processDictData(localKey, dictData);
}
return dictData;
} catch (error) {
console.error(`加载字典 ${key} 失败:`, error);
return [];
}
},
// 处理字典数据
processDictData(localKey, dictData) {
if (!Array.isArray(dictData) || dictData.length === 0) return;
let formattedOptions = dictData.map(item => ({
text: item.name || '',
value: item.value || ''
}));
// 特殊处理来源渠道,添加线下选项
if (localKey === 'source_channel') {
formattedOptions.unshift({
text: '线下',
value: '0'
});
}
// 确保 picker_config 存在
if (!this.picker_config[localKey]) {
this.picker_config[localKey] = { options: [], text: '点击选择' };
}
this.picker_config[localKey].options = formattedOptions;
},
// 回退方案:单个获取字典
async fallbackGetDict() {
console.log('使用回退方案获取字典数据');
try {
// 优先获取关键字典
await Promise.all([
this.getDict('source_channel'),
this.getDict('source')
]);
// 延迟加载其他字典
setTimeout(async () => {
try {
await Promise.all([
this.getDict('purchasing_power'),
this.getDict('initial_intent'),
this.getDict('cognitive_idea'),
this.getDict('status'),
this.getDict('decision_maker'),
this.getDict('distance')
]);
} catch (error) {
console.error('回退方案第二阶段失败:', error);
}
}, 100);
} catch (error) {
console.error('回退方案也失败了:', error);
}
},
async get_campus_list(){
let res = await apiRoute.common_getCampusesList({})
@ -693,7 +822,7 @@
customer_type: customerResource.customer_type || '', // 客户分类
//六要素信息
purchasing_power: sixSpeed.purchase_power || '', //购买力
purchasing_power: sixSpeed.purchasing_power || '', //购买力
cognitive_idea: sixSpeed.concept_awareness || '', //认知理念
communication: sixSpeed.communication || '', //沟通备注
staff_id: sixSpeed.staff_id || '', //人员ID
@ -763,7 +892,7 @@
this.setPickerTextByValue('customer_type', this.formData.customer_type, customerResource.customer_type_name);
// 六要素相关
this.setPickerTextByValue('purchasing_power', this.formData.purchasing_power, sixSpeed.purchase_power_name);
this.setPickerTextByValue('purchasing_power', this.formData.purchasing_power, sixSpeed.purchasing_power_name);
this.setPickerTextByValue('cognitive_idea', this.formData.cognitive_idea, sixSpeed.concept_awareness_name);
this.setPickerTextByValue('distance', this.formData.distance, sixSpeed.distance_name);
// 不再需要设置call_intent的选择器文本,因为已改为单选组件
@ -776,12 +905,26 @@
// 根据值设置选择器文本
setPickerTextByValue(pickerName, value, defaultText) {
// 使用空值缓存加速处理
if (!value) {
this.picker_config[pickerName] = this.picker_config[pickerName] || {};
this.picker_config[pickerName].text = '点击选择';
return;
}
// 创建映射缓存,避免重复查找
if (!this._valueTextMapping) {
this._valueTextMapping = {};
}
// 检查缓存
const cacheKey = `${pickerName}_${value}`;
if (this._valueTextMapping[cacheKey]) {
this.picker_config[pickerName] = this.picker_config[pickerName] || {};
this.picker_config[pickerName].text = this._valueTextMapping[cacheKey];
return;
}
// 确保 picker_config[pickerName] 存在
if (!this.picker_config[pickerName]) {
this.picker_config[pickerName] = { options: [] };
@ -791,14 +934,19 @@
const options = this.picker_config[pickerName].options || [];
const option = options.find(opt => String(opt.value) === String(value));
let textValue;
if (option) {
this.picker_config[pickerName].text = option.text;
textValue = option.text;
} else if (defaultText) {
// 如果找不到匹配的选项但有默认文本,则使用默认文本
this.picker_config[pickerName].text = defaultText;
textValue = defaultText;
} else {
this.picker_config[pickerName].text = '点击选择';
textValue = '点击选择';
}
// 保存到缓存
this._valueTextMapping[cacheKey] = textValue;
this.picker_config[pickerName].text = textValue;
},
// 获取资源详情(缓存数据,避免重复请求)

Loading…
Cancel
Save