changes
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "趣味运动会"
|
||||
"navigationBarTitleText": "赛事管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -101,7 +101,7 @@
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "#000000",
|
||||
"navigationBarTitleText": "趣味运动会",
|
||||
"navigationBarTitleText": "赛事管理",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
|
||||
@@ -49,10 +49,9 @@
|
||||
style="flex-shrink:0;width: 120px;height: 90px;border-radius: 5px;" fit="fill"/>
|
||||
<view
|
||||
style="margin-left:10px;flex:1;overflow:hidden;display: flex;flex-direction: column;justify-content:space-around;color: #534d4d;">
|
||||
<view
|
||||
style="display: flex; align-items: center; column-gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<view class="activity-date-tag">
|
||||
<t-icon name="time"></t-icon>
|
||||
{{ moment(item.startTime).format('MM-DD') }}
|
||||
<text>{{ moment(item.startTime).format('YYYY年MM月DD日') }}</text>
|
||||
</view>
|
||||
<view style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<t-icon name="location"></t-icon>
|
||||
@@ -255,6 +254,22 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.activity-date-tag {
|
||||
align-self: flex-start;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
column-gap: 4px;
|
||||
max-width: 100%;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
box-sizing: border-box;
|
||||
color: #f45a5a;
|
||||
background-color: rgba(244, 90, 90, 0.12);
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
padding: 40rpx 0;
|
||||
text-align: center;
|
||||
|
||||
@@ -4,13 +4,20 @@
|
||||
<view class="tabs-container">
|
||||
<view class="tabs-header">
|
||||
<view class="tabs-title">
|
||||
<text class="title-text">报名人员</text>
|
||||
<text class="title-text">报名人员(总{{ unitRegistrationTotal }}人)</text>
|
||||
<view class="header-actions">
|
||||
<view class="person-counter">
|
||||
<text class="counter-text">{{ formData.userList.length }}人</text>
|
||||
</view>
|
||||
<view class="header-add-btn" @click="addNewUser">
|
||||
<t-icon name="add" size="16px"></t-icon>
|
||||
<view class="header-action-btn header-add-btn" @click="addNewUser">
|
||||
<AddIcon class="header-action-icon" size="18px" />
|
||||
<text>添加</text>
|
||||
</view>
|
||||
<view v-if="formData.userList.length > 0"
|
||||
class="header-action-btn header-remove-btn"
|
||||
@click="removeUser(activeTabIndex)">
|
||||
<DeleteIcon class="header-action-icon" size="17px" />
|
||||
<text>删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -25,9 +32,6 @@
|
||||
@click="switchTab(index)"
|
||||
>
|
||||
<view class="tab-content">
|
||||
<view class="tab-icon" v-if="activeTabIndex === index">
|
||||
<t-icon name="user" size="16px"></t-icon>
|
||||
</view>
|
||||
<text class="tab-text">{{ user.userName || `人员${index + 1}` }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -45,7 +49,6 @@
|
||||
label-align="left"
|
||||
scroll-to-first-error="auto"
|
||||
labelWidth="100px"
|
||||
:requiredMark="true"
|
||||
>
|
||||
<!-- <view class="form-block">-->
|
||||
<!-- <view class="form-block-title">-->
|
||||
@@ -68,104 +71,139 @@
|
||||
<view class="form-block-title">
|
||||
<t-icon name="usergroup"></t-icon>
|
||||
<text>报名人员信息</text>
|
||||
<text class="form-subtitle">{{ activeTabIndex + 1 }}/{{ formData.userList.length }}</text>
|
||||
<view class="user-actions">
|
||||
<view class="remove-user" @click="removeUser(activeTabIndex)" v-if="formData.userList.length > 1">
|
||||
<t-icon name="delete" size="18px"></t-icon>
|
||||
</view>
|
||||
</view>
|
||||
<text class="form-subtitle">
|
||||
{{ formData.userList.length > 0 ? activeTabIndex + 1 : 0 }}/{{ formData.userList.length }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-if="formData.userList.length === 0" class="form-empty">
|
||||
暂无报名人员,请点击“添加”
|
||||
</view>
|
||||
|
||||
<!-- 所有人员的表单,使用v-show控制显示 -->
|
||||
<view v-for="(user, index) in formData.userList" :key="index" class="form-fields-container"
|
||||
v-show="activeTabIndex === index">
|
||||
<t-form-item label="姓名" :name="`userList[${index}].userName`"
|
||||
:rules="[{required:true,message: '请输入姓名'}]">
|
||||
<t-form-item v-if="isFieldVisible('userName')" label="姓名" :name="`userList[${index}].userName`"
|
||||
:rules="fieldRules('userName', '请输入姓名')">
|
||||
<t-input v-model="user.userName" borderless placeholder="请输入内容"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item label="性别" :name="`userList[${index}].sex`" :rules="[{required:true,message: '请选择性别'}]">
|
||||
<t-radio-group v-model="user.sex" borderless class="radio-box">
|
||||
<t-form-item v-if="isFieldVisible('sex')" label="性别" :name="`userList[${index}].sex`"
|
||||
:rules="fieldRules('sex', '请选择性别')">
|
||||
<t-radio-group v-model="user.sex" borderless class="radio-box" @change="handleSexChange(user)">
|
||||
<t-radio :block="false" label="男" value="男"/>
|
||||
<t-radio :block="false" label="女" value="女"/>
|
||||
</t-radio-group>
|
||||
</t-form-item>
|
||||
<t-form-item label="体重(KG)" :name="`userList[${index}].weight`"
|
||||
:rules="[{required:true,message: '请输入体重拔河比赛分组需要'}]">
|
||||
<t-form-item v-if="isFieldVisible('personType')" label="人员类型"
|
||||
:name="'userList[' + index + '].personType'"
|
||||
:rules="fieldRules('personType', '请选择人员类型')">
|
||||
<t-checkbox-group v-model="user.personType" class="radio-box dict-radio-box"
|
||||
@change="handlePersonTypeChange(user, $event)">
|
||||
<t-checkbox v-for="option in fieldOptions('personType')" :key="option.value"
|
||||
:label="option.label" :value="option.value"
|
||||
:block="false" borderless
|
||||
:disabled="isPersonTypeOptionDisabled(user, option)"/>
|
||||
</t-checkbox-group>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isFieldVisible('projectType')" label="项目类型"
|
||||
:name="'userList[' + index + '].projectType'"
|
||||
:rules="fieldRules('projectType', '请选择项目类型')">
|
||||
<t-checkbox-group v-model="user.projectType" class="radio-box dict-radio-box"
|
||||
@change="handleProjectTypeChange(user, $event)">
|
||||
<t-checkbox v-for="option in fieldOptions('projectType')" :key="option.value"
|
||||
:label="option.label" :value="option.value" :block="false" borderless
|
||||
:disabled="isProjectTypeOptionDisabled(user, option)"/>
|
||||
</t-checkbox-group>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isFieldVisible('weight')" label="体重(KG)" :name="`userList[${index}].weight`"
|
||||
:rules="fieldRules('weight', '请输入体重拔河比赛分组需要')">
|
||||
<t-input placeholder="拔河比赛分组需要" type="number" borderless v-model="user.weight"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item label="单位名称">
|
||||
<t-form-item v-if="isFieldVisible('unitName')" label="单位名称"
|
||||
:name="`userList[${index}].unitName`"
|
||||
:rules="fieldRules('unitName', '请输入单位名称')">
|
||||
<t-input v-model="user.unitName" borderless placeholder="请输入单位名称" readonly></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item label="职务" :name="`userList[${index}].position`"
|
||||
requiredMark
|
||||
:rules="[{required:true,message: '请输入职务'}]">
|
||||
<t-form-item v-if="isFieldVisible('unitName')" label="单位简称">
|
||||
<t-input :model-value="unitAliasName" borderless readonly></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isFieldVisible('position')" label="职务" :name="`userList[${index}].position`"
|
||||
:rules="fieldRules('position', '请输入职务')">
|
||||
<t-input v-model="user.position" borderless placeholder="请输入职务"></t-input>
|
||||
</t-form-item>
|
||||
<!-- <t-form-item label="身份证号" :name="`userList[${index}].idCard`"-->
|
||||
<!-- :rules="[{required:true,message: '请输入身份证号码'},{idcard:true, message: '请输入正确的身份证号码'}]">-->
|
||||
<!-- <t-input v-model="user.idCard" borderless placeholder="请输入身份证号码"></t-input>-->
|
||||
<!-- </t-form-item>-->
|
||||
<t-form-item label="联系电话" :name="`userList[${index}].phone`"
|
||||
:rules="[{required:true,message: '请输入联系电话'},{telnumber:true, message: '请输入正确的联系电话'}]">
|
||||
<t-form-item v-if="isFieldVisible('phone')" label="联系电话" :name="`userList[${index}].phone`"
|
||||
:rules="fieldRules('phone', '请输入联系电话', [{telnumber:true, message: '请输入正确的联系电话'}])">
|
||||
<t-input v-model="user.phone" borderless placeholder="请输入联系电话"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item label="是否住宿" :name="`userList[${index}].isStay`"
|
||||
:rules="[{required:true,message: '请选择是否住宿'}]">
|
||||
<t-form-item v-if="isFieldVisible('birthday')" label="出生日期"
|
||||
:name="'userList[' + index + '].birthday'"
|
||||
:rules="fieldRules('birthday', '请选择出生日期')">
|
||||
<t-input v-model="user.birthday" type="date" borderless placeholder="请选择出生日期"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isFieldVisible('isStay')" label="是否住宿" :name="`userList[${index}].isStay`"
|
||||
:rules="fieldRules('isStay', '请选择是否住宿')">
|
||||
<t-radio-group v-model="user.isStay" class="radio-box">
|
||||
<t-radio :block="false" name="radio" :value="true" label="是"/>
|
||||
<t-radio :block="false" name="radio" :value="false" label="否"/>
|
||||
</t-radio-group>
|
||||
</t-form-item>
|
||||
<t-form-item label="是否联络员" :name="`userList[${index}].isContacts`"
|
||||
:rules="[{required:true,message: '请选择是否为联络员'}]">
|
||||
<t-form-item v-if="isFieldVisible('isContacts')" label="是否联络员" :name="`userList[${index}].isContacts`"
|
||||
:rules="fieldRules('isContacts', '请选择是否为联络员')">
|
||||
<t-radio-group v-model="user.isContacts" class="radio-box">
|
||||
<t-radio :block="false" name="radio" :value="true" label="是"/>
|
||||
<t-radio :block="false" name="radio" :value="false" label="否"/>
|
||||
</t-radio-group>
|
||||
</t-form-item>
|
||||
<t-form-item label="交通方式" :name="`userList[${index}].traffic`"
|
||||
:rules="[{required:true,message: '请选择交通方式'}]">
|
||||
<t-form-item v-if="isFieldVisible('traffic')" label="交通方式" :name="`userList[${index}].traffic`"
|
||||
:rules="fieldRules('traffic', '请选择交通方式')">
|
||||
<t-radio-group v-model="user.traffic" class="radio-box">
|
||||
<t-radio :block="false" name="radio" :value="'自驾'" label="自驾"/>
|
||||
<t-radio :block="false" name="radio" :value="'公共交通'" label="公共交通"/>
|
||||
</t-radio-group>
|
||||
</t-form-item>
|
||||
|
||||
<t-form-item v-if="user.traffic === '自驾'" label="车牌号" :name="`userList[${index}].carNo`"
|
||||
:rules="[{required:true,message: '请输入车牌号'}]">
|
||||
<t-form-item v-if="isFieldVisible('carNo') && user.traffic === '自驾'" label="车牌号"
|
||||
:name="`userList[${index}].carNo`" :rules="fieldRules('carNo', '请输入车牌号')">
|
||||
<t-input v-model="user.carNo" borderless placeholder="请输入车牌号"></t-input>
|
||||
</t-form-item>
|
||||
|
||||
<t-form-item v-if="user.traffic === '公共交通'" label="是否接站" :name="`userList[${index}].isStation`"
|
||||
:rules="[{required:true,message: '请选择是否接站'}]">
|
||||
<t-form-item v-if="isFieldVisible('isStation') && user.traffic === '公共交通'" label="是否接站"
|
||||
:name="`userList[${index}].isStation`" :rules="fieldRules('isStation', '请选择是否接站')">
|
||||
<t-radio-group v-model="user.isStation" class="radio-box">
|
||||
<t-radio :block="false" name="radio" :value="true" label="是"/>
|
||||
<t-radio :block="false" name="radio" :value="false" label="否"/>
|
||||
</t-radio-group>
|
||||
</t-form-item>
|
||||
|
||||
<t-form-item v-if="user.isStation" label="接站地址" :name="`userList[${index}].stationAddress`"
|
||||
:rules="[{required:true,message: '请输入接站地址'}]">
|
||||
<t-form-item v-if="isFieldVisible('stationAddress') && user.traffic === '公共交通' && user.isStation"
|
||||
label="接站地址" :name="`userList[${index}].stationAddress`"
|
||||
:rules="fieldRules('stationAddress', '请输入接站地址')">
|
||||
<t-input v-model="user.stationAddress" borderless placeholder="请输入接站地址" maxlength="50"></t-input>
|
||||
</t-form-item>
|
||||
|
||||
|
||||
<!-- <t-form-item label="展示才艺" :name="`userList[${index}].isShowSkill`" :rules="[{required:true,message: '请选择是否展示才艺'}]">-->
|
||||
<!-- <t-radio-group v-model="user.isShowSkill" class="radio-box">-->
|
||||
<!-- <t-radio :block="false" name="radio" :value="true" label="是"/>-->
|
||||
<!-- <t-radio :block="false" name="radio" :value="false" label="否"/>-->
|
||||
<!-- </t-radio-group>-->
|
||||
<!-- </t-form-item>-->
|
||||
<!-- <template v-if="formData.userList[index]?.isShowSkill">-->
|
||||
<!-- <t-form-item label="才艺名称" :name="`userList[${index}].skillName`" :rules="[{required:user.isShowSkill===true,message: '请填写才艺名称'}]">-->
|
||||
<!-- <t-input v-model="user.skillName" borderless placeholder="请输入才艺名称"></t-input>-->
|
||||
<!-- </t-form-item>-->
|
||||
<!-- <t-form-item label="所需器材" :name="`userList[${index}].equip`" :rules="[{required:user.isShowSkill===true,message: '请填写才艺所需器材'}]">-->
|
||||
<!-- <t-input v-model="user.equip" borderless placeholder="请填写才艺所需器材"></t-input>-->
|
||||
<!-- </t-form-item>-->
|
||||
<!-- </template>-->
|
||||
<t-form-item label="参加茶叙会" :name="`userList[${index}].isParty`"
|
||||
:rules="[{required:true,message: '请选择是否参加茶叙会'}]">
|
||||
<t-form-item v-if="isFieldVisible('isShowSkill')" label="展示才艺"
|
||||
:name="`userList[${index}].isShowSkill`"
|
||||
:rules="fieldRules('isShowSkill', '请选择是否展示才艺')">
|
||||
<t-radio-group v-model="user.isShowSkill" class="radio-box">
|
||||
<t-radio :block="false" name="radio" :value="true" label="是"/>
|
||||
<t-radio :block="false" name="radio" :value="false" label="否"/>
|
||||
</t-radio-group>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isFieldVisible('skillName') && user.isShowSkill" label="才艺名称"
|
||||
:name="`userList[${index}].skillName`" :rules="fieldRules('skillName', '请填写才艺名称')">
|
||||
<t-input v-model="user.skillName" borderless placeholder="请输入才艺名称"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isFieldVisible('equip') && user.isShowSkill" label="所需器材"
|
||||
:name="`userList[${index}].equip`" :rules="fieldRules('equip', '请填写才艺所需器材')">
|
||||
<t-input v-model="user.equip" borderless placeholder="请填写才艺所需器材"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="isFieldVisible('isParty')" label="参加茶叙会" :name="`userList[${index}].isParty`"
|
||||
:rules="fieldRules('isParty', '请选择是否参加茶叙会')">
|
||||
<t-radio-group v-model="user.isParty" class="radio-box">
|
||||
<t-radio :block="false" name="radio" :value="true" label="是"/>
|
||||
<t-radio :block="false" name="radio" :value="false" label="否"/>
|
||||
@@ -254,10 +292,237 @@ import {useRequest} from '@/utils/request';
|
||||
import {Dialog, Toast} from "tdesign-mobile-vue";
|
||||
import {onLoad} from "@dcloudio/uni-app";
|
||||
import {useUserStore} from "@/store";
|
||||
import {showConfirmWithHistory} from "@/utils/popupHistory";
|
||||
import {AddIcon, DeleteIcon} from "tdesign-icons-vue-next";
|
||||
|
||||
const request = useRequest()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 单位简称属于当前登录用户关联的单位信息,不随单个人员报名数据重复提交。
|
||||
const unitAliasName = computed(() => userStore?.userInfo?.unit?.aliasName || '未设置')
|
||||
|
||||
// 后端不可用时仍按历史字段展示,保证已有活动的报名页面不会变成空表单。
|
||||
const registrationFields = ref([
|
||||
{fieldKey: 'userName', required: true, options: []},
|
||||
{fieldKey: 'sex', required: true, options: []},
|
||||
{fieldKey: 'weight', required: true, options: []},
|
||||
{fieldKey: 'unitName', required: true, options: []},
|
||||
{fieldKey: 'position', required: true, options: []},
|
||||
{fieldKey: 'phone', required: true, options: []},
|
||||
{fieldKey: 'isStay', required: true, options: []},
|
||||
{fieldKey: 'isContacts', required: true, options: []},
|
||||
{fieldKey: 'traffic', required: true, options: []},
|
||||
{fieldKey: 'carNo', required: true, options: []},
|
||||
{fieldKey: 'isStation', required: true, options: []},
|
||||
{fieldKey: 'stationAddress', required: true, options: []},
|
||||
{fieldKey: 'isParty', required: true, options: []}
|
||||
])
|
||||
const registrationFieldMap = computed(() => {
|
||||
const result = {}
|
||||
registrationFields.value.forEach((item) => {
|
||||
result[item.fieldKey] = item
|
||||
})
|
||||
return result
|
||||
})
|
||||
// 单项目活动按同一单位限制教练、领队各1人;已有数量不包含当前账号即将被本表单覆盖的旧记录。
|
||||
const personTypeRuleStatus = ref({
|
||||
enabled: false,
|
||||
coachValue: '',
|
||||
leaderValue: '',
|
||||
coachCount: 0,
|
||||
leaderCount: 0,
|
||||
maxCount: 1
|
||||
})
|
||||
// 唯一项目的报名规则状态;existingCount不包含当前账号将被草稿覆盖的旧名单。
|
||||
const projectTypeRuleStatus = ref({
|
||||
enabled: false,
|
||||
rules: []
|
||||
})
|
||||
const isFieldVisible = (fieldKey) => Boolean(registrationFieldMap.value[fieldKey])
|
||||
const fieldOptions = (fieldKey) => registrationFieldMap.value[fieldKey]?.options || []
|
||||
// 红色必填标识与前端必填校验统一读取当前活动的报名字段配置,避免显示字段被全局标记为必填。
|
||||
const isFieldRequired = (fieldKey) => Boolean(registrationFieldMap.value[fieldKey]?.required)
|
||||
const fieldRules = (fieldKey, message, extraRules = []) => {
|
||||
return [{required: isFieldRequired(fieldKey), message}, ...extraRules]
|
||||
}
|
||||
|
||||
// 人员类型字典值可能由管理员维护,业务判断始终按“运动员”名称动态取得实际编码。
|
||||
const getAthleteTypeValue = () => fieldOptions('personType').find((item) => item.label === '运动员')?.value
|
||||
|
||||
// 人员类型允许“运动员+教练”或“运动员+领队”,但教练和领队不能同时选择。
|
||||
const isPersonTypeOptionDisabled = (user, option) => {
|
||||
const selectedValues = Array.isArray(user.personType) ? user.personType : []
|
||||
if (selectedValues.includes(option.value)) {
|
||||
return false
|
||||
}
|
||||
const options = fieldOptions('personType')
|
||||
const coachValue = options.find((item) => item.label === '教练')?.value
|
||||
const leaderValue = options.find((item) => item.label === '领队')?.value
|
||||
return option.label === '教练' && selectedValues.includes(leaderValue)
|
||||
|| option.label === '领队' && selectedValues.includes(coachValue)
|
||||
}
|
||||
|
||||
// 选择人员类型后立即合并后台已有数量和当前页面全部人员;超限时撤销本次人员的对应角色。
|
||||
const handlePersonTypeChange = (user, changedValues) => {
|
||||
const selectedValues = parseMultiValue(changedValues)
|
||||
user.personType = selectedValues
|
||||
const athleteValue = getAthleteTypeValue()
|
||||
if (athleteValue && !selectedValues.includes(athleteValue) && parseMultiValue(user.projectType).length > 0) {
|
||||
// 取消运动员身份后原项目已不再合法,立即清除,避免无效组合进入提交阶段。
|
||||
user.projectType = []
|
||||
Toast({message: '已取消运动员类型,原选择项目已清空', theme: 'warning'})
|
||||
}
|
||||
const status = personTypeRuleStatus.value
|
||||
if (!status.enabled) {
|
||||
return
|
||||
}
|
||||
const maxCount = Number(status.maxCount) || 1
|
||||
const countDraftType = (typeValue) => formData.value.userList.filter((item) => {
|
||||
return parseMultiValue(item.personType).includes(typeValue)
|
||||
}).length
|
||||
const coachTotal = Number(status.coachCount || 0) + countDraftType(status.coachValue)
|
||||
if (status.coachValue && selectedValues.includes(status.coachValue) && coachTotal > maxCount) {
|
||||
user.personType = selectedValues.filter((value) => value !== status.coachValue)
|
||||
Toast({message: '同一活动、同一单位最多选择1名教练', theme: 'warning'})
|
||||
return
|
||||
}
|
||||
const leaderTotal = Number(status.leaderCount || 0) + countDraftType(status.leaderValue)
|
||||
if (status.leaderValue && selectedValues.includes(status.leaderValue) && leaderTotal > maxCount) {
|
||||
user.personType = selectedValues.filter((value) => value !== status.leaderValue)
|
||||
Toast({message: '同一活动、同一单位最多选择1名领队', theme: 'warning'})
|
||||
}
|
||||
}
|
||||
|
||||
// 参加任何项目都必须先选择运动员类型;不满足时撤销本次项目选择并即时提醒。
|
||||
const handleProjectTypeChange = (user, changedValues) => {
|
||||
const athleteValue = getAthleteTypeValue()
|
||||
if (!athleteValue) {
|
||||
user.projectType = []
|
||||
Toast({message: '人员类型数据字典配置不完整,请联系管理员', theme: 'warning'})
|
||||
return
|
||||
}
|
||||
if (!parseMultiValue(user.personType).includes(athleteValue)) {
|
||||
user.projectType = []
|
||||
Toast({message: '请先选择人员类型中的“运动员”,再选择项目', theme: 'warning'})
|
||||
return
|
||||
}
|
||||
const selectedValues = parseMultiValue(changedValues)
|
||||
user.projectType = selectedValues
|
||||
const status = projectTypeRuleStatus.value
|
||||
if (!status.enabled) {
|
||||
return
|
||||
}
|
||||
// 同一子项按“其他账号已占用人数+当前草稿全部人员”统计;超限项从本次选择中撤销。
|
||||
const exceededRules = (status.rules || []).filter((rule) => {
|
||||
if (!selectedValues.includes(rule.typeCode)) {
|
||||
return false
|
||||
}
|
||||
const draftCount = formData.value.userList.filter((item) => {
|
||||
return parseMultiValue(item.projectType).includes(rule.typeCode)
|
||||
}).length
|
||||
return Number(rule.existingCount || 0) + draftCount > Number(rule.personCount)
|
||||
})
|
||||
if (exceededRules.length === 0) {
|
||||
return
|
||||
}
|
||||
const exceededValues = exceededRules.map((rule) => rule.typeCode)
|
||||
user.projectType = selectedValues.filter((value) => !exceededValues.includes(value))
|
||||
const firstRule = exceededRules[0]
|
||||
if (Number(firstRule.personCount) === 0) {
|
||||
Toast({message: '“' + firstRule.typeName + '”当前不允许报名', theme: 'warning'})
|
||||
return
|
||||
}
|
||||
Toast({
|
||||
message: '“' + firstRule.typeName + '”每个单位最多报名' + firstRule.personCount + '人,请按规则重新选择',
|
||||
theme: 'warning'
|
||||
})
|
||||
}
|
||||
|
||||
// 同性别的单打和双打互斥;男子/女子项目受人员性别限制,混合双打和其他不参与互斥。
|
||||
const isProjectTypeOptionDisabled = (user, option) => {
|
||||
const selectedValues = parseMultiValue(user.projectType)
|
||||
if (selectedValues.includes(option.value)) {
|
||||
return false
|
||||
}
|
||||
const options = fieldOptions('projectType')
|
||||
const manSingleValue = options.find((item) => item.label === '男子单打')?.value
|
||||
const womanSingleValue = options.find((item) => item.label === '女子单打')?.value
|
||||
const manDoubleValue = options.find((item) => item.label === '男子双打')?.value
|
||||
const womanDoubleValue = options.find((item) => item.label === '女子双打')?.value
|
||||
if (['男子单打', '男子双打'].includes(option.label) && user.sex !== '男') {
|
||||
return true
|
||||
}
|
||||
if (['女子单打', '女子双打'].includes(option.label) && user.sex !== '女') {
|
||||
return true
|
||||
}
|
||||
return option.label === '男子单打' && selectedValues.includes(manDoubleValue)
|
||||
|| option.label === '男子双打' && selectedValues.includes(manSingleValue)
|
||||
|| option.label === '女子单打' && selectedValues.includes(womanDoubleValue)
|
||||
|| option.label === '女子双打' && selectedValues.includes(womanSingleValue)
|
||||
}
|
||||
|
||||
// 后端使用逗号分隔保存多选字典代码;页面编辑时统一转换为数组。
|
||||
const parseMultiValue = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
return String(value).split(',').map((item) => item.trim()).filter(Boolean)
|
||||
}
|
||||
|
||||
// 旧字典的Single、Double按人员性别映射到拆分后的项目,避免历史报名重新提交时校验失败。
|
||||
const normalizeLegacyProjectType = (user) => {
|
||||
const selectedValues = parseMultiValue(user.projectType)
|
||||
const options = fieldOptions('projectType')
|
||||
const legacyMappings = [
|
||||
{
|
||||
value: 'Single',
|
||||
targetLabel: user.sex === '男' ? '男子单打' : user.sex === '女' ? '女子单打' : ''
|
||||
},
|
||||
{
|
||||
value: 'Double',
|
||||
targetLabel: user.sex === '男' ? '男子双打' : user.sex === '女' ? '女子双打' : ''
|
||||
}
|
||||
]
|
||||
const normalizedValues = [...selectedValues]
|
||||
legacyMappings.forEach((mapping) => {
|
||||
if (!normalizedValues.includes(mapping.value) || !mapping.targetLabel) {
|
||||
return
|
||||
}
|
||||
const targetValue = options.find((item) => item.label === mapping.targetLabel)?.value
|
||||
if (!targetValue) {
|
||||
return
|
||||
}
|
||||
normalizedValues.splice(normalizedValues.indexOf(mapping.value), 1, targetValue)
|
||||
})
|
||||
user.projectType = [...new Set(normalizedValues)]
|
||||
}
|
||||
|
||||
const normalizeLegacyProjectTypes = () => {
|
||||
formData.value.userList.forEach((user) => normalizeLegacyProjectType(user))
|
||||
}
|
||||
|
||||
// 性别变化后清除不再匹配的单打和双打项目,防止界面保留不可提交的非法组合。
|
||||
const handleSexChange = (user) => {
|
||||
const options = fieldOptions('projectType')
|
||||
const manSingleValue = options.find((item) => item.label === '男子单打')?.value
|
||||
const womanSingleValue = options.find((item) => item.label === '女子单打')?.value
|
||||
const manDoubleValue = options.find((item) => item.label === '男子双打')?.value
|
||||
const womanDoubleValue = options.find((item) => item.label === '女子双打')?.value
|
||||
normalizeLegacyProjectType(user)
|
||||
user.projectType = parseMultiValue(user.projectType).filter((value) => {
|
||||
if (user.sex === '男') {
|
||||
return value !== womanSingleValue && value !== womanDoubleValue
|
||||
}
|
||||
if (user.sex === '女') {
|
||||
return value !== manSingleValue && value !== manDoubleValue
|
||||
}
|
||||
return ![manSingleValue, womanSingleValue, manDoubleValue, womanDoubleValue].includes(value)
|
||||
})
|
||||
}
|
||||
|
||||
// 当前选中的选项卡索引
|
||||
const activeTabIndex = ref(0)
|
||||
|
||||
@@ -273,8 +538,11 @@ function addNewUser() {
|
||||
formData.value.userList.push({
|
||||
userName: '',
|
||||
sex: null,
|
||||
personType: [],
|
||||
projectType: [],
|
||||
unitName: userStore?.userInfo?.unit?.name,
|
||||
position: null,
|
||||
birthday: null,
|
||||
// idCard: null,
|
||||
phone: null,
|
||||
isStay: null,
|
||||
@@ -282,6 +550,10 @@ function addNewUser() {
|
||||
isShowSkill: false,
|
||||
skillName: null,
|
||||
equip: null,
|
||||
traffic: null,
|
||||
isStation: false,
|
||||
stationAddress: null,
|
||||
carNo: null,
|
||||
isParty: false
|
||||
})
|
||||
// 切换到新添加的用户选项卡
|
||||
@@ -366,11 +638,19 @@ const guideSteps = [
|
||||
// 报名信息
|
||||
const actId = ref(null)
|
||||
const isSignUp = ref(null)
|
||||
const agreementChecked = ref(false)
|
||||
// 报名页默认视为已阅读并同意,连续维护多人或重新提交时不重复要求阅读。
|
||||
const agreementChecked = ref(true)
|
||||
const formRef = ref(null)
|
||||
const formData = ref({
|
||||
userList: []
|
||||
})
|
||||
// 后端返回当前单位已保存总数;编辑原报名时先扣除旧名单,再合并当前页面草稿人数,避免重复累计。
|
||||
const savedUnitRegistrationCount = ref(0)
|
||||
const originalSignupUserCount = ref(0)
|
||||
const unitRegistrationTotal = computed(() => {
|
||||
const otherSavedCount = Math.max(0, savedUnitRegistrationCount.value - originalSignupUserCount.value)
|
||||
return otherSavedCount + formData.value.userList.length
|
||||
})
|
||||
// 协议弹窗相关
|
||||
const agreementPopupVisible = ref(false)
|
||||
const agreementCountdown = ref(5)
|
||||
@@ -385,32 +665,123 @@ onLoad(async (option) => {
|
||||
if (option.id) {
|
||||
actId.value = option.id
|
||||
formData.value.activityId = option.id
|
||||
loadRegistrationFields()
|
||||
loadUnitRegistrationCount()
|
||||
await listSignUpUser()
|
||||
await getIsSignUp()
|
||||
await getAgreement()
|
||||
// 没报名进行引导
|
||||
// 未报名时仍默认带出本人信息;按当前业务要求不再自动显示报名人员操作引导。
|
||||
if (!formData.value.userList || formData.value.userList.length === 0) {
|
||||
guideCurrent.value = 0
|
||||
// 默认是本人的信息
|
||||
formData.value.userList.push({
|
||||
userName: userStore.userInfo.userName,
|
||||
sex: userStore.userInfo.sex,
|
||||
personType: [],
|
||||
projectType: [],
|
||||
unitName: userStore?.userInfo?.unit?.name,
|
||||
position: null,
|
||||
birthday: userStore.userInfo.birthday,
|
||||
// idCard: userStore.userInfo.idCard,
|
||||
phone: userStore.userInfo.phone,
|
||||
isStay: null
|
||||
isStay: null,
|
||||
isContacts: false,
|
||||
isShowSkill: false,
|
||||
skillName: null,
|
||||
equip: null,
|
||||
traffic: null,
|
||||
isStation: false,
|
||||
stationAddress: null,
|
||||
carNo: null,
|
||||
isParty: false
|
||||
})
|
||||
}
|
||||
// 页面加载完成后自动显示重要提醒弹窗
|
||||
setTimeout(() => {
|
||||
showNoticePopup()
|
||||
}, 500)
|
||||
// 按当前业务要求屏蔽页面加载时自动显示“重要提醒”弹窗。
|
||||
} else {
|
||||
Toast({message: 'ID不存在', theme: 'error'});
|
||||
}
|
||||
})
|
||||
|
||||
// 获取当前活动启用的字段、必填规则以及数据字典选项。
|
||||
const loadRegistrationFields = () => {
|
||||
request.get('/client/activity/registration-fields/' + actId.value)
|
||||
.then(({code, data, msg}) => {
|
||||
if (code === 0 && data) {
|
||||
registrationFields.value = data
|
||||
normalizeLegacyProjectTypes()
|
||||
loadPersonTypeRuleStatus()
|
||||
loadProjectTypeRuleStatus()
|
||||
} else {
|
||||
Toast({message: msg || '加载报名字段失败', theme: 'error'})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
Toast({message: '加载报名字段失败,已使用默认报名字段', theme: 'error'})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取同单位其他报名账号已占用的教练、领队人数,供页面选择时即时校验。
|
||||
const loadPersonTypeRuleStatus = () => {
|
||||
if (!isFieldVisible('personType')) {
|
||||
personTypeRuleStatus.value = {...personTypeRuleStatus.value, enabled: false}
|
||||
return
|
||||
}
|
||||
request.get('/client/activity/person-type-rule-status/' + actId.value)
|
||||
.then(({code, data, msg}) => {
|
||||
if (code === 0 && data) {
|
||||
personTypeRuleStatus.value = data
|
||||
} else {
|
||||
personTypeRuleStatus.value = {...personTypeRuleStatus.value, enabled: false}
|
||||
Toast({message: msg || '加载人员类型规则失败', theme: 'error'})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
personTypeRuleStatus.value = {...personTypeRuleStatus.value, enabled: false}
|
||||
Toast({message: '加载人员类型规则失败,提交时将再次校验', theme: 'error'})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取唯一项目各子项人数上限和同单位其他账号已占用人数,供项目选择时即时校验。
|
||||
const loadProjectTypeRuleStatus = () => {
|
||||
if (!isFieldVisible('projectType')) {
|
||||
projectTypeRuleStatus.value = {...projectTypeRuleStatus.value, enabled: false}
|
||||
return
|
||||
}
|
||||
request.get('/client/activity/project-type-rule-status/' + actId.value)
|
||||
.then(({code, data, msg}) => {
|
||||
if (code === 0 && data) {
|
||||
projectTypeRuleStatus.value = data
|
||||
} else {
|
||||
projectTypeRuleStatus.value = {enabled: false, rules: []}
|
||||
Toast({message: msg || '加载项目报名规则失败', theme: 'error'})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
projectTypeRuleStatus.value = {enabled: false, rules: []}
|
||||
Toast({message: '加载项目报名规则失败,提交时将再次校验', theme: 'error'})
|
||||
})
|
||||
}
|
||||
|
||||
// 查询同一活动、同一单位已保存的报名人数;添加和删除后的变化由计算属性实时合并。
|
||||
const loadUnitRegistrationCount = () => {
|
||||
request.get('/client/activity/unit-registration-count/' + actId.value)
|
||||
.then(({code, data, msg}) => {
|
||||
if (code === 0) {
|
||||
savedUnitRegistrationCount.value = Number(data) || 0
|
||||
} else {
|
||||
savedUnitRegistrationCount.value = 0
|
||||
Toast({message: msg || '加载本单位报名总人数失败', theme: 'error'})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
savedUnitRegistrationCount.value = 0
|
||||
Toast({message: '加载本单位报名总人数失败', theme: 'error'})
|
||||
})
|
||||
}
|
||||
|
||||
// 查询参赛承诺书
|
||||
async function getAgreement() {
|
||||
try {
|
||||
@@ -428,8 +799,14 @@ async function listSignUpUser() {
|
||||
try {
|
||||
const {code, data} = await request.get('/client/activity/list-signup-user/' + actId.value)
|
||||
if (code === 0 && data) {
|
||||
originalSignupUserCount.value = data.length
|
||||
if (data.length > 0) {
|
||||
formData.value.userList = data
|
||||
formData.value.userList = data.map((user) => ({
|
||||
...user,
|
||||
personType: parseMultiValue(user.personType),
|
||||
projectType: parseMultiValue(user.projectType)
|
||||
}))
|
||||
normalizeLegacyProjectTypes()
|
||||
}
|
||||
} else {
|
||||
Toast({message: '查询报名人员信息失败', theme: 'error'});
|
||||
@@ -467,11 +844,11 @@ async function validaForm() {
|
||||
// 承诺书
|
||||
async function validateAgreement() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Always show the popup regardless of checkbox state
|
||||
// if (agreementChecked.value) {
|
||||
// resolve(true)
|
||||
// return
|
||||
// }
|
||||
// 已同意时直接提交;只有用户主动取消勾选后才重新进入承诺书阅读流程。
|
||||
if (agreementChecked.value) {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
|
||||
// 重置倒计时
|
||||
agreementCountdown.value = 5
|
||||
@@ -549,66 +926,99 @@ function closeAgreementPopup() {
|
||||
|
||||
// 提交表单
|
||||
async function onSubmit() {
|
||||
// 页面允许暂时删除全部人员,但提交报名时至少需要保留一名报名人员。
|
||||
if (formData.value.userList.length === 0) {
|
||||
Toast('请至少添加一名报名人员')
|
||||
return
|
||||
}
|
||||
|
||||
const formRes = await validaForm()
|
||||
if (!formRes) return
|
||||
|
||||
const agreementRes = await validateAgreement()
|
||||
if (!agreementRes) return
|
||||
|
||||
// 提交表单
|
||||
await submitForm()
|
||||
// 重新提交会覆盖当前活动的原报名信息,提交前要求用户再次核对。
|
||||
if (isSignUp.value) {
|
||||
showConfirmWithHistory({
|
||||
title: '重新提交确认',
|
||||
content: '请再次确认报名信息是否正确,确认后将更新原报名信息。',
|
||||
cancelBtn: '返回检查',
|
||||
confirmBtn: '确认提交',
|
||||
closeOnOverlayClick: false,
|
||||
onConfirm: () => {
|
||||
submitForm()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 首次报名保持原有流程,校验通过后直接提交。
|
||||
submitForm()
|
||||
}
|
||||
|
||||
// 报名
|
||||
async function submitForm() {
|
||||
function submitForm() {
|
||||
uni.showLoading({
|
||||
title: '提交中...',
|
||||
mask: true,
|
||||
})
|
||||
console.log(formData.value)
|
||||
try {
|
||||
const {code, data, msg} = await request.post('/client/activity/sign-up', formData.value)
|
||||
if (code === 0) {
|
||||
Toast.success('报名成功')
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/activity/registration-form/success?id=' + actId.value
|
||||
})
|
||||
}, 500)
|
||||
} else {
|
||||
Dialog.alert({
|
||||
title: '报名失败',
|
||||
content: msg,
|
||||
confirmBtn: '确定'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
Toast.error(e.message)
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
// 仅转换提交副本,保留页面数组结构,避免提交失败后多选控件状态异常。
|
||||
const submitData = {
|
||||
...formData.value,
|
||||
userList: formData.value.userList.map((user) => ({
|
||||
...user,
|
||||
personType: parseMultiValue(user.personType).join(','),
|
||||
projectType: parseMultiValue(user.projectType).join(',')
|
||||
}))
|
||||
}
|
||||
request.post('/client/activity/sign-up', submitData)
|
||||
.then(({code, msg}) => {
|
||||
if (code === 0) {
|
||||
Toast.success('报名成功')
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/activity/registration-form/success?id=' + actId.value
|
||||
})
|
||||
}, 500)
|
||||
} else {
|
||||
Dialog.alert({
|
||||
title: '报名失败',
|
||||
content: msg,
|
||||
confirmBtn: '确定'
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
Toast.error(error.message)
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
|
||||
// 移除人员
|
||||
async function removeUser(index) {
|
||||
if (formData.value.userList.length === 1) {
|
||||
Toast('至少需要一个报名人员')
|
||||
return
|
||||
}
|
||||
|
||||
Dialog.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除该报名人员吗?',
|
||||
confirmBtn: '确定',
|
||||
function removeUser(index) {
|
||||
const user = formData.value.userList[index]
|
||||
const userName = user?.userName || '人员' + (index + 1)
|
||||
showConfirmWithHistory({
|
||||
title: '删除报名人员',
|
||||
content: '确定删除“' + userName + '”吗?如需提交报名,请至少保留或重新添加一名报名人员。',
|
||||
confirmBtn: '确认删除',
|
||||
cancelBtn: '取消',
|
||||
closeOnOverlayClick: false,
|
||||
onConfirm: () => {
|
||||
formData.value.userList.splice(index, 1)
|
||||
// 如果删除的是当前选中的选项卡,则选中第一个选项卡
|
||||
if (index === activeTabIndex.value) {
|
||||
if (formData.value.userList.length === 0) {
|
||||
activeTabIndex.value = 0
|
||||
return
|
||||
}
|
||||
// 如果删除的是前面的选项卡,则当前选中的选项卡索引减1
|
||||
// 删除当前人员时优先显示其后一人;删除末项时显示前一人,保持操作上下文连续。
|
||||
if (index === activeTabIndex.value) {
|
||||
activeTabIndex.value = Math.min(index, formData.value.userList.length - 1)
|
||||
}
|
||||
// 删除当前选中项之前的人员后,索引前移以继续显示原选中人员。
|
||||
else if (index < activeTabIndex.value) {
|
||||
activeTabIndex.value--
|
||||
}
|
||||
@@ -672,13 +1082,18 @@ function closeNoticePopup() {
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.person-counter {
|
||||
width: 64px;
|
||||
height: 30px;
|
||||
background-color: #f45a5a;
|
||||
border-radius: 12px;
|
||||
padding: 2px 10px;
|
||||
margin-right: 8px;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.counter-text {
|
||||
@@ -687,20 +1102,46 @@ function closeNoticePopup() {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.header-add-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #f45a5a, #ff7676);
|
||||
color: #fff;
|
||||
.header-action-btn {
|
||||
width: 64px;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 6px rgba(244, 90, 90, 0.25);
|
||||
gap: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.header-add-btn:active {
|
||||
.header-action-btn text {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 使用独立 SVG 图标组件,避免 H5 环境下通用 t-icon 未渲染图形。 */
|
||||
.header-action-icon {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-add-btn {
|
||||
background: linear-gradient(135deg, #f45a5a, #ff7676);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 6px rgba(244, 90, 90, 0.25);
|
||||
}
|
||||
|
||||
.header-remove-btn {
|
||||
color: #f45a5a;
|
||||
background-color: #fff1f0;
|
||||
border: 1px solid rgba(244, 90, 90, 0.35);
|
||||
}
|
||||
|
||||
.header-action-btn:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
@@ -740,6 +1181,7 @@ function closeNoticePopup() {
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -749,13 +1191,10 @@ function closeNoticePopup() {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
margin-right: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -916,6 +1355,13 @@ function closeNoticePopup() {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.form-empty {
|
||||
padding: 36px 0;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0.6;
|
||||
@@ -925,23 +1371,16 @@ function closeNoticePopup() {
|
||||
}
|
||||
}
|
||||
|
||||
.user-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.remove-user {
|
||||
padding: 5px;
|
||||
color: #f45a5a;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-box {
|
||||
display: flex;
|
||||
column-gap: 20px;
|
||||
}
|
||||
|
||||
.dict-radio-box {
|
||||
flex-wrap: wrap;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
@@ -24,7 +24,8 @@ const viewData = ref(null);
|
||||
function getWonderfulMoment() {
|
||||
request.post('/platform/activity/wonderfulMoment/getWonderfulMomentById/' + momentId.value).then(res=>{
|
||||
if(res.code === 0){
|
||||
res.data.content = res?.data?.content.replaceAll('/open/file/upload/download?filepath=','')
|
||||
// 后台富文本保存的是后端相对预览地址,H5 端需通过 /api 代理访问实际上传文件。
|
||||
res.data.content = res?.data?.content.replaceAll('/open/file/upload/download?filepath=', '/api/open/file/upload/download?inline=true&filepath=')
|
||||
viewData.value = res.data
|
||||
}else{
|
||||
Toast({message: res.msg, theme: 'error'});
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<image class="header-bg"
|
||||
src="/static/img/index4.png"
|
||||
mode="aspectFill"></image>
|
||||
<view v-if="currentActivityTitle" class="header-activity-title">
|
||||
{{ displayActivityTitle }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-container">
|
||||
@@ -55,7 +58,7 @@
|
||||
<t-swiper-item v-for="(item, index) in actList" :key="index" style="height: 192px">
|
||||
<view class="activity-list" @click="navigateTo('/pages/activity/detail/index?id=' + item.id)">
|
||||
<view class="activity-item">
|
||||
<t-image :src=config.fileDomain+item.cover shape="round"
|
||||
<t-image :src="getActivityCoverUrl(item.cover)" shape="round"
|
||||
class="activity-image" fit="fill"/>
|
||||
<view class="activity-info">
|
||||
<view class="activity-title">{{ item.title }}</view>
|
||||
@@ -105,7 +108,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {onLoad, onShow} from "@dcloudio/uni-app";
|
||||
import {Toast} from "tdesign-mobile-vue";
|
||||
import {useRequest} from "@/utils/request";
|
||||
@@ -116,6 +119,20 @@ const request = useRequest()
|
||||
|
||||
const actList = ref([])
|
||||
const wonderfulMomentList = ref([])
|
||||
// 首页头图展示当前最新开启活动的标题,数据口径与登录页保持一致。
|
||||
const currentActivityTitle = ref('')
|
||||
// 当前活动标题在“(公司)”后分行展示,其他标题仍保留原文并按容器宽度自然换行。
|
||||
const displayActivityTitle = computed(() => {
|
||||
return currentActivityTitle.value.replace('(公司)', '(公司)\n')
|
||||
})
|
||||
|
||||
// 后台仅保存封面相对路径,首页通过文件预览接口读取活动管理中上传的实际图片。
|
||||
const getActivityCoverUrl = (cover) => {
|
||||
if (!cover) {
|
||||
return ''
|
||||
}
|
||||
return '/api/open/file/upload/download?inline=true&filepath=' + encodeURIComponent(cover)
|
||||
}
|
||||
|
||||
function navigateTo(url) {
|
||||
uni.navigateTo({
|
||||
@@ -133,6 +150,17 @@ function getActivity() {
|
||||
})
|
||||
}
|
||||
|
||||
// 公开接口返回当前最新开启活动信息;请求失败时不显示标题,也不阻断首页其他内容。
|
||||
function getLatestActivityTitle() {
|
||||
request.get('/open/common/latestActivityOrganizer').then(({code, data}) => {
|
||||
if (code === 0 && data) {
|
||||
currentActivityTitle.value = data.title || ''
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('获取当前活动标题失败', error)
|
||||
})
|
||||
}
|
||||
|
||||
function getWonderfulMoment() {
|
||||
request.post('/platform/activity/wonderfulMoment/getWonderfulMoment').then(res=>{
|
||||
if(res.code === 0){
|
||||
@@ -154,6 +182,7 @@ function handleWonderfulMoment(row) {
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
getLatestActivityTitle()
|
||||
getActivity()
|
||||
getWonderfulMoment()
|
||||
})
|
||||
@@ -178,6 +207,22 @@ uni-page-body {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.header-activity-title {
|
||||
position: absolute;
|
||||
top: 61%;
|
||||
left: 50%;
|
||||
z-index: 1;
|
||||
width: 62%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
white-space: pre-line;
|
||||
text-shadow: 0 1px 3px rgba(122, 24, 24, 0.45);
|
||||
}
|
||||
|
||||
// 菜单区域样式
|
||||
.menu-container {
|
||||
display: flex;
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
|
||||
<view class="title-content">
|
||||
<text class="main-title">第二届"钟声杯"直属工会趣味运动会</text>
|
||||
<text class="main-title">{{ activityOrganizer.title || '赛事管理' }}</text>
|
||||
<view class="sub-title-container">
|
||||
<text class="sub-title">欢迎使用</text>
|
||||
</view>
|
||||
@@ -89,7 +89,8 @@
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 验证码输入框 -->
|
||||
<!-- 测试阶段临时关闭登录验证码输入和发送入口;恢复正式登录时需取消本段注释。 -->
|
||||
<!--
|
||||
<view class="input-box" v-if="loginForm.loginMethod==='sms'">
|
||||
<t-icon name="shield-error" size="20px" class="input-icon"/>
|
||||
<input
|
||||
@@ -103,9 +104,10 @@
|
||||
{{ countDownText }}
|
||||
</view>
|
||||
</view>
|
||||
-->
|
||||
|
||||
<!-- 密码输入框 -->
|
||||
<view class="input-box" v-else>
|
||||
<view class="input-box" v-if="loginForm.loginMethod==='password'">
|
||||
<t-icon name="user-password" size="20px" class="input-icon"/>
|
||||
<input
|
||||
class="input"
|
||||
@@ -131,8 +133,8 @@
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<view class="footer-info">
|
||||
<view class="footer-item">主办单位:浙江省教育工会</view>
|
||||
<view class="footer-item">承办单位:杭州医学院</view>
|
||||
<view class="footer-item">主办单位:{{ activityOrganizer.hostUnit || '--' }}</view>
|
||||
<view class="footer-item">承办单位:{{ activityOrganizer.coHostUnit || '--' }}</view>
|
||||
<view class="footer-item">技术支持:武汉地大三思科技有限公司(智慧工会提供商)</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -155,6 +157,27 @@ const loginForm = reactive({
|
||||
loginMethod: 'sms'
|
||||
})
|
||||
|
||||
// 当前最新开启活动的主办、承办单位;没有可用活动时页面显示“--”。
|
||||
const activityOrganizer = reactive({
|
||||
title: '',
|
||||
hostUnit: '',
|
||||
coHostUnit: ''
|
||||
})
|
||||
|
||||
// 登录页处于未登录状态,因此通过公开接口获取当前最新开启活动的单位信息。
|
||||
const getLatestActivityOrganizer = () => {
|
||||
request.get('/open/common/latestActivityOrganizer').then(({code, data}) => {
|
||||
if (code === 0 && data) {
|
||||
activityOrganizer.title = data.title || ''
|
||||
activityOrganizer.hostUnit = data.hostUnit || ''
|
||||
activityOrganizer.coHostUnit = data.coHostUnit || ''
|
||||
}
|
||||
}).catch((error) => {
|
||||
// 单位信息属于辅助展示,请求失败时保留占位内容,不阻断登录流程。
|
||||
console.error('获取当前活动单位信息失败', error)
|
||||
})
|
||||
}
|
||||
|
||||
// 倒计时相关
|
||||
const countdown = ref(0)
|
||||
const timer = ref(null)
|
||||
@@ -237,7 +260,9 @@ const startCountDown = () => {
|
||||
|
||||
// 在页面挂载时恢复倒计时状态
|
||||
onMounted(() => {
|
||||
restoreCountdownState()
|
||||
// 测试阶段不启用登录验证码,因此不再恢复验证码倒计时状态。
|
||||
// restoreCountdownState()
|
||||
getLatestActivityOrganizer()
|
||||
})
|
||||
|
||||
// 在页面卸载前清除定时器
|
||||
@@ -248,82 +273,76 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// 获取验证码
|
||||
async function getSmsCode() {
|
||||
// 获取登录短信验证码:手机号通过路径参数传递,接口返回 code 和 msg 表示发送结果。
|
||||
/* 测试阶段临时关闭登录验证码发送;恢复正式登录时需同时恢复验证码输入和后端校验。
|
||||
const getSmsCode = () => {
|
||||
if (!loginForm.phone) {
|
||||
Toast('请输入手机号码');
|
||||
return
|
||||
}
|
||||
|
||||
// 验证手机号码
|
||||
if (!/^1[3-9]\d{9}$/.test(loginForm.phone)) {
|
||||
Toast('请输入正确的手机号码');
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (isCountingDown.value) return
|
||||
try {
|
||||
const {code, data, msg} = await request.post('/client/auth/send-sms-code/' + loginForm.phone)
|
||||
request.post('/client/auth/send-sms-code/' + loginForm.phone).then(({code, msg}) => {
|
||||
if (code === 0) {
|
||||
Toast({message: '验证码发送成功', theme: 'success'})
|
||||
// 开始倒计时
|
||||
startCountDown()
|
||||
} else {
|
||||
Toast({message: msg, theme: 'error'})
|
||||
}
|
||||
} catch (e) {
|
||||
Toast({message: e.message, theme: 'error'})
|
||||
}
|
||||
}).catch((error) => {
|
||||
Toast({message: error.message, theme: 'error'})
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
async function handleLogin() {
|
||||
const handleLogin = () => {
|
||||
if (!loginForm.phone) {
|
||||
Toast('请输入手机号码');
|
||||
return
|
||||
}
|
||||
if (!loginForm.smsCode && loginForm.loginMethod === 'sms') {
|
||||
Toast('请输入验证码');
|
||||
return
|
||||
}
|
||||
// 测试阶段允许已注册手机号直接登录,暂不校验验证码是否填写。
|
||||
// if (!loginForm.smsCode && loginForm.loginMethod === 'sms') {
|
||||
// Toast('请输入验证码');
|
||||
// return
|
||||
// }
|
||||
if (!loginForm.password && loginForm.loginMethod === 'password') {
|
||||
Toast('请输入密码');
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
userStore.smsLogin(loginForm).then(() => {
|
||||
userStore.getUserInfo().then(() => {
|
||||
Toast({message: '登录成功', theme: 'success'})
|
||||
const redirect = uni.getStorageSync("REDIRECT_URL")
|
||||
if(redirect){
|
||||
if(redirect.startsWith('/pages/index/index') || redirect.startsWith('/pages/activity/rank/index') || redirect.startsWith('/pages/profile/index')){
|
||||
uni.switchTab({
|
||||
url: redirect
|
||||
})
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url: redirect
|
||||
})
|
||||
}
|
||||
uni.removeStorageSync("REDIRECT_URL")
|
||||
}else{
|
||||
userStore.smsLogin(loginForm).then(() => {
|
||||
userStore.getUserInfo().then(() => {
|
||||
Toast({message: '登录成功', theme: 'success'})
|
||||
const redirect = uni.getStorageSync("REDIRECT_URL")
|
||||
if(redirect){
|
||||
if(redirect.startsWith('/pages/index/index') || redirect.startsWith('/pages/activity/rank/index') || redirect.startsWith('/pages/profile/index')){
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
url: redirect
|
||||
})
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url: redirect
|
||||
})
|
||||
}
|
||||
}).catch(e => {
|
||||
console.log(e)
|
||||
Toast({message: e.message, theme: 'error'})
|
||||
})
|
||||
}).catch(e => {
|
||||
uni.removeStorageSync("REDIRECT_URL")
|
||||
}else{
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.log(e)
|
||||
Toast({message: e.message, theme: 'error'})
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}).catch((e) => {
|
||||
console.log(e)
|
||||
Toast({message: e.message, theme: 'error'})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function navigateToRegister() {
|
||||
@@ -384,7 +403,7 @@ body {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 36px;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</div>
|
||||
|
||||
<view class="title-content">
|
||||
<text class="main-title">第二届"钟声杯"直属工会趣味运动会</text>
|
||||
<text class="main-title">{{ currentActivityTitle || '赛事管理' }}</text>
|
||||
<view class="sub-title-container">
|
||||
<text class="sub-title">用户注册</text>
|
||||
</view>
|
||||
@@ -95,6 +95,19 @@
|
||||
<t-icon name="chevron-right" size="20px" class="arrow-icon"/>
|
||||
</view>
|
||||
|
||||
<!-- 单位简称首次登记后作为单位公共信息使用,已有简称由后台回填且注册时不会被覆盖。 -->
|
||||
<view class="input-box">
|
||||
<t-icon name="edit" size="20px" class="input-icon"/>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="formData.unitAliasName"
|
||||
placeholder="请输入单位简称(如:南邮工会)"
|
||||
maxlength="100"
|
||||
:disabled="unitAliasLocked"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- <!– 身份证输入框 –>-->
|
||||
<!-- <view class="input-box">-->
|
||||
<!-- <t-icon name="creditcard" size="20px" class="input-icon"/>-->
|
||||
@@ -116,8 +129,8 @@
|
||||
</t-radio-group>
|
||||
</view>
|
||||
|
||||
<!-- 出生年月输入框 -->
|
||||
<view class="input-box">
|
||||
<!-- 当前注册流程暂不采集出生日期,保留原选择控件便于后续恢复。 -->
|
||||
<view v-if="false" class="input-box">
|
||||
<t-icon name="creditcard" size="20px" class="input-icon"/>
|
||||
<input
|
||||
class="input"
|
||||
@@ -155,6 +168,12 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 短信可能被系统拦截或受双卡设置影响,提前提示用户检查手机接收状态。 -->
|
||||
<view class="sms-reminder">
|
||||
<text class="sms-reminder-title">温馨提醒:</text>
|
||||
<text>如果您长时间没有收到短信,请检查手机是否当做骚扰短信被拦截,或您是否有两个手机卡号,可能需要切换主副卡</text>
|
||||
</view>
|
||||
|
||||
<!-- 注册按钮 -->
|
||||
<view class="submit-btn" @click="handleRegister">
|
||||
{{ registering ? '注册中...' : '立即注册' }}
|
||||
@@ -242,14 +261,29 @@ import {useUserStore} from "@/store";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 注册页与登录页使用同一口径展示当前最新开启活动的标题。
|
||||
const currentActivityTitle = ref('');
|
||||
|
||||
const getLatestActivityTitle = () => {
|
||||
useRequest().get('/open/common/latestActivityOrganizer').then(({code, data}) => {
|
||||
if (code === 0 && data) {
|
||||
currentActivityTitle.value = data.title || '';
|
||||
}
|
||||
}).catch((error) => {
|
||||
// 标题属于辅助展示,请求失败时使用“赛事管理”占位,不阻断注册流程。
|
||||
console.error('获取当前活动标题失败', error);
|
||||
});
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
userName: '',
|
||||
phone: '',
|
||||
unitId: '',
|
||||
unitName: '',
|
||||
unitAliasName: '',
|
||||
// idCard: '',
|
||||
birthday: '',
|
||||
birthday: null,
|
||||
smsCode: ''
|
||||
});
|
||||
|
||||
@@ -264,6 +298,7 @@ const unitList = ref([]);
|
||||
const searchKeyword = ref('');
|
||||
const selectedUnitId = ref('');
|
||||
const scrollViewId = ref('');
|
||||
const unitAliasLocked = ref(false);
|
||||
|
||||
// 筛选单位列表
|
||||
const filteredUnits = computed(() => {
|
||||
@@ -341,6 +376,8 @@ const confirmUnitSelection = () => {
|
||||
if (selectedUnit) {
|
||||
formData.unitId = selectedUnit.id;
|
||||
formData.unitName = selectedUnit.name;
|
||||
formData.unitAliasName = selectedUnit.aliasName || '';
|
||||
unitAliasLocked.value = Boolean(selectedUnit.aliasName);
|
||||
}
|
||||
showUnitPicker.value = false;
|
||||
};
|
||||
@@ -371,7 +408,7 @@ const sendVerificationCode = async () => {
|
||||
|
||||
sendingCode.value = true;
|
||||
try {
|
||||
const {code, msg} = await useRequest().post('/client/auth//send-reg-code/' + formData.phone);
|
||||
const {code, msg} = await useRequest().post('/client/auth/send-reg-code/' + formData.phone);
|
||||
|
||||
if (code === 0) {
|
||||
Toast({message: '验证码已发送'});
|
||||
@@ -416,14 +453,17 @@ const handleRegister = async () => {
|
||||
// Toast('请输入身份证号');
|
||||
// return
|
||||
// }
|
||||
if (!formData.birthday) {
|
||||
Toast('请选择出生日期');
|
||||
return
|
||||
}
|
||||
// 当前注册流程暂不要求出生日期,恢复字段显示时再同步启用必填校验。
|
||||
if (!formData.unitId) {
|
||||
Toast('请选择单位');
|
||||
return
|
||||
}
|
||||
const unitAliasName = formData.unitAliasName.trim();
|
||||
if (!unitAliasName) {
|
||||
Toast('请输入单位简称');
|
||||
return
|
||||
}
|
||||
formData.unitAliasName = unitAliasName;
|
||||
// if (!formData.idCard.match(/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/)) {
|
||||
// Toast('请输入正确的身份证号');
|
||||
// return
|
||||
@@ -474,6 +514,7 @@ const goToLogin = () => {
|
||||
|
||||
// 页面加载时获取单位列表
|
||||
onMounted(() => {
|
||||
getLatestActivityTitle();
|
||||
getUnitList();
|
||||
});
|
||||
</script>
|
||||
@@ -527,7 +568,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 36px;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
@@ -620,6 +661,19 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.sms-reminder {
|
||||
margin: -6px 10px 10px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sms-reminder-title {
|
||||
color: #f45a5a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 32px;
|
||||
height: 50px;
|
||||
@@ -637,6 +691,10 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.sms-reminder + .submit-btn {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.login-link-box {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view style="display: flex;">
|
||||
<image :src="item.cover" mode="aspectFill"
|
||||
<image :src="getActivityCoverUrl(item.cover)" mode="aspectFill"
|
||||
style="flex-shrink:0;width: 120px;height: 90px;border-radius: 5px;"></image>
|
||||
<view
|
||||
style="margin-left:10px;flex:1;overflow:hidden;display: flex;flex-direction: column;justify-content:space-around;color: #534d4d;">
|
||||
@@ -118,6 +118,14 @@ const loading = ref(false);
|
||||
const hasMore = ref(true);
|
||||
const refresherTriggered = ref(false);
|
||||
|
||||
// 后台保存活动封面的相对路径,H5端统一通过当前环境的文件预览代理读取。
|
||||
const getActivityCoverUrl = (cover) => {
|
||||
if (!cover) {
|
||||
return '';
|
||||
}
|
||||
return '/api/open/file/upload/download?inline=true&filepath=' + encodeURIComponent(cover);
|
||||
};
|
||||
|
||||
// 选择状态筛选
|
||||
const selectStatusFilter = (status) => {
|
||||
pageForm.status = status;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 127 KiB |
@@ -1,3 +1,4 @@
|
||||
export const config = {
|
||||
fileDomain: 'https://3skj.ltd',
|
||||
}
|
||||
// 后台图片字段保存相对路径,统一通过当前环境的 API 代理读取,避免绑定固定部署域名。
|
||||
fileDomain: '/api/open/file/upload/download?inline=true&filepath=',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import {Dialog} from 'tdesign-mobile-vue';
|
||||
|
||||
const popupHistoryStack = [];
|
||||
let popupHistorySequence = 0;
|
||||
let popstateListenerRegistered = false;
|
||||
|
||||
const canUseBrowserHistory = () => typeof window !== 'undefined' && Boolean(window.history);
|
||||
|
||||
// 浏览器返回时只关闭最后打开的弹框,避免直接离开当前业务页面。
|
||||
const handlePopState = () => {
|
||||
const popupEntry = popupHistoryStack.pop();
|
||||
if (popupEntry && !popupEntry.programmaticClosing) {
|
||||
popupEntry.closePopup();
|
||||
}
|
||||
};
|
||||
|
||||
const ensurePopstateListener = () => {
|
||||
if (!canUseBrowserHistory() || popstateListenerRegistered) {
|
||||
return;
|
||||
}
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
popstateListenerRegistered = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 为弹框增加一层浏览器历史。
|
||||
*
|
||||
* @param {Function} closePopup 用户通过浏览器返回键关闭弹框时执行的方法
|
||||
* @return {number|null} 弹框历史记录ID;非浏览器环境返回null
|
||||
*/
|
||||
const pushPopupHistory = (closePopup) => {
|
||||
if (!canUseBrowserHistory()) {
|
||||
return null;
|
||||
}
|
||||
ensurePopstateListener();
|
||||
popupHistorySequence += 1;
|
||||
const id = popupHistorySequence;
|
||||
popupHistoryStack.push({id, closePopup, programmaticClosing: false});
|
||||
window.history.pushState({popupHistoryId: id}, '', window.location.href);
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户点击弹框按钮时同步回退对应的浏览器历史。
|
||||
*
|
||||
* @param {number|null} id pushPopupHistory返回的弹框历史记录ID
|
||||
* @return {void}
|
||||
*/
|
||||
const closePopupHistory = (id) => {
|
||||
if (!canUseBrowserHistory() || id === null) {
|
||||
return;
|
||||
}
|
||||
const popupEntry = popupHistoryStack[popupHistoryStack.length - 1];
|
||||
if (!popupEntry || popupEntry.id !== id || popupEntry.programmaticClosing) {
|
||||
return;
|
||||
}
|
||||
popupEntry.programmaticClosing = true;
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示带H5返回键支持的确认框。
|
||||
*
|
||||
* @param {Object} options TDesign确认框参数;onConfirm、onCancel、onClose分别处理确认、取消和关闭
|
||||
* @return {Object|undefined} TDesign Dialog实例;服务端渲染环境可能返回undefined
|
||||
*/
|
||||
export const showConfirmWithHistory = (options = {}) => {
|
||||
let popupHistoryId = null;
|
||||
let dialogInstance = null;
|
||||
const closeHistory = () => {
|
||||
closePopupHistory(popupHistoryId);
|
||||
};
|
||||
dialogInstance = Dialog.confirm({
|
||||
...options,
|
||||
onConfirm: (context) => {
|
||||
closeHistory();
|
||||
if (options.onConfirm) {
|
||||
options.onConfirm(context);
|
||||
}
|
||||
},
|
||||
onCancel: (context) => {
|
||||
closeHistory();
|
||||
if (options.onCancel) {
|
||||
options.onCancel(context);
|
||||
}
|
||||
},
|
||||
onClose: (context) => {
|
||||
closeHistory();
|
||||
if (options.onClose) {
|
||||
options.onClose(context);
|
||||
}
|
||||
}
|
||||
});
|
||||
popupHistoryId = pushPopupHistory(() => {
|
||||
if (dialogInstance) {
|
||||
dialogInstance.hide();
|
||||
}
|
||||
});
|
||||
return dialogInstance;
|
||||
};
|
||||
@@ -34,8 +34,8 @@ export default defineConfig({
|
||||
},*/
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://119.91.224.39:9090/',
|
||||
// target: 'http://192.168.118.1:9090/',
|
||||
// target: 'http://119.91.224.39:9090/',
|
||||
target: 'http://127.0.0.1:9090/',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user