Compare commits

...

2 Commits

Author SHA1 Message Date
于宏哲PHP 5000aa31e8 Merge branch 'master' of http://gitlab.frkj.cc/php/zhjwxt 7 months ago
于宏哲PHP 237ebe5660 1 7 months ago
  1. 113
      admin/src/app/views/contract/contract.vue
  2. 18
      admin/src/app/views/order_table/components/order-table-edit.vue
  3. 6686
      admin/yarn.lock
  4. 34
      niucloud/app/adminapi/controller/contract/ContractDistribution.php
  5. 6
      niucloud/app/adminapi/route/salary.php
  6. 8
      niucloud/app/api/controller/apiController/Chat.php
  7. 41
      niucloud/app/api/controller/apiController/OrderTable.php
  8. 50
      niucloud/app/api/controller/apiController/PhysicalTest.php
  9. 16
      niucloud/app/api/controller/personnel/WechatBind.php
  10. 20
      niucloud/app/api/route/route.php
  11. 4
      niucloud/app/api/route/student.php
  12. 148
      niucloud/app/common.php
  13. 20
      niucloud/app/model/attendance/Attendance.php
  14. 29
      niucloud/app/service/admin/classroom/ClassroomService.php
  15. 5
      niucloud/app/service/admin/contract/ContractDistributionService.php
  16. 11
      niucloud/app/service/admin/order_table/OrderTableService.php
  17. 2
      niucloud/app/service/admin/personnel/PersonnelService.php
  18. 13
      niucloud/app/service/api/apiService/ContractService.php
  19. 30
      niucloud/app/service/api/apiService/OrderTableService.php
  20. 29
      niucloud/app/service/api/apiService/ResourceSharingService.php
  21. 26
      niucloud/app/service/api/apiService/TeachingResearchService.php
  22. 78
      niucloud/app/service/api/personnel/WechatBindService.php
  23. 44
      niucloud/app/service/api/student/PhysicalTestService.php
  24. 3
      niucloud/composer.json
  25. 2
      uniapp/api/member.js
  26. 4
      uniapp/common/config.js
  27. 2
      uniapp/manifest.json
  28. 31
      uniapp/pages-market/clue/add_clues.vue
  29. 2
      uniapp/pages-market/clue/index.vue
  30. 602
      uniapp/pages-market/my/set_up.vue
  31. 1
      uniapp/pages-student/orders/index.vue
  32. 2
      uniapp/pages-student/physical-test/index.vue

113
admin/src/app/views/contract/contract.vue

