Browse Source

修改 bug

master
王泽彦 8 months ago
parent
commit
0b41171bf7
  1. 9
      admin/src/app/api/customer_resources.ts
  2. 6
      admin/src/app/views/customer_resources/components/UserProfile.vue
  3. 242
      admin/src/app/views/customer_resources/components/gift_records.vue
  4. 11
      niucloud/app/adminapi/controller/customer_resources/CustomerResources.php
  5. 2
      niucloud/app/adminapi/route/customer_resources.php
  6. 42
      niucloud/app/api/controller/apiController/CustomerResources.php
  7. 6
      niucloud/app/api/route/route.php
  8. 22
      niucloud/app/service/admin/customer_resources/CustomerResourcesService.php
  9. 185
      niucloud/app/service/api/apiService/CustomerResourcesService.php
  10. 12
      uniapp/api/apiRoute.js
  11. 155
      uniapp/components/gift-record-card/gift-record-card.vue
  12. 169
      uniapp/components/student-edit-popup/student-edit-popup.less
  13. 322
      uniapp/components/student-edit-popup/student-edit-popup.vue
  14. 89
      uniapp/pages-market/clue/clue_info.vue

9
admin/src/app/api/customer_resources.ts

@ -93,4 +93,13 @@ export function toLeadInto(params: Record<string, any>) {
return request.post(`customer_resources/to_lead_into`, params, { showErrorMessage: true, showSuccessMessage: true }) return request.post(`customer_resources/to_lead_into`, params, { showErrorMessage: true, showSuccessMessage: true })
} }
/**
*
* @param params
* @returns
*/
export function getGiftRecordList(params: Record<string, any>) {
return request.get('customer_resources/gift_records', { params })
}

6
admin/src/app/views/customer_resources/components/UserProfile.vue

@ -29,6 +29,7 @@
<el-tab-pane label="学生情况" name="student" /> <el-tab-pane label="学生情况" name="student" />
<el-tab-pane label="订单列表" name="orders" /> <el-tab-pane label="订单列表" name="orders" />
<el-tab-pane label="沟通记录列表" name="communication_records" /> <el-tab-pane label="沟通记录列表" name="communication_records" />
<el-tab-pane label="赠品记录" name="gift_records" />
</el-tabs> </el-tabs>
<!-- 六要素信息卡片 --> <!-- 六要素信息卡片 -->
@ -85,6 +86,10 @@
<CommunicationRecords :customer_resource_id="user.id"/> <CommunicationRecords :customer_resource_id="user.id"/>
</el-card> </el-card>
<el-card v-if="activeTab === 'gift_records'">
<GiftRecords :customer_resource_id="user.id"/>
</el-card>
</el-dialog> </el-dialog>
</template> </template>
@ -96,6 +101,7 @@
import Student from '@/app/views/customer_resources/components/student_courses.vue' import Student from '@/app/views/customer_resources/components/student_courses.vue'
import Orders from '@/app/views/customer_resources/components/order_table.vue' import Orders from '@/app/views/customer_resources/components/order_table.vue'
import CommunicationRecords from '@/app/views/communication_records/communication_records.vue' import CommunicationRecords from '@/app/views/communication_records/communication_records.vue'
import GiftRecords from '@/app/views/customer_resources/components/gift_records.vue'
let showDialog = ref(false) let showDialog = ref(false)

242
admin/src/app/views/customer_resources/components/gift_records.vue

