Browse Source

修改 bug

develop
王泽彦 5 months ago
parent
commit
1d3fffba6a
  1. 380
      admin/src/app/views/personnel/components/BaseInfoForm.vue
  2. 350
      admin/src/app/views/personnel/components/DetailInfoForm.vue
  3. 844
      admin/src/app/views/personnel/components/personnel-edit.vue
  4. 32
      niucloud/app/api/controller/apiController/PersonCourseSchedule.php
  5. 51
      niucloud/app/api/controller/apiController/StudentCourse.php
  6. 2
      niucloud/app/api/route/route.php
  7. 40
      niucloud/app/common.php
  8. 1
      niucloud/app/service/api/apiService/CoachStudentService.php
  9. 237
      niucloud/app/service/api/apiService/CourseScheduleService.php
  10. 5
      niucloud/app/service/api/apiService/CourseService.php
  11. 239
      niucloud/app/service/api/apiService/PersonCourseScheduleService.php
  12. 5
      niucloud/app/service/api/student/ContractService.php
  13. 5
      uniapp/api/apiRoute.js
  14. 290
      uniapp/components/course-info-card/index.vue
  15. 24
      uniapp/pages-common/contract/staff-contract-sign.vue
  16. 1614
      uniapp/pages-market/course/course_detail.vue
  17. 10
      uniapp/pages.json

380
admin/src/app/views/personnel/components/BaseInfoForm.vue

@ -0,0 +1,380 @@
<template>
<el-form
:model="formData"
label-width="120px"
ref="formRef"
:rules="formRules"
class="base-info-form"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('name')" prop="name">
<el-input
v-model="formData.name"
clearable
:placeholder="t('namePlaceholder')"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('gender')">
<el-radio-group
v-model="formData.gender"
:placeholder="t('genderPlaceholder')"
>
<el-radio
v-for="(item, index) in genderList"
:key="index"
:label="item.value"
>
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('phone')" prop="phone">
<el-input
v-model="formData.phone"
clearable
:placeholder="t('phonePlaceholder')"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('address')">
<el-input
v-model="formData.address"
clearable
:placeholder="t('addressPlaceholder')"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('nativePlace')">
<el-input
v-model="formData.native_place"
clearable
:placeholder="t('nativePlacePlaceholder')"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="头像">
<upload-image v-model="formData.head_img" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('education')">
<el-select
class="input-width"
v-model="formData.education"
clearable
:placeholder="t('educationPlaceholder')"
>
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in educationList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('status')" prop="status">
<el-select
class="input-width"
v-model="formData.status"
clearable
:placeholder="t('statusPlaceholder')"
>
<el-option label="请选择" value=""></el-option>
<el-option
v-for="(item, index) in statusList"
:key="index"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item :label="t('profile')">
<el-input
v-model="formData.profile"
type="textarea"
rows="3"
clearable
:placeholder="t('profilePlaceholder')"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('emergencyContactPhone')">
<el-input
v-model="formData.emergency_contact_phone"
clearable
:placeholder="t('emergencyContactPhonePlaceholder')"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('isSysUser')" prop="is_sys_user">
<el-radio-group
v-model="formData.is_sys_user"
:placeholder="t('isSysUserPlaceholder')"
>
<el-radio
v-for="(item, index) in is_sys_userList"
:key="index"
:label="item.value"
>
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="t('idCardFront')">
<upload-image v-model="formData.id_card_front" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="t('idCardBack')">
<upload-image v-model="formData.id_card_back" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="所属校区" prop="campus_id">
<el-select
class="input-width"
v-model="formData.campus_id"
clearable
placeholder="请选择所属校区"
style="width: 300px;"
>
<el-option label="请选择" value=""></el-option>
<el-option
v-for="item in campusList"
:key="item.id"
:label="item.campus_name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useDictionary } from '@/app/api/dict'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { getWithCampusList } from '@/app/api/campus_person_role'
// Props
interface Props {
id?: string | number
}
const props = withDefaults(defineProps<Props>(), {
id: ''
})
//
const initialFormData = {
name: '',
gender: '',
head_img: '',
phone: '',
address: '',
native_place: '',
education: '',
profile: '',
emergency_contact_phone: '',
id_card_front: '',
id_card_back: '',
status: '',
is_sys_user: '',
campus_id: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
name: [{ required: true, message: t('namePlaceholder'), trigger: 'blur' }],
gender: [
{ required: true, message: t('genderPlaceholder'), trigger: 'blur' },
],
phone: [
{ required: true, message: t('phonePlaceholder'), trigger: 'blur' },
],
status: [
{ required: true, message: t('statusPlaceholder'), trigger: 'blur' },
],
is_sys_user: [
{ required: true, message: t('isSysUserPlaceholder'), trigger: 'blur' },
],
}
})
//
let genderList = ref([])
let educationList = ref([])
let statusList = ref([])
let is_sys_userList = ref([])
let campusList = ref([])
//
const genderDictList = async () => {
genderList.value = await (await useDictionary('gender')).data.dictionary
}
const educationDictList = async () => {
educationList.value = await (await useDictionary('education')).data.dictionary
}
const statusDictList = async () => {
statusList.value = await (await useDictionary('personnel_status')).data.dictionary
}
const is_sys_userDictList = async () => {
is_sys_userList.value = await (await useDictionary('global_true_or_false')).data.dictionary
}
const getCampusList = async () => {
const res = await getWithCampusList({})
if (res.data) {
campusList.value = res.data
}
}
//
watch(
() => genderList.value,
() => {
if (genderList.value.length > 0 && !formData.gender) {
formData.gender = genderList.value[0].value
}
}
)
watch(
() => educationList.value,
() => {
if (educationList.value.length > 0 && !formData.education) {
formData.education = educationList.value[0].value
}
}
)
watch(
() => statusList.value,
() => {
if (statusList.value.length > 0 && !formData.status) {
formData.status = statusList.value[0].value
}
}
)
watch(
() => is_sys_userList.value,
() => {
if (is_sys_userList.value.length > 0 && !formData.is_sys_user) {
formData.is_sys_user = is_sys_userList.value[0].value
}
}
)
//
const setFormData = async (data: any = null) => {
Object.assign(formData, initialFormData)
if (data) {
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) {
formData[key] = data[key]
}
})
}
}
//
const getFormData = () => {
return { ...formData }
}
//
const validateForm = async () => {
if (!formRef.value) return false
return await formRef.value.validate()
}
//
onMounted(() => {
genderDictList()
educationDictList()
statusDictList()
is_sys_userDictList()
getCampusList()
})
//
defineExpose({
setFormData,
getFormData,
validateForm,
formData
})
</script>
<style lang="scss" scoped>
.base-info-form {
.input-width {
width: 100%;
}
.el-form-item {
margin-bottom: 18px;
}
.el-textarea {
:deep(.el-textarea__inner) {
resize: vertical;
}
}
}
</style>

350
admin/src/app/views/personnel/components/DetailInfoForm.vue

@ -0,0 +1,350 @@
<template>
<el-form
:model="formData"
label-width="120px"
ref="formRef"
:rules="formRules"
class="detail-info-form"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="花名" prop="name">
<el-input
v-model="formData.name"
placeholder="请填写花名"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="门店" prop="store">
<el-input
v-model="formData.store"
placeholder="请填写门店"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="民族" prop="ethnicity">
<el-input
v-model="formData.ethnicity"
placeholder="请填写民族"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生日日期" prop="birthday">
<el-date-picker
v-model="formData.birthday"
type="date"
placeholder="请选择生日日期"
class="input-width"
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="age">
<el-input
v-model="formData.age"
placeholder="请填写年龄"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="司龄" prop="tenure">
<el-input
v-model="formData.tenure"
placeholder="请填写司龄"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="转正时间" prop="regular_date">
<el-date-picker
v-model="formData.regular_date"
type="date"
placeholder="请选择转正时间"
class="input-width"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否转正" prop="is_regular">
<el-select
v-model="formData.is_regular"
placeholder="请选择是否转正"
class="input-width"
clearable
>
<el-option label="是" value="是" />
<el-option label="否" value="否" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="政治面貌" prop="politics">
<el-input
v-model="formData.politics"
placeholder="请填写政治面貌"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="毕业院校" prop="university">
<el-input
v-model="formData.university"
placeholder="请填写毕业院校"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业" prop="major">
<el-input
v-model="formData.major"
placeholder="请填写专业"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="毕业时间" prop="graduation_date">
<el-date-picker
v-model="formData.graduation_date"
type="date"
placeholder="请选择毕业时间"
class="input-width"
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="household_place">
<el-input
v-model="formData.household_place"
placeholder="请填写户籍所在地"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="户籍类型" prop="household_type">
<el-input
v-model="formData.household_type"
placeholder="请填写户籍类型"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="婚否" prop="marital_status">
<el-select
v-model="formData.marital_status"
placeholder="请选择婚否"
class="input-width"
clearable
>
<el-option label="已婚" value="已婚" />
<el-option label="未婚" value="未婚" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="银行卡号" prop="bank_card">
<el-input
v-model="formData.bank_card"
placeholder="请填写银行卡号"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开户行" prop="bank_name">
<el-input
v-model="formData.bank_name"
placeholder="请填写开户行"
class="input-width"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同到期时间" prop="contract_expire">
<el-date-picker
v-model="formData.contract_expire"
type="date"
placeholder="请选择合同到期时间"
class="input-width"
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="is_rehired">
<el-select
v-model="formData.is_rehired"
placeholder="请选择是否复聘"
class="input-width"
clearable
>
<el-option label="是" value="是" />
<el-option label="否" value="否" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
placeholder="请填写备注"
class="input-width"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import type { FormInstance } from 'element-plus'
// Props
interface Props {
id?: string | number
}
const props = withDefaults(defineProps<Props>(), {
id: ''
})
//
const initialFormData = {
name: '',
store: '',
ethnicity: '',
birthday: '',
age: '',
tenure: '',
regular_date: '',
is_regular: '',
politics: '',
university: '',
education: '',
major: '',
graduation_date: '',
native_place: '',
household_place: '',
household_type: '',
household_address: '',
current_address: '',
emergency_contact: '',
emergency_phone: '',
marital_status: '',
bank_card: '',
bank_name: '',
contract_expire: '',
is_rehired: '',
remark: ''
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
//
name: [{ required: false, message: '请填写花名', trigger: 'blur' }],
store: [{ required: false, message: '请填写门店', trigger: 'blur' }],
}
})
//
const setFormData = async (data: any = null) => {
Object.assign(formData, initialFormData)
if (data) {
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) {
formData[key] = data[key]
}
})
}
}
//
const getFormData = () => {
return { ...formData }
}
//
const validateForm = async () => {
if (!formRef.value) return false
return await formRef.value.validate()
}
//
defineExpose({
setFormData,
getFormData,
validateForm,
formData
})
</script>
<style lang="scss" scoped>
.detail-info-form {
.input-width {
width: 100%;
}
.el-form-item {
margin-bottom: 18px;
}
.el-date-picker {
width: 100%;
:deep(.el-input__wrapper) {
width: 100%;
}
:deep(.el-input) {
width: 100%;
}
}
}
</style>