@ -80,10 +80,10 @@
<el-button type="primary" size="small" @click="configPlaceholder(row)">
配置占位符
</el-button>
<el-button
v-if="row.contract_type === '内部'"
type="success"
size="small"
<el-button
v-if="row.contract_type === '内部'"
type="success"
size="small"
@click="distributeContract(row)">
分发给员工
</el-button>
@ -133,13 +133,13 @@
<div class="file-upload-area">
<input
type="file"
accept=".docx,.doc"
accept=".docx"
@change="handleFileSelect"
class="file-input"
ref="fileInput"
:key="fileInputKey"
/>
<div class="upload-tip">支持 .docx .doc 格式文件文件大小不超过 10MB</div>
<div class="upload-tip">支持 .docx 格式文件文件大小不超过 10MB</div>
<div v-if="uploadForm.file_name" class="file-info">
<span>📄 {{ uploadForm.file_name }}</span>
<button type="button" @click="clearFile" class="clear-file-btn">×</button>
@ -189,32 +189,32 @@
<h4>检测到的占位符 (合同ID: {{ currentContractId }})</h4>
<div class="header-buttons">
<!-- 如果没有Word文档显示上传按钮 -->
<button
v-if="!currentTemplateInfo?.contract_template"
@click="showReuploadSection = true"
<button
v-if="!currentTemplateInfo?.contract_template"
@click="showReuploadSection = true"
class="btn-upload"
:disabled="reuploading">
{{ reuploading ? '上传中...' : '上传Word文档' }}
</button>
<!-- 如果有Word文档显示重新识别按钮 -->
<button
v-if="currentTemplateInfo?.contract_template"
@click="reidentifyPlaceholders"
class="btn-reidentify"
<button
v-if="currentTemplateInfo?.contract_template"
@click="reidentifyPlaceholders"
class="btn-reidentify"
:disabled="reidentifying">
{{ reidentifying ? '识别中...' : '重新识别占位符' }}
</button>
<!-- 如果有Word文档也提供重新上传选项 -->
<button
v-if="currentTemplateInfo?.contract_template"
@click="showReuploadSection = !showReuploadSection"
<button
v-if="currentTemplateInfo?.contract_template"
@click="showReuploadSection = !showReuploadSection"
class="btn-secondary"
:disabled="reuploading">
{{ showReuploadSection ? '取消上传' : '更换文档' }}
</button>
</div>
</div>
<!-- 重新上传Word文档区域 -->
<div v-if="showReuploadSection" class="reupload-section">
<h5>重新上传Word文档</h5>
@ -292,14 +292,14 @@
<!-- <option v-if="config.table_name === 'school_student'" value="member_label">会员标签</option>-->
<!-- <option v-if="config.table_name === 'school_student'" value="user_id">关联用户ID</option>-->
<option v-if="config.table_name === 'school_student'" value="campus_id">所属校区</option>
<!-- 用户表字段 (school_customer_resources) -->
<!-- <option v-if="config.table_name === 'school_customer_resources'" value="id">用户ID</option>-->
<option v-if="config.table_name === 'school_customer_resources'" value="name">家长姓名</option>
<option v-if="config.table_name === 'school_customer_resources'" value="phone_number">手机号</option>
<option v-if="config.table_name === 'school_customer_resources'" value="campus">所属校区</option>
<!-- <option v-if="config.table_name === 'school_customer_resources'" value="member_id">会员ID</option>-->
<!-- 订单表字段 (school_order_table) -->
<option v-if="config.table_name === 'school_order_table'" value="id">订单ID</option>
<option v-if="config.table_name === 'school_order_table'" value="payment_id">支付编号</option>
@ -311,7 +311,7 @@
<option v-if="config.table_name === 'school_order_table'" value="course_plan_id">课程计划</option>
<option v-if="config.table_name === 'school_order_table'" value="discount_amount">优惠金额</option>
<!-- <option v-if="config.table_name === 'school_order_table'" value="student_id">学员ID</option>-->
<!-- 员工表字段 (school_personnel) -->
<!-- <option v-if="config.table_name === 'school_personnel'" value="id">员工ID</option>-->
<option v-if="config.table_name === 'school_personnel'" value="name">姓名</option>
@ -520,11 +520,11 @@
</tr>
</thead>
<tbody>
<tr v-for="staff in filteredStaffList" :key="staff.id"
<tr v-for="staff in filteredStaffList" :key="staff.id"
:class="{ 'selected': selectedStaff.some(s => s.id === staff.id) }">
<td>
<input
type="checkbox"
<input
type="checkbox"
:checked="selectedStaff.some(s => s.id === staff.id)"
@change="toggleStaffSelection(staff)"
/>
@ -678,14 +678,14 @@ const getList = async () => {
limit: pagination.limit
}
const { data } = await contractTemplateApi.getList(params)
//
const processedData = data.data.map((item: any) => ({
...item,
formatted_file_size: item.file_size ? formatFileSize(item.file_size) : '-',
formatted_created_at: item.created_at ? new Date(item.created_at).toLocaleString() : '-'
}))
tableData.value = processedData
pagination.total = data.total
} catch (error) {
@ -702,11 +702,11 @@ const validateSystemFunction = (functionName: string) => {
const validFunctions = [
//
'get_current_date', 'get_current_time', 'get_current_datetime',
'get_current_year', 'get_current_month', 'get_current_day',
'get_current_year', 'get_current_month', 'get_current_day',
'get_current_week', 'get_current_quarter',
//
'get_current_campus', 'get_current_login_user', 'get_current_personnel',
'get_contract_generate_time', 'get_contract_sign_time',
'get_contract_generate_time', 'get_contract_sign_time',
//
'get_system_name', 'get_system_version',
'get_random_number', 'get_contract_sequence', 'format_currency',
@ -729,7 +729,7 @@ const formatFileSize = (bytes: number) => {
const getContractTypeText = (type: string) => {
const typeMap: Record<string, string> = {
'内部': '内部合同',
'外部': '外部合同',
'外部': '外部合同',
'员工': '员工合同',
'学员': '学员合同',
'会员': '会员合同',
@ -743,7 +743,7 @@ const getContractTypeText = (type: string) => {
const getContractStatusType = (status: string) => {
const statusMap: Record<string, string> = {
'draft': 'info',
'active': 'success',
'active': 'success',
'inactive': 'warning',
'archived': 'info',
'expired': 'danger'
@ -812,7 +812,7 @@ const saveEdit = async () => {
ElMessage.error('请输入模板名称')
return
}
if (!editForm.contract_type) {
ElMessage.error('请选择合同类型')
return
@ -823,7 +823,7 @@ const saveEdit = async () => {
contract_type: editForm.contract_type,
remarks: editForm.remarks
})
ElMessage.success('编辑成功')
showEditDialog.value = false
getList()
@ -863,7 +863,7 @@ const distributeContract = async (row: ContractTemplate) => {
currentContractId.value = row.id
showDistributeDialog.value = true
selectedStaff.value = []
//
await loadStaffList()
}
@ -874,7 +874,7 @@ const loadStaffList = async () => {
try {
// APItype=1
const { data } = await contractDistributionApi.getPersonnelList({ type: 1 })
// API
const processedData = data.map((staff: any) => ({
id: staff.id,
@ -885,15 +885,15 @@ const loadStaffList = async () => {
role: staff.role || '员工',
status: staff.status === 1 ? '在职' : '离职'
}))
staffList.value = processedData
filteredStaffList.value = processedData
console.log('员工列表加载成功:', staffList.value)
} catch (error) {
console.error('加载员工列表失败:', error)
ElMessage.error('加载员工列表失败')
// API使
const mockStaffData = [
{ id: 1, name: '张三', phone: '13800138001', department: '教务部', role: '老师', status: '在职' },
@ -902,7 +902,7 @@ const loadStaffList = async () => {
{ id: 4, name: '赵六', phone: '13800138004', department: '管理部', role: '主管', status: '在职' },
{ id: 5, name: '孙七', phone: '13800138005', department: '销售部', role: '经理', status: '在职' }
]
staffList.value = mockStaffData
filteredStaffList.value = mockStaffData
} finally {
@ -947,29 +947,29 @@ const confirmDistribute = async () => {
ElMessage.warning('请选择要分发的员工')
return
}
distributingContract.value = true
try {
const staffIds = selectedStaff.value.map(staff => staff.id)
//
await contractDistributionApi.manualDistribute({
contract_id: currentContractId.value,
personnel_ids: staffIds,
type: 1 // 1
})
console.log('分发合同成功:', {
contractId: currentContractId.value,
contractName: currentContract.value?.contract_name,
staffIds: staffIds,
staffNames: selectedStaff.value.map(s => s.name)
})
ElMessage.success(`合同已成功分发给 ${selectedStaff.value.length} 名员工`)
showDistributeDialog.value = false
selectedStaff.value = []
} catch (error) {
console.error('分发合同失败:', error)
ElMessage.error(`分发合同失败: ${error.message || '未知错误'}`)
@ -993,33 +993,33 @@ const reidentifyPlaceholders = async () => {
reidentifying.value = true
try {
console.log('🔍 开始重新识别占位符, 合同ID:', currentContractId.value)
//
const { data } = await contractTemplateApi.reidentifyPlaceholders(currentContractId.value)
console.log('✅ 重新识别成功, 返回数据:', data)
//
const { placeholders, placeholder_count, new_placeholders, removed_placeholders } = data
let message = `成功识别到 ${placeholder_count} 个占位符`
if (new_placeholders && new_placeholders.length > 0) {
message += `,新增 ${new_placeholders.length}`
}
if (removed_placeholders && removed_placeholders.length > 0) {
message += `,移除 ${removed_placeholders.length}`
}
ElMessage.success(message)
//
await loadPlaceholderConfig(currentContractId.value)
} catch (error) {
console.error('❌ 重新识别失败:', error)
//
let errorMessage = error.message || '未知错误'
if (errorMessage.includes('模板未上传Word文档')) {
ElMessage.error('请先上传Word文档模板后再进行占位符识别')
} else if (errorMessage.includes('模板文件不存在')) {
@ -1038,7 +1038,7 @@ const loadPlaceholderConfig = async (contractId: number) => {
try {
const { data } = await contractTemplateApi.getPlaceholderConfig(contractId)
console.log('API返回数据:', data)
//
if (data && typeof data === 'object') {
currentTemplateInfo.value = data
@ -1399,7 +1399,7 @@ const onDataTypeChange = (config: any) => {
if (config.data_type !== 'signature') {
config.signature_type = ''
}
//
// config.sign_party
}
@ -1465,6 +1465,7 @@ const handleFileSelect = (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
console.log('📁 文件选择事件触发:', file)
if (!file) {
@ -1476,11 +1477,11 @@ const handleFileSelect = (event: Event) => {
// - .docx .doc
const fileName = file.name.toLowerCase()
const allowedExtensions = ['.docx', '.doc']
const allowedExtensions = ['.docx']
const isValidType = allowedExtensions.some(ext => fileName.endsWith(ext))
if (!isValidType) {
ElMessage.error('只支持上传 .docx 和 .doc 格式的文件!')
ElMessage.error('只支持上传 .docx格式的文件!')
// input
uploadForm.file_data = null
uploadForm.file_name = ''
@ -1599,7 +1600,7 @@ const submitReupload = async () => {
// API
await contractTemplateApi.updateTemplateFile(currentContractId.value, formData)
console.log('✅ 重新上传成功')
ElMessage.success('Word文档更新成功')
@ -2387,7 +2388,7 @@ onMounted(() => {
align-items: flex-start;
gap: 8px;
}
.sign-config-tip {
width: 100%;
}

18
admin/src/app/views/order_table/components/order-table-edit.vue

@ -51,11 +51,11 @@
<el-form-item :label="t('orderAmount')" prop="order_amount">
<el-input v-model="formData.order_amount" :placeholder="t('orderAmountPlaceholder')" class="input-width" disabled/>
<el-input v-model="formData.order_amount" :placeholder="t('orderAmountPlaceholder')" class="input-width" :disabled="formData.payment_type != 'deposit'"/>
</el-form-item>
<el-form-item :label="t('classId')" prop="class_id">
<el-select class="input-width" v-model="formData.class_id" clearable :placeholder="t('classIdPlaceholder')">
<el-option label="请选择" value=""></el-option>
@ -67,7 +67,7 @@
/>
</el-select>
</el-form-item>
</el-form>
@ -118,27 +118,27 @@ const formRules = computed(() => {
return {
resource_id: [
{ required: true, message: t('resourceIdPlaceholder'), trigger: 'blur' },
]
,
payment_type: [
{ required: true, message: t('paymentTypePlaceholder'), trigger: 'blur' },
]
,
order_amount: [
{ required: true, message: t('orderAmountPlaceholder'), trigger: 'blur' },
]
,
course_id: [
{ required: true, message: t('courseIdPlaceholder'), trigger: 'blur' },
]
,
class_id: [
{ required: true, message: t('classIdPlaceholder'), trigger: 'blur' },
]
,
}
@ -186,7 +186,7 @@ const confirm = async (formEl: FormInstance | undefined) => {
watch(() => payment_typeList.value, () => { formData.payment_type = payment_typeList.value[0].value })
const resourceIdList = ref([] as any[])
const setResourceIdList = async () => {
resourceIdList.value = await (await getWithCustomerResourcesList({})).data

6686
admin/yarn.lock

File diff suppressed because it is too large

34
niucloud/app/adminapi/controller/contract/ContractDistribution.php

@ -37,7 +37,7 @@ class ContractDistribution extends BaseAdminController
['page', 1],
['limit', 20]
]);
return success((new ContractDistributionService())->getDistributionList($data));
}
@ -52,15 +52,15 @@ class ContractDistribution extends BaseAdminController
['personnel_ids', []],
['type', 1]
]);
$this->validate($data, 'app\validate\contract\ContractDistribution.manualDistribute');
(new ContractDistributionService())->manualDistribute(
$data['contract_id'],
$data['personnel_ids'],
$data['contract_id'],
$data['personnel_ids'],
$data['type']
);
return success('DISTRIBUTE_SUCCESS');
}
@ -73,11 +73,11 @@ class ContractDistribution extends BaseAdminController
$data = $this->request->params([
['distributions', []]
]);
$this->validate($data, 'app\validate\contract\ContractDistribution.batchDistribute');
(new ContractDistributionService())->batchDistribute($data['distributions']);
return success('BATCH_DISTRIBUTE_SUCCESS');
}
@ -99,16 +99,16 @@ class ContractDistribution extends BaseAdminController
public function getAvailablePersonnel(): Response
{
$type = $this->request->param('type', 1);
if ($type == 1) {
// 内部员工 - 从school_personnel表查询
$personnel = \think\facade\Db::table('school_personnel')
->where('status', 1)
->where('status', 2)
->where('deleted_at', 0)
->field('id, name, phone, email, account_type as role')
->select()
->toArray();
// 处理数据格式,添加部门信息
foreach ($personnel as &$person) {
$person['department'] = $person['role'] === 'teacher' ? '教务部' : '销售部';
@ -117,12 +117,12 @@ class ContractDistribution extends BaseAdminController
} else {
// 外部会员
$personnel = \think\facade\Db::table('member')
->where('status', 1)
->where('status', 2)
->field('member_id as id, nickname as name, mobile as phone, email')
->select()
->toArray();
}
return success($personnel);
}
@ -133,19 +133,19 @@ class ContractDistribution extends BaseAdminController
public function getDistributionStats(): Response
{
$contractId = $this->request->param('contract_id', 0);
$where = [];
if ($contractId) {
$where[] = ['contract_id', '=', $contractId];
}
$stats = [
'total' => \app\model\contract_sign\ContractSign::where($where)->count(),
'pending' => \app\model\contract_sign\ContractSign::where($where)->where('status', 'pending')->count(),
'signed' => \app\model\contract_sign\ContractSign::where($where)->where('status', 'signed')->count(),
'rejected' => \app\model\contract_sign\ContractSign::where($where)->where('status', 'rejected')->count(),
];
return success($stats);
}
}

6
niucloud/app/adminapi/route/salary.php

@ -39,17 +39,17 @@ Route::group('salary', function () {
Route::get('personnel_all','salary.Salary/getPersonnelAll');
Route::get('departments_all','salary.Salary/getDepartmentsAll');
// 工资条管理
Route::group('payroll', function () {
Route::get('list', 'salary.Payroll/list');
Route::get('info/:id', 'salary.Payroll/info');
Route::get('info', 'salary.Payroll/info');
Route::post('add', 'salary.Payroll/add');
Route::post('edit', 'salary.Payroll/edit');
Route::post('delete', 'salary.Payroll/delete');
Route::post('import', 'salary.Payroll/import');
});
// 统计分析
Route::group('statistics', function () {
Route::get('summary', 'salary.Statistics/summary');

8
niucloud/app/api/controller/apiController/Chat.php

@ -24,14 +24,18 @@ use core\base\BaseApiService;
*/
class Chat extends BaseApiService
{
public function __construct()
{
parent::__construct();
}
//获取好友关系列表
public function getChatFriendsList(Request $request)
{
$personnel_id = $request->param('personnel_id', '');//员工人力资源表id(两个参数2选1)
$customer_resources_id = $request->param('customer_resources_id', '');//学生资源表id(两个参数2选1)
if (empty($personnel_id) && empty($customer_resources_id)) {
return fail('缺少参数');
// return fail('缺少参数');
$personnel_id = $this->member_id;
}
$where = [

41
niucloud/app/api/controller/apiController/OrderTable.php

@ -33,7 +33,7 @@ class OrderTable extends BaseApiService
$resource_id = $request->param('resource_id', '');//客户资源表school_customer_resources表id
$staff_id = $request->param('staff_id', '');//员工表school_personnel表id
$student_id = $request->param('student_id', '');//学生表school_student表id
// 至少需要一个查询条件
if (empty($resource_id) && empty($staff_id) && empty($student_id)) {
return fail('缺少查询参数');
@ -91,7 +91,7 @@ class OrderTable extends BaseApiService
["gift_type", ""], // 赠品核销类型(可选):1-减现, 2-赠课
["remark", ""] // 备注(可选)
]);
// 验证必要参数
$missing_params = [];
if(empty($params['payment_type'])) $missing_params[] = 'payment_type(支付方式)';
@ -100,20 +100,20 @@ class OrderTable extends BaseApiService
if(empty($params['resource_id'])) $missing_params[] = 'resource_id(客户资源ID)';
if(empty($params['order_type'])) $missing_params[] = 'order_type(订单类型)';
if(empty($params['student_id'])) $missing_params[] = 'student_id(学生ID)';
if(!empty($missing_params)) {
return fail('缺少必要参数: ' . implode(', ', $missing_params));
}
// 验证赠品相关参数
if (!empty($params['gift_id']) && empty($params['gift_type'])) {
return fail('选择赠品时必须指定核销类型');
}
if (!empty($params['gift_type']) && !in_array($params['gift_type'], ['1', '2'])) {
return fail('无效的赠品核销类型,只能是1(减现)或2(赠课)');
}
// 如果前端没提供员工ID,使用当前登录的员工ID
if(empty($params['staff_id'])) {
if(empty($staff_id)) {
@ -129,7 +129,7 @@ class OrderTable extends BaseApiService
}
$class = $class->toArray();
$campus_id = $class['campus_id'] ?? 0;
if(empty($campus_id)) {
return fail('班级没有关联校区');
}
@ -174,24 +174,31 @@ class OrderTable extends BaseApiService
["order_status", ""], // 订单状态必填: pending-待支付, paid-已支付, partial-部分支付, cancelled-已取消
["payment_id", ""], // 支付单号(可选)
]);
$order = new \app\model\order_table\OrderTable();
$info = $order->where(['id' => $params['order_id']])->find();
if(!$params['order_status']){
$params['order_status'] = $info['order_status'] == 'pending' ? 'paid' : $info['order_status'];
}
// 验证必要参数
if(empty($params['order_id']) || empty($params['order_status'])) {
return fail('缺少必要参数');
}
// 验证订单状态值
$allowedStatus = ['pending', 'paid', 'partial', 'cancelled', 'completed', 'refunded'];
if(!in_array($params['order_status'], $allowedStatus)) {
return fail('无效的订单状态');
}
$res = (new OrderTableService())->updatePaymentStatus($params);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
}
@ -199,20 +206,20 @@ class OrderTable extends BaseApiService
public function checkOrderPaymentStatus(Request $request)
{
$order_no = $request->param('order_no', ''); // 订单号
if (empty($order_no)) {
return fail('缺少订单号参数');
}
// 查询订单状态
$order = \app\model\order_table\OrderTable::where('payment_id', $order_no)->find();
if (!$order) {
return fail('订单不存在');
}
$orderData = $order->toArray();
return success([
'order_id' => $orderData['id'],
'order_no' => $order_no,

50
niucloud/app/api/controller/apiController/PhysicalTest.php

@ -15,6 +15,8 @@ use app\Request;
use app\service\api\apiService\ChatService;
use app\service\api\apiService\PhysicalTestService;
use core\base\BaseApiService;
use Exception;
use Imagick;
/**
* 体测报告-控制器相关接口
@ -64,7 +66,7 @@ class PhysicalTest extends BaseApiService
public function add(Request $request)
{
$data = $request->param();
// 验证必填字段 - 根据新的数据库表结构调整
$required_fields = ['resource_id', 'student_id', 'height', 'weight'];
foreach ($required_fields as $field) {
@ -88,7 +90,7 @@ class PhysicalTest extends BaseApiService
public function edit(Request $request)
{
$data = $request->param();
if (empty($data['id'])) {
return fail('缺少参数:id');
}
@ -108,7 +110,7 @@ class PhysicalTest extends BaseApiService
public function delete(Request $request)
{
$id = $request->param('id', '');
if (empty($id)) {
return fail('缺少参数:id');
}
@ -156,4 +158,46 @@ class PhysicalTest extends BaseApiService
return fail('上传失败:' . $e->getMessage());
}
}
public function pdfToImage(Request $request){
$pdfUrl = $request->param('pdf_url', '');
$saveDir = public_path() . "/uploads/pdf_images/";
if (!is_dir($saveDir)) {
mkdir($saveDir, 0777, true);
}
$tempPdf = $saveDir . uniqid() . ".pdf";
file_put_contents($tempPdf, file_get_contents($pdfUrl));
try {
$imagick = new Imagick();
$imagick->setResolution(150, 150); // 清晰度 DPI
$imagick->readImage($tempPdf."[0]"); // 只取第一页
$imagick->setImageFormat('jpg'); // 输出 jpg
$imagePath = $saveDir . uniqid("pdf_preview_") . ".jpg";
$imagick->writeImage($imagePath);
$imagick->clear();
$imagick->destroy();
// 删除临时 PDF
unlink($tempPdf);
// 返回 URL
$imageUrl = "https://api.hnhbty.cn/uploads/pdf_images/" . basename($imagePath);
header('Content-Type: application/json');
echo json_encode([
'code' => 1,
'data' => ['image_url' => $imageUrl]
]);
} catch (Exception $e) {
echo json_encode([
'code' => 0,
'message' => $e->getMessage()
]);
}
}
}

16
niucloud/app/api/controller/personnel/WechatBind.php

@ -32,7 +32,7 @@ class WechatBind extends BaseApiController
['code', ''],
['type', 'miniprogram'] // miniprogram: 小程序, official: 公众号
]);
return success('获取成功', (new WechatBindService())->getWechatOpenid($data['code'], $data['type']));
}
@ -47,7 +47,7 @@ class WechatBind extends BaseApiController
['official_openid', ''],
['type', 'miniprogram'] // miniprogram: 小程序绑定, official: 公众号绑定, both: 同时绑定
]);
return success('绑定成功', (new WechatBindService())->bindWechatOpenid($data));
}
@ -61,7 +61,7 @@ class WechatBind extends BaseApiController
['redirect_uri', ''],
['state', '']
]);
return (new WechatBindService())->wechatAuthorize($data['redirect_uri'], $data['state']);
}
@ -73,10 +73,12 @@ class WechatBind extends BaseApiController
{
$data = $this->request->params([
['code', ''],
['state', '']
['state', ''],
['personnel_id',''],
['from','']
]);
return (new WechatBindService())->wechatCallback($data['code'], $data['state']);
return (new WechatBindService())->wechatCallback($data['code'], $data['state'],$data['personnel_id'],$data['from']);
}
/**
@ -88,4 +90,4 @@ class WechatBind extends BaseApiController
return success('获取成功', (new WechatBindService())->getBindStatus());
}
}
}

20
niucloud/app/api/route/route.php

@ -34,10 +34,10 @@ Route::group(function () {
Route::post('niucloud/notify', function () {
return (new CoreNotifyService())->notify();
});
// 协议接口不需要token验证
Route::get('agreement/:key', 'agreement.Agreement/info');
// 发送验证码不需要token验证
Route::post('send/mobile/:type', 'login.Login/sendMobileCode');
@ -48,10 +48,13 @@ Route::group(function () {
//员工端-添加新员工信息
Route::post('personnel/add', 'apiController.Personnel/add');
//微信公众号授权相关接口(不需要token验证)
Route::get('personnel/wechatAuthorize', 'personnel.WechatBind/wechatAuthorize');
Route::get('personnel/wechatCallback', 'personnel.WechatBind/wechatCallback');
});
/**
@ -166,7 +169,8 @@ Route::group(function () {
Route::get('test', 'login.Login/test');
Route::get('class_reminder', 'login.Login/class_reminder');
//员工端-订单管理-更新支付状态
Route::post('orderTable/updatePaymentStatus', 'apiController.OrderTable/updatePaymentStatus');
})->middleware(ApiChannel::class)
->middleware(ApiCheckToken::class)
->middleware(ApiLog::class);
@ -310,8 +314,6 @@ Route::group(function () {
Route::get('orderTable/info', 'apiController.OrderTable/info');
//员工端-订单管理-创建
Route::post('orderTable/add', 'apiController.OrderTable/add');
//员工端-订单管理-更新支付状态
Route::post('orderTable/updatePaymentStatus', 'apiController.OrderTable/updatePaymentStatus');
//员工端-查询订单支付状态
Route::get('checkOrderPaymentStatus', 'apiController.OrderTable/checkOrderPaymentStatus');
@ -372,7 +374,7 @@ Route::group(function () {
Route::get('venue/list', 'apiController.CourseSchedule/getVenueList');
//获取场地可用时间段
Route::get('venue/timeSlots', 'apiController.CourseSchedule/getVenueAvailableTime');
//获取教练列表(用于课程人员配置)
Route::get('course/coachList', 'apiController.Course/getCoachList');
//获取教务人员列表(用于课程人员配置)
@ -394,6 +396,7 @@ Route::group(function () {
Route::get('class/jlGetClasses/list', 'apiController.classApi/jlGetClassesList');
//体测报告-列表
Route::get('class/physicalTest', 'apiController.classApi/PhysicalTestList');
//体测报告-详情
Route::get('class/physicalTest/info', 'apiController.classApi/PhysicalTestInfo');
//获取学员详情
@ -471,7 +474,7 @@ Route::group(function () {
// 员工端和学员端合同签署接口
Route::post('contract/signStaffContract', 'apiController.Contract/signStaffContract');
Route::post('contract/signStudentContract', 'apiController.Contract/signStudentContract');
//服务管理
Route::get('personnel/myServiceLogs', 'apiController.Personnel/myServiceLogs');
@ -616,6 +619,7 @@ Route::group(function () {
// 员工端生成合同
Route::post('contract/confirmGenerateContract', 'apiController.Contract/confirmGenerateContract');
})->middleware(ApiChannel::class)
->middleware(ApiPersonnelCheckToken::class, true)
->middleware(ApiLog::class);

4
niucloud/app/api/route/student.php

@ -46,7 +46,7 @@ Route::group('physical-test', function () {
// 获取体测趋势数据
Route::get('trend', 'app\api\controller\student\PhysicalTestController@getPhysicalTestTrend');
// PDF转图片分享
Route::post('share/:test_id', 'app\api\controller\student\PhysicalTestController@sharePhysicalTestPdf');
Route::post('share', 'app\api\controller\student\PhysicalTestController@sharePhysicalTestPdf');
})->middleware(['ApiCheckToken']);
// 课程预约管理
@ -150,4 +150,4 @@ Route::get('wechat/callback', 'login.WechatLogin/callback');
Route::group('student', function () {
// 获取合同签署表单(无需验证)
Route::get('contract/sign-form', 'student.StudentContract/getSignForm');
});
});

148
niucloud/app/common.php

@ -14,7 +14,7 @@ use app\service\core\upload\CoreImageService;
use app\service\core\sys\CoreSysConfigService;
use app\service\admin\performance\PerformanceService;
// 应用公共文件
// 应用公共文件123
/**
* 接口操作成功,返回信息
@ -1521,11 +1521,11 @@ function getValidCourseByStudentId($studentId)
* @param int $campus_id 校区ID(必填)
* @param array $dept_ids 部门ID数组,例如:[1, 2, 23] 对应市场、教务、教练部门
* @return array 人员列表,包含person_id、name、phone等信息
*
*
* 使用示例:
* // 获取校区1的市场人员
* $market_staff = get_personnel_by_campus_dept(1, [1]);
*
*
* // 获取校区2的教练和教务人员
* $coaches_and_educators = get_personnel_by_campus_dept(2, [24, 2]);
*/
@ -1534,18 +1534,18 @@ function get_personnel_by_campus_dept($campus_id, $dept_ids = [])
if (empty($campus_id) || empty($dept_ids)) {
return [];
}
try {
// 1. 根据部门ID获取对应的角色ID,使用正确的表名
$role_ids = \think\facade\Db::table('school_sys_role')->where([
['dept_id', 'in', $dept_ids],
['status', '=', 1]
])->column('role_id');
if (empty($role_ids)) {
return [];
}
// 2. 根据校区ID和角色ID获取人员
$personnel_list = \think\facade\Db::table('school_campus_person_role')
->alias('cpr')
@ -1559,9 +1559,9 @@ function get_personnel_by_campus_dept($campus_id, $dept_ids = [])
->field('cpr.person_id, p.name, p.phone, cpr.role_id, cpr.dept_id')
->select()
->toArray();
return $personnel_list;
} catch (\Exception $e) {
\think\facade\Log::write('获取校区部门人员失败:' . $e->getMessage());
return [];
@ -1573,7 +1573,7 @@ function get_personnel_by_campus_dept($campus_id, $dept_ids = [])
* @param int $campus_id 校区ID
* @param array $role_ids 角色ID数组
* @return array 人员列表
*
*
* 使用示例:
* // 获取指定校区的教练主管和普通教练
* $coaches = get_personnel_by_campus_role(1, [1, 5]);
@ -1583,7 +1583,7 @@ function get_personnel_by_campus_role($campus_id, $role_ids = [])
if (empty($campus_id) || empty($role_ids)) {
return [];
}
try {
// 根据校区ID和角色ID直接获取人员
$personnel_list = \think\facade\Db::table('school_campus_person_role')
@ -1598,9 +1598,9 @@ function get_personnel_by_campus_role($campus_id, $role_ids = [])
->field('cpr.person_id, p.name, p.phone, cpr.role_id, cpr.dept_id')
->select()
->toArray();
return $personnel_list;
} catch (\Exception $e) {
\think\facade\Log::write('获取校区角色人员失败:' . $e->getMessage());
return [];
@ -1722,11 +1722,11 @@ function get_current_campus($campus_id = 0)
// 这里需要根据实际的用户会话机制来获取
return '默认校区';
}
$campus = \think\facade\Db::table('school_campus')
->where('id', $campus_id)
->value('campus_name');
return $campus ?: '未知校区';
} catch (\Exception $e) {
return '获取校区失败';
@ -1853,7 +1853,7 @@ function get_class_schedule_rules($params = [])
'advance_booking_days' => 7, // 提前预约天数
'cancel_deadline_hours' => 24, // 取消课程截止时间(小时)
];
// 合并自定义参数
return array_merge($default_rules, $params);
}
@ -1869,29 +1869,29 @@ function generate_class_schedule($start_date, $end_date, $rules = [])
{
$rules = get_class_schedule_rules($rules);
$schedule = [];
$current_date = strtotime($start_date);
$end_timestamp = strtotime($end_date);
while ($current_date <= $end_timestamp) {
$date_str = date('Y-m-d', $current_date);
$day_of_week = date('w', $current_date);
// 检查是否为周末
if (!$rules['weekend_enabled'] && ($day_of_week == 0 || $day_of_week == 6)) {
$current_date = strtotime('+1 day', $current_date);
continue;
}
// 生成当天的课程时间段
$daily_schedule = generate_daily_time_slots($date_str, $rules);
if (!empty($daily_schedule)) {
$schedule[$date_str] = $daily_schedule;
}
$current_date = strtotime('+1 day', $current_date);
}
return $schedule;
}
@ -1908,18 +1908,18 @@ function generate_daily_time_slots($date, $rules)
$end_time = strtotime($date . ' ' . $rules['end_time']);
$lunch_start = strtotime($date . ' ' . $rules['lunch_break_start']);
$lunch_end = strtotime($date . ' ' . $rules['lunch_break_end']);
$slot_number = 1;
while ($current_time < $end_time) {
$slot_end = $current_time + ($rules['class_duration'] * 60);
// 检查是否与午休时间冲突
if ($current_time < $lunch_end && $slot_end > $lunch_start) {
$current_time = $lunch_end;
continue;
}
$slots[] = [
'slot_number' => $slot_number,
'start_time' => date('H:i', $current_time),
@ -1927,11 +1927,11 @@ function generate_daily_time_slots($date, $rules)
'duration' => $rules['class_duration'],
'available' => true
];
$current_time = $slot_end + ($rules['break_duration'] * 60);
$slot_number++;
}
return $slots;
}
@ -1951,41 +1951,41 @@ function check_class_time_conflict($date, $start_time, $end_time, $teacher_id =
['date', '=', $date],
['status', '=', 1], // 正常状态
];
// 时间冲突检查:新课程的开始时间在已有课程时间范围内,或新课程的结束时间在已有课程时间范围内
$time_conflict = [
['start_time', '<', $end_time],
['end_time', '>', $start_time]
];
$where = array_merge($where, $time_conflict);
// 检查教师冲突
if ($teacher_id > 0) {
$teacher_conflict = \think\facade\Db::table('school_class_schedule')
->where($where)
->where('teacher_id', $teacher_id)
->count();
if ($teacher_conflict > 0) {
return true; // 有冲突
}
}
// 检查教室冲突
if ($classroom_id > 0) {
$classroom_conflict = \think\facade\Db::table('school_class_schedule')
->where($where)
->where('classroom_id', $classroom_id)
->count();
if ($classroom_conflict > 0) {
return true; // 有冲突
}
}
return false; // 无冲突
} catch (\Exception $e) {
\think\facade\Log::write('检查上课时间冲突失败:' . $e->getMessage());
return true; // 异常情况下认为有冲突,确保安全
@ -2005,23 +2005,23 @@ function get_campus_seal_image($campus_id)
if (empty($campus_id)) {
return '';
}
$seal_image = \think\facade\Db::table('school_campus')
->where('id', $campus_id)
->value('seal_image');
if (empty($seal_image)) {
return '';
}
// 如果已经是完整URL,直接返回
if (str_contains($seal_image, 'http://') || str_contains($seal_image, 'https://')) {
return $seal_image;
}
// 转换为完整的URL路径
return get_file_url($seal_image);
} catch (\Exception $e) {
\think\facade\Log::write('获取校区印章图片失败:' . $e->getMessage());
return '';
@ -2046,9 +2046,9 @@ function process_signature_image($file, $options = [])
'max_width' => 800, // 最大宽度
'max_height' => 600, // 最大高度
];
$config = array_merge($default_options, $options);
// 验证文件大小
if ($file['size'] > $config['max_size']) {
return [
@ -2056,7 +2056,7 @@ function process_signature_image($file, $options = [])
'message' => '图片文件大小不能超过' . ($config['max_size'] / 1024 / 1024) . 'MB'
];
}
// 验证文件类型
$file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($file_ext, $config['allowed_types'])) {
@ -2065,7 +2065,7 @@ function process_signature_image($file, $options = [])
'message' => '只支持' . implode('、', $config['allowed_types']) . '格式的图片'
];
}
// 创建保存目录
$save_dir = $config['save_path'] . date('Y/m/d') . '/';
if (!is_dir($save_dir) && !mkdir($save_dir, 0755, true)) {
@ -2074,11 +2074,11 @@ function process_signature_image($file, $options = [])
'message' => '创建保存目录失败'
];
}
// 生成文件名
$filename = 'signature_' . date('YmdHis') . '_' . uniqid() . '.' . $file_ext;
$save_path = $save_dir . $filename;
// 移动文件
if (!move_uploaded_file($file['tmp_name'], $save_path)) {
return [
@ -2086,13 +2086,13 @@ function process_signature_image($file, $options = [])
'message' => '文件保存失败'
];
}
// 压缩图片(如果需要)
$compressed_path = compress_signature_image($save_path, $config);
if ($compressed_path) {
$save_path = $compressed_path;
}
return [
'success' => true,
'message' => '签名图片上传成功',
@ -2103,7 +2103,7 @@ function process_signature_image($file, $options = [])
'original_name' => $file['name']
]
];
} catch (\Exception $e) {
\think\facade\Log::write('处理签名图片失败:' . $e->getMessage());
return [
@ -2127,19 +2127,19 @@ function compress_signature_image($source_path, $config)
if (!$image_info) {
return false;
}
[$width, $height, $type] = $image_info;
// 如果图片尺寸已经符合要求,不需要压缩
if ($width <= $config['max_width'] && $height <= $config['max_height']) {
return $source_path;
}
// 计算新尺寸
$ratio = min($config['max_width'] / $width, $config['max_height'] / $height);
$new_width = intval($width * $ratio);
$new_height = intval($height * $ratio);
// 创建源图像资源
switch ($type) {
case IMAGETYPE_JPEG:
@ -2154,14 +2154,14 @@ function compress_signature_image($source_path, $config)
default:
return false;
}
if (!$source_image) {
return false;
}
// 创建新图像
$new_image = imagecreatetruecolor($new_width, $new_height);
// 保持透明度(PNG/GIF)
if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) {
imagealphablending($new_image, false);
@ -2169,14 +2169,14 @@ function compress_signature_image($source_path, $config)
$transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
imagefill($new_image, 0, 0, $transparent);
}
// 重新采样
imagecopyresampled($new_image, $source_image, 0, 0, 0, 0,
imagecopyresampled($new_image, $source_image, 0, 0, 0, 0,
$new_width, $new_height, $width, $height);
// 生成压缩后的文件路径
$compressed_path = str_replace('.', '_compressed.', $source_path);
// 保存压缩后的图片
$saved = false;
switch ($type) {
@ -2190,19 +2190,19 @@ function compress_signature_image($source_path, $config)
$saved = imagegif($new_image, $compressed_path);
break;
}
// 释放资源
imagedestroy($source_image);
imagedestroy($new_image);
if ($saved) {
// 删除原文件
unlink($source_path);
return $compressed_path;
}
return false;
} catch (\Exception $e) {
\think\facade\Log::write('压缩签名图片失败:' . $e->getMessage());
return false;
@ -2221,7 +2221,7 @@ function get_user_signature_records($user_id, $user_type = 'staff')
if (empty($user_id)) {
return [];
}
$signatures = \think\facade\Db::table('school_user_signatures')
->where([
['user_id', '=', $user_id],
@ -2232,16 +2232,16 @@ function get_user_signature_records($user_id, $user_type = 'staff')
->order('created_at', 'desc')
->select()
->toArray();
// 处理签名图片URL
foreach ($signatures as &$signature) {
if (!empty($signature['signature_image'])) {
$signature['signature_url'] = get_file_url($signature['signature_image']);
}
}
return $signatures;
} catch (\Exception $e) {
\think\facade\Log::write('获取用户签名记录失败:' . $e->getMessage());
return [];
@ -2262,7 +2262,7 @@ function save_user_signature($data)
throw new \Exception("缺少必要字段:{$field}");
}
}
$insert_data = [
'user_id' => $data['user_id'],
'user_type' => $data['user_type'],
@ -2276,19 +2276,19 @@ function save_user_signature($data)
'updated_at' => date('Y-m-d H:i:s'),
'deleted_at' => 0
];
$signature_id = \think\facade\Db::table('school_user_signatures')
->insertGetId($insert_data);
if ($signature_id) {
\think\facade\Log::write('保存用户签名记录成功:' . json_encode($insert_data));
return $signature_id;
}
return false;
} catch (\Exception $e) {
\think\facade\Log::write('保存用户签名记录失败:' . $e->getMessage());
return false;
}
}
}

20
niucloud/app/model/attendance/Attendance.php

@ -29,7 +29,7 @@ use app\model\personnel\Personnel;
class Attendance extends BaseModel
{
/**
* 数据表主键
@ -52,7 +52,7 @@ class Attendance extends BaseModel
'leave'=>'请假'
];
/**
* 搜索器:考勤校区
@ -65,7 +65,7 @@ class Attendance extends BaseModel
$query->where("campus_id", $value);
}
}
/**
* 搜索器:考勤人员
* @param $value
@ -77,7 +77,7 @@ class Attendance extends BaseModel
$query->where("staff_id", $value);
}
}
/**
* 搜索器:考勤考勤日期
* @param $value
@ -89,7 +89,7 @@ class Attendance extends BaseModel
$query->where("attendance_date", $value);
}
}
/**
* 搜索器:考勤考勤状态
* @param $value
@ -116,7 +116,7 @@ class Attendance extends BaseModel
$val = (String)$data['status'];
if ((!empty($val) || isset($val)) && $val !== '') {
$dict = Dict::where('key',$key)->find();
$dictionary = $dict['dictionary'] ?? [];
$dictionary = $dict['dictionary'] ? json_decode(json_decode($dict['dictionary'],true),true) : [];
// 查找匹配的 name
$res = '';
foreach ($dictionary as $item) {
@ -130,12 +130,12 @@ class Attendance extends BaseModel
return '';
}
}
public function campus(){
return $this->hasOne(Campus::class, 'id', 'campus_id')->joinType('left')->withField('campus_name,id')->bind(['campus_id_name'=>'campus_name']);
}

29
niucloud/app/service/admin/classroom/ClassroomService.php

@ -70,6 +70,8 @@ class ClassroomService extends BaseAdminService
*/
public function add(array $data)
{
$campus = new Campus();
$data['campus_name'] = $campus->where(['id' => $data['campus_id']])->value('campus_name');
$res = $this->model->create($data);
return $res->id;
@ -83,7 +85,8 @@ class ClassroomService extends BaseAdminService
*/
public function edit(int $id, array $data)
{
$campus = new Campus();
$data['campus_name'] = $campus->where(['id' => $data['campus_id']])->value('campus_name');
$this->model->where([['id', '=', $id]])->update($data);
return true;
}
@ -113,18 +116,18 @@ class ClassroomService extends BaseAdminService
public function getPersonnelAll($params = [])
{
$campus_id = $params['campus_id'] ?? 0;
// 如果没有传校区ID,返回所有人员
if (empty($campus_id)) {
$personnelModel = new Personnel();
return $personnelModel->field('id, name, phone')->select()->toArray();
}
// 根据校区ID获取对应的教练、教务、市场人员
// 部门ID: 1=市场, 2=教务, 23=教练(实际系统中教练角色的dept_id是23)
$dept_ids = [1, 2, 23];
$personnel_list = get_personnel_by_campus_dept($campus_id, $dept_ids);
// 格式化返回数据,保持与原接口一致
$result = [];
foreach ($personnel_list as $person) {
@ -136,7 +139,7 @@ class ClassroomService extends BaseAdminService
'dept_id' => $person['dept_id']
];
}
return $result;
}
@ -146,18 +149,18 @@ class ClassroomService extends BaseAdminService
public function getCoachPersonnel($params = [])
{
$campus_id = $params['campus_id'] ?? 0;
// 如果没有传校区ID,返回所有教练人员
if (empty($campus_id)) {
// 这里可以直接查询所有教练角色的人员,但为了保持一致性,我们使用公共方法
$personnelModel = new Personnel();
return $personnelModel->field('id, name, phone')->select()->toArray();
}
// 根据校区ID获取教练人员(角色ID: 1=教练主管, 5=普通教练)
$role_ids = [1, 5];
$personnel_list = get_personnel_by_campus_role($campus_id, $role_ids);
// 格式化返回数据
$result = [];
foreach ($personnel_list as $person) {
@ -169,7 +172,7 @@ class ClassroomService extends BaseAdminService
'dept_id' => $person['dept_id']
];
}
return $result;
}
@ -179,17 +182,17 @@ class ClassroomService extends BaseAdminService
public function getEducationalPersonnel($params = [])
{
$campus_id = $params['campus_id'] ?? 0;
// 如果没有传校区ID,返回所有教务人员
if (empty($campus_id)) {
$personnelModel = new Personnel();
return $personnelModel->field('id, name, phone')->select()->toArray();
}
// 根据校区ID获取教务人员(部门ID: 2=教务)
$dept_ids = [2];
$personnel_list = get_personnel_by_campus_dept($campus_id, $dept_ids);
// 格式化返回数据
$result = [];
foreach ($personnel_list as $person) {
@ -201,7 +204,7 @@ class ClassroomService extends BaseAdminService
'dept_id' => $person['dept_id']
];
}
return $result;
}

5
niucloud/app/service/admin/contract/ContractDistributionService.php

@ -72,10 +72,9 @@ class ContractDistributionService extends BaseAdminService
'contract_id' => $contractId,
'personnel_id' => $personnelId,
'type' => $type,
'status' => 'pending',
'status' => 1,
'source_type' => 'manual',
'source_id' => null,
'created_at' => time()
'source_id' => null
];
$this->model->create($data);

11
niucloud/app/service/admin/order_table/OrderTableService.php

@ -87,15 +87,16 @@ class OrderTableService extends BaseAdminService
public function add(array $data)
{
$personnel = new Personnel();
$data['staff_id'] = $personnel->where(['sys_user_id' => $this->uid])->value("id");
if(!$data['staff_id']){
return fail("操作失败");
}
// $data['staff_id'] = $personnel->where(['sys_user_id' => $this->uid])->value("id");
// if(!$data['staff_id']){
// return fail("操作人员角色不对");
// }
$cr = new CustomerResources();
$data['campus_id'] = $cr->where(['id' => $data['resource_id']])->value("campus");
// $data['staff_id'] = 1;
$data['staff_id'] = 51;
$res = $this->model->create($data);
return success("操作成功");

2
niucloud/app/service/admin/personnel/PersonnelService.php

@ -170,7 +170,7 @@ class PersonnelService extends BaseAdminService
}
}
$this->model->where([['id', '=', $id]])->update($data);
unset($info['birthday']);
(new PersonnelInfo())->where(['person_id' => $id])->update($info);
Db::commit();

13
niucloud/app/service/api/apiService/ContractService.php

@ -48,6 +48,7 @@ class ContractService extends BaseApiService
return $res;
}
// 查询合同签订记录,关联合同表
// type=1是给员工签的合同,关联的是school_personnel表
$contractSignModel = new ContractSign();
@ -219,7 +220,7 @@ class ContractService extends BaseApiService
// 生成签署后的合同文档
$generatedFile = null;
$useSignatureImage = $signature_image ?: $sign_file; // 优先使用新的signature_image字段
if ($useSignatureImage || $form_data) {
$generatedFile = $this->generateStaffSignedContract($contract_id, $personnel_id, $form_data, $useSignatureImage);
}
@ -302,7 +303,7 @@ class ContractService extends BaseApiService
}
$signData = $contractSign->toArray();
// 判断签订状态
$signData['is_signed'] = !empty($signData['sign_file']);
$signData['can_sign'] = $signData['status'] == 1 && empty($signData['sign_file']);
@ -495,7 +496,7 @@ class ContractService extends BaseApiService
foreach ($configs as $config) {
$placeholder = str_replace(['{{', '}}'], '', $config['placeholder']);
$value = $formData[$placeholder] ?? '';
if (empty($value)) {
throw new \Exception("必填字段 {$placeholder} 不能为空");
}
@ -551,11 +552,11 @@ class ContractService extends BaseApiService
if ($signatureImage) {
// 处理签名图片
$signImagePath = $this->processStaffSignatureImage($signatureImage);
// 使用ContractSign服务插入签名
$contractSignService = new ContractSignService();
$contractSignService->setSign($templatePath, $outputFullPath, $signImagePath, '员工签名');
// 清理临时文件
if (file_exists($signImagePath)) {
unlink($signImagePath);
@ -718,4 +719,4 @@ class ContractService extends BaseApiService
return $tempImagePath;
}
}
}

30
niucloud/app/service/api/apiService/OrderTableService.php

@ -223,17 +223,23 @@ class OrderTableService extends BaseApiService
*/
public function handlePaymentSuccess(array $orderArray)
{
// 1. 为学员分配课程
$this->assignCourseToStudent($orderArray);
if($orderArray['payment_type'] !== 'deposit'){
// 1. 为学员分配课程
$this->assignCourseToStudent($orderArray);
// 2. 创建合同签署记录
$this->createContractSign($orderArray);
// 2. 创建合同签署记录
$this->createContractSign($orderArray);
// 3. 创建支付记录
$this->createPaymentRecord($orderArray);
// 4.处理赠品
$this->handleGift($orderArray);
}
if($orderArray['payment_type'] == 'deposit'){
// 3. 创建支付记录
$this->createPaymentRecord($orderArray);
}
// 4.处理赠品
$this->handleGift($orderArray);
}
/**
@ -295,7 +301,7 @@ class OrderTableService extends BaseApiService
];
$result = Db::table('school_student_courses')->insert($insertData, true);
// 更新订单表的课程计划ID
if ($result) {
OrderTable::where('id', $orderData['id'])->update(['course_plan_id' => $result]);
@ -330,12 +336,12 @@ class OrderTableService extends BaseApiService
];
$result = Db::table('school_student_courses')->insert($insertData, true);
// 更新订单表的课程计划ID
if ($result) {
OrderTable::where('id', $orderData['id'])->update(['course_plan_id' => $result]);
}
\think\facade\Log::info('学员课程创建成功', [
'student_id' => $student_id,
'course_id' => $course_id,
@ -475,7 +481,7 @@ class OrderTableService extends BaseApiService
// 确保$config是数组
if (!is_array($config)) {
\think\facade\Log::warning('跳过无效配置项', [
'placeholder' => $placeholder_name,
'placeholder' => $placeholder_name,
'config' => $config
]);
continue;

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

@ -226,12 +226,12 @@ class ResourceSharingService extends BaseApiService
if (!empty($where['phone_number'])) {
$resource_conditions[] = ['phone_number', 'like', '%' . $where['phone_number'] . '%'];
}
// 来源查询
if (!empty($where['source'])) {
$resource_conditions[] = ['source', 'like', '%' . $where['source'] . '%'];
}
// 来源渠道查询
if (!empty($where['source_channel'])) {
$resource_conditions[] = ['source_channel', 'like', '%' . $where['source_channel'] . '%'];
@ -259,7 +259,7 @@ class ResourceSharingService extends BaseApiService
$model = $model->where('shared_at', '>=', $where['shared_at_arr'][0])
->where('shared_at', '<=', $where['shared_at_arr'][1]);
}
// 处理有效性查询 - 需要通过six_speed表关联查询
if (!empty($where['valid_type'])) {
$six_speed_model = new \app\model\six_speed\SixSpeed();
@ -268,14 +268,14 @@ class ResourceSharingService extends BaseApiService
} else if ($where['valid_type'] === '无效') {
$valid_resource_ids = $six_speed_model->where('efficacious', 0)->column('resource_id');
}
if (isset($valid_resource_ids)) {
if (empty($resource_ids)) {
$resource_ids = $valid_resource_ids;
} else {
$resource_ids = array_intersect($resource_ids, $valid_resource_ids);
}
if (empty($resource_ids)) {
return [
'count' => 0,
@ -288,7 +288,7 @@ class ResourceSharingService extends BaseApiService
$model = $model->whereIn('resource_id', $resource_ids);
}
}
// 处理成交类型查询 - 通过订单表关联查询
if (!empty($where['deal_type'])) {
$order_model = new OrderTable();
@ -299,15 +299,20 @@ class ResourceSharingService extends BaseApiService
$paid_resource_ids = $order_model->where('order_status', 'paid')->column('resource_id');
$all_resource_ids = (new CustomerResources())->column('id');
$deal_resource_ids = array_diff($all_resource_ids, $paid_resource_ids);
} else if ($where['deal_type'] === '定金') {
// 没有付费订单的资源
$paid_resource_ids = $order_model->where('payment_type', 'deposit')->column('resource_id');
$all_resource_ids = (new CustomerResources())->column('id');
$deal_resource_ids = array_diff($all_resource_ids, $paid_resource_ids);
}
if (isset($deal_resource_ids)) {
if (empty($resource_ids)) {
$resource_ids = $deal_resource_ids;
} else {
$resource_ids = array_intersect($resource_ids, $deal_resource_ids);
}
if (empty($resource_ids)) {
return [
'count' => 0,
@ -320,7 +325,7 @@ class ResourceSharingService extends BaseApiService
$model = $model->whereIn('resource_id', $resource_ids);
}
}
// 处理沟通情况查询 - 通过沟通记录表关联查询
if (!empty($where['communication_status'])) {
if ($where['communication_status'] === '已沟通') {
@ -331,14 +336,14 @@ class ResourceSharingService extends BaseApiService
$all_resource_ids = (new CustomerResources())->column('id');
$comm_resource_ids = array_diff($all_resource_ids, $comm_resource_ids_with_records);
}
if (isset($comm_resource_ids)) {
if (empty($resource_ids)) {
$resource_ids = $comm_resource_ids;
} else {
$resource_ids = array_intersect($resource_ids, $comm_resource_ids);
}
if (empty($resource_ids)) {
return [
'count' => 0,
@ -504,7 +509,7 @@ class ResourceSharingService extends BaseApiService
$six_speed_record = array_filter($six_speed_records, function($record) use ($resource_id) {
return $record['resource_id'] == $resource_id;
});
if (!empty($six_speed_record)) {
$six_speed_record = array_shift($six_speed_record);
$order_status[$resource_id] = $six_speed_record['is_closed'] ? '已开单' : '未开单';

26
niucloud/app/service/api/apiService/TeachingResearchService.php

@ -48,12 +48,12 @@ class TeachingResearchService extends BaseApiService
}
if ($id !== null && $id !== '') {
// 根据personnel_id获取sys_user_id
$sys_user_id = Db::table('school_personnel')
->where('id', $id)
->value('sys_user_id');
if ($sys_user_id) {
$where[] = [Db::raw("FIND_IN_SET($sys_user_id, user_permission)"), '>', 0];
// $sys_user_id = Db::table('school_personnel')
// ->where('id', $id)
// ->value('sys_user_id');
if ($id) {
$where[] = [Db::raw("FIND_IN_SET($id, user_permission)"), '>', 0];
}
}
$search_model = $LessonCourseTeaching->where($where)->field($field)->order($order);
@ -80,14 +80,16 @@ class TeachingResearchService extends BaseApiService
$where = [];
if ($id !== null && $id !== '') {
// 根据personnel_id获取sys_user_id
$sys_user_id = Db::table('school_personnel')
->where('id', $id)
->value('sys_user_id');
if ($sys_user_id) {
$where[] = [Db::raw("FIND_IN_SET($sys_user_id, user_permission)"), '>', 0];
// $sys_user_id = Db::table('school_personnel')
// ->where('id', $id)
// ->value('sys_user_id');
if ($id) {
$where[] = [Db::raw("FIND_IN_SET($id, user_permission)"), '>', 0];
}
}
$list = $LessonCourseTeaching->where($where)->distinct(true)->column('table_type');
return $list;
}

78
niucloud/app/service/api/personnel/WechatBindService.php

@ -23,7 +23,7 @@ use think\Response;
/**
* 人员微信绑定服务类
* Class WechatBindService
* Class WechatBindService
* @package app\service\api\personnel
*/
class WechatBindService extends BaseApiService
@ -46,7 +46,7 @@ class WechatBindService extends BaseApiService
// 小程序获取openid
$openid = $this->getMiniProgramOpenid($code);
} else {
// 公众号获取openid
// 公众号获取openid
$openid = $this->getOfficialOpenid($code);
}
@ -67,7 +67,7 @@ class WechatBindService extends BaseApiService
public function bindWechatOpenid(array $data)
{
$personnel_id = $this->request->memberId(); // 从token中获取当前登录的人员ID
if (empty($personnel_id)) {
throw new ApiException('用户未登录');
}
@ -79,7 +79,7 @@ class WechatBindService extends BaseApiService
}
$update_data = [];
// 根据类型绑定不同的openid
if ($data['type'] === 'miniprogram' && !empty($data['miniprogram_openid'])) {
// 检查小程序openid是否已被其他用户绑定
@ -90,7 +90,7 @@ class WechatBindService extends BaseApiService
throw new ApiException('该微信小程序已绑定其他账号');
}
$update_data['wxminiopenid'] = $data['miniprogram_openid'];
} elseif ($data['type'] === 'official' && !empty($data['official_openid'])) {
// 检查公众号openid是否已被其他用户绑定
$exists = SchoolPersonnel::where('wxgzhopenid', $data['official_openid'])
@ -100,7 +100,7 @@ class WechatBindService extends BaseApiService
throw new ApiException('该微信公众号已绑定其他账号');
}
$update_data['wxgzhopenid'] = $data['official_openid'];
} elseif ($data['type'] === 'both') {
// 同时绑定两个openid
if (!empty($data['miniprogram_openid'])) {
@ -112,7 +112,7 @@ class WechatBindService extends BaseApiService
}
$update_data['wxminiopenid'] = $data['miniprogram_openid'];
}
if (!empty($data['official_openid'])) {
$exists = SchoolPersonnel::where('wxgzhopenid', $data['official_openid'])
->where('id', '<>', $personnel_id)
@ -149,15 +149,18 @@ class WechatBindService extends BaseApiService
if (empty($redirect_uri)) {
throw new ApiException('回调地址不能为空');
}
$state_data = json_decode($state, true);
try {
// 使用现有的微信授权服务
$core_wechat_service = new CoreWechatServeService();
$callback_url = $core_wechat_service->authorization($redirect_uri . '?state=' . urlencode($state), 'snsapi_userinfo');
$callback_url = $core_wechat_service->authorization($redirect_uri . '?state=' . urlencode($state).'&personnel_id='.$state_data['personnel_id'].'&from='.$state_data['from'], 'snsapi_userinfo');
// 直接跳转到微信授权页面
return redirect($callback_url);
} catch (\Exception $e) {
throw new ApiException('生成授权链接失败:' . $e->getMessage());
}
@ -169,7 +172,7 @@ class WechatBindService extends BaseApiService
* @param string $state 状态参数
* @return Response
*/
public function wechatCallback(string $code, string $state)
public function wechatCallback(string $code, string $state, string $personnel_id, string $from)
{
try {
if (empty($code)) {
@ -178,19 +181,20 @@ class WechatBindService extends BaseApiService
// 解析state参数
$state_data = json_decode(urldecode($state), true);
if (!$state_data || !isset($state_data['personnel_id'])) {
if (!$personnel_id) {
return $this->buildCallbackPage('wechat_bind_error', '参数错误');
}
// 获取公众号openid
$official_openid = $this->getOfficialOpenid($code);
// 绑定公众号openid
$bind_result = $this->bindOfficialOpenid($state_data['personnel_id'], $official_openid);
$bind_result = $this->bindOfficialOpenid($personnel_id, $official_openid);
if ($bind_result) {
// 根据来源返回不同的页面
if (isset($state_data['from']) && $state_data['from'] === 'h5') {
if ($from == 'h5') {
// H5环境,返回简单的成功页面
return $this->buildH5SuccessPage('微信绑定成功!');
} else {
@ -200,7 +204,7 @@ class WechatBindService extends BaseApiService
} else {
return $this->buildCallbackPage('wechat_bind_error', '绑定失败');
}
} catch (\Exception $e) {
return $this->buildCallbackPage('wechat_bind_error', '绑定失败:' . $e->getMessage());
}
@ -213,7 +217,7 @@ class WechatBindService extends BaseApiService
public function getBindStatus()
{
$personnel_id = $this->request->memberId();
if (empty($personnel_id)) {
throw new ApiException('用户未登录');
}
@ -241,13 +245,13 @@ class WechatBindService extends BaseApiService
{
// 获取小程序配置
$weapp_config = $this->getWeappConfig();
// 构建请求URL
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=" . $weapp_config['app_id'] .
"&secret=" . $weapp_config['secret'] .
"&js_code=" . $code .
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=" . $weapp_config['app_id'] .
"&secret=" . $weapp_config['secret'] .
"&js_code=" . $code .
"&grant_type=authorization_code";
// 使用cURL发起HTTP请求,更稳定
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
@ -255,33 +259,33 @@ class WechatBindService extends BaseApiService
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false) {
throw new ApiException('获取小程序openid失败:网络请求失败');
}
if ($http_code !== 200) {
throw new ApiException('获取小程序openid失败:HTTP状态码' . $http_code);
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new ApiException('获取小程序openid失败:响应数据格式错误');
}
if (isset($result['errcode']) && $result['errcode'] != 0) {
throw new ApiException('获取小程序openid失败:' . ($result['errmsg'] ?? '未知错误') . '(错误码:' . $result['errcode'] . ')');
}
if (empty($result['openid'])) {
throw new ApiException('获取小程序openid失败:返回数据中无openid');
}
return $result['openid'];
}
@ -295,11 +299,11 @@ class WechatBindService extends BaseApiService
// 使用现有的微信服务获取用户信息
$core_wechat_service = new CoreWechatServeService();
$user = $core_wechat_service->userFromCode($code);
if (!$user) {
throw new ApiException('获取公众号openid失败');
}
return $user->getId(); // openid
}
@ -491,17 +495,17 @@ class WechatBindService extends BaseApiService
$config = Db::table('school_sys_config')
->where('config_key', 'weapp')
->value('value');
if (empty($config)) {
throw new ApiException('微信小程序未配置');
}
$config_data = is_string($config) ? json_decode($config, true) : $config;
if (empty($config_data['app_id']) || empty($config_data['app_secret'])) {
throw new ApiException('微信小程序配置不完整,缺少app_id或app_secret');
}
return [
'app_id' => $config_data['app_id'] ?? '',
'secret' => $config_data['app_secret'] ?? '',
@ -509,4 +513,4 @@ class WechatBindService extends BaseApiService
'aes_key' => $config_data['encoding_aes_key'] ?? ''
];
}
}
}

44
niucloud/app/service/api/student/PhysicalTestService.php

@ -41,11 +41,11 @@ class PhysicalTestService extends BaseService
$item['test_date'] = date('Y-m-d', strtotime($item['created_at']));
$item['height_text'] = $item['height'] . 'cm';
$item['weight_text'] = $item['weight'] . 'kg';
// 处理体测报告PDF文件
$item['has_report'] = !empty($item['physical_test_report']);
$item['report_files'] = [];
if ($item['physical_test_report']) {
$files = explode(',', $item['physical_test_report']);
foreach ($files as $file) {
@ -79,18 +79,18 @@ class PhysicalTestService extends BaseService
public function getPhysicalTestDetail($testId)
{
$physicalTest = (new PhysicalTest())->where('id', $testId)->find();
if (!$physicalTest) {
throw new CommonException('体测记录不存在');
}
$data = $physicalTest->toArray();
// 格式化数据
$data['test_date'] = date('Y-m-d', strtotime($data['created_at']));
$data['height_text'] = $data['height'] . 'cm';
$data['weight_text'] = $data['weight'] . 'kg';
// 处理各项体测指标
$indicators = [
'seated_forward_bend' => ['name' => '坐位体前屈', 'unit' => 'cm'],
@ -148,7 +148,7 @@ class PhysicalTestService extends BaseService
// 获取指定月份内的体测数据
$startDate = date('Y-m-d', strtotime("-{$months} months"));
$trendData = (new PhysicalTest())
->where('student_id', $studentId)
->where('created_at', '>=', $startDate)
@ -177,7 +177,7 @@ class PhysicalTestService extends BaseService
// 计算增长情况
$heightGrowth = 0;
$weightGrowth = 0;
if (count($heightData) >= 2) {
$heightGrowth = end($heightData)['value'] - $heightData[0]['value'];
$weightGrowth = end($weightData)['value'] - $weightData[0]['value'];
@ -206,22 +206,32 @@ class PhysicalTestService extends BaseService
public function convertPdfToImage($testId)
{
$physicalTest = (new PhysicalTest())->where('id', $testId)->find();
if (!$physicalTest) {
throw new CommonException('体测记录不存在');
}
// 本地保存路径
$saveDir = public_path() . "/upload/pdf_images/";
if (!is_dir($saveDir)) {
mkdir($saveDir, 0777, true);
}
// 下载远程 PDF 到本地临时文件
$pdfPath = $saveDir . uniqid() . ".pdf";
file_put_contents($pdfPath, file_get_contents($physicalTest['physical_test_report']));
// 获取第一个PDF文件
$files = explode(',', $physicalTest['physical_test_report']);
$pdfFile = trim($files[0]);
if (!$pdfFile) {
// $files = explode('/upload', $physicalTest['physical_test_report']);
// $pdfFile = '/upload'.trim($files[0]);
if (!$pdfPath) {
throw new CommonException('PDF文件路径无效');
}
// 构建完整文件路径
$pdfPath = public_path() . $pdfFile;
if (!file_exists($pdfPath)) {
throw new CommonException('PDF文件不存在');
}
@ -257,7 +267,7 @@ class PhysicalTestService extends BaseService
return [
'image_url' => get_image_url($imagePath),
'image_path' => $imagePath,
'pdf_url' => get_image_url($pdfFile),
'pdf_url' => get_image_url($pdfPath),
'message' => 'PDF转换为图片成功,可以分享给朋友了'
];
@ -266,4 +276,4 @@ class PhysicalTestService extends BaseService
}
}
}
}

3
niucloud/composer.json

@ -60,7 +60,8 @@
"hyperf/pimple": "~2.2.0",
"365taofang/huaweicloud-sdk-php-obs": "^3.23",
"ext-openssl": "*",
"phpoffice/phpword": "^1.3"
"phpoffice/phpword": "^1.3",
"ext-imagick": "*"
},
"require-dev": {
"symfony/var-dumper": "v6.0.19",

2
uniapp/api/member.js

@ -71,7 +71,7 @@ export default {
// 生成体测分享图片
async generateShareImage(data = {}) {
return await http.post('/share-image', data);
return await http.post('/physical-test/share', data);
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----添加孩子相关接口-----↓↓↓↓↓↓↓↓↓↓↓↓

4
uniapp/common/config.js

@ -3,8 +3,8 @@
const env = 'prod'
const isMockEnabled = false // 默认禁用Mock优先模式,仅作为回退
const isDebug = false // 默认启用调试模式
const devurl = 'http://localhost:20080/api'
const devimgurl = 'http://localhost:20080'
const devurl = 'https://api.hnhbty.cn/api'
const devimgurl = 'https://api.hnhbty.cn'
const produrl = 'https://api.hnhbty.cn/api'
const prodimgurl = 'https://api.hnhbty.cn'
// API配置 - 支持环境变量

2
uniapp/manifest.json

@ -1,6 +1,6 @@
{
"name" : "智慧教务",
"appid" : "__UNI__D7D400A",
"appid" : "__UNI__70E6889",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",

31
uniapp/pages-market/clue/add_clues.vue

@ -347,13 +347,13 @@
</view>
</view>
</fui-form-item>
<view style="color: #ffffff;width: 100%;text-align: left;margin: 10px;">
时间/距离
</view>
<!-- <view style="color: #ffffff;width: 100%;text-align: left;margin: 10px;">
时间/距离
</view> -->
<!--可选上课时间-->
<fui-form-item
label="1、 可选上课时间"
label="四、可选上课时间"
labelWidth="240"
labelSize='26'
prop=""
@ -381,7 +381,7 @@
</fui-form-item>
<!--承诺到访时间-->
<fui-form-item
label="2、 承诺到访时间"
label="五、承诺到访时间"
labelSize='26'
labelWidth="240"
prop=""
@ -393,14 +393,14 @@
<view
class="input-title"
style="margin-right:14rpx;"
@click="openDate(`promised_visit_time`)">
@click="openDateTime(`promised_visit_time`)">
{{ (formData.promised_visit_time) ? formData.promised_visit_time : '点击选择' }}
</view>
</view>
</fui-form-item>
<!--距离-->
<fui-form-item
label="3、 距离"
label="六、距离"
labelSize='26'
label-width="210"
prop="title"
@ -417,7 +417,7 @@
</view>
</fui-form-item>
<!-- 决策人 -->
<fui-form-item label="、决策人" labelSize='26' prop="decision_maker" background='#434544' labelColor='#fff'
<fui-form-item label="、决策人" labelSize='26' prop="decision_maker" background='#434544' labelColor='#fff'
:bottomBorder='false'>
<view class="input-title" style="margin-right:14rpx;">
<view class="input-title" style="margin-right:14rpx;" @click="openCicker('decision_maker')">
@ -427,7 +427,7 @@
</fui-form-item>
<!-- 情感粘度 -->
<fui-form-item
label="、情感粘度"
label="、情感粘度"
labelSize='26'
label-width="210"
prop="emotional_stickiness_score" background='#434544' labelColor='#fff'
@ -440,8 +440,9 @@
</fui-form-item>
<!--沟通备注-->
<fui-form-item
label="沟通备注"
label="九、沟通备注"
labelSize='26'
label-width="210"
prop="title"
background='#434544'
labelColor='#fff'
@ -471,7 +472,7 @@
<fui-date-picker
:show="date_picker_show"
type="5"
:startYear="2020"
:startYear="2025"
:endYear="2030"
:value="getCurrentDate()"
@change="change_date"
@ -481,8 +482,8 @@
<!-- 日期时间选择器 -->
<fui-date-picker
:show="datetime_picker_show"
type="1"
:startYear="2020"
type="5"
:startYear="2025"
:endYear="2030"
:value="getCurrentDateTime()"
@change="change_datetime"
@ -1671,7 +1672,7 @@ export default {
//
this.$nextTick(() => {
this.date_picker_show = true
this.datetime_picker_show = true
})
},
//
@ -1750,6 +1751,8 @@ export default {
val = val.replace(/\//g, '-')
}
}
let input_name = this.datetime_picker_input_name
this.formData[input_name] = val

2
uniapp/pages-market/clue/index.vue

@ -416,7 +416,7 @@
attendanceIndex: 0,
attendanceOptions: ['全部', '一访未到', '一访已到', '二访未到', '二访已到', '未到访'],
dealIndex: 0,
dealOptions: ['全部', '已成交', '未成交'],
dealOptions: ['全部', '已成交','定金', '未成交'],
validIndex: 0,
validOptions: ['全部', '有效', '无效'],
communicationIndex: 0,

602
uniapp/pages-market/my/set_up.vue

@ -7,7 +7,7 @@
<view class="option" @click="privacy_agreement(1)">用户协议</view>
<view class="option" @click="privacy_agreement(2)">隐私策略</view>
<view class="option" @click="clearCache()">清空缓存</view>
<view style="width:90%;margin: 60rpx auto;">
<fui-button background="#29d3b4" @click="loginOut()">退出账号</fui-button>
</view>
@ -15,31 +15,33 @@
</template>
<script>
import {Api_url} from '@/common/config.js'
import apiRoute from '@/api/apiRoute.js'
import {
Api_url
} from '@/common/config.js'
import apiRoute from '@/api/apiRoute.js'
export default {
data() {
return {
userInfo:{}
}
},
methods: {
//退
loginOut(){
this.$util.loginOut()
},
//退
loginOut() {
this.$util.loginOut()
},
privacy_agreement(type){
privacy_agreement(type) {
uni.navigateTo({
url: '/pages-common/privacy_agreement?type='+type
url: '/pages-common/privacy_agreement?type=' + type
})
},
update_pass(){
update_pass() {
uni.navigateTo({
url: '/pages-market/my/update_pass'
})
},
//
clearCache() {
uni.showModal({
@ -52,14 +54,14 @@ import apiRoute from '@/api/apiRoute.js'
}
});
},
//
performClearCache() {
try {
//
const storageInfo = uni.getStorageInfoSync();
const keys = storageInfo.keys;
// dict_
let clearCount = 0;
keys.forEach(key => {
@ -68,14 +70,14 @@ import apiRoute from '@/api/apiRoute.js'
clearCount++;
}
});
//
uni.showToast({
title: `已清空${clearCount}个字典缓存`,
icon: 'success',
duration: 2000
});
console.log(`清空缓存完成,共清理${clearCount}个dict_开头的缓存项`);
} catch (error) {
console.error('清空缓存失败:', error);
@ -86,284 +88,310 @@ import apiRoute from '@/api/apiRoute.js'
});
}
},
//
async buildwx(){
try {
console.log('开始微信绑定流程')
//
// #ifdef MP-WEIXIN
console.log('微信小程序环境')
await this.bindWeixinInMiniProgram()
// #endif
// #ifdef H5
console.log('H5环境')
if (this.isWeixinBrowser()) {
console.log('微信浏览器环境')
await this.bindWeixinInH5()
} else {
console.log('非微信浏览器环境')
uni.showModal({
title: '提示',
content: '请在微信中打开此页面进行微信绑定',
showCancel: false,
confirmText: '我知道了'
})
return
}
// #endif
// #ifndef MP-WEIXIN || H5
//
uni.showToast({
title: '当前环境不支持微信绑定',
icon: 'none'
})
// #endif
} catch (error) {
console.error('绑定微信失败:', error)
uni.hideLoading()
uni.showToast({
title: '绑定失败,请重试',
icon: 'none'
})
}
},
// - openidwebview
async bindWeixinInMiniProgram() {
uni.showLoading({
title: '正在获取微信信息...'
})
// 1. openid
console.log('步骤1: 获取小程序openid')
const miniProgramOpenid = await this.getMiniProgramOpenid()
console.log('获取到的openid:', miniProgramOpenid)
if (!miniProgramOpenid) {
uni.hideLoading()
uni.showToast({
title: '获取小程序openid失败',
icon: 'none'
})
return
}
// 2. openid
console.log('步骤2: 绑定小程序openid')
const bindResult = await this.bindMiniProgramOpenid(miniProgramOpenid)
console.log('绑定结果:', bindResult)
if (!bindResult) {
uni.hideLoading()
return
}
uni.hideLoading()
// 3. webviewopenid
console.log('步骤3: 跳转webview绑定公众号')
const webviewUrl = this.buildWebviewUrl(miniProgramOpenid)
console.log('webview URL:', webviewUrl)
uni.navigateTo({
url: `/pages-common/webview/wechat_bind?url=${encodeURIComponent(webviewUrl)}`
})
},
// H5
async bindWeixinInH5() {
uni.showLoading({
title: '正在跳转微信授权...'
})
try {
// H5
const baseUrl = Api_url
const redirectUri = encodeURIComponent(`${baseUrl}/personnel/wechatCallback`)
const state = encodeURIComponent(JSON.stringify({
personnel_id: this.$store.state.userInfo.id,
from: 'h5',
timestamp: Date.now()
}))
const authUrl = `${baseUrl}/personnel/wechatAuthorize?redirect_uri=${redirectUri}&state=${state}`
console.log('H5授权URL:', authUrl)
uni.hideLoading()
//
location.href = authUrl
} catch (error) {
console.error('H5微信绑定失败:', error)
uni.hideLoading()
uni.showToast({
title: '授权跳转失败',
icon: 'none'
})
}
},
// openid
getMiniProgramOpenid() {
return new Promise((resolve, reject) => {
console.log('开始获取小程序openid')
// #ifdef MP-WEIXIN
console.log('在微信小程序环境中')
uni.login({
provider: 'weixin',
success: (loginResult) => {
console.log('微信登录成功,code:', loginResult.code)
if (loginResult.code) {
// openid
apiRoute.getWechatOpenid({
code: loginResult.code,
type: 'miniprogram'
}).then(res => {
console.log('获取openid接口响应:', res)
if (res.code === 1) {
resolve(res.data.openid)
} else {
uni.showToast({
title: res.msg || '获取openid失败',
icon: 'none'
})
resolve(null)
}
}).catch(error => {
console.error('获取openid接口调用失败:', error)
resolve(null)
})
} else {
uni.showToast({
title: '微信登录失败',
icon: 'none'
})
resolve(null)
}
},
fail: (error) => {
console.error('微信登录失败:', error)
uni.showToast({
title: '微信登录失败',
icon: 'none'
})
resolve(null)
}
})
// #endif
// #ifndef MP-WEIXIN
console.log('非微信小程序环境,启用测试模式')
// 使codeAPI
uni.showModal({
title: '测试模式',
content: '当前非小程序环境,是否使用测试code调试接口?',
success: (res) => {
if (res.confirm) {
console.log('用户选择测试模式')
// 使code
apiRoute.getWechatOpenid({
code: 'test_code_' + Date.now(),
type: 'miniprogram'
}).then(res => {
console.log('测试模式接口响应:', res)
if (res.code === 1) {
resolve(res.data.openid)
} else {
console.log('API返回错误:', res.msg)
uni.showToast({
title: '测试模式:' + (res.msg || '获取openid失败'),
icon: 'none',
duration: 3000
})
// 使openid
resolve('test_openid_' + Date.now())
}
}).catch(error => {
console.error('测试模式接口调用失败:', error)
// openid
resolve('test_openid_' + Date.now())
})
} else {
console.log('用户取消测试')
resolve(null)
}
}
})
// #endif
})
},
// openid
bindMiniProgramOpenid(openid) {
return new Promise((resolve) => {
apiRoute.bindWechatOpenid({
miniprogram_openid: openid,
type: 'miniprogram'
}).then(res => {
if (res.code === 1) {
uni.showToast({
title: '小程序绑定成功',
icon: 'success'
})
resolve(true)
} else {
uni.showToast({
title: res.msg || '小程序绑定失败',
icon: 'none'
})
resolve(false)
}
}).catch(error => {
console.error('绑定小程序openid失败:', error)
uni.showToast({
title: '绑定失败,请重试',
icon: 'none'
})
resolve(false)
})
})
},
// webview URL
buildWebviewUrl(miniOpenid) {
const baseUrl = Api_url
const redirectUri = encodeURIComponent(`${baseUrl}/personnel/wechatCallback`)
const state = encodeURIComponent(JSON.stringify({
mini_openid: miniOpenid,
personnel_id: this.$store.state.userInfo.id,
from: 'miniprogram',
timestamp: Date.now()
}))
const authUrl = `${baseUrl}/personnel/wechatAuthorize?redirect_uri=${redirectUri}&state=${state}`
console.log('构建的授权URL:', authUrl)
return authUrl
},
//
isWeixinBrowser() {
const ua = navigator.userAgent.toLowerCase()
return ua.includes('micromessenger')
}
//
async buildwx() {
await this.getUserInfo()//
try {
console.log('开始微信绑定流程')
//
// #ifdef MP-WEIXIN
console.log('微信小程序环境')
await this.bindWeixinInMiniProgram()
// #endif
// #ifdef H5
// await this.bindWeixinInH5()
if (this.isWeixinBrowser()) {
console.log('微信浏览器环境')
await this.bindWeixinInH5()
} else {
console.log('非微信浏览器环境')
uni.showModal({
title: '提示',
content: '请在微信中打开此页面进行微信绑定',
showCancel: false,
confirmText: '我知道了'
})
return
}
// #endif
// #ifndef MP-WEIXIN || H5
//
uni.showToast({
title: '当前环境不支持微信绑定',
icon: 'none'
})
// #endif
} catch (error) {
console.error('绑定微信失败:', error)
uni.hideLoading()
uni.showToast({
title: '绑定失败,请重试',
icon: 'none'
})
}
},
// - openidwebview
async bindWeixinInMiniProgram() {
uni.showLoading({
title: '正在获取微信信息...'
})
// 1. openid
console.log('步骤1: 获取小程序openid')
const miniProgramOpenid = await this.getMiniProgramOpenid()
console.log('获取到的openid:', miniProgramOpenid)
if (!miniProgramOpenid) {
uni.hideLoading()
uni.showToast({
title: '获取小程序openid失败',
icon: 'none'
})
return
}
// 2. openid
console.log('步骤2: 绑定小程序openid')
const bindResult = await this.bindMiniProgramOpenid(miniProgramOpenid)
console.log('绑定结果:', bindResult)
if (!bindResult) {
uni.hideLoading()
return
}
uni.hideLoading()
// 3. webviewopenid
console.log('步骤3: 跳转webview绑定公众号')
const webviewUrl = this.buildWebviewUrl(miniProgramOpenid)
console.log('webview URL:', webviewUrl)
uni.navigateTo({
url: `/pages-common/webview/wechat_bind?url=${encodeURIComponent(webviewUrl)}`
})
},
// H5
async bindWeixinInH5() {
uni.showLoading({
title: '正在跳转微信授权...'
})
try {
// H5
const baseUrl = Api_url
const redirectUri = encodeURIComponent(`${baseUrl}/personnel/wechatCallback`)
const state = encodeURIComponent(JSON.stringify({
personnel_id: this.userInfo?.id,
from: 'h5',
timestamp: Date.now()
}))
const authUrl = `${baseUrl}/personnel/wechatAuthorize?redirect_uri=${redirectUri}&state=${state}`
console.log('H5授权URL:', authUrl)
uni.hideLoading()
//
location.href = authUrl
} catch (error) {
console.error('H5微信绑定失败:', error)
uni.hideLoading()
uni.showToast({
title: '授权跳转失败',
icon: 'none'
})
}
},
async getUserInfo() {
let data = {}
let res = await apiRoute.getPersonnelInfo(data);
if (res.code != 1) {
uni.showToast({
title: res.msg,
icon: 'none'
})
return
}
res.data.cameus_dept_arr.forEach((v, k) => {
let d_arr = []
v.dept_arr.forEach((dv, dk) => {
d_arr.push(dv.dept_name)
})
//
v.dept_name_str = d_arr.join(',')
})
this.userInfo = res.data
},
// openid
getMiniProgramOpenid() {
return new Promise((resolve, reject) => {
console.log('开始获取小程序openid')
// #ifdef MP-WEIXIN
console.log('在微信小程序环境中')
uni.login({
provider: 'weixin',
success: (loginResult) => {
console.log('微信登录成功,code:', loginResult.code)
if (loginResult.code) {
// openid
apiRoute.getWechatOpenid({
code: loginResult.code,
type: 'miniprogram'
}).then(res => {
console.log('获取openid接口响应:', res)
if (res.code === 1) {
resolve(res.data.openid)
} else {
uni.showToast({
title: res.msg || '获取openid失败',
icon: 'none'
})
resolve(null)
}
}).catch(error => {
console.error('获取openid接口调用失败:', error)
resolve(null)
})
} else {
uni.showToast({
title: '微信登录失败',
icon: 'none'
})
resolve(null)
}
},
fail: (error) => {
console.error('微信登录失败:', error)
uni.showToast({
title: '微信登录失败',
icon: 'none'
})
resolve(null)
}
})
// #endif
// #ifndef MP-WEIXIN
console.log('非微信小程序环境,启用测试模式')
// 使codeAPI
uni.showModal({
title: '测试模式',
content: '当前非小程序环境,是否使用测试code调试接口?',
success: (res) => {
if (res.confirm) {
console.log('用户选择测试模式')
// 使code
apiRoute.getWechatOpenid({
code: 'test_code_' + Date.now(),
type: 'miniprogram'
}).then(res => {
console.log('测试模式接口响应:', res)
if (res.code === 1) {
resolve(res.data.openid)
} else {
console.log('API返回错误:', res.msg)
uni.showToast({
title: '测试模式:' + (res.msg || '获取openid失败'),
icon: 'none',
duration: 3000
})
// 使openid
resolve('test_openid_' + Date.now())
}
}).catch(error => {
console.error('测试模式接口调用失败:', error)
// openid
resolve('test_openid_' + Date.now())
})
} else {
console.log('用户取消测试')
resolve(null)
}
}
})
// #endif
})
},
// openid
bindMiniProgramOpenid(openid) {
return new Promise((resolve) => {
apiRoute.bindWechatOpenid({
miniprogram_openid: openid,
type: 'miniprogram'
}).then(res => {
if (res.code === 1) {
uni.showToast({
title: '小程序绑定成功',
icon: 'success'
})
resolve(true)
} else {
uni.showToast({
title: res.msg || '小程序绑定失败',
icon: 'none'
})
resolve(false)
}
}).catch(error => {
console.error('绑定小程序openid失败:', error)
uni.showToast({
title: '绑定失败,请重试',
icon: 'none'
})
resolve(false)
})
})
},
// webview URL
buildWebviewUrl(miniOpenid) {
const baseUrl = Api_url
const redirectUri = encodeURIComponent(`${baseUrl}/personnel/wechatCallback`)
const state = encodeURIComponent(JSON.stringify({
mini_openid: miniOpenid,
personnel_id: this.userInfo?.id,
from: 'miniprogram',
timestamp: Date.now()
}))
const authUrl = `${baseUrl}/personnel/wechatAuthorize?redirect_uri=${redirectUri}&state=${state}`
console.log('构建的授权URL:', authUrl)
return authUrl
},
//
isWeixinBrowser() {
const ua = navigator.userAgent.toLowerCase()
return ua.includes('micromessenger')
}
}
}
</script>
<style lang="less" scoped>
.assemble{
.assemble {
width: 100%;
height: 100vh;
background: #333333;
background: #333333;
}
.option{
.option {
margin-bottom: 20rpx;
background: #404045;
width: 100%;
@ -372,4 +400,4 @@ import apiRoute from '@/api/apiRoute.js'
line-height: 56rpx;
padding: 20rpx 0 20rpx 100rpx;
}
</style>
</style>

1
uniapp/pages-student/orders/index.vue

@ -317,6 +317,7 @@
try {
//
const userInfo = uni.getStorageSync('userInfo')
if (userInfo && userInfo.id) {
// URLstudent_id使ID
if (!this.studentId) {

2
uniapp/pages-student/physical-test/index.vue

@ -279,7 +279,7 @@
try {
// APIPDF
const response = await apiRoute.convertPdfToImage({
const response = await apiRoute.generateShareImage({
pdf_url: pdfUrl,
test_id: testId,
pdf_index: pdfIndex

Loading…
Cancel
Save