@ -0,0 +1,242 @@
<template>
<div class="gift-records">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold">赠品记录</h3>
<el-button type="primary" @click="refreshRecords">刷新</el-button>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="text-center py-8">
<el-icon class="is-loading mr-2"><Loading /></el-icon>
加载中...
</div>
<!-- 空状态 -->
<div v-else-if="!giftRecords || giftRecords.length === 0" class="text-center py-8 text-gray-500">
<el-icon class="text-4xl mb-2"><Box /></el-icon>
<p>暂无赠品记录</p>
</div>
<!-- 赠品记录列表 -->
<div v-else class="gift-records-list">
<el-card
v-for="record in giftRecords"
:key="record.id"
class="mb-4 gift-record-card"
shadow="hover"
>
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="flex items-center mb-2">
<h4 class="font-bold text-lg mr-3">{{ record.gift_name }}</h4>
<el-tag
:type="getStatusTagType(record.gift_status)"
size="small"
>
{{ record.gift_status_text }}
</el-tag>
</div>
<div class="grid grid-cols-2 gap-4 text-sm text-gray-600">
<div>
<span class="font-medium">赠品类型</span>
<el-tag size="small" type="info">{{ record.gift_type_text }}</el-tag>
</div>
<div>
<span class="font-medium">赠送人</span>
{{ record.giver_name }}
<span v-if="record.giver_phone" class="text-gray-500">{{ record.giver_phone }}</span>
</div>
<div>
<span class="font-medium">发放时间</span>
{{ record.gift_time_formatted }}
</div>
<div v-if="record.use_time_formatted">
<span class="font-medium">使用时间</span>
{{ record.use_time_formatted }}
</div>
</div>
</div>
<div class="text-right text-sm text-gray-500">
创建时间{{ record.create_time }}
</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, defineProps } from 'vue'
import { Loading, Box } from '@element-plus/icons-vue'
import { getGiftRecordList } from '@/app/api/customer_resources'
const props = defineProps({
customer_resource_id: {
type: [String, Number],
required: true
}
})
const loading = ref(false)
const giftRecords = ref([])
//
const fetchGiftRecords = async () => {
if (!props.customer_resource_id) return
loading.value = true
try {
const response = await getGiftRecordList({
resource_id: props.customer_resource_id
})
if (response.code === 1) {
giftRecords.value = response.data || []
} else {
console.error('获取赠品记录失败:', response.msg)
giftRecords.value = []
}
} catch (error) {
console.error('获取赠品记录异常:', error)
giftRecords.value = []
} finally {
loading.value = false
}
}
//
const refreshRecords = () => {
fetchGiftRecords()
}
//
const getStatusTagType = (status) => {
switch (status) {
case 0: return 'info' //
case 1: return 'success' // 使
case 2: return 'warning' // 使
default: return 'info'
}
}
//
onMounted(() => {
fetchGiftRecords()
})
// customer_resource_id
import { watch } from 'vue'
watch(() => props.customer_resource_id, (newId) => {
if (newId) {
fetchGiftRecords()
}
})
</script>
<style scoped>
.gift-records {
padding: 16px;
}
.gift-record-card {
transition: all 0.3s ease;
}
.gift-record-card:hover {
transform: translateY(-2px);
}
.grid {
display: grid;
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.gap-4 {
gap: 1rem;
}
.text-gray-500 {
color: #6b7280;
}
.text-gray-600 {
color: #4b5563;
}
.font-medium {
font-weight: 500;
}
.font-bold {
font-weight: 700;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.mr-3 {
margin-right: 0.75rem;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.flex {
display: flex;
}
.flex-1 {
flex: 1 1 0%;
}
.items-center {
align-items: center;
}
.items-start {
align-items: flex-start;
}
.justify-between {
justify-content: space-between;
}
</style>

11
niucloud/app/adminapi/controller/customer_resources/CustomerResources.php

@ -230,6 +230,17 @@ class CustomerResources extends BaseAdminController
return (new CustomerResourcesService())->to_lead_into($data); return (new CustomerResourcesService())->to_lead_into($data);
} }
/**
* 获取客户赠品记录列表
* @return \think\Response
*/
public function gift_records()
{
$data = $this->request->params([
["resource_id", ""]
]);
return success((new CustomerResourcesService())->gift_records($data));
}
} }

2
niucloud/app/adminapi/route/customer_resources.php

@ -54,6 +54,8 @@ Route::group('customer_resources', function () {
Route::get('order_table','customer_resources.CustomerResources/order_table'); Route::get('order_table','customer_resources.CustomerResources/order_table');
Route::post('to_lead_into', 'customer_resources.CustomerResources/to_lead_into'); Route::post('to_lead_into', 'customer_resources.CustomerResources/to_lead_into');
Route::get('gift_records', 'customer_resources.CustomerResources/gift_records');
})->middleware([ })->middleware([
AdminCheckToken::class, AdminCheckToken::class,
AdminCheckRole::class, AdminCheckRole::class,

42
niucloud/app/api/controller/apiController/CustomerResources.php

@ -255,4 +255,46 @@ class CustomerResources extends BaseApiService
return success([]); return success([]);
} }
//获取客户赠品记录列表
public function getGiftRecordList(Request $request)
{
$resource_id = $request->param('resource_id', '');
if (empty($resource_id)) {
return fail('缺少客户资源ID');
}
$res = (new CustomerResourcesService())->getGiftRecordList(['resource_id' => $resource_id]);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
}
//获取学生标签信息
public function getStudentLabel(Request $request)
{
$id = $request->param('id', '');
if (empty($id)) {
return fail('缺少标签ID');
}
$res = (new CustomerResourcesService())->getStudentLabel(['label_id' => $id]);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
}
//获取所有学生标签列表
public function getAllStudentLabels(Request $request)
{
$res = (new CustomerResourcesService())->getAllStudentLabels();
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
}
} }

6
niucloud/app/api/route/route.php