844
admin/src/app/views/personnel/components/personnel-edit.vue

@ -1,656 +1,274 @@
<template>
<el-dialog v-model="showDialog" :title="formData.id ? t('updatePersonnel') : t('addPersonnel')" width="50%"
class="diy-dialog-wrap" :destroy-on-close="true">
<el-tabs v-model="activeTab" class="tab-pane-half">
<el-dialog
v-model="showDialog"
:title="formData.id ? t('updatePersonnel') : t('addPersonnel')"
width="70%"
class="personnel-edit-dialog"
:destroy-on-close="true"
:close-on-click-modal="false"
>
<el-tabs v-model="activeTab" class="personnel-tabs">
<!-- Tab 1: 基本信息 -->
<el-tab-pane label="基本信息" name="base">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('name')" prop="name">
<el-input v-model="formData.name" clearable :placeholder="t('namePlaceholder')"
class="input-width" />
</el-form-item>
<el-form-item :label="t('gender')">
<el-radio-group v-model="formData.gender" :placeholder="t('genderPlaceholder')">
<el-radio v-for="(item, index) in genderList" :key="index" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('phone')" prop="phone">
<el-input v-model="formData.phone" clearable :placeholder="t('phonePlaceholder')"
class="input-width" />
</el-form-item>
<el-form-item :label="t('address')">
<el-input v-model="formData.address" clearable :placeholder="t('addressPlaceholder')"
class="input-width" />
</el-form-item>
<el-form-item :label="t('nativePlace')">
<el-input v-model="formData.native_place" clearable :placeholder="t('nativePlacePlaceholder')"
class="input-width" />
</el-form-item>
<el-form-item label="头像">
<upload-image v-model="formData.head_img" />
</el-form-item>
<el-form-item :label="t('education')">
<el-select class="input-width" v-model="formData.education" clearable
:placeholder="t('educationPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option v-for="(item, index) in educationList" :key="index" :label="item.name"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="t('profile')">
<el-input v-model="formData.profile" type="textarea" rows="4" clearable
:placeholder="t('profilePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('emergencyContactPhone')">
<el-input v-model="formData.emergency_contact_phone" clearable
:placeholder="t('emergencyContactPhonePlaceholder')" class="input-width" />
</el-form-item>
<el-form-item :label="t('idCardFront')">
<upload-image v-model="formData.id_card_front" />
</el-form-item>
<el-form-item :label="t('idCardBack')">
<upload-image v-model="formData.id_card_back" />
</el-form-item>
<el-form-item :label="t('status')" prop="status">
<el-select class="input-width" v-model="formData.status" clearable
:placeholder="t('statusPlaceholder')">
<el-option label="请选择" value=""></el-option>
<el-option v-for="(item, index) in statusList" :key="index" :label="item.name"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="t('isSysUser')" prop="is_sys_user">
<el-radio-group v-model="formData.is_sys_user" :placeholder="t('isSysUserPlaceholder')">
<el-radio v-for="(item, index) in is_sys_userList" :key="index" :label="item.value">
{{ item.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="所属校区" prop="campus_id">
<el-select class="input-width" v-model="formData.campus_id" clearable placeholder="请选择所属校区">
<el-option label="请选择" value=""></el-option>
<el-option v-for="item in campusList" :key="item.id" :label="item.campus_name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<div class="tab-content">
<BaseInfoForm
ref="baseInfoFormRef"
:id="formData.id"
/>
</div>
</el-tab-pane>
<!-- Tab 2: 详情信息 -->
<el-tab-pane label="详情信息" name="info">
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="花名" prop="info.name">
<el-input v-model="formData.info.name" placeholder="请填写花名" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="门店" prop="info.store">
<el-input v-model="formData.info.store" placeholder="请填写门店" class="input-width" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="民族" prop="info.ethnicity">
<el-input v-model="formData.info.ethnicity" placeholder="请填写民族" class="input-width" />
</el-form-item>
</el-col>
<el-form-item label="生日日期" prop="info.birthday">
<el-date-picker
v-model="formData.info.birthday"
type="date"
placeholder="请选择生日日期"
class="input-width"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年龄" prop="info.age">
<el-input v-model="formData.info.age" placeholder="请填写年龄" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="司龄" prop="info.tenure">
<el-input v-model="formData.info.tenure" placeholder="请填写司龄" class="input-width" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="转正时间" prop="info.regular_date">
<el-date-picker
v-model="formData.info.regular_date"
type="date"
placeholder="请选择转正时间"
class="input-width"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否转正" prop="info.is_regular">
<el-select
v-model="formData.info.is_regular"
placeholder="请选择是否转正"
class="input-width"
clearable
>
<el-option label="是" value="是" />
<el-option label="否" value="否" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="政治面貌" prop="info.politics">
<el-input v-model="formData.info.politics" placeholder="请填写政治面貌" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="毕业院校" prop="info.university">
<el-input v-model="formData.info.university" placeholder="请填写毕业院校" class="input-width" />
</el-form-item>
</el-col>
</el-row>
<!-- <el-row :gutter="20">
<el-col :span="12">
<el-form-item label="学历" prop="info.education">
<el-input v-model="formData.info.education" placeholder="请填写学历" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业" prop="info.major">
<el-input v-model="formData.info.major" placeholder="请填写专业" class="input-width" />
</el-form-item>
</el-col>
</el-row> -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业" prop="info.major">
<el-input v-model="formData.info.major" placeholder="请填写专业" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="毕业时间" prop="info.graduation_date">
<el-date-picker
v-model="formData.info.graduation_date"
type="date"
placeholder="请选择毕业时间"
class="input-width"
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="info.household_place">
<el-input v-model="formData.info.household_place" placeholder="请填写户籍所在地" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="户籍类型" prop="info.household_type">
<el-input v-model="formData.info.household_type" placeholder="请填写户籍类型" class="input-width" />
</el-form-item>
</el-col>
</el-row>
<!-- <el-row :gutter="20">
<el-col :span="12">
<el-form-item label="户籍地址" prop="info.household_address">
<el-input v-model="formData.info.household_address" placeholder="请填写户籍地址" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="现居地址" prop="info.current_address">
<el-input v-model="formData.info.current_address" placeholder="请填写现居地址" class="input-width" />
</el-form-item>
</el-col>
</el-row> -->
<!--
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="紧急联系人" prop="info.emergency_contact">
<el-input v-model="formData.info.emergency_contact" placeholder="请填写紧急联系人" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急联系人联系电话" prop="info.emergency_phone">
<el-input v-model="formData.info.emergency_phone" placeholder="请填写紧急联系人联系电话" class="input-width" />
</el-form-item>
</el-col>
</el-row> -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="婚否" prop="info.marital_status">
<el-select
v-model="formData.info.marital_status"
placeholder="请选择婚否"
class="input-width"
clearable
>
<el-option label="已婚" value="已婚" />
<el-option label="未婚" value="未婚" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="银行卡号" prop="info.bank_card">
<el-input v-model="formData.info.bank_card" placeholder="请填写银行卡号" class="input-width" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开户行" prop="info.bank_name">
<el-input v-model="formData.info.bank_name" placeholder="请填写开户行" class="input-width" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同到期时间" prop="info.contract_expire">
<el-date-picker
v-model="formData.info.contract_expire"
type="date"
placeholder="请选择合同到期时间"
class="input-width"
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="info.is_rehired">
<el-select
v-model="formData.info.is_rehired"
placeholder="请选择是否复聘"
class="input-width"
clearable
>
<el-option label="是" value="是" />
<el-option label="否" value="否" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="info.remark">
<el-input v-model="formData.info.remark" placeholder="请填写备注" class="input-width" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="tab-content">
<DetailInfoForm
ref="detailInfoFormRef"
:id="formData.id"
/>
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="loading"
@click="confirm(formRef)">{{ t('confirm') }}</el-button>
@click="confirm">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useDictionary } from '@/app/api/dict'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import {
addPersonnel,
editPersonnel,
getPersonnelInfo,
} from '@/app/api/personnel'
import { getWithCampusList } from '@/app/api/campus_person_role'
let showDialog = ref(false)
const loading = ref(false)
const activeTab = ref('base')
/**
* 表单数据
*/
const initialFormData = {
id: '',
name: '',
gender: '',
head_img: '',
phone: '',
address: '',
native_place: '',
education: '',
profile: '',
emergency_contact_phone: '',
id_card_front: '',
id_card_back: '',
status: '',
is_sys_user: '',
campus_id: '',
info:{
name:'',
store:'',
ethnicity:'',
birthday:'',
age:'',
tenure:'',
regular_date:'',
is_regular:'',
politics:'',
university:'',
education:'',
major:'',
graduation_date:'',
native_place:'',
household_place:'',
household_type:'',
household_address:'',
current_address:'',
emergency_contact:'',
emergency_phone:'',
marital_status:'',
bank_card:'',
bank_name:'',
contract_expire:'',
is_rehired:'',
remark:''
}
}
const formData : Record<string, any> = reactive({ ...initialFormData })
const formRef = ref<FormInstance>()
//
const formRules = computed(() => {
return {
name: [{ required: true, message: t('namePlaceholder'), trigger: 'blur' }],
gender: [
{ required: true, message: t('genderPlaceholder'), trigger: 'blur' },
],
phone: [
{ required: true, message: t('phonePlaceholder'), trigger: 'blur' },
],
address: [
{ required: true, message: t('addressPlaceholder'), trigger: 'blur' },
],
native_place: [
{ required: true, message: t('nativePlacePlaceholder'), trigger: 'blur' },
],
education: [
{ required: true, message: t('educationPlaceholder'), trigger: 'blur' },
],
profile: [
{ required: true, message: t('profilePlaceholder'), trigger: 'blur' },
],
emergency_contact_phone: [
{
required: true,
message: t('emergencyContactPhonePlaceholder'),
trigger: 'blur',
},
],
id_card_front: [
{ required: true, message: t('idCardFrontPlaceholder'), trigger: 'blur' },
],
id_card_back: [
{ required: true, message: t('idCardBackPlaceholder'), trigger: 'blur' },
],
status: [
{ required: true, message: t('statusPlaceholder'), trigger: 'blur' },
],
is_sys_user: [
{ required: true, message: t('isSysUserPlaceholder'), trigger: 'blur' },
],
}
})
const emit = defineEmits(['complete'])
/**
* 确认
* @param formEl
*/
const confirm = async (formEl : FormInstance | undefined) => {
if (loading.value || !formEl) return
let save = formData.id ? editPersonnel : addPersonnel
await formEl.validate(async (valid) => {
if (valid) {
loading.value = true
let data = formData
save(data)
.then((res) => {
loading.value = false
showDialog.value = false
emit('complete')
})
.catch((err) => {
loading.value = false
})
}
})
import { ref, reactive } from 'vue'
import { t } from '@/lang'
import {
addPersonnel,
editPersonnel,
getPersonnelInfo,
} from '@/app/api/personnel'
import BaseInfoForm from './BaseInfoForm.vue'
import DetailInfoForm from './DetailInfoForm.vue'
let showDialog = ref(false)
const loading = ref(false)
const activeTab = ref('base')
//
const baseInfoFormRef = ref()
const detailInfoFormRef = ref()
/**
* 表单数据
*/
const initialFormData = {
id: '',
info: {}
}
const formData: Record<string, any> = reactive({ ...initialFormData })
const emit = defineEmits(['complete'])
/**
* 确认
*/
const confirm = async () => {
if (loading.value) return
//
const baseInfoValid = await baseInfoFormRef.value?.validateForm()
const detailInfoValid = await detailInfoFormRef.value?.validateForm()
if (!baseInfoValid) {
activeTab.value = 'base'
return
}
//
let genderList = ref([])
const genderDictList = async () => {
genderList.value = await (await useDictionary('gender')).data.dictionary
}
genderDictList()
watch(
() => genderList.value,
() => {
formData.gender = genderList.value[0].value
}
)
let educationList = ref([])
const educationDictList = async () => {
educationList.value = await (await useDictionary('education')).data.dictionary
}
educationDictList()
watch(
() => educationList.value,
() => {
formData.education = educationList.value[0].value
}
)
let statusList = ref([])
const statusDictList = async () => {
statusList.value = await (
await useDictionary('personnel_status')
).data.dictionary
}
statusDictList()
watch(
() => statusList.value,
() => {
formData.status = statusList.value[0].value
}
)
let is_sys_userList = ref([])
const is_sys_userDictList = async () => {
is_sys_userList.value = await (
await useDictionary('global_true_or_false')
).data.dictionary
}
is_sys_userDictList()
watch(
() => is_sys_userList.value,
() => {
formData.is_sys_user = is_sys_userList.value[0].value
}
)
//
let campusList = ref([])
const getCampusList = async () => {
const res = await getWithCampusList({})
if (res.data) {
campusList.value = res.data
loading.value = true
try {
//
const baseData = baseInfoFormRef.value?.getFormData() || {}
const detailData = detailInfoFormRef.value?.getFormData() || {}
const submitData = {
...baseData,
...formData,
info: detailData
}
const save = formData.id ? editPersonnel : addPersonnel
await save(submitData)
loading.value = false
showDialog.value = false
emit('complete')
} catch (err) {
loading.value = false
}
}
onMounted(() => {
getCampusList()
})
/**
* 设置表单数据
*/
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
const setFormData = async (row : any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
try {
if (row) {
const data = await (await getPersonnelInfo(row.id)).data
if (data)
Object.keys(formData).forEach((key : string) => {
if (data[key] != undefined) formData[key] = data[key]
})
if (data) {
formData.id = data.id
//
await baseInfoFormRef.value?.setFormData(data)
//
await detailInfoFormRef.value?.setFormData(data.info || {})
}
} else {
//
await baseInfoFormRef.value?.setFormData()
await detailInfoFormRef.value?.setFormData()
}
} catch (error) {
console.error('设置表单数据失败:', error)
} finally {
loading.value = false
}
}
//
const mobileVerify = (rule : any, value : any, callback : any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('generateMobile')))
} else {
callback()
}
defineExpose({
showDialog,
setFormData,
})
</script>
<style lang="scss" scoped>
.personnel-edit-dialog {
:deep(.el-dialog) {
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
//
const idCardVerify = (rule : any, value : any, callback : any) => {
if (
value &&
!/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
value
)
) {
callback(new Error(t('generateIdCard')))
} else {
callback()
:deep(.el-dialog__header) {
padding: 20px 24px 16px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
border-radius: 8px 8px 0 0;
.el-dialog__title {
font-size: 18px;
font-weight: 600;
color: #1f2937;
}
}
//
const emailVerify = (rule : any, value : any, callback : any) => {
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) {
callback(new Error(t('generateEmail')))
} else {
callback()
}
:deep(.el-dialog__body) {
padding: 24px;
max-height: 70vh;
overflow-y: auto;
}
//
const numberVerify = (rule : any, value : any, callback : any) => {
if (!Number.isInteger(value)) {
callback(new Error(t('generateNumber')))
} else {
callback()
:deep(.el-dialog__footer) {
padding: 16px 24px 20px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
border-radius: 0 0 8px 8px;
}
}
.personnel-tabs {
:deep(.el-tabs__header) {
margin-bottom: 24px;
border-bottom: 2px solid #e5e7eb;
.el-tabs__nav-wrap {
&::after {
display: none;
}
}
.el-tabs__item {
padding: 12px 24px;
font-size: 15px;
font-weight: 500;
color: #6b7280;
border-bottom: none;
transition: all 0.3s ease;
&:hover {
color: #3b82f6;
}
&.is-active {
color: #3b82f6;
background: #eff6ff;
border: 1px solid #3b82f6;
border-bottom: 1px solid #eff6ff;
border-radius: 8px 8px 0 0;
position: relative;
top: 2px;
}
}
}
defineExpose({
showDialog,
setFormData,
})
</script>
.tab-content {
padding: 20px;
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
}
.dialog-footer {
.el-button {
padding: 10px 24px;
font-size: 14px;
font-weight: 500;
border-radius: 6px;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
}
}
</style>
<style lang="scss" scoped></style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label {
height: auto !important;
.personnel-edit-dialog .el-form-item__label {
height: auto !important;
line-height: 1.6;
}
.personnel-edit-dialog .el-tabs__content {
overflow: visible;
}
.personnel-edit-dialog .el-dialog__body {
padding: 24px;
}
//
.personnel-edit-dialog .el-dialog__body::-webkit-scrollbar {
width: 6px;
}
.personnel-edit-dialog .el-dialog__body::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.personnel-edit-dialog .el-dialog__body::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
&:hover {
background: #a8a8a8;
}
}
</style>

32
niucloud/app/api/controller/apiController/PersonCourseSchedule.php

@ -161,17 +161,17 @@ class PersonCourseSchedule extends BaseApiService
$resource_id = $request->param('resource_id', '');//客户资源ID
$member_id = $request->param('member_id', '');//会员ID
$student_id = $request->param('student_id', '');//学生ID
if (empty($resource_id)) {
return fail('缺少参数resource_id');
}
$where = [
'resource_id' => $resource_id,
'member_id' => $member_id,
'student_id' => $student_id,
];
$res = (new PersonCourseScheduleService())->getStudentCourseInfo($where);
if(!$res['code']){
return fail($res['msg']);
@ -194,21 +194,41 @@ class PersonCourseSchedule extends BaseApiService
$main_coach_id = $request->param('main_coach_id', '');//主教练ID
$education_id = $request->param('education_id', '');//教务ID
$assistant_ids = $request->param('assistant_ids', '');//助教IDs,逗号分隔
if (empty($student_course_id)) {
return fail('缺少参数student_course_id');
}
$data = [
'main_coach_id' => $main_coach_id,
'education_id' => $education_id,
'assistant_ids' => $assistant_ids,
];
$res = (new PersonCourseScheduleService())->updateStudentCoursePersonnel($student_course_id, $data);
if(!$res['code']){
return fail($res['msg']);
}
return success($res['data']);
}
//获取学生课程详情包含已上课的情况和订单情况
public function getStudentCourseDetail(Request $request)
{
$student_course_id = $request->param('student_course_id', '');
if (empty($student_course_id)) {
return fail('缺少参数student_course_id');
}
$where = [
'student_course_id' => $student_course_id,
];
$res = (new PersonCourseScheduleService())->getStudentCourseDetail($where);
if(!$res['code']){
return fail($res['msg']);
}
return success($res['data']);
}
}

51
niucloud/app/api/controller/apiController/StudentCourse.php

@ -32,39 +32,39 @@ class StudentCourse extends BaseApiService
{
$course_id = $request->param('course_id', '');
$resource_id = $request->param('resource_id', '');
if (empty($course_id)) {
return fail('课程ID不能为空');
}
// 如果没有传resource_id,尝试从当前登录用户获取
if (empty($resource_id)) {
// 这里需要根据实际情况获取当前学员的resource_id
// 可能需要从member_id获取对应的resource_id
$resource_id = $this->getResourceIdByMemberId($this->member_id);
}
if (empty($resource_id)) {
return fail('资源ID不能为空');
}
$where = [
'course_id' => $course_id,
'resource_id' => $resource_id
];
try {
$res = (new StudentCourseService())->getCourseDetail($where);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取课程详情失败:' . $e->getMessage());
}
}
/**
* 获取学员服务记录
* @param Request $request
@ -73,28 +73,28 @@ class StudentCourse extends BaseApiService
public function getServiceList(Request $request)
{
$student_id = $request->param('student_id', '');
// 如果没有传student_id,尝试从当前登录用户获取
if (empty($student_id)) {
$student_id = $this->getResourceIdByMemberId($this->member_id);
}
if (empty($student_id)) {
return fail('学员ID不能为空');
}
try {
$res = (new ServiceService())->getStudentServiceList((int)$student_id);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取服务记录失败:' . $e->getMessage());
}
}
/**
* 获取教练列表
* @param Request $request
@ -108,13 +108,13 @@ class StudentCourse extends BaseApiService
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取教练列表失败:' . $e->getMessage());
}
}
/**
* 获取教务人员列表
* @param Request $request
@ -128,13 +128,13 @@ class StudentCourse extends BaseApiService
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取教务人员列表失败:' . $e->getMessage());
}
}
/**
* 更新学员课程信息
* @param Request $request
@ -150,22 +150,22 @@ class StudentCourse extends BaseApiService
["education_id", 0],
["class_id", 0] // 可选,如果需要更新班级关联
]);
if (empty($data['student_course_id'])) {
return fail('学员课程ID不能为空');
}
$res = (new StudentCourseService())->updateCourseInfo($data);
if (!$res['code']) {
return fail($res['msg']);
}
return success('更新成功', $res['data']);
} catch (\Exception $e) {
return fail('更新失败:' . $e->getMessage());
}
}
/**
* 检查学员班级关联情况
* @param Request $request
@ -175,21 +175,21 @@ class StudentCourse extends BaseApiService
{
try {
$resource_id = $request->param('resource_id', '');
if (empty($resource_id)) {
// 尝试从当前登录用户获取
$resource_id = $this->getResourceIdByMemberId($this->member_id);
}
if (empty($resource_id)) {
return fail('资源ID不能为空');
}
$res = (new StudentCourseService())->checkClassRelation($resource_id);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('检查班级关联失败:' . $e->getMessage());
@ -208,4 +208,5 @@ class StudentCourse extends BaseApiService
$customerResource = \app\model\customer_resources\CustomerResources::where('member_id', $member_id)->find();
return $customerResource ? $customerResource->id : '';
}
}
}

2
niucloud/app/api/route/route.php

@ -582,6 +582,8 @@ Route::group(function () {
//获取学生课程信息列表(包含教练配置)
Route::get('getStudentCourseInfo', 'apiController.PersonCourseSchedule/getStudentCourseInfo');
//获取学生课程详情包含已上课的情况和订单情况
Route::post('getStudentCourseDetail', 'apiController.PersonCourseSchedule/getStudentCourseDetail');
//获取人员列表(教练、教务、助教)
Route::get('getPersonnelList', 'apiController.PersonCourseSchedule/getPersonnelList');
//更新学生课程人员配置

40
niucloud/app/common.php

@ -2305,3 +2305,43 @@ function save_user_signature($data)
return false;
}
}
// ==================== 合同占位符处理函数 ====================
/**
* 将合同内容中的占位符替换为下划线输入框
* 使用正则表达式将 {{占位符}} 格式替换为指定长度的下划线
*
* @param string $contractContent 合同内容
* @param int $underlineLength 下划线长度(默认为10个字符)
* @return string 替换后的合同内容
*/
function replace_placeholders_with_underlines($contractContent, $underlineLength = 10)
{
if (empty($contractContent)) {
return $contractContent;
}
// 使用正则表达式匹配 {{}} 格式的占位符
// \{\{ 匹配 {{
// [^}]+ 匹配一个或多个非 } 字符(占位符内容)
// \}\} 匹配 }}
$pattern = '/\{\{[^}]+\}\}/';
// 生成替换字符串 - 根据占位符内容长度动态调整下划线数量
$replacement = function($matches) use ($underlineLength) {
$placeholder = $matches[0];
// 提取占位符中的内容
$content = preg_replace('/^\{\{|\}\}$/', '', $placeholder);
// 根据内容长度动态决定下划线数量,最少8个,最多20个
$contentLength = mb_strlen($content, 'UTF-8');
$adjustedLength = max(8, min(20, $contentLength + 4));
return str_repeat('_', $adjustedLength); // 使用全角下划线,显示效果更好
};
// 执行替换
return preg_replace_callback($pattern, $replacement, $contractContent);
}

1
niucloud/app/service/api/apiService/CoachStudentService.php

@ -285,7 +285,6 @@ class CoachStudentService extends BaseApiService
// resource_sharing_id 是 school_resource_assignment 表的 id
$resourceAssignment = Db::table('school_resource_assignment')
->where('resource_id', $customerResource['id'])
->where('assignee_type', 'user')
->field('id')
->order('assigned_at', 'desc')
->find();

237
niucloud/app/service/api/apiService/CourseScheduleService.php

@ -2771,6 +2771,231 @@ class CourseScheduleService extends BaseApiService
}
}
/**
* 处理正式学员课程消减逻辑
* @param array $enrollment 学员课程安排记录
* @param Student $student 学员信息
* @throws \Exception
*/
private function handlePaidStudentCourseDeduction($enrollment, $student)
{
try {
$scheduleId = $enrollment['schedule_id'] ?? 0;
if (empty($scheduleId)) {
throw new \Exception('课程安排ID不能为空');
}
// 获取课程安排信息以确定课程日期
$schedule = Db::name('course_schedule')
->where('id', $scheduleId)
->find();
if (!$schedule) {
throw new \Exception('找不到课程安排信息');
}
$courseDate = $schedule['course_date'];
// 根据课程日期动态查找应该核销的课程包
$studentCourse = $this->findApplicableStudentCourse($student->id, $courseDate);
if (!$studentCourse) {
throw new \Exception('找不到该日期有效的学员课程包');
}
// 获取课程信息以计算消耗课时数
$course = Db::name('course')
->where('id', $studentCourse['course_id'])
->find();
if (!$course) {
throw new \Exception('找不到课程信息');
}
// 计算本次消耗的课时数
$deductHours = $this->calculateCourseDeduction($course, $scheduleId);
// 检查剩余课时是否足够
$remainingHours = $studentCourse['total_hours'] - $studentCourse['use_total_hours'];
if ($remainingHours < $deductHours) {
throw new \Exception('剩余课时不足,当前剩余:' . $remainingHours . ',需要消耗:' . $deductHours);
}
// 1. 更新学员课程的已使用课时数
$updateResult = Db::name('student_courses')
->where('id', $studentCourse['id'])
->inc('use_total_hours', $deductHours)
->update(['updated_at' => date('Y-m-d H:i:s')]);
if (!$updateResult) {
throw new \Exception('更新学员课程课时失败');
}
// 2. 插入课程消减记录
$usageData = [
'student_course_id' => $studentCourse['id'],
'student_id' => $student->id,
'resource_id' => $enrollment['resources_id'] ?? null,
'used_hours' => $deductHours,
'usage_date' => date('Y-m-d'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
$usageResult = Db::name('student_course_usage')->insert($usageData);
if (!$usageResult) {
throw new \Exception('插入课程消减记录失败');
}
// 记录日志
trace('Paid student course deduction processed', 'info');
trace('Deduction details: ' . json_encode([
'student_id' => $student->id,
'student_course_id' => $studentCourse['id'],
'course_start_date' => $studentCourse['start_date'],
'course_end_date' => $studentCourse['end_date'],
'schedule_date' => $courseDate,
'deducted_hours' => $deductHours,
'remaining_hours' => $remainingHours - $deductHours,
'usage_date' => date('Y-m-d')
]), 'info');
} catch (\Exception $e) {
// 抛出异常以便外层事务回滚
throw new \Exception('处理正式学员课程消减失败:' . $e->getMessage());
}
}
/**
* 根据课程日期查找适用的学员课程包
* 优先选择:
* 1. 在有效期内(开始日期 ≤ 课程日期 ≤ 结束日期)
* 2. 还有剩余课时
* 3. 最早开始的课程包
* @param int $studentId 学员ID
* @param string $courseDate 课程日期
* @return array|null 学员课程记录
*/
private function findApplicableStudentCourse($studentId, $courseDate)
{
try {
// 查找在有效期内且有剩余课时的课程包
$applicableCourses = Db::name('student_courses')
->where('student_id', $studentId)
->where('status', 1) // 有效状态
->where('start_date', '<=', $courseDate) // 开始日期 <= 课程日期
->where('end_date', '>=', $courseDate) // 结束日期 >= 课程日期
->whereRaw('total_hours > use_total_hours') // 有剩余课时
->order('start_date ASC, created_at ASC') // 按开始日期升序,创建时间升序
->select()
->toArray();
if (empty($applicableCourses)) {
return null;
}
// 返回最早开始的有效课程包
return $applicableCourses[0];
} catch (\Exception $e) {
trace('Find applicable student course error: ' . $e->getMessage(), 'error');
return null;
}
}
/**
* 计算课程消耗课时数
* @param array $course 课程信息
* @param int $scheduleId 课程安排ID
* @return float 消耗课时数
*/
private function calculateCourseDeduction($course, $scheduleId)
{
try {
// 获取课程安排信息
$schedule = Db::name('course_schedule')
->where('id', $scheduleId)
->find();
if (!$schedule) {
throw new \Exception('找不到课程安排信息');
}
// 根据课程类型计算消耗课时数
switch ($course['course_type']) {
case 1: // 按课时
// 解析时间段计算实际课时
$timeSlot = $schedule['time_slot'] ?? '';
$deductHours = $this->parseTimeSlotToHours($timeSlot);
break;
case 2: // 按次卡
// 按次卡每次消耗1次
$deductHours = 1;
break;
case 3: // 按周期
// 周期课程每次消耗1次
$deductHours = 1;
break;
case 4: // 按时长
// 按实际时长计算,转换为小时
$duration = $course['duration'] ?? 0; // 分钟
$deductHours = $duration / 60;
break;
default:
// 默认每次消耗1课时
$deductHours = 1;
break;
}
// 确保返回正数
return max(0.01, $deductHours);
} catch (\Exception $e) {
// 计算失败时默认消耗1课时
return 1;
}
}
/**
* 解析时间段为小时数
* @param string $timeSlot 时间段,格式:08:00-08:30
* @return float 小时数
*/
private function parseTimeSlotToHours($timeSlot)
{
try {
if (empty($timeSlot) || strpos($timeSlot, '-') === false) {
return 1; // 默认1小时
}
$times = explode('-', $timeSlot);
if (count($times) !== 2) {
return 1;
}
$startTime = strtotime($times[0]);
$endTime = strtotime($times[1]);
if (!$startTime || !$endTime) {
return 1;
}
$minutes = ($endTime - $startTime) / 60;
$hours = $minutes / 60;
// 如果计算结果小于0.1小时,按0.1小时计算
return max(0.1, $hours);
} catch (\Exception $e) {
return 1; // 解析失败时默认1小时
}
}
/**
* 处理单个学员签到(内部方法,不管理事务)
* @param int $scheduleId 课程安排ID
@ -2856,9 +3081,15 @@ class CourseScheduleService extends BaseApiService
return ['success' => false, 'error' => '更新签到状态失败'];
}
// 处理试听课签到逻辑(仅在签到成功且学员未付费时处理)
if ($status === 1 && $student->pay_status != 1) {
$this->handleTrialClassCheckin($student);
// 处理签到后的课程消减逻辑
if ($status === 1) {
if ($student->pay_status != 1) {
// 处理试听课签到逻辑
$this->handleTrialClassCheckin($student);
} else {
// 处理正式学员的课程消减逻辑
$this->handlePaidStudentCourseDeduction($enrollment, $student);
}
}
return ['success' => true, 'error' => ''];

5
niucloud/app/service/api/apiService/CourseService.php

@ -793,9 +793,12 @@ class CourseService extends BaseApiService
$phone = $student['student']['contact_phone'] ?: '';
$trialClassCount = $student['student']['trial_class_count'] ?: 0;
// 获取学员最新的付费课程信息
// 获取学员最新的有效课程信息
$studentCourseInfo = Db::name('student_courses')
->where('student_id', $student['student_id'])
->where('status', 1) // 只获取状态为1的有效课程
->where('start_date', '<=', date('Y-m-d')) // 开始时间小于等于当前时间
->where('end_date', '>=', date('Y-m-d')) // 结束时间大于等于当前时间
->order('created_at DESC')
->find();

239
niucloud/app/service/api/apiService/PersonCourseScheduleService.php

@ -406,6 +406,31 @@ class PersonCourseScheduleService extends BaseApiService
->where('status', 2) // 2表示请假
->count();
// 获取班级关联信息
$classInfo = null;
try {
$classRel = \app\model\class_resources_rel\ClassResourcesRel::alias('crr')
->join(['school_class' => 'c'], 'crr.class_id = c.id', 'left')
->where([
'crr.resource_id' => $where['resource_id'],
'crr.status' => 1
])
->field([
'crr.class_id',
'c.class_name',
'c.head_coach',
'c.educational_id'
])
->find();
if ($classRel) {
$classInfo = $classRel->toArray();
}
} catch (\Exception $e) {
// 班级查询失败,不影响整体功能
$classInfo = null;
}
// 获取教练配置信息
$mainCoach = null;
$education = null;
@ -471,12 +496,19 @@ class PersonCourseScheduleService extends BaseApiService
'status' => $status, // 课程状态
'db_status' => $dbStatus, // 数据库原始状态
'single_session_count' => $course['single_session_count'] ?? 1, // 单次消课数量
'resource_id' => $course['resource_id'] ?? null, // 添加资源ID
'student_course_id' => $course['id'], // 添加学生课程ID
'main_coach_id' => $course['main_coach_id'] ?? null,
'main_coach_name' => $mainCoach['name'] ?? '未分配',
'education_id' => $course['education_id'] ?? null,
'education_name' => $education['name'] ?? '未分配',
'assistant_ids' => $course['assistant_ids'] ?? '',
'assistant_names' => implode(', ', array_column($assistants, 'name')) ?: '无'
'assistant_names' => implode(', ', array_column($assistants, 'name')) ?: '无',
// 班级相关字段
'class_id' => $classInfo['class_id'] ?? null,
'class_name' => $classInfo['class_name'] ?? null,
'has_class' => !empty($classInfo), // 是否有班级关联
'class_info' => $classInfo // 完整的班级信息
];
}
@ -553,6 +585,209 @@ class PersonCourseScheduleService extends BaseApiService
return $res;
}
//获取学生课程详情(包含课时使用记录和订单信息)
public function getStudentCourseDetail(array $where)
{
$res = [
'code' => 0,
'msg' => '获取失败',
'data' => []
];
try {
$studentCourseId = $where['student_course_id'];
// 1. 获取学员课程基础信息
$studentCourse = StudentCourses::where('id', $studentCourseId)
->with([
'course' => function($query) {
$query->field('id,course_name');
},
'student' => function($query) {
$query->field('id,name');
}
])
->find();
if (!$studentCourse) {
$res['msg'] = '学员课程不存在';
return $res;
}
$studentCourse = $studentCourse->toArray();
// 2. 获取课时使用记录(一对多关联)
$usageRecords = StudentCourseUsage::where('student_course_id', $studentCourseId)
->order('usage_date', 'desc')
->select()
->toArray();
// 3. 获取关联的订单信息(一对一关联,通过course_plan_id)
$orderInfo = \app\model\order_table\OrderTable::where('course_plan_id', $studentCourseId)
->with([
'course' => function($query) {
$query->field('id,course_name');
},
'personnel' => function($query) {
$query->field('id,name');
},
'campus' => function($query) {
$query->field('id,campus_name');
}
])
->find();
// 4. 计算课时统计信息
$totalHours = ($studentCourse['total_hours'] ?? 0) + ($studentCourse['gift_hours'] ?? 0);
$usedHours = ($studentCourse['use_total_hours'] ?? 0) + ($studentCourse['use_gift_hours'] ?? 0);
$remainingHours = $totalHours - $usedHours;
// 5. 获取教练配置信息
$mainCoach = null;
$education = null;
$assistants = [];
if (!empty($studentCourse['main_coach_id'])) {
$mainCoach = Personnel::where('id', $studentCourse['main_coach_id'])->field('id,name')->find();
}
if (!empty($studentCourse['education_id'])) {
$education = Personnel::where('id', $studentCourse['education_id'])->field('id,name')->find();
}
if (!empty($studentCourse['assistant_ids'])) {
$assistantIds = array_filter(explode(',', $studentCourse['assistant_ids']));
if (!empty($assistantIds)) {
$assistants = Personnel::whereIn('id', $assistantIds)->field('id,name')->select()->toArray();
}
}
// 6. 计算课程状态
$status = 'active';
$dbStatus = $studentCourse['status'] ?? 1;
switch ($dbStatus) {
case 1:
$status = 'active';
break;
case 2:
$status = 'expired';
break;
case 3:
$status = 'waiting';
break;
case 4:
$status = 'delayed';
break;
}
if ($status === 'active' && !empty($studentCourse['end_date'])) {
if (strtotime($studentCourse['end_date']) < time()) {
$status = 'expired';
}
}
if ($remainingHours <= 0) {
$status = 'completed';
}
// 7. 组装返回数据
$data = [
// 学员课程基础信息
'student_course_info' => [
'id' => $studentCourse['id'],
'student_id' => $studentCourse['student_id'],
'student_name' => $studentCourse['student']['name'] ?? '未知学员',
'course_id' => $studentCourse['course_id'],
'course_name' => $studentCourse['course']['course_name'] ?? '未知课程',
'total_hours' => $studentCourse['total_hours'] ?? 0,
'gift_hours' => $studentCourse['gift_hours'] ?? 0,
'use_total_hours' => $studentCourse['use_total_hours'] ?? 0,
'use_gift_hours' => $studentCourse['use_gift_hours'] ?? 0,
'start_date' => $studentCourse['start_date'] ?? '',
'end_date' => $studentCourse['end_date'] ?? '',
'status' => $status,
'db_status' => $dbStatus,
'single_session_count' => $studentCourse['single_session_count'] ?? 1,
'resource_id' => $studentCourse['resource_id'] ?? null,
'main_coach_id' => $studentCourse['main_coach_id'] ?? null,
'education_id' => $studentCourse['education_id'] ?? null,
'assistant_ids' => $studentCourse['assistant_ids'] ?? '',
// 课时统计
'total_class_hours' => $totalHours,
'used_class_hours' => $usedHours,
'remaining_class_hours' => $remainingHours,
// 教练信息
'main_coach_name' => $mainCoach['name'] ?? '未分配',
'education_name' => $education['name'] ?? '未分配',
'assistant_names' => implode(', ', array_column($assistants, 'name')) ?: '无',
'coach_details' => [
'main_coach' => $mainCoach,
'education' => $education,
'assistants' => $assistants
]
],
// 课时使用记录列表
'usage_records' => array_map(function($record) {
return [
'id' => $record['id'],
'used_hours' => $record['used_hours'],
'usage_date' => $record['usage_date'],
'created_at' => $record['created_at'],
'updated_at' => $record['updated_at'],
'student_id' => $record['student_id'],
'resource_id' => $record['resource_id']
];
}, $usageRecords),
// 订单信息(如果存在)
'order_info' => $orderInfo ? [
'id' => $orderInfo['id'],
'payment_id' => $orderInfo['payment_id'] ?? '',
'order_type' => $orderInfo['order_type'] ?? '',
'order_status' => $orderInfo['order_status'] ?? '',
'payment_type' => $orderInfo['payment_type'] ?? '',
'order_amount' => $orderInfo['order_amount'] ?? 0,
'course_id' => $orderInfo['course_id'] ?? 0,
'class_id' => $orderInfo['class_id'] ?? null,
'staff_id' => $orderInfo['staff_id'] ?? 0,
'resource_id' => $orderInfo['resource_id'] ?? 0,
'campus_id' => $orderInfo['campus_id'] ?? 0,
'student_id' => $orderInfo['student_id'] ?? null,
'discount_amount' => $orderInfo['discount_amount'] ?? 0,
'remark' => $orderInfo['remark'] ?? '',
'payment_time' => $orderInfo['payment_time'] ?? '',
'created_at' => $orderInfo['created_at'] ?? '',
// 关联信息
'course_name' => $orderInfo['course']['course_name'] ?? '',
'staff_name' => $orderInfo['personnel']['name'] ?? '',
'campus_name' => $orderInfo['campus']['campus_name'] ?? ''
] : null,
// 统计信息
'statistics' => [
'total_usage_records' => count($usageRecords),
'total_used_hours_from_records' => array_sum(array_column($usageRecords, 'used_hours')),
'has_order' => !empty($orderInfo),
'usage_date_range' => $usageRecords ? [
'first_usage' => min(array_column($usageRecords, 'usage_date')),
'last_usage' => max(array_column($usageRecords, 'usage_date'))
] : null
]
];
$res = [
'code' => 1,
'msg' => '获取成功',
'data' => $data
];
} catch (\Exception $e) {
$res['msg'] = '获取异常:' . $e->getMessage();
}
return $res;
}
//更新学生课程人员配置
public function updateStudentCoursePersonnel($studentCourseId, array $data)
{
@ -585,7 +820,7 @@ class PersonCourseScheduleService extends BaseApiService
// 执行更新
$result = StudentCourses::where('id', $studentCourseId)->update($updateData);
if ($result !== false) {
$res = [
'code' => 1,

5
niucloud/app/service/api/student/ContractService.php

@ -1096,7 +1096,7 @@ class ContractService extends BaseService
/**
* 格式化字段值
* 对特定类型的字段值进行格式化处理
*
*
* @param string $fieldName 字段名
* @param mixed $value 原始值
* @return string 格式化后的值
@ -1133,4 +1133,5 @@ class ContractService extends BaseService
return (string)$value;
}
}
}

5
uniapp/api/apiRoute.js

@ -1083,6 +1083,11 @@ export default {
return response
},
// 获取学员课程详情
async getStudentCourseDetail(data = {}) {
return await http.post('/getStudentCourseDetail', data)
},

290
uniapp/components/course-info-card/index.vue

@ -3,11 +3,11 @@
<view class="course-info-card">
<!-- 课程信息列表 -->
<view class="course-list" v-if="courseList && courseList.length > 0">
<view
class="course-item"
<view
class="course-item"
v-for="(course, index) in courseList"
:key="course.id || index"
@tap="viewCourseDetail(course)"
@tap="viewCourseDetail(courseList[index])"
>
<view class="course-header">
<view class="course-title">{{ course.course_name || '未知课程' }}</view>
@ -20,12 +20,12 @@
</view>
</view>
</view>
<!-- 课程进度 -->
<view class="course-progress" v-if="course.total_count">
<view class="progress-bar">
<view
class="progress-fill"
<view
class="progress-fill"
:style="{ width: getProgressPercent(course) + '%' }"
></view>
</view>
@ -33,7 +33,7 @@
{{ course.used_count || 0 }}/{{ course.total_count }}
</view>
</view>
<view class="course-details">
<!-- 基本信息 -->
<view class="detail-section">
@ -50,7 +50,7 @@
<text class="detail-value highlight">{{ getRemainingCount(course) }}</text>
</view>
</view>
<!-- 时间信息 -->
<view class="detail-section" v-if="course.start_date || course.end_date || course.expiry_date">
<view class="detail-item" v-if="course.start_date">
@ -62,7 +62,7 @@
<text class="detail-value">{{ formatDate(course.end_date || course.expiry_date) }}</text>
</view>
</view>
<!-- 其他信息 -->
<view class="detail-section">
<view class="detail-item" v-if="course.course_price">
@ -74,7 +74,7 @@
<text class="detail-value">{{ formatTime(course.create_time) }}</text>
</view>
</view>
<!-- 备注信息 -->
<view class="detail-section" v-if="course.remark">
<view class="remark-item">
@ -85,14 +85,14 @@
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else>
<view class="empty-icon">📖</view>
<view class="empty-text">暂无课程信息</view>
<view class="empty-tip">学生还未报名任何课程</view>
</view>
<!-- 编辑弹窗 -->
<view v-if="showEditModal" class="modal-overlay" @click="closeEditModal">
<view class="modal-content" @click.stop>
@ -100,17 +100,17 @@
<view class="modal-title">编辑课程信息</view>
<view class="close-btn" @click="closeEditModal">×</view>
</view>
<view class="modal-body">
<view class="form-section">
<view class="section-title">人员配置</view>
<!-- 主教练选择 -->
<view class="form-item">
<text class="form-label">主教练</text>
<picker
:value="selectedMainCoachIndex"
:range="coachList"
<picker
:value="selectedMainCoachIndex"
:range="coachList"
range-key="name"
@change="onMainCoachChange"
style="width: 100%"
@ -121,15 +121,15 @@
</view>
</picker>
</view>
<!-- 助教选择 -->
<view class="form-item">
<text class="form-label">助教</text>
<picker
style="width: 100%"
mode="multiSelector"
:value="selectedAssistantIndexes"
:range="[coachList]"
mode="multiSelector"
:value="selectedAssistantIndexes"
:range="[coachList]"
:range-key="['name']"
@change="onAssistantChange"
>
@ -139,14 +139,14 @@
</view>
</picker>
</view>
<!-- 教务选择 -->
<view class="form-item">
<text class="form-label">教务</text>
<picker
style="width: 100%"
:value="selectedEducationIndex"
:range="educationList"
:value="selectedEducationIndex"
:range="educationList"
range-key="name"
@change="onEducationChange"
>
@ -157,7 +157,7 @@
</picker>
</view>
</view>
<!-- 班级选择区域 - 始终允许编辑 -->
<view class="form-section">
<view class="section-title">班级配置</view>
@ -165,8 +165,8 @@
<text class="form-label">所属班级</text>
<picker
style="width: 100%"
:value="selectedClassIndex"
:range="classList"
:value="selectedClassIndex"
:range="classList"
range-key="class_name"
@change="onClassChange"
>
@ -182,7 +182,7 @@
</view>
</view>
</view>
<view class="modal-footer">
<button class="btn btn-cancel" @tap="closeEditModal">取消</button>
<button class="btn btn-confirm" @tap="confirmEdit" :loading="saving">保存</button>
@ -204,14 +204,14 @@ export default {
default: () => []
}
},
data() {
return {
//
showEditModal: false,
saving: false,
currentCourse: null,
//
editForm: {
student_course_id: '',
@ -224,34 +224,39 @@ export default {
class_id: '',
class_name: ''
},
//
selectedMainCoachIndex: 0,
selectedAssistantIndexes: [0],
selectedEducationIndex: 0,
selectedClassIndex: 0,
//
coachList: [],
educationList: [],
classList: [],
//
hasClass: false,
currentClassInfo: {}
}
},
mounted() {
// 使
if (!this.courseList || this.courseList.length === 0) {
console.log('使用测试课程数据')
}
},
methods: {
//
viewCourseDetail(course) {
console.log('viewCourseDetail 被调用,course:', course)
console.log('course 类型:', typeof course)
console.log('course.id:', course?.id)
console.log('course.student_course_id:', course?.student_course_id)
if (!course) {
console.error('viewCourseDetail: course参数为空')
uni.showToast({
@ -260,15 +265,26 @@ export default {
})
return
}
//
if (!course.id && !course.student_course_id) {
console.error('viewCourseDetail: 课程缺少ID信息')
uni.showToast({
title: '课程信息不完整',
icon: 'none'
})
return
}
this.$emit('view-detail', course)
},
//
handleEditClick(e) {
console.log('编辑按钮点击事件:', e)
const courseIndex = parseInt(e.currentTarget.dataset.courseIndex)
console.log('课程索引:', courseIndex, '类型:', typeof courseIndex)
if (!isNaN(courseIndex) && this.courseList && this.courseList[courseIndex]) {
const course = this.courseList[courseIndex]
console.log('通过索引获取到的课程数据:', course)
@ -289,7 +305,7 @@ export default {
//
async editCourse(course) {
console.log('编辑课程数据:', course)
// course
if (!course) {
console.error('editCourse: course参数为空')
@ -299,7 +315,7 @@ export default {
})
return
}
//
if (!course.id && !course.student_course_id) {
console.error('editCourse: 缺少课程ID', { id: course.id, student_course_id: course.student_course_id })
@ -309,7 +325,7 @@ export default {
})
return
}
if (!course.resource_id) {
console.error('editCourse: 缺少resource_id', { resource_id: course.resource_id })
uni.showToast({
@ -318,19 +334,19 @@ export default {
})
return
}
this.currentCourse = course
// -
console.log('原始课程数据:', course)
//
const mainCoachId = course.main_coach_id || course.head_coach || course.coach_id || course.teacher_id || ''
const educationId = course.education_id || course.educational_id || course.education || ''
//
const classId = course.class_id || course.current_class_id || course.belong_class_id || ''
const className = course.class_name || course.current_class_name || course.belong_class_name || ''
this.editForm = {
student_course_id: course.student_course_id || course.id,
main_coach_id: mainCoachId,
@ -342,26 +358,26 @@ export default {
class_id: classId,
class_name: className
}
console.log('处理后的editForm:', this.editForm)
try {
//
uni.showLoading({
title: '加载中...'
})
//
await this.loadBaseData()
//
await this.checkClassRelation(course.resource_id)
//
this.setPickerIndexes()
uni.hideLoading()
//
this.showEditModal = true
} catch (error) {
@ -373,18 +389,18 @@ export default {
})
}
},
//
formatAssistantNames(assistantIds) {
if (!assistantIds) return ''
//
return ''
},
//
async loadBaseData() {
console.log('开始加载基础数据')
try {
// 使API
const [coachRes, educationRes, classRes] = await Promise.all([
@ -395,11 +411,11 @@ export default {
//
apiRoute.jlGetClassesList()
])
console.log('教练列表响应:', coachRes)
console.log('教务列表响应:', educationRes)
console.log('班级列表响应:', classRes)
// -
if (coachRes && coachRes.code === 1 && coachRes.data) {
// common_getCoachListcoach_list
@ -414,7 +430,7 @@ export default {
{ id: 4, name: '张教练', phone: '13800138004' }
]
}
//
if (educationRes && educationRes.code === 1) {
this.educationList = educationRes.data || []
@ -426,7 +442,7 @@ export default {
{ id: 2, name: '刘教务', phone: '13800138004' }
]
}
// -
if (classRes && classRes.code === 1) {
// jlGetClassesListdata
@ -445,13 +461,13 @@ export default {
{ id: 2, class_name: '测试班级2', head_coach: 6, educational_id: 0 }
]
}
console.log('最终数据:', {
coachList: this.coachList,
educationList: this.educationList,
classList: this.classList
})
} catch (error) {
console.error('加载基础数据失败:', error)
// 使
@ -470,7 +486,7 @@ export default {
{ id: 1, class_name: '测试班级1', head_coach: 5, educational_id: 0 },
{ id: 2, class_name: '测试班级2', head_coach: 6, educational_id: 0 }
]
uni.showToast({
title: '使用模拟数据进行测试',
icon: 'none',
@ -478,19 +494,19 @@ export default {
})
}
},
//
async checkClassRelation(resourceId) {
try {
console.log('检查班级关联,资源ID:', resourceId)
// 使API
const res = await apiRoute.checkClassRelation({ resource_id: resourceId })
if (res && res.code === 1) {
this.hasClass = res.data.has_class
this.currentClassInfo = res.data.class_info || {}
// editForm
if (this.hasClass && this.currentClassInfo) {
this.editForm.class_id = this.currentClassInfo.id || this.currentClassInfo.class_id || ''
@ -506,7 +522,7 @@ export default {
this.hasClass = false
this.currentClassInfo = {}
}
} catch (error) {
console.error('检查班级关联失败:', error)
//
@ -514,7 +530,7 @@ export default {
this.currentClassInfo = {}
}
},
//
setPickerIndexes() {
console.log('设置选择器索引,当前数据:', {
@ -524,13 +540,13 @@ export default {
coachListLength: this.coachList.length,
educationListLength: this.educationList.length
})
//
if (!this.editForm) {
console.warn('editForm未初始化,跳过索引设置')
return
}
//
if (this.editForm.main_coach_id) {
console.log('主教练ID类型:', typeof this.editForm.main_coach_id, '值:', this.editForm.main_coach_id)
@ -541,7 +557,7 @@ export default {
})
}
}
// -
if (this.editForm.main_coach_id && this.coachList.length > 0) {
//
@ -555,7 +571,7 @@ export default {
console.log('未找到匹配的主教练ID:', this.editForm.main_coach_id, '教练列表:', this.coachList.map(c => c.id))
}
}
// -
if (this.editForm.assistant_ids && this.coachList.length > 0) {
const assistantIds = this.editForm.assistant_ids.split(',').map(id => id.trim()).filter(id => id)
@ -583,7 +599,7 @@ export default {
this.selectedAssistantIndexes = [0]
this.editForm.assistant_names = ''
}
// -
if (this.editForm.education_id && this.educationList.length > 0) {
//
@ -597,22 +613,42 @@ export default {
console.log('未找到匹配的教务ID:', this.editForm.education_id, '教务列表:', this.educationList.map(e => e.id))
}
}
// -
if (this.classList.length > 0) {
// class_id0
const targetId = String(this.editForm.class_id || 0)
const classIndex = this.classList.findIndex(item => String(item.id) === targetId)
console.log('班级索引设置调试信息:', {
targetId,
targetIdType: typeof targetId,
editFormClassId: this.editForm.class_id,
editFormClassIdType: typeof this.editForm.class_id,
classList: this.classList.map(c => ({ id: c.id, idType: typeof c.id, name: c.class_name })),
classIndex,
classIndexType: typeof classIndex
})
if (classIndex >= 0) {
this.selectedClassIndex = classIndex
this.editForm.class_name = this.classList[classIndex].class_name
console.log('班级设置成功:', this.classList[classIndex].class_name, '索引:', classIndex)
this.selectedClassIndex = Number(classIndex) //
// 使使API
const listClassName = this.classList[classIndex].class_name
this.editForm.class_name = listClassName || this.editForm.class_name || '请选择班级'
console.log('班级设置成功:', {
className: this.editForm.class_name,
listClassName,
classIndex: this.selectedClassIndex,
classIndexType: typeof this.selectedClassIndex
})
} else {
// ""
// API""
this.selectedClassIndex = 0
this.editForm.class_id = 0
this.editForm.class_name = '无班级'
console.log('未找到匹配的班级ID,默认设置为无班级:', this.editForm.class_id, '班级列表:', this.classList.map(c => c.id))
// class_idclass_name0
console.log('未找到匹配的班级ID,保留原有值:', {
classId: this.editForm.class_id,
className: this.editForm.class_name,
classList: this.classList.map(c => c.id)
})
}
} else {
console.log('班级列表为空:', {
@ -620,7 +656,7 @@ export default {
classList_length: this.classList.length
})
}
console.log('选择器索引设置完成:', {
selectedMainCoachIndex: this.selectedMainCoachIndex,
selectedAssistantIndexes: this.selectedAssistantIndexes,
@ -628,7 +664,7 @@ export default {
selectedClassIndex: this.selectedClassIndex
})
},
//
onMainCoachChange(e) {
const index = e.detail.value
@ -639,17 +675,17 @@ export default {
this.editForm.main_coach_name = selectedCoach.name
}
},
//
onAssistantChange(e) {
const indexes = e.detail.value
this.selectedAssistantIndexes = indexes
const selectedAssistants = indexes.map(index => this.coachList[index]).filter(Boolean)
this.editForm.assistant_ids = selectedAssistants.map(item => item.id).join(',')
this.editForm.assistant_names = selectedAssistants.map(item => item.name).join(',')
},
//
onEducationChange(e) {
const index = e.detail.value
@ -660,7 +696,7 @@ export default {
this.editForm.education_name = selectedEducation.name
}
},
//
onClassChange(e) {
const index = e.detail.value
@ -669,7 +705,7 @@ export default {
if (selectedClass) {
this.editForm.class_id = selectedClass.id
this.editForm.class_name = selectedClass.class_name
// ""
if (selectedClass.id > 0 && selectedClass.head_coach) {
//
@ -680,7 +716,7 @@ export default {
this.editForm.main_coach_name = this.coachList[coachIndex].name
}
}
if (selectedClass.id > 0 && selectedClass.educational_id) {
//
const educationIndex = this.educationList.findIndex(item => item.id == selectedClass.educational_id)
@ -692,14 +728,14 @@ export default {
}
}
},
//
closeEditModal() {
this.showEditModal = false
this.currentCourse = null
this.resetForm()
},
//
resetForm() {
console.log('重置表单数据')
@ -720,17 +756,17 @@ export default {
this.selectedClassIndex = 0
this.hasClass = false
this.currentClassInfo = {}
//
this.coachList = []
this.educationList = []
this.classList = []
},
//
async confirmEdit() {
if (this.saving) return
//
if (!this.editForm.student_course_id) {
uni.showToast({
@ -739,23 +775,23 @@ export default {
})
return
}
try {
this.saving = true
console.log('提交编辑数据:', this.editForm)
// 使API
const res = await apiRoute.updateStudentCoursePersonnel(this.editForm)
console.log('更新响应:', res)
if (res && res.code === 1) {
uni.showToast({
title: '更新成功',
icon: 'success'
})
//
this.$emit('course-updated', this.currentCourse)
this.closeEditModal()
@ -766,7 +802,7 @@ export default {
title: '更新成功(模拟)',
icon: 'success'
})
//
this.$emit('course-updated', this.currentCourse)
this.closeEditModal()
@ -778,7 +814,7 @@ export default {
title: '更新成功(模拟)',
icon: 'success'
})
//
this.$emit('course-updated', this.currentCourse)
this.closeEditModal()
@ -787,7 +823,7 @@ export default {
}
},
//
getStatusClass(status) {
const statusMap = {
@ -798,7 +834,7 @@ export default {
}
return statusMap[status] || 'status-default'
},
//
getStatusText(status) {
const statusMap = {
@ -809,7 +845,7 @@ export default {
}
return statusMap[status] || '未知状态'
},
//
getProgressPercent(course) {
if (!course) return 0
@ -817,7 +853,7 @@ export default {
const used = course.used_count || 0
return Math.round((used / course.total_count) * 100)
},
//
getRemainingCount(course) {
if (!course) return 0
@ -825,7 +861,7 @@ export default {
const used = course.used_count || 0
return Math.max(0, total - used)
},
//
formatTime(timeStr) {
if (!timeStr) return ''
@ -836,7 +872,7 @@ export default {
return timeStr
}
},
//
formatDate(dateStr) {
if (!dateStr) return ''
@ -872,7 +908,7 @@ export default {
padding: 32rpx;
border: 1px solid #404040;
transition: all 0.3s ease;
&:active {
background: #4A4A4A;
}
@ -896,7 +932,7 @@ export default {
background: rgba(41, 211, 180, 0.1);
border-radius: 8rpx;
border: 1px solid #29D3B4;
&:active {
background: rgba(41, 211, 180, 0.2);
}
@ -920,27 +956,27 @@ export default {
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
&.status-active {
background: rgba(41, 211, 180, 0.2);
color: #29D3B4;
}
&.status-completed {
background: rgba(76, 175, 80, 0.2);
color: #4CAF50;
}
&.status-expired {
background: rgba(244, 67, 54, 0.2);
color: #F44336;
}
&.status-pending {
background: rgba(255, 193, 7, 0.2);
color: #FFC107;
}
&.status-default {
background: rgba(158, 158, 158, 0.2);
color: #9E9E9E;
@ -1012,17 +1048,17 @@ export default {
color: #ffffff;
flex: 1;
text-align: right;
&.highlight {
color: #29D3B4;
font-weight: 600;
}
&.price {
color: #FFC107;
font-weight: 600;
}
&.remark {
text-align: left;
line-height: 1.5;
@ -1131,7 +1167,7 @@ export default {
font-size: 32rpx;
color: #999999;
border-radius: 50%;
&:active {
background: rgba(255, 255, 255, 0.1);
}
@ -1146,7 +1182,7 @@ export default {
.form-section {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
@ -1189,7 +1225,7 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
&:active {
border-color: #29D3B4;
background: #4A4A4A;
@ -1253,7 +1289,7 @@ export default {
font-weight: 500;
border: none;
outline: none;
&.btn-cancel {
background: #404040;
color: #ffffff;
@ -1261,25 +1297,25 @@ export default {
background: #4A4A4A;
}
}
&.btn-confirm {
background: #29D3B4;
color: #ffffff;
&:active {
background: #24B89E;
}
&:disabled {
background: #666666;
color: #999999;
}
}
&.btn-test {
background: #FF6B35;
color: #ffffff;
&:active {
background: #E55A2B;
}
@ -1303,4 +1339,4 @@ export default {
.modal-body::-webkit-scrollbar-thumb:hover {
background: #24B89E;
}
</style>
</style>

24
uniapp/pages-common/contract/staff-contract-sign.vue

@ -35,8 +35,8 @@
<view class="form_section" v-if="!loading">
<view class="form_title">请填写以下信息</view>
<view class="form_content">
<view
v-for="(field, index) in formFields"
<view
v-for="(field, index) in partyBFormFields"
:key="index"
class="form_field"
>
@ -180,7 +180,7 @@ export default {
//
renderContractContent() {
if (!this.contractContent) return ''
let content = this.contractContent
//
Object.keys(this.formData).forEach(key => {
@ -188,8 +188,13 @@ export default {
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g')
content = content.replace(regex, value)
})
return content
},
// party_b
partyBFormFields() {
return this.formFields.filter(field => field.sign_party === 'party_b')
}
},
@ -276,7 +281,8 @@ export default {
initFormData() {
const data = {}
this.formFields.forEach(field => {
// party_b
this.partyBFormFields.forEach(field => {
const key = field.placeholder || field.name
//
if (field.data_type === 'database' || field.data_type === 'system') {
@ -400,8 +406,8 @@ export default {
},
validateForm() {
//
for (const field of this.formFields) {
// party_b
for (const field of this.partyBFormFields) {
if (field.is_required) {
const key = field.placeholder || field.name
const value = this.formData[key]
@ -466,8 +472,8 @@ export default {
},
getSignatureImage() {
// signature
for (const field of this.formFields) {
// signatureparty_b
for (const field of this.partyBFormFields) {
if (field.data_type === 'signature') {
const key = field.placeholder || field.name
return this.formData[key] || ''

1614
uniapp/pages-market/course/course_detail.vue

File diff suppressed because it is too large

10
uniapp/pages.json

@ -395,6 +395,14 @@
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
},
{
"path": "course/course_detail",
"style": {
"navigationBarTitleText": "课程详情",
"navigationBarBackgroundColor": "#292929",
"navigationBarTextStyle": "white"
}
}
]
},
@ -469,7 +477,7 @@
"style": {
"navigationBarTitleText": "我的工资",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#29d3b4",
"navigationBarBackgroundColor": "#171717",
"navigationBarTextStyle": "white"
}
},

Loading…
Cancel
Save