diff --git a/admin/src/app/api/salary.ts b/admin/src/app/api/salary.ts index 0ea2d149..4844c27a 100644 --- a/admin/src/app/api/salary.ts +++ b/admin/src/app/api/salary.ts @@ -123,7 +123,7 @@ export const getStatisticsTrend = (params: { months?: number; campus_id?: number // 获取员工列表 export const getPersonnelList = () => { - return request.get('/personnel/list') + return request.get('/lesson_course_teaching/personnel_data_all',{params:{is_all:1}}) } // 获取校区列表 diff --git a/admin/src/app/views/salary/components/salary-edit.vue b/admin/src/app/views/salary/components/salary-edit.vue index 18c5563a..5fbb202d 100644 --- a/admin/src/app/views/salary/components/salary-edit.vue +++ b/admin/src/app/views/salary/components/salary-edit.vue @@ -45,7 +45,7 @@ diff --git a/admin/src/app/views/salary/list.vue b/admin/src/app/views/salary/list.vue index 9d655481..7e896a78 100644 --- a/admin/src/app/views/salary/list.vue +++ b/admin/src/app/views/salary/list.vue @@ -29,7 +29,7 @@ @@ -223,6 +223,7 @@ import { type QueryParams, type SalaryItem } from '@/app/api/salary' + import SalaryEdit from './components/salary-edit.vue' import SalaryDetail from './components/salary-detail.vue' @@ -275,7 +276,27 @@ const loadTableData = async () => { loading.value = true try { const res = await getSalaryList(queryParams) - tableData.value = res.data.list + + // 修正数据结构访问路径 + const rawData = res.data.data || [] + + // 数据类型转换处理 + tableData.value = rawData.map((item: any) => ({ + ...item, + base_salary: Number(item.base_salary) || 0, + work_salary: Number(item.work_salary) || 0, + mgr_performance: Number(item.mgr_performance) || 0, + performance_bonus: Number(item.performance_bonus) || 0, + other_subsidies: Number(item.other_subsidies) || 0, + deductions: Number(item.deductions) || 0, + gross_salary: Number(item.gross_salary) || 0, + social_security: Number(item.social_security) || 0, + individual_income_tax: Number(item.individual_income_tax) || 0, + net_salary: Number(item.net_salary) || 0, + attendance: Number(item.attendance) || 0, + full_attendance_days: Number(item.full_attendance_days) || 0 + })) + total.value = res.data.total } catch (error) { console.error('加载数据失败:', error) diff --git a/admin/src/components/direct-upload/index.vue b/admin/src/components/direct-upload/index.vue index 15883040..c2ec5d15 100644 --- a/admin/src/components/direct-upload/index.vue +++ b/admin/src/components/direct-upload/index.vue @@ -139,7 +139,7 @@ const handleFileChange = async (uploadFile: UploadFile) => { /** * 直传上传 */ -const handleDirectUpload = async (file: File, uploadFile: UploadFile) => { +const handleDirectUpload = async (file: File, uploadFileObj: UploadFile) => { const result = await uploadFile({ file, fileType: props.fileType, @@ -151,7 +151,7 @@ const handleDirectUpload = async (file: File, uploadFile: UploadFile) => { if (result.success) { ElMessage.success('上传成功') - emit('success', result.url, uploadFile) + emit('success', result.url, uploadFileObj) } else { throw new Error(result.error || '上传失败') } @@ -160,7 +160,7 @@ const handleDirectUpload = async (file: File, uploadFile: UploadFile) => { /** * 后备上传(传统方式) */ -const handleFallbackUpload = async (file: File, uploadFile: UploadFile) => { +const handleFallbackUpload = async (file: File, uploadFileObj: UploadFile) => { if (!props.fallbackUrl) { throw new Error('直传不可用且未配置后备上传接口') } @@ -186,7 +186,7 @@ const handleFallbackUpload = async (file: File, uploadFile: UploadFile) => { const response = JSON.parse(xhr.responseText) if (response.code === 1) { ElMessage.success('上传成功') - emit('success', response.data.url, uploadFile) + emit('success', response.data.url, uploadFileObj) resolve() } else { reject(new Error(response.msg || '上传失败')) diff --git a/admin/src/utils/directUpload.ts b/admin/src/utils/directUpload.ts index ade06c20..f7e71b29 100644 --- a/admin/src/utils/directUpload.ts +++ b/admin/src/utils/directUpload.ts @@ -119,13 +119,38 @@ export class DirectUpload { file: File, credentials: UploadCredentials, onProgress?: (percent: number) => void + ): Promise { + return new Promise(async (resolve, reject) => { + const filePath = credentials.file_path.replace('${filename}', file.name) + + // 尝试直传,如果CORS失败则使用代理 + try { + const directResult = await this.directUploadToTencent(file, credentials, filePath, onProgress) + resolve(directResult) + } catch (error) { + console.warn('Direct upload failed, trying proxy:', error) + try { + const proxyResult = await this.proxyUploadToTencent(file, credentials, filePath, onProgress) + resolve(proxyResult) + } catch (proxyError) { + reject(proxyError) + } + } + }) + } + + /** + * 腾讯云COS直接上传 + */ + private async directUploadToTencent( + file: File, + credentials: UploadCredentials, + filePath: string, + onProgress?: (percent: number) => void ): Promise { return new Promise((resolve, reject) => { const formData = new FormData() - // 生成文件路径(替换模板中的${filename}) - const filePath = credentials.file_path.replace('${filename}', file.name) - // 添加腾讯云COS必需的字段 formData.append('key', filePath) formData.append('policy', credentials.credentials.policy) @@ -171,6 +196,69 @@ export class DirectUpload { }) } + /** + * 腾讯云COS代理上传(开发环境临时方案) + */ + private async proxyUploadToTencent( + file: File, + credentials: UploadCredentials, + filePath: string, + onProgress?: (percent: number) => void + ): Promise { + return new Promise((resolve, reject) => { + const formData = new FormData() + + // 添加后端代理需要的参数(分别发送各个字段,避免JSON解析问题) + formData.append('upload_url', credentials.upload_url) + formData.append('key', filePath) + formData.append('policy', credentials.credentials.policy) + formData.append('q-sign-algorithm', credentials.credentials['q-sign-algorithm']) + formData.append('q-ak', credentials.credentials['q-ak']) + formData.append('q-key-time', credentials.credentials['q-key-time']) + formData.append('q-signature', credentials.credentials['q-signature']) + formData.append('domain', credentials.domain) + formData.append('file', file) + + const xhr = new XMLHttpRequest() + + // 上传进度 + if (onProgress) { + xhr.upload.addEventListener('progress', (event) => { + if (event.lengthComputable) { + const percent = Math.round((event.loaded / event.total) * 100) + onProgress(percent) + } + }) + } + + // 上传完成 + xhr.addEventListener('load', () => { + try { + const response = JSON.parse(xhr.responseText) + if (response.code === 1) { + resolve({ + success: true, + url: response.data.url + }) + } else { + reject(new Error(response.msg || '上传失败')) + } + } catch (error) { + reject(new Error('响应解析失败')) + } + }) + + // 上传错误 + xhr.addEventListener('error', () => { + reject(new Error('网络错误,上传失败')) + }) + + // 执行代理上传 + xhr.open('POST', `${this.baseUrl}/adminapi/sys/direct/cos_proxy`, true) + xhr.send(formData) + }) + } + /** * 阿里云OSS直传 */ diff --git a/niucloud/app/adminapi/controller/lesson_course_teaching/LessonCourseTeaching.php b/niucloud/app/adminapi/controller/lesson_course_teaching/LessonCourseTeaching.php index 0ab918d7..d92792f5 100644 --- a/niucloud/app/adminapi/controller/lesson_course_teaching/LessonCourseTeaching.php +++ b/niucloud/app/adminapi/controller/lesson_course_teaching/LessonCourseTeaching.php @@ -756,7 +756,8 @@ class LessonCourseTeaching extends BaseAdminController ["name",""], ["phone",""], ["role_id",""], - ["dept_id",""] + ["dept_id",""], + ['is_all',""] ]); return success((new LessonCourseTeachingService())->getPersonnelDataAll($data)); } diff --git a/niucloud/app/adminapi/route/sys.php b/niucloud/app/adminapi/route/sys.php index 3a57de1d..24239852 100644 --- a/niucloud/app/adminapi/route/sys.php +++ b/niucloud/app/adminapi/route/sys.php @@ -333,6 +333,8 @@ Route::group('sys', function() { Route::get('direct/credentials', 'upload.DirectUpload/getUploadCredentials'); //确认上传完成 Route::post('direct/confirm', 'upload.DirectUpload/confirmUpload'); + //COS代理上传(开发环境临时方案) + Route::post('direct/cos_proxy', 'upload.CosProxy/proxyUpload'); //附件列表 Route::get('attachment', 'sys.Attachment/lists'); diff --git a/niucloud/app/service/admin/lesson_course_teaching/LessonCourseTeachingService.php b/niucloud/app/service/admin/lesson_course_teaching/LessonCourseTeachingService.php index ad5822da..76160cdf 100644 --- a/niucloud/app/service/admin/lesson_course_teaching/LessonCourseTeachingService.php +++ b/niucloud/app/service/admin/lesson_course_teaching/LessonCourseTeachingService.php @@ -734,8 +734,12 @@ class LessonCourseTeachingService extends BaseAdminService ->where($whereArr) ->group("a.id") ->order($order); - - $list = $this->pageQuery($search_model); + if ($where['is_all']){ + $list = $search_model->select()->toArray(); + }else{ + $list = $this->pageQuery($search_model); + } + return $list; } diff --git a/niucloud/app/service/admin/upload/DirectUploadService.php b/niucloud/app/service/admin/upload/DirectUploadService.php index 26d3be16..578fa89b 100644 --- a/niucloud/app/service/admin/upload/DirectUploadService.php +++ b/niucloud/app/service/admin/upload/DirectUploadService.php @@ -85,13 +85,20 @@ class DirectUploadService extends BaseAdminService } } - // 生成策略 - $expired = time() + 3600; // 1小时过期 + $current_time = time(); + $expired = $current_time + 3600; // 1小时过期 + $key_time = $current_time . ';' . $expired; + + // 生成POST策略 (腾讯云COS对象格式) $policy = [ 'expiration' => gmdate('Y-m-d\TH:i:s.000\Z', $expired), 'conditions' => [ - ['bucket' => $config['bucket']], - ['starts-with' => '$key', $file_path] + // key路径限制 - starts-with格式 + ['starts-with', '$key', dirname($file_path) . '/'], + // 签名字段 - 对象格式 (腾讯云COS要求) + ['$q-sign-algorithm' => 'sha1'], + ['$q-ak' => $config['access_key']], + ['$q-key-time' => $key_time] ] ]; @@ -101,9 +108,12 @@ class DirectUploadService extends BaseAdminService $policy['conditions'][] = ['content-length-range', 0, $max_size]; } - $policy_encoded = base64_encode(json_encode($policy)); - $signature = hash_hmac('sha1', $policy_encoded, $config['secret_key'], true); - $signature_encoded = base64_encode($signature); + $policy_encoded = base64_encode(json_encode($policy, JSON_UNESCAPED_SLASHES)); + + // 腾讯云COS POST签名算法 + $sign_key = hash_hmac('sha1', $key_time, $config['secret_key']); + $string_to_sign = $policy_encoded; + $signature = hash_hmac('sha1', $string_to_sign, $sign_key); return [ 'storage_type' => 'tencent', @@ -112,8 +122,8 @@ class DirectUploadService extends BaseAdminService 'policy' => $policy_encoded, 'q-sign-algorithm' => 'sha1', 'q-ak' => $config['access_key'], - 'q-key-time' => time() . ';' . $expired, - 'q-signature' => $signature_encoded, + 'q-key-time' => $key_time, + 'q-signature' => $signature, ], 'file_path' => $file_path, 'domain' => $config['domain'] ?? "https://{$config['bucket']}.cos.{$config['region']}.myqcloud.com",