@ -239,6 +239,12 @@ Route::group(function () {
Route::get('customerResources/getEditLogList', 'apiController.CustomerResources/getEditLogList'); Route::get('customerResources/getEditLogList', 'apiController.CustomerResources/getEditLogList');
//客户资源-获取全部客户资源列表 //客户资源-获取全部客户资源列表
Route::get('customerResources/getAll', 'apiController.CustomerResources/getAll'); Route::get('customerResources/getAll', 'apiController.CustomerResources/getAll');
//客户资源-获取赠品记录列表
Route::get('customerResources/getGiftRecordList', 'apiController.CustomerResources/getGiftRecordList');
//客户资源-获取学生标签信息
Route::get('customerResources/getStudentLabel', 'apiController.CustomerResources/getStudentLabel');
//客户资源-获取所有学生标签列表
Route::get('customerResources/getAllStudentLabels', 'apiController.CustomerResources/getAllStudentLabels');
//资源共享-列表 //资源共享-列表

22
niucloud/app/service/admin/customer_resources/CustomerResourcesService.php

@ -675,4 +675,26 @@ class CustomerResourcesService extends BaseAdminService
} }
/**
* 获取客户赠品记录列表
* @param array $data
* @return array
*/
public function gift_records(array $data = [])
{
if (empty($data['resource_id'])) {
return [];
}
// 使用API服务层的方法
$api_service = new \app\service\api\apiService\CustomerResourcesService();
$result = $api_service->getGiftRecordList(['resource_id' => $data['resource_id']]);
if ($result['code']) {
return $result['data'];
} else {
return [];
}
}
} }

185
niucloud/app/service/api/apiService/CustomerResourcesService.php

@ -763,4 +763,189 @@ class CustomerResourcesService extends BaseApiService
return ['code' => false, 'msg' => '更新失败:' . $e->getMessage()]; return ['code' => false, 'msg' => '更新失败:' . $e->getMessage()];
} }
} }
/**
* 获取客户赠品记录列表
* @param array $where 查询条件
* @return array
*/
public function getGiftRecordList(array $where)
{
try {
$resource_id = $where['resource_id'] ?? 0;
if (empty($resource_id)) {
return ['code' => false, 'msg' => '缺少客户资源ID'];
}
// 查询赠品记录,同时关联查询赠送人信息
$gift_records = Db::table('shcool_resources_gift')
->alias('g')
->leftJoin('customer_resources cr', 'g.giver_id = cr.id')
->where('g.resource_id', $resource_id)
->where('g.delete_time', 0)
->field([
'g.*',
'cr.name as giver_name',
'cr.phone_number as giver_phone'
])
->order('g.create_time', 'desc')
->select()
->toArray();
// 处理数据格式
$formatted_records = [];
foreach ($gift_records as $record) {
$formatted_records[] = [
'id' => $record['id'],
'gift_name' => $record['gift_name'],
'gift_type' => $record['gift_type'],
'gift_type_text' => $this->getGiftTypeText($record['gift_type']),
'gift_time' => $record['gift_time'],
'gift_time_formatted' => date('Y-m-d H:i:s', $record['gift_time']),
'giver_id' => $record['giver_id'],
'giver_name' => $record['giver_name'] ?: '系统赠送',
'giver_phone' => $record['giver_phone'] ?: '',
'order_id' => $record['order_id'],
'gift_status' => $record['gift_status'],
'gift_status_text' => $this->getGiftStatusText($record['gift_status']),
'use_time' => $record['use_time'],
'use_time_formatted' => $record['use_time'] ? date('Y-m-d H:i:s', $record['use_time']) : '',
'create_time' => date('Y-m-d H:i:s', $record['create_time'])
];
}
return [
'code' => true,
'msg' => '获取成功',
'data' => $formatted_records
];
} catch (\Exception $e) {
Log::error('获取赠品记录失败:' . $e->getMessage());
return [
'code' => false,
'msg' => '获取赠品记录失败:' . $e->getMessage()
];
}
}
/**
* 获取赠品类型文本
* @param string $gift_type
* @return string
*/
private function getGiftTypeText($gift_type)
{
$type_map = [
'referral_reward' => '转介绍奖励',
'sign_reward' => '签到奖励',
'course_reward' => '课程奖励',
'activity_reward' => '活动奖励',
'other' => '其他'
];
return $type_map[$gift_type] ?? $gift_type;
}
/**
* 获取赠品状态文本
* @param int $gift_status
* @return string
*/
private function getGiftStatusText($gift_status)
{
$status_map = [
0 => '已失效',
1 => '可使用',
2 => '已使用'
];
return $status_map[$gift_status] ?? '未知状态';
}
/**
* 获取学生标签信息
* @param array $where
* @return array
*/
public function getStudentLabel($where)
{
try {
$label_id = $where['label_id'] ?? '';
if (empty($label_id)) {
return [
'code' => false,
'msg' => '标签ID不能为空'
];
}
// 查询学生标签信息
$label_info = Db::table('school_student_label')
->where('label_id', $label_id)
->field('label_id, label_name, memo')
->find();
if (!$label_info) {
return [
'code' => false,
'msg' => '标签不存在'
];
}
return [
'code' => true,
'msg' => '获取成功',
'data' => $label_info
];
} catch (\Exception $e) {
Log::error('获取学生标签失败:' . $e->getMessage());
return [
'code' => false,
'msg' => '获取学生标签失败:' . $e->getMessage()
];
}
}
/**
* 获取所有学生标签列表
* @return array
*/
public function getAllStudentLabels()
{
try {
// 查询所有学生标签信息
$label_list = Db::table('school_student_label')
->field('label_id, label_name, memo, sort')
->order('sort asc, label_id asc')
->select();
// 确保返回数组格式
$result = [];
if ($label_list) {
foreach ($label_list as $label) {
$result[] = [
'label_id' => (int)$label['label_id'],
'label_name' => $label['label_name'],
'memo' => $label['memo'],
'sort' => (int)$label['sort']
];
}
}
return [
'code' => true,
'msg' => '获取成功',
'data' => $result
];
} catch (\Exception $e) {
Log::error('获取学生标签列表失败:' . $e->getMessage());
return [
'code' => false,
'msg' => '获取学生标签列表失败:' . $e->getMessage()
];
}
}
} }

