2 changed files with 736 additions and 0 deletions
@ -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> |
||||
@ -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…
Reference in new issue