Browse Source

修改 bug

master
王泽彦 7 months ago
parent
commit
d4e6e89a87
  1. 597
      admin/src/app/views/course_schedule/components/student-detail-modal.vue
  2. 139
      niucloud/app/adminapi/controller/upload/CosProxy.php

597
admin/src/app/views/course_schedule/components/student-detail-modal.vue

@ -0,0 +1,597 @@
<template>
<el-dialog
v-model="visible"
title="学员详情"
width="800px"
class="student-detail-dialog"
:destroy-on-close="true"
>
<div v-loading="loading" class="student-detail-container">
<!-- 学员基本信息 -->
<el-card class="info-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">基本信息</span>
<el-button
v-if="!editMode"
type="primary"
size="small"
@click="enterEditMode"
>
编辑
</el-button>
<div v-else class="edit-actions">
<el-button size="small" @click="cancelEdit">取消</el-button>
<el-button
type="primary"
size="small"
:loading="saving"
@click="saveChanges"
>
保存
</el-button>
</div>
</div>
</template>
<el-form
ref="studentFormRef"
:model="formData"
:rules="formRules"
label-width="120px"
class="student-form"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input
v-model="formData.name"
:disabled="!editMode"
placeholder="请输入学员姓名"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="gender">
<el-select
v-model="formData.gender"
:disabled="!editMode"
placeholder="请选择性别"
style="width: 100%"
>
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年龄" prop="age">
<el-input-number
v-model="formData.age"
:disabled="!editMode"
:min="0"
:max="150"
:precision="0"
placeholder="年龄"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生日" prop="birthday">
<el-date-picker
v-model="formData.birthday"
:disabled="!editMode"
type="date"
placeholder="请选择生日"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系电话" prop="contact_phone">
<el-input
v-model="formData.contact_phone"
:disabled="!editMode"
placeholder="请输入联系电话"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急联系人" prop="emergency_contact">
<el-input
v-model="formData.emergency_contact"
:disabled="!editMode"
placeholder="请输入紧急联系人"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="校区" prop="campus_id">
<el-select
v-model="formData.campus_id"
:disabled="!editMode"
placeholder="请选择校区"
style="width: 100%"
@change="handleCampusChange"
>
<el-option
v-for="campus in campusList"
:key="campus.id"
:label="campus.name"
:value="campus.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班级" prop="class_id">
<el-select
v-model="formData.class_id"
:disabled="!editMode"
placeholder="请选择班级"
style="width: 100%"
>
<el-option
v-for="classItem in classList"
:key="classItem.id"
:label="classItem.name"
:value="classItem.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="顾问" prop="consultant_id">
<el-select
v-model="formData.consultant_id"
:disabled="!editMode"
placeholder="请选择顾问"
style="width: 100%"
>
<el-option
v-for="consultant in consultantList"
:key="consultant.id"
:label="consultant.name"
:value="consultant.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教练" prop="coach_id">
<el-select
v-model="formData.coach_id"
:disabled="!editMode"
placeholder="请选择教练"
style="width: 100%"
>
<el-option
v-for="coach in coachList"
:key="coach.id"
:label="coach.name"
:value="coach.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="会员标签" prop="member_label">
<el-input
v-model="formData.member_label"
:disabled="!editMode"
placeholder="请输入会员标签"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="试听次数" prop="trial_class_count">
<el-input-number
v-model="formData.trial_class_count"
:disabled="!editMode"
:min="0"
placeholder="试听次数"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select
v-model="formData.status"
:disabled="!editMode"
placeholder="请选择状态"
style="width: 100%"
>
<el-option label="正常" :value="1" />
<el-option label="暂停" :value="0" />
<el-option label="其他" :value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建时间">
<el-input
:value="formatDateTime(formData.created_at)"
disabled
placeholder="创建时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="note">
<el-input
v-model="formData.note"
:disabled="!editMode"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 学员记录信息 -->
<el-card class="info-card" shadow="never">
<template #header>
<span class="card-title">记录信息</span>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="用户ID">
{{ formData.user_id || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学员ID">
{{ formData.id || '-' }}
</el-descriptions-item>
<el-descriptions-item label="首次到店">
{{ formatDateTime(formData.first_come) || '-' }}
</el-descriptions-item>
<el-descriptions-item label="二次到店">
{{ formatDateTime(formData.second_come) || '-' }}
</el-descriptions-item>
<el-descriptions-item label="创建人ID">
{{ formData.created_person_id || '-' }}
</el-descriptions-item>
<el-descriptions-item label="最后更新">
{{ formatDateTime(formData.updated_at) || '-' }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">关闭</el-button>
<el-button
v-if="!editMode"
type="primary"
@click="enterEditMode"
>
编辑学员信息
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { getStudentInfo, editStudent } from '@/app/api/student'
import { getWithCampusList, getWithClassGradeList } from '@/app/api/student'
//
interface StudentDetailInfo {
id: number
name: string
gender: number
age: number
birthday: string
user_id: number
campus_id: number
class_id: number
note: string
status: number
emergency_contact: string
contact_phone: string
member_label: string
consultant_id: string
coach_id: string
trial_class_count: number
headimg: string
first_come: string
second_come: string
created_person_id: number
created_at: string
updated_at: string
}
//
interface OptionItem {
id: number
name: string
}
//
const visible = ref(false)
const loading = ref(false)
const saving = ref(false)
const editMode = ref(false)
const studentFormRef = ref<FormInstance>()
//
const formData = reactive<Partial<StudentDetailInfo>>({})
const originalData = reactive<Partial<StudentDetailInfo>>({})
//
const campusList = ref<OptionItem[]>([])
const classList = ref<OptionItem[]>([])
const consultantList = ref<OptionItem[]>([])
const coachList = ref<OptionItem[]>([])
//
const formRules: FormRules = {
name: [
{ required: true, message: '请输入学员姓名', trigger: 'blur' },
{ min: 2, max: 50, message: '姓名长度在 2 到 50 个字符', trigger: 'blur' }
],
gender: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
contact_phone: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
}
//
const open = async (studentId: number) => {
if (!studentId) {
ElMessage.error('学员ID不能为空')
return
}
visible.value = true
editMode.value = false
await loadStudentDetail(studentId)
}
//
const loadStudentDetail = async (studentId: number) => {
try {
loading.value = true
//
const [studentResponse, campusResponse, classResponse] = await Promise.all([
getStudentInfo(studentId),
getWithCampusList({}),
getWithClassGradeList({})
])
if (studentResponse.code === 1 && studentResponse.data) {
//
Object.assign(formData, studentResponse.data)
Object.assign(originalData, studentResponse.data)
console.log('学员详情加载成功:', studentResponse.data)
} else {
ElMessage.error(studentResponse.msg || '获取学员详情失败')
return
}
//
if (campusResponse.code === 1) {
campusList.value = campusResponse.data || []
}
if (classResponse.code === 1) {
classList.value = classResponse.data || []
}
//
await loadPersonnelData()
} catch (error) {
console.error('加载学员详情失败:', error)
ElMessage.error('加载学员详情失败')
} finally {
loading.value = false
}
}
//
const loadPersonnelData = async () => {
try {
//
const campusId = formData.campus_id
// API - API
// API
consultantList.value = [
{ id: 1, name: '张顾问' },
{ id: 2, name: '李顾问' },
{ id: 3, name: '王顾问' }
]
coachList.value = [
{ id: 1, name: '赵教练' },
{ id: 2, name: '钱教练' },
{ id: 3, name: '孙教练' }
]
// API
// const personnelResponse = await getPersonnelByDept(campusId, [1, 24]) // 1:(), 24:
// if (personnelResponse.code === 1 && personnelResponse.data) {
// const personnel = personnelResponse.data
// consultantList.value = personnel.filter(p => p.dept_id === 1) //
// coachList.value = personnel.filter(p => p.dept_id === 24) //
// }
} catch (error) {
console.error('加载人员数据失败:', error)
}
}
//
const handleCampusChange = (campusId: number) => {
//
formData.class_id = undefined
//
// API
console.log('校区变化:', campusId)
}
//
const enterEditMode = () => {
editMode.value = true
//
Object.assign(originalData, formData)
}
//
const cancelEdit = () => {
editMode.value = false
//
Object.assign(formData, originalData)
}
//
const saveChanges = async () => {
if (!studentFormRef.value) return
try {
//
const valid = await studentFormRef.value.validate()
if (!valid) return
saving.value = true
const response = await editStudent({
id: formData.id,
...formData
})
if (response.code === 1) {
ElMessage.success('学员信息更新成功')
editMode.value = false
//
Object.assign(originalData, formData)
} else {
ElMessage.error(response.msg || '更新失败')
}
} catch (error) {
console.error('保存学员信息失败:', error)
ElMessage.error('保存失败')
} finally {
saving.value = false
}
}
//
const formatDateTime = (dateTime: string) => {
if (!dateTime) return ''
try {
return new Date(dateTime).toLocaleString('zh-CN')
} catch {
return dateTime
}
}
//
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.student-detail-dialog {
.student-detail-container {
max-height: 70vh;
overflow-y: auto;
}
.info-card {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.edit-actions {
display: flex;
gap: 8px;
}
}
}
.student-form {
.el-form-item {
margin-bottom: 20px;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
}
:deep(.el-dialog__body) {
padding: 20px;
}
:deep(.el-card__header) {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
}
:deep(.el-card__body) {
padding: 20px;
}
:deep(.el-descriptions__label) {
font-weight: 600;
color: #606266;
}
:deep(.el-descriptions__content) {
color: #303133;
}
</style>

139
niucloud/app/adminapi/controller/upload/CosProxy.php

@ -0,0 +1,139 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\adminapi\controller\upload;
use core\base\BaseAdminController;
use core\exception\AdminException;
/**
* 腾讯云COS代理上传(临时开发环境解决方案)
* 注意:生产环境应该直接配置COS的CORS规则而不是使用代理
*/
class CosProxy extends BaseAdminController
{
/**
* 代理上传到腾讯云COS
* @return \think\Response
*/
public function proxyUpload()
{
try {
// 从表单或JSON中获取数据
$upload_url = $this->request->post('upload_url');
// 尝试多种方式获取表单数据
$form_data = [];
// 方式1: 从form_data字段获取JSON字符串
$form_data_json = $this->request->post('form_data', '');
if (!empty($form_data_json)) {
$form_data = json_decode($form_data_json, true);
if (json_last_error() === JSON_ERROR_NONE) {
// JSON解析成功
} else {
$form_data = [];
}
}
// 如果JSON解析失败,检查是否有独立的字段
if (empty($form_data)) {
$possible_fields = ['key', 'policy', 'q-sign-algorithm', 'q-ak', 'q-key-time', 'q-signature', 'domain'];
foreach ($possible_fields as $field) {
$value = $this->request->post($field);
if (!empty($value)) {
$form_data[$field] = $value;
}
}
}
if (empty($upload_url)) {
throw new AdminException('缺少上传地址');
}
// 处理文件上传
$file = $this->request->file('file');
if (empty($file)) {
throw new AdminException('缺少上传文件');
}
// 添加调试模式 - 如果upload_url包含debug,则返回解析的数据不执行实际上传
if (strpos($upload_url, 'debug') !== false) {
return success([
'debug' => true,
'upload_url' => $upload_url,
'form_data' => $form_data,
'form_data_count' => count($form_data),
'file_info' => [
'name' => $file->getOriginalName(),
'size' => $file->getSize(),
'mime' => $file->getMime()
]
]);
}
// 腾讯云COS要求特定的字段顺序
$post_data = [];
// 添加所有表单字段,确保file在最后
foreach ($form_data as $key => $value) {
$post_data[$key] = $value;
}
// 最后添加文件(腾讯云COS要求file字段在最后)
$curl_file = new \CURLFile($file->getPathname(), $file->getMime(), $file->getOriginalName());
$post_data['file'] = $curl_file;
// 发送请求到腾讯云COS
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $upload_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_HEADER, true);
$full_response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new AdminException('上传失败: ' . $error);
}
// 解析响应头和内容
$header = substr($full_response, 0, $header_size);
$body = substr($full_response, $header_size);
if ($http_code === 204 || $http_code === 200) {
// 上传成功
$domain = rtrim($form_data['domain'] ?? '', '/');
$key = $form_data['key'] ?? '';
$file_url = $domain . '/' . $key;
return success([
'url' => $file_url,
'http_code' => $http_code,
'response_body' => substr($body, 0, 100) // 调试信息
]);
} else {
throw new AdminException("上传失败,HTTP状态码: {$http_code}, 响应: " . substr($body, 0, 200));
}
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
}
Loading…
Cancel
Save