12
uniapp/api/apiRoute.js

@ -459,6 +459,18 @@ export default {
async xs_customerResourcesGetEditLogList(data = {}) { async xs_customerResourcesGetEditLogList(data = {}) {
return await http.get('/customerResources/getEditLogList', data); return await http.get('/customerResources/getEditLogList', data);
}, },
//销售端-客户资源-获取赠品记录列表
async xs_customerResourcesGetGiftRecordList(data = {}) {
return await http.get('/customerResources/getGiftRecordList', data);
},
//销售端-客户资源-获取学生标签信息
async getStudentLabel(data = {}) {
return await http.get('/customerResources/getStudentLabel', data);
},
//销售端-客户资源-获取所有学生标签列表
async getAllStudentLabels(data = {}) {
return await http.get('/customerResources/getAllStudentLabels', data);
},
//销售端-资源共享-列表 //销售端-资源共享-列表
async xs_resourceSharingIndex(data = {}) { async xs_resourceSharingIndex(data = {}) {
return await http.get('/resourceSharing/index', data); return await http.get('/resourceSharing/index', data);

155
uniapp/components/gift-record-card/gift-record-card.vue

@ -0,0 +1,155 @@
<!--赠品记录卡片组件-->
<template>
<view class="gift-record-card">
<view class="gift-header">
<view class="gift-name">{{ record.gift_name }}</view>
<view class="gift-status" :class="getStatusClass(record.gift_status)">
{{ record.gift_status_text }}
</view>
</view>
<view class="gift-info">
<view class="info-row">
<text class="info-label">赠品类型</text>
<text class="info-value">{{ record.gift_type_text }}</text>
</view>
<view class="info-row">
<text class="info-label">获得时间</text>
<text class="info-value">{{ formatTime(record.gift_time_formatted) }}</text>
</view>
<view class="info-row" v-if="record.giver_name">
<text class="info-label">赠送人</text>
<text class="info-value">{{ record.giver_name }}</text>
</view>
<view class="info-row" v-if="record.use_time_formatted">
<text class="info-label">使用时间</text>
<text class="info-value">{{ formatTime(record.use_time_formatted) }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'GiftRecordCard',
props: {
record: {
type: Object,
required: true
}
},
methods: {
formatTime(timeStr) {
if (!timeStr) return '未知时间'
try {
// 使使
if (this.$util && this.$util.formatToDateTime) {
return this.$util.formatToDateTime(timeStr, 'Y-m-d H:i')
}
//
if (timeStr.includes('-')) {
const date = new Date(timeStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
return timeStr
} catch (error) {
console.error('时间格式化失败:', error)
return timeStr
}
},
getStatusClass(status) {
const statusMap = {
0: 'status-expired', //
1: 'status-available', // 使
2: 'status-used' // 使
}
return statusMap[status] || 'status-unknown'
}
}
}
</script>
<style lang="less" scoped>
.gift-record-card {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
}
.gift-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
.gift-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.gift-status {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
font-weight: 500;
&.status-available {
background: #E8F5E8;
color: #52C41A;
}
&.status-used {
background: #F0F0F0;
color: #8C8C8C;
}
&.status-expired {
background: #FFF2F0;
color: #FF4D4F;
}
&.status-unknown {
background: #F6F6F6;
color: #999;
}
}
}
.gift-info {
.info-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 28rpx;
color: #999;
width: 160rpx;
flex-shrink: 0;
}
.info-value {
font-size: 28rpx;
color: #333;
flex: 1;
}
}
}
</style>

169
uniapp/components/student-edit-popup/student-edit-popup.less

@ -226,3 +226,172 @@
} }
} }
} }
// 标签选择器样式
.tag-selector {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 88rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 12rpx;
border: 1rpx solid #e9ecef;
cursor: pointer;
&:active {
background: #e9ecef;
}
.selected-tags {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
flex: 1;
.tag-item {
padding: 8rpx 16rpx;
background: #29d3b4;
color: white;
border-radius: 20rpx;
font-size: 24rpx;
white-space: nowrap;
}
}
.placeholder {
color: #999;
font-size: 28rpx;
}
.picker-arrow {
color: #999;
font-size: 32rpx;
transform: rotate(90deg);
margin-left: 20rpx;
}
}
// 标签选择弹窗样式
.tag-popup {
background: white;
border-radius: 24rpx 24rpx 0 0;
max-height: 80vh;
.tag-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 40rpx;
border-bottom: 1rpx solid #f0f0f0;
.tag-popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.tag-popup-close {
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:active {
background: #f0f0f0;
border-radius: 50%;
}
}
}
.tag-list {
max-height: 50vh;
overflow-y: auto;
padding: 20rpx 40rpx;
.tag-option {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f5f5;
cursor: pointer;
&:last-child {
border-bottom: none;
}
&:active {
background: #f8f9fa;
}
&.selected {
.tag-checkbox {
background: #29d3b4;
border-color: #29d3b4;
color: white;
}
}
.tag-checkbox {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #ddd;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
font-size: 24rpx;
color: transparent;
transition: all 0.2s;
}
.tag-name {
font-size: 28rpx;
color: #333;
flex: 1;
}
}
}
.tag-popup-footer {
display: flex;
padding: 30rpx 40rpx;
gap: 30rpx;
border-top: 1rpx solid #f0f0f0;
.tag-popup-btn {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12rpx;
font-size: 28rpx;
cursor: pointer;
&.cancel {
background: #f8f9fa;
color: #666;
border: 1rpx solid #e9ecef;
&:active {
background: #e9ecef;
}
}
&.confirm {
background: #29d3b4;
color: white;
&:active {
background: #26c4a6;
}
}
}
}
}

322
uniapp/components/student-edit-popup/student-edit-popup.vue

@ -1,116 +1,156 @@
<!--学生信息编辑弹窗组件--> <!--学生信息编辑弹窗组件-->
<template> <template>
<uni-popup ref="popup" type="center"> <view>
<view class="popup-container student-edit-popup"> <uni-popup ref="popup" type="center">
<view class="popup-header"> <view class="popup-container student-edit-popup">
<view class="popup-title">{{ isEditing ? '编辑学生信息' : '添加学生信息' }}</view> <view class="popup-header">
<view class="popup-close" @click="close"></view> <view class="popup-title">{{ isEditing ? '编辑学生信息' : '添加学生信息' }}</view>
</view> <view class="popup-close" @click="close"></view>
<scroll-view class="student-form-container" scroll-y="true"> </view>
<view class="form-section"> <scroll-view class="student-form-container" scroll-y="true">
<!-- 基本信息 --> <view class="form-section">
<view class="form-group"> <!-- 基本信息 -->
<view class="form-group-title">基本信息</view> <view class="form-group">
<view class="form-group-title">基本信息</view>
<view class="form-item">
<view class="form-label required">姓名</view> <view class="form-item">
<view class="form-input"> <view class="form-label required">姓名</view>
<input type="text" v-model="studentData.name" placeholder="请输入学生姓名" maxlength="20" /> <view class="form-input">
<input type="text" v-model="studentData.name" placeholder="请输入学生姓名" maxlength="20" />
</view>
</view> </view>
</view>
<view class="form-item"> <view class="form-item">
<view class="form-label required">性别</view> <view class="form-label required">性别</view>
<view class="form-input"> <view class="form-input">
<picker :range="genderOptions" range-key="label" @change="onGenderChange"> <picker :range="genderOptions" range-key="label" @change="onGenderChange">
<view class="picker-display"> <view class="picker-display">
{{ $util.formatGender(studentData.gender) || '请选择性别' }} {{ $util.formatGender(studentData.gender) || '请选择性别' }}
<text class="picker-arrow"></text> <text class="picker-arrow"></text>
</view> </view>
</picker> </picker>
</view>
</view> </view>
</view>
<view class="form-item"> <view class="form-item">
<view class="form-label required">出生日期</view> <view class="form-label required">出生日期</view>
<view class="form-input"> <view class="form-input">
<picker mode="date" :value="studentData.birthday" @change="onBirthdayChange"> <picker mode="date" :value="studentData.birthday" @change="onBirthdayChange">
<view class="picker-display"> <view class="picker-display">
{{ studentData.birthday || '请选择出生日期' }} {{ studentData.birthday || '请选择出生日期' }}
<text class="picker-arrow"></text> <text class="picker-arrow"></text>
</view> </view>
</picker> </picker>
</view>
</view> </view>
</view>
<view class="form-item"> <view class="form-item">
<view class="form-label">联系电话</view> <view class="form-label">联系电话</view>
<view class="form-input"> <view class="form-input">
<input type="number" v-model="studentData.contact_phone" placeholder="请输入联系电话" maxlength="20" /> <input type="number" v-model="studentData.contact_phone" placeholder="请输入联系电话" maxlength="20" />
</view>
</view> </view>
</view>
<view class="form-item"> <view class="form-item">
<view class="form-label">紧急联系人</view> <view class="form-label">紧急联系人</view>
<view class="form-input"> <view class="form-input">
<input type="text" v-model="studentData.emergency_contact" placeholder="请输入紧急联系人" maxlength="255" /> <input type="text" v-model="studentData.emergency_contact" placeholder="请输入紧急联系人" maxlength="255" />
</view>
</view> </view>
</view> </view>
</view>
<!-- 学员信息 --> <!-- 学员信息 -->
<view class="form-group"> <view class="form-group">
<view class="form-group-title">学员信息</view> <view class="form-group-title">学员信息</view>
<view class="form-item"> <view class="form-item">
<view class="form-label">会员标签</view> <view class="form-label">学员标签</view>
<view class="form-input"> <view class="form-input">
<input type="text" v-model="studentData.member_label" placeholder="请输入会员标签" maxlength="255" /> <view class="tag-selector" @click="showTagSelector">
<view class="selected-tags" v-if="selectedTagNames.length > 0">
<view class="tag-item" v-for="tagName in selectedTagNames" :key="tagName">
{{ tagName }}
</view>
</view>
<view class="placeholder" v-else>请选择学员标签</view>
<text class="picker-arrow"></text>
</view>
</view>
</view> </view>
</view>
<view class="form-item"> <!-- <view class="form-item">-->
<view class="form-label">体验课次数</view> <!-- <view class="form-label">体验课次数</view>-->
<view class="form-input"> <!-- <view class="form-input">-->
<input type="number" v-model="studentData.trial_class_count" placeholder="体验课次数" /> <!-- <input type="number" v-model="studentData.trial_class_count" placeholder="体验课次数" />-->
<!-- </view>-->
<!-- </view>-->
<view class="form-item">
<view class="form-label">学员状态</view>
<view class="form-input">
<picker :range="statusOptions" range-key="label" @change="onStatusChange">
<view class="picker-display">
{{ studentData.status === 1 ? '有效' : '无效' }}
<text class="picker-arrow"></text>
</view>
</picker>
</view>
</view> </view>
</view> </view>
<view class="form-item"> <!-- 其他信息 -->
<view class="form-label">学员状态</view> <view class="form-group">
<view class="form-input"> <view class="form-group-title">其他信息</view>
<picker :range="statusOptions" range-key="label" @change="onStatusChange">
<view class="picker-display"> <view class="form-item">
{{ studentData.status === 1 ? '有效' : '无效' }} <view class="form-label">备注</view>
<text class="picker-arrow"></text> <view class="form-textarea">
</view> <textarea v-model="studentData.note" placeholder="请输入备注信息" maxlength="200"></textarea>
</picker> </view>
</view> </view>
</view> </view>
</view> </view>
</scroll-view>
<!-- 其他信息 --> <view class="popup-footer">
<view class="form-group"> <view class="popup-btn cancel-btn" @click="close">取消</view>
<view class="form-group-title">其他信息</view> <view class="popup-btn confirm-btn" @click="confirm">保存</view>
</view>
<view class="form-item"> </view>
<view class="form-label">备注</view> </uni-popup>
<view class="form-textarea">
<textarea v-model="studentData.note" placeholder="请输入备注信息" maxlength="200"></textarea> <!-- 标签选择器弹窗 -->
</view> <uni-popup ref="tagPopup" type="bottom">
<view class="tag-popup">
<view class="tag-popup-header">
<view class="tag-popup-title">选择学员标签</view>
<view class="tag-popup-close" @click="closeTagSelector"></view>
</view>
<view class="tag-list">
<view
class="tag-option"
v-for="tag in studentTagOptions"
:key="tag.label_id"
:class="{ 'selected': isTagSelected(tag.label_id) }"
@click="toggleTag(tag.label_id)"
>
<view class="tag-checkbox">
<text v-if="isTagSelected(tag.label_id)"></text>
</view> </view>
<view class="tag-name">{{ tag.label_name }}</view>
</view> </view>
</view> </view>
</scroll-view> <view class="tag-popup-footer">
<view class="popup-footer"> <view class="tag-popup-btn cancel" @click="closeTagSelector">取消</view>
<view class="popup-btn cancel-btn" @click="close">取消</view> <view class="tag-popup-btn confirm" @click="confirmTagSelection">确定</view>
<view class="popup-btn confirm-btn" @click="confirm">保存</view> </view>
</view> </view>
</view> </uni-popup>
</uni-popup> </view>
</template> </template>
<script> <script>
import apiRoute from '@/api/apiRoute.js'
export default { export default {
name: 'StudentEditPopup', name: 'StudentEditPopup',
props: { props: {
@ -155,7 +195,26 @@ export default {
statusOptions: [ statusOptions: [
{ label: '无效', value: 0 }, { label: '无效', value: 0 },
{ label: '有效', value: 1 } { label: '有效', value: 1 }
] ],
//
studentTagOptions: [], //
selectedTagIds: [], // ID
selectedTagNames: [] //
}
},
mounted() {
this.loadStudentTagOptions()
},
watch: {
//
studentTagOptions: {
handler(newOptions) {
if (newOptions.length > 0 && this.studentData.member_label) {
this.parseExistingTags()
}
},
immediate: true
} }
}, },
methods: { methods: {
@ -189,6 +248,8 @@ export default {
trial_class_count: student.trial_class_count, trial_class_count: student.trial_class_count,
actionsExpanded: student.actionsExpanded || false actionsExpanded: student.actionsExpanded || false
} }
//
this.parseExistingTags()
this.$refs.popup.open() this.$refs.popup.open()
}, },
@ -220,6 +281,9 @@ export default {
trial_class_count: 2, trial_class_count: 2,
actionsExpanded: false actionsExpanded: false
} }
//
this.selectedTagIds = []
this.selectedTagNames = []
}, },
// //
@ -314,6 +378,92 @@ export default {
isEditing: this.isEditing, isEditing: this.isEditing,
studentData: params studentData: params
}) })
},
//
async loadStudentTagOptions() {
try {
const res = await apiRoute.getAllStudentLabels()
if (res.code === 1) {
this.studentTagOptions = res.data || []
} else {
console.warn('获取学员标签失败:', res.msg)
// 使
this.studentTagOptions = [
{ label_id: 1, label_name: '标签一' },
{ label_id: 2, label_name: '标签二' },
{ label_id: 3, label_name: '意向用户' }
]
}
} catch (error) {
console.error('加载学员标签失败:', error)
// 使
this.studentTagOptions = [
{ label_id: 1, label_name: '优秀学员' },
{ label_id: 2, label_name: '积极参与' },
{ label_id: 3, label_name: '需要关注' }
]
}
},
//
showTagSelector() {
this.$refs.tagPopup.open()
},
//
closeTagSelector() {
this.$refs.tagPopup.close()
},
//
isTagSelected(tagId) {
return this.selectedTagIds.includes(tagId)
},
//
toggleTag(tagId) {
const index = this.selectedTagIds.indexOf(tagId)
if (index > -1) {
//
this.selectedTagIds.splice(index, 1)
} else {
//
this.selectedTagIds.push(tagId)
}
},
//
confirmTagSelection() {
//
this.selectedTagNames = this.selectedTagIds.map(tagId => {
const tag = this.studentTagOptions.find(t => t.label_id === tagId)
return tag ? tag.label_name : ''
}).filter(name => name)
// member_label ID
this.studentData.member_label = this.selectedTagIds.join(',')
this.closeTagSelector()
},
//
parseExistingTags() {
if (this.studentData.member_label) {
// ID
this.selectedTagIds = this.studentData.member_label.split(',')
.map(id => parseInt(id.trim()))
.filter(id => !isNaN(id))
//
this.selectedTagNames = this.selectedTagIds.map(tagId => {
const tag = this.studentTagOptions.find(t => t.label_id === tagId)
return tag ? tag.label_name : ''
}).filter(name => name)
} else {
this.selectedTagIds = []
this.selectedTagNames = []
}
} }
} }
} }

89
uniapp/pages-market/clue/clue_info.vue

@ -135,6 +135,19 @@
<text class="empty-text">暂无学习计划</text> <text class="empty-text">暂无学习计划</text>
</view> </view>
</view> </view>
<!-- 赠品记录标签内容 -->
<view class="gift-record-section" v-if="switch_tags_type == 8">
<view v-if="giftRecords.length === 0" class="empty-state">
<text class="empty-icon">🎁</text>
<text class="empty-text">暂无赠品记录</text>
</view>
<GiftRecordCard
v-for="record in giftRecords"
:key="record.id"
:record="record"
/>
</view>
</view> </view>
<!-- 底部弹窗组件 --> <!-- 底部弹窗组件 -->
@ -291,6 +304,7 @@ import StudentInfoCard from '@/components/student-info-card/student-info-card.vu
import TabSwitcher from '@/components/tab-switcher/tab-switcher.vue' import TabSwitcher from '@/components/tab-switcher/tab-switcher.vue'
import CallRecordCard from '@/components/call-record-card/call-record-card.vue' import CallRecordCard from '@/components/call-record-card/call-record-card.vue'
import FitnessRecordCard from '@/components/fitness-record-card/fitness-record-card.vue' import FitnessRecordCard from '@/components/fitness-record-card/fitness-record-card.vue'
import GiftRecordCard from '@/components/gift-record-card/gift-record-card.vue'
// //
import BottomPopup from '@/components/bottom-popup/index.vue' import BottomPopup from '@/components/bottom-popup/index.vue'
import StudyPlanCard from '@/components/study-plan-card/index.vue' import StudyPlanCard from '@/components/study-plan-card/index.vue'
@ -310,6 +324,7 @@ export default {
TabSwitcher, TabSwitcher,
CallRecordCard, CallRecordCard,
FitnessRecordCard, FitnessRecordCard,
GiftRecordCard,
BottomPopup, BottomPopup,
StudyPlanCard, StudyPlanCard,
CourseInfoCard, CourseInfoCard,
@ -332,6 +347,7 @@ export default {
listCallUp: [], listCallUp: [],
courseInfo: [], courseInfo: [],
fitnessRecords: [], fitnessRecords: [],
giftRecords: [],
studentList: [], studentList: [],
currentStudentIndex: 0, currentStudentIndex: 0,
@ -636,6 +652,7 @@ export default {
if (tabId === 7) this.$navigateToPage(`/pages-market/clue/edit_clues`, { if (tabId === 7) this.$navigateToPage(`/pages-market/clue/edit_clues`, {
resource_sharing_id: this.clientInfo.id resource_sharing_id: this.clientInfo.id
}) })
if (tabId === 8) await this.getGiftRecords()
}, },
handleStudentAction({ action, student }) { handleStudentAction({ action, student }) {
@ -905,17 +922,37 @@ export default {
async getStudentLabel(labelId) { async getStudentLabel(labelId) {
if (!labelId) return [] if (!labelId) return []
// TODO: student_label try {
const res = await apiRoute.getStudentLabel({ id: labelId }) // school_student_label
const res = await apiRoute.getStudentLabel({ id: labelId })
if (res.code === 1) {
return res.data ? [res.data.label_name] : []
}
// 使
console.warn('接口调用失败,使用模拟数据:', res.msg)
const labelMap = {
1: '优秀学员',
2: '积极参与',
3: '需要关注',
4: '进步明显',
5: '需要鼓励'
}
// return labelMap[labelId] ? [labelMap[labelId]] : []
const labelMap = { } catch (error) {
1: '优秀学员', console.error('获取学生标签失败:', error)
2: '积极参与', // 使
3: '需要关注' const labelMap = {
} 1: '优秀学员',
2: '积极参与',
3: '需要关注',
4: '进步明显',
5: '需要鼓励'
}
return labelMap[labelId] ? [labelMap[labelId]] : [] return labelMap[labelId] ? [labelMap[labelId]] : []
}
}, },
// //
@ -955,7 +992,18 @@ export default {
} }
}, },
openAddStudentDialog() { openAddStudentDialog() {
this.$refs.studentEditPopup.openAdd() //
this.$nextTick(() => {
if (this.$refs.studentEditPopup) {
this.$refs.studentEditPopup.openAdd()
} else {
console.error('studentEditPopup 组件引用不存在')
uni.showToast({
title: '组件加载中,请稍后再试',
icon: 'none'
})
}
})
}, },
@ -1439,6 +1487,27 @@ ${orderInfo.paid_at ? '支付时间:' + this.formatOrderTime(orderInfo.paid_at
} catch (e) { } catch (e) {
return timeStr return timeStr
} }
},
//
async getGiftRecords() {
if (!this.clientInfo?.resource_id) return
try {
const res = await apiRoute.xs_customerResourcesGetGiftRecordList({
resource_id: this.clientInfo.resource_id
})
if (res.code === 1) {
this.giftRecords = res.data || []
} else {
console.error('获取赠品记录失败:', res.msg)
this.giftRecords = []
}
} catch (error) {
console.error('获取赠品记录异常:', error)
this.giftRecords = []
}
} }
}, },
} }

Loading…
Cancel
Save