shinnosuke
2026-08-24 9e6b0d57c26f98aa70aa1dc1903375b1f556648f
又又更新
11个文件已修改
213 ■■■■■ 已修改文件
rencaika/pages/card/card.uvue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/pages/talent-code/talent-code.uvue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/pages/merchant/merchant.uvue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/pages/scan/scan.uvue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/pages/withdrawal-detail/withdrawal-detail.uvue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/layouts/AdminLayout.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/types/admin.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/MerchantCreateView.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/RoleFormView.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/StaffManagementView.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/pages/card/card.uvue
@@ -48,14 +48,14 @@
                <text class="refresh-tip">{{canShowCode ? refreshTip : '请联系服务专员了解卡片状态'}}</text>
            </view>
            <view v-if="advisorName != ''" class="service-panel">
            <view v-if="canShowCode" class="service-panel">
                <view class="service-title-row"><view class="title-line"></view><text>{{cardLevel}}专属服务专员</text><view class="title-line"></view></view>
                <text class="service-advisor">{{advisorDepartment}} {{advisorName}}</text>
                <text class="service-advisor">{{advisorName != '' ? advisorDepartment + ' ' + advisorName : '暂未配置服务专员'}}</text>
                <view class="service-actions">
                    <view v-if="advisorConsultUrl != ''" class="service-action" hover-class="service-action-active" @tap="onlineConsult">
                    <view class="service-action" hover-class="service-action-active" @tap="onlineConsult">
                        <image src="/static/card-assets/consult.png"></image><text>在线咨询</text>
                    </view>
                    <view v-if="advisorMobile != ''" class="service-action" hover-class="service-action-active" @tap="callAdvisor">
                    <view class="service-action" hover-class="service-action-active" @tap="callAdvisor">
                        <image src="/static/card-assets/phone.png"></image><text>拨打电话</text>
                    </view>
                </view>
@@ -75,8 +75,11 @@
        <view v-if="showApplyNotice" class="apply-mask" @tap="closeApplyNotice">
            <view class="apply-dialog" @tap.stop="preventClose">
                <text class="dialog-title">温馨提示</text>
                <text class="dialog-copy">请前往东区政务服务中心(东区大渡口街86号)区人才综合服务专窗线下进行人才认定。</text>
                <text class="dialog-phone">0812-3509008,东区人才综合服务专窗</text>
                <text class="dialog-copy">  请前往东区政务服务中心(东区大渡口街86号)线下进行人才认定。</text>
                <view class="dialog-contact">
                    <text class="dialog-phone">联系电话:0812-3509008</text>
                    <text class="dialog-office">东区人才综合服务专窗</text>
                </view>
                <view class="dialog-confirm" hover-class="dialog-confirm-active" @tap="closeApplyNotice"><text>确定</text></view>
            </view>
        </view>
@@ -209,7 +212,7 @@
        if (!advisorConsultUrl.value.match(/^https:\/\//)) { uni.showToast({ title: '暂未配置在线咨询', icon: 'none' }); return }
        uni.navigateTo({ url: '/pages/external-webview/external-webview?source=advisor&talentId=' + encodeURIComponent(talentId.value) })
    }
    const callAdvisor = () => { if (advisorMobile.value != '') uni.makePhoneCall({ phoneNumber: advisorMobile.value }) }
    const callAdvisor = () => { if (advisorMobile.value == '') { uni.showToast({ title: '暂未配置服务专员电话', icon: 'none' }); return }; uni.makePhoneCall({ phoneNumber: advisorMobile.value }) }
    const goHome = () => uni.reLaunch({ url: '/pages/index/index' })
    const goBenefits = () => uni.reLaunch({ url: '/pages/benefits/benefits' })
    const goPolicy = () => uni.reLaunch({ url: '/pages/policy/policy' })
@@ -293,7 +296,9 @@
    .apply-dialog { width: 100%; box-sizing: border-box; padding: 59rpx 47rpx 39rpx; border-radius: 32rpx; background-color: #FFFFFF; }
    .dialog-title { display: block; color: #386BB0; font-size: 43rpx; line-height: 60rpx; font-weight: 700; text-align: center; }
    .dialog-copy { display: block; margin-top: 44rpx; color: #737F91; font-size: 27rpx; line-height: 42rpx; font-weight: 600; }
    .dialog-phone { display: block; margin-top: 16rpx; color: #FF8957; font-size: 27rpx; line-height: 39rpx; font-weight: 700; }
    .dialog-contact { display: flex; margin-top: 16rpx; flex-direction: column; align-items: flex-end; }
    .dialog-phone, .dialog-office { display: block; color: #FF8957; font-size: 27rpx; line-height: 39rpx; font-weight: 700; text-align: right; }
    .dialog-office { margin-top: 4rpx; }
    .dialog-confirm { display: flex; width: 200rpx; height: 60rpx; margin: 39rpx auto 0; border-radius: 31rpx; background-image: linear-gradient(90deg, #FF4A32 0%, #FF8A1B 100%); justify-content: center; align-items: center; }
    .dialog-confirm text { color: #FFFFFF; font-size: 27rpx; font-weight: 700; }
    .dialog-confirm-active { opacity: .78; }
rencaika/pages/talent-code/talent-code.uvue
@@ -132,7 +132,6 @@
        stopTimers()
        codeLoading.value = true
        codeLoadFailed.value = false
        codeImageUrl.value = ''
        codeMessage.value = '正在生成动态人才码'
        issueTalentDynamicCodeForScene(talentId.value, scene.value, merchantId.value, (data: any) => {
            codeLoading.value = false
@@ -141,8 +140,13 @@
            codeImageUrl.value = getApiBaseUrl().replace(/\/$/, '') + (data.imagePath as string)
            expiresAt.value = Number(data.expiresAt)
            setCountdown()
            const seconds = data.refreshAfterSeconds != null ? Number(data.refreshAfterSeconds) : 60
            refreshTimer = setTimeout(() => requestDynamicCode(), Math.max(1, seconds - 2) * 1000)
            // Refresh only after the displayed code expires so the countdown never jumps back early.
            const refreshDelay = Math.max(1, expiresAt.value - Date.now())
            refreshTimer = setTimeout(() => {
                remainingSeconds.value = 0
                refreshTip.value = '正在刷新动态人才码'
                requestDynamicCode()
            }, refreshDelay)
        }, (error) => { if (pageVisible) handleCodeFailure(error.message); else codeLoading.value = false })
    }
    const handleCodeFailure = (message: string) => { stopTimers(); codeLoading.value = false; codeImageUrl.value = ''; codeLoadFailed.value = true; codeMessage.value = message == '' ? '动态人才码加载失败' : message; refreshTip.value = '动态人才码已失效,请重新获取' }
rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
@@ -18,7 +18,7 @@
                <view class="field-block">
                    <text class="field-label">统一社会信用代码</text>
                    <view class="input-shell" :class="invalidField == 'creditCode' ? 'field-invalid' : ''"><input class="text-input" v-model="creditCode" maxlength="18" placeholder="请输入18位统一社会信用代码" placeholder-class="input-placeholder" @input="normalizeCreditCode" /></view>
                    <view class="input-shell" :class="invalidField == 'creditCode' ? 'field-invalid' : ''"><input class="text-input" :value="creditCode" maxlength="18" placeholder="请输入18位统一社会信用代码" placeholder-class="input-placeholder" @input="normalizeCreditCode" /></view>
                    <text v-if="invalidField == 'creditCode'" class="field-error">{{errorMessage}}</text>
                </view>
@@ -160,8 +160,9 @@
        }
    }
    const normalizeCreditCode = () => {
        creditCode.value = creditCode.value.toUpperCase().replace(/[^0-9A-Z]/g, '')
    const normalizeCreditCode = (event: any) => {
        const input = event != null && event.detail != null && event.detail.value != null ? '' + event.detail.value : ''
        creditCode.value = input.toUpperCase().replace(/[^0-9A-Z]/g, '')
        clearFieldError('creditCode')
    }
rencaika_merchant/pages/merchant/merchant.uvue
@@ -19,8 +19,6 @@
            </view>
            <view class="setting-card">
                <view class="setting-row" @tap="handleMenuAction('结算管理')"><view class="setting-leading"><image class="setting-icon" src="/static/icons/lucide/hand-coins.svg" mode="aspectFit"></image><text class="setting-label">结算管理</text></view><image class="setting-chevron" src="/static/icons/lucide/chevron-right.svg" mode="aspectFit"></image></view>
                <view class="setting-row" @tap="handleMenuAction('商户提现')"><view class="setting-leading"><image class="setting-icon" src="/static/icons/lucide/wallet-cards-gray.svg" mode="aspectFit"></image><text class="setting-label">商户提现</text></view><image class="setting-chevron" src="/static/icons/lucide/chevron-right.svg" mode="aspectFit"></image></view>
                <view class="setting-row">
                    <view class="setting-leading"><image class="setting-icon" src="/static/icons/lucide/store.svg" mode="aspectFit"></image><text class="setting-label">营业状态</text></view>
                    <view class="business-state" :class="businessUpdating ? 'business-state-disabled' : ''" @tap="toggleBusiness"><text class="business-state-text">{{isBusinessOpen ? '营业中' : '休息中'}}</text><view class="state-switch" :class="isBusinessOpen ? 'open' : ''"><view class="state-thumb"></view></view></view>
@@ -93,10 +91,6 @@
        }
        if (name === '商户提现') {
            uni.navigateTo({ url: '/pages/withdrawal/withdrawal' })
            return
        }
        if (name === '结算管理') {
            uni.navigateTo({ url: '/pages/settlement-management/settlement-management' })
            return
        }
        if (name === '营业时间') {
rencaika_merchant/pages/scan/scan.uvue
@@ -101,7 +101,10 @@
                canRetryVerification.value = false
                return
            }
            if (result.credentialType == 'TALENT_CODE') {
            // An order QR must always enter the confirmation page. Keep the order
            // number as a fallback discriminator for older/incorrect type payloads.
            const isTalentCode = result.credentialType == 'TALENT_CODE' && result.orderNo.length == 0
            if (isTalentCode) {
                openedManualConsumption = true
                uni.navigateTo({ url: '/pages/consume-input/consume-input?verificationId=' + encodeURIComponent(result.verificationId) })
                return
rencaika_merchant/pages/withdrawal-detail/withdrawal-detail.uvue
@@ -1,15 +1,14 @@
<template>
    <view class="page">
        <view class="navigation-bar"><view class="back-button" @tap="goBack"><image class="back-icon" src="/static/icons/lucide/back-left.svg" mode="aspectFit"></image></view><text class="page-title">商户提现</text></view>
        <view class="navigation-bar"><view class="back-button" @tap="goBack"><image class="back-icon" src="/static/icons/lucide/back-left-white.svg" mode="aspectFit"></image></view><text class="page-title">商户提现</text></view>
        <scroll-view class="form-scroll" scroll-y="true" :show-scrollbar="false" :enhanced="false">
            <view class="available-row"><text class="available-label">当前可提</text><text class="available-value">¥{{availableText}}</text></view>
            <view class="form-card">
                <view class="available-row"><text class="available-label">当前可提</text><text class="available-value">¥{{availableText}}</text></view>
                <view class="field-block">
                    <view class="field-row amount-field-row">
                        <text class="field-label">提现金额</text>
                        <view class="amount-input-shell" :class="error.length > 0 ? 'field-invalid' : ''">
                            <text class="amount-prefix">¥</text>
                            <input class="amount-input" v-model="amount" type="digit" maxlength="10" placeholder="请输入提现金额" placeholder-class="input-placeholder" @input="clearError" />
                            <input class="amount-input" v-model="amount" type="digit" maxlength="10" placeholder="请输入" placeholder-class="input-placeholder" @input="clearError" />
                        </view>
                    </view>
                    <text v-if="error.length > 0" class="field-error">{{error}}</text>
@@ -17,18 +16,17 @@
                <view class="field-block bank-field-block">
                    <view class="field-row bank-field-row">
                        <text class="field-label">提现银行卡</text>
                        <view class="bank-value">
                        <view v-if="selected != null" class="bank-value">
                            <view v-if="selected != null" class="selected-bank-copy">
                                <text class="selected-bank-name">{{selected.bankName}}</text>
                                <text class="selected-card-number">{{selected.cardMasked}}</text>
                            </view>
                            <text v-else class="bank-placeholder">请选择银行卡</text>
                        </view>
                        <view class="select-bank-button" @tap="chooseBank"><text>{{selected == null ? '选择' : '更换'}}</text></view>
                    </view>
                </view>
                <view class="submit-button" :class="submitting ? 'submit-disabled' : ''" @tap="submit"><text>{{submitting ? '提交中' : '发起提现'}}</text></view>
            </view>
            <view class="submit-button" :class="submitting ? 'submit-disabled' : ''" @tap="submit"><text>{{submitting ? '提交中' : '发起提现'}}</text></view>
            <view class="safe-space"></view>
        </scroll-view>
    </view>
@@ -101,4 +99,34 @@
    .select-bank-button { width: 240rpx; height: 76rpx; margin: 18rpx 0 0; border-color: #F0D1B9; border-radius: 38rpx; color: #D97643; background-color: #FFF9F4; font-size: 28rpx; }
    .submit-button { width: 100%; height: 88rpx; margin: 62rpx 0 0; border-radius: 44rpx; color: #FFFFFF; background-image: linear-gradient(90deg, #FF7625 0%, #FF9630 100%); font-size: 29rpx; line-height: 41rpx; font-weight: 700; box-shadow: 0 12rpx 24rpx rgba(255, 107, 36, 0.20); }
    .safe-space { height: 70rpx; }
    /* Keep the withdrawal form aligned with the compact prototype layout. */
    page { background-color: #FFFFFF; }
    .page { background-color: #FFFFFF; }
    .navigation-bar { height: 144rpx; padding: 64rpx 32rpx 0; background-color: #B7B7B7; align-items: center; }
    .back-button { left: 32rpx; top: 64rpx; width: 64rpx; height: 64rpx; }
    .back-icon { width: 40rpx; height: 40rpx; }
    .page-title { color: #FFFFFF; font-size: 30rpx; line-height: 42rpx; }
    .form-scroll { padding: 34rpx 26rpx 0; background-color: #FFFFFF; }
    .available-row { display: flex; height: 88rpx; padding: 0; flex-direction: row; justify-content: flex-start; align-items: center; }
    .available-label, .available-value { color: #777777; font-size: 27rpx; line-height: 38rpx; font-weight: 400; }
    .available-value { margin-left: 12rpx; }
    .form-card { padding: 0 20rpx; border-radius: 0; background-color: #FFFFFF; box-shadow: none; }
    .field-row { min-height: 158rpx; flex-direction: row; align-items: center; }
    .field-label, .amount-field-row .field-label, .bank-field-row .field-label { width: 142rpx; margin: 0; padding: 0; flex: none; color: #222222; font-size: 28rpx; line-height: 40rpx; font-weight: 400; text-align: left; }
    .amount-field-row { min-height: 158rpx; flex-direction: row; align-items: center; }
    .amount-input-shell { width: calc(100% - 142rpx); height: 104rpx; padding: 0 18rpx; flex: none; border: 1rpx solid #D7D7D7; border-radius: 5rpx; background-color: #FFFFFF; }
    .amount-prefix { color: #999999; font-size: 28rpx; }
    .amount-input { height: 102rpx; margin-left: 8rpx; color: #333333; font-size: 28rpx; }
    .input-placeholder { color: #999999; font-size: 28rpx; }
    .bank-field-block { margin-top: 0; }
    .bank-field-row { min-height: 158rpx; flex-direction: row; align-items: center; }
    .bank-value { min-width: 0; margin-right: 12rpx; flex: 1; }
    .selected-bank-copy { flex-direction: column; }
    .selected-bank-name { color: #333333; font-size: 26rpx; }
    .selected-card-number { margin-top: 2rpx; color: #888888; font-size: 23rpx; }
    .select-bank-button { width: 134rpx; height: 58rpx; padding: 0; margin: 0; border: 0; border-radius: 5rpx; color: #FFFFFF; background-color: #3478FF; font-size: 27rpx; font-weight: 400; }
    .submit-button { width: calc(100% - 40rpx); height: 80rpx; margin: 70rpx 0 0 20rpx; border-radius: 6rpx; color: #FFFFFF; background-color: #3478FF; background-image: none; font-size: 28rpx; line-height: 40rpx; font-weight: 400; box-shadow: none; }
    .field-error { margin-left: 142rpx; }
    .safe-space { height: 70rpx; }
</style>
talent-card-admin/src/layouts/AdminLayout.vue
@@ -83,7 +83,6 @@
const reservationMenus: MenuItem[] = [
  { path: '/medical-reservations', label: '体检预约管理' },
  { path: '/medical-applications', label: '就医申请管理' },
  { path: '/parking-plates', label: '停车车牌管理' },
  { path: '/fuel-services', label: '加油服务管理' },
  { path: '/spouse-employment', label: '配偶就业管理' },
talent-card-admin/src/types/admin.ts
@@ -181,6 +181,7 @@
  id: number | string
  merchantName: string
  merchantType?: string
  merchantTypeCode?: string
  contactName?: string
  contactMobile?: string
  logoUrl?: string
talent-card-admin/src/views/MerchantCreateView.vue
@@ -80,35 +80,61 @@
const isEditMode = computed(() => route.query.mode === 'edit' && hasValidMerchantId.value)
const hasExistingMerchant = computed(() => isEditMode.value)
const normalizedMerchantType = computed(() => String(form.merchantType || '').trim())
const isFoodMerchant = computed(() => normalizedMerchantType.value === '餐饮服务')
const isApartmentMerchant = computed(() => normalizedMerchantType.value === '公寓租赁')
const isHotelMerchant = computed(() => normalizedMerchantType.value === '酒店服务')
const isCarWashMerchant = computed(() => normalizedMerchantType.value === '自助洗车')
const isHealthCheckMerchant = computed(() => normalizedMerchantType.value === '免费体检')
const isParkingMerchant = computed(() => normalizedMerchantType.value === '停车服务')
const isFuelMerchant = computed(() => normalizedMerchantType.value === '加油服务')
const isChildcareMerchant = computed(() => normalizedMerchantType.value === '子女托管')
const isFinancialMerchant = computed(() => normalizedMerchantType.value === '金融服务')
const merchantTypeAliases: Record<string, string> = {
  '餐饮服务': 'food', food: 'food',
  '景区服务': 'scenic', scenic: 'scenic',
  '酒店服务': 'hotel', hotel: 'hotel',
  '公寓租赁': 'apartment', apartment: 'apartment',
  '自助洗车': 'carWash', '自助洗车服务': 'carWash', carWash: 'carWash',
  '免费体检': 'medical', medical: 'medical',
  '停车服务': 'parking', parking: 'parking',
  '加油服务': 'fuel', fuel: 'fuel',
  '子女托管': 'children', children: 'children',
  '金融服务': 'finance', finance: 'finance',
  '配偶推荐就业': 'employment', employment: 'employment',
  '行政审批': 'approval', approval: 'approval',
  '观影服务': 'cinema', cinema: 'cinema', theatre: 'cinema', '电影院': 'cinema', '影院': 'cinema', '影城': 'cinema', '剧院': 'cinema',
  '购房优惠': 'housing', housing: 'housing',
  '购车服务': 'car', car: 'car',
  '家电服务': 'appliance', appliance: 'appliance',
  '数码服务': 'digital', digital: 'digital',
  '文化服务': 'culture', culture: 'culture', book: 'culture',
  '电信服务': 'telecom', telecom: 'telecom',
  '口腔治疗保健': 'dental', dental: 'dental',
}
const merchantTypeCode = computed(() => {
  const raw = String(form.merchantTypeCode || normalizedMerchantType.value).trim()
  return merchantTypeAliases[raw] || raw
})
const isFoodMerchant = computed(() => merchantTypeCode.value === 'food')
const isApartmentMerchant = computed(() => merchantTypeCode.value === 'apartment')
const isHotelMerchant = computed(() => merchantTypeCode.value === 'hotel')
const isCarWashMerchant = computed(() => merchantTypeCode.value === 'carWash')
const isHealthCheckMerchant = computed(() => merchantTypeCode.value === 'medical')
const isParkingMerchant = computed(() => merchantTypeCode.value === 'parking')
const isFuelMerchant = computed(() => merchantTypeCode.value === 'fuel')
const isChildcareMerchant = computed(() => merchantTypeCode.value === 'children')
const isFinancialMerchant = computed(() => merchantTypeCode.value === 'finance')
const isMovieMerchant = computed(() => [
  '观影服务', 'cinema', 'theatre', '电影院', '影院', '影城', '剧院',
].includes(normalizedMerchantType.value))
const isSpouseEmploymentMerchant = computed(() => normalizedMerchantType.value === '配偶推荐就业')
const isApprovalMerchant = computed(() => normalizedMerchantType.value === '行政审批')
const isServiceMerchant = computed(() => isFoodMerchant.value || normalizedMerchantType.value === '景区服务')
  'cinema',
].includes(merchantTypeCode.value))
const isSpouseEmploymentMerchant = computed(() => merchantTypeCode.value === 'employment')
const isApprovalMerchant = computed(() => merchantTypeCode.value === 'approval')
const isServiceMerchant = computed(() => isFoodMerchant.value || merchantTypeCode.value === 'scenic')
const supportsStoreConsumption = computed(() => [
  '酒店服务', '景区服务', '餐饮服务', '口腔治疗保健', '免费体检',
].includes(normalizedMerchantType.value) || (isCarWashMerchant.value && form.miniProgramJumpType === 'normal'))
  'hotel', 'scenic', 'food', 'dental', 'medical',
].includes(merchantTypeCode.value) || (isCarWashMerchant.value && form.miniProgramJumpType === 'normal'))
const merchantAppLoginOptionalTypes = [
  '购房优惠', '购车服务', '家电服务', '数码服务', '文化服务', '电信服务', '口腔治疗保健', '加油服务',
  'housing', 'car', 'appliance', 'digital', 'culture', 'telecom', 'dental', 'fuel',
]
const simplifiedMerchantSettingsTypes = [
  '购房优惠', '购车服务', '家电服务', '数码服务', '文化服务', '电信服务', '口腔治疗保健', '加油服务',
  'housing', 'car', 'appliance', 'digital', 'culture', 'telecom', 'dental', 'fuel',
]
const usesSimplifiedMerchantSettings = computed(() => simplifiedMerchantSettingsTypes.includes(normalizedMerchantType.value))
const usesSimplifiedMerchantSettings = computed(() => simplifiedMerchantSettingsTypes.includes(merchantTypeCode.value))
const settingsAndRightsOnlyMerchantTypes = [
  '加油服务', '购房优惠', '购车服务', '家电服务', '数码服务', '文化服务', '电信服务',
  'fuel', 'housing', 'car', 'appliance', 'digital', 'culture', 'telecom',
]
const usesSettingsAndRightsOnlyTabs = computed(() => settingsAndRightsOnlyMerchantTypes.includes(normalizedMerchantType.value))
const usesSettingsAndRightsOnlyTabs = computed(() => settingsAndRightsOnlyMerchantTypes.includes(merchantTypeCode.value))
const supportsMerchantAppLogin = computed(() => (
  isServiceMerchant.value
  || isApartmentMerchant.value
@@ -121,10 +147,10 @@
  || isMovieMerchant.value
  || isSpouseEmploymentMerchant.value
  || isApprovalMerchant.value
  || merchantAppLoginOptionalTypes.includes(normalizedMerchantType.value)
  || merchantAppLoginOptionalTypes.includes(merchantTypeCode.value)
))
const requiresMerchantOperator = computed(() => (!isEditMode.value || !merchantHasLoginAccount.value) && (
  normalizedMerchantType.value === '景区服务'
  merchantTypeCode.value === 'scenic'
  || (supportsMerchantAppLogin.value && Number(form.canLoginMerchantApp) === 1)
))
function carWashJumpMeta() {
@@ -167,7 +193,8 @@
function defaultMerchantRecommend() {
  const merchantType = queryString('merchantType')
  return merchantType === '餐饮服务' || merchantType === '景区服务' ? 1 : 0
  const merchantTypeCode = merchantTypeAliases[merchantType] || merchantType
  return merchantTypeCode === 'food' || merchantTypeCode === 'scenic' ? 1 : 0
}
function applyNewMerchantDefaults() {
@@ -175,7 +202,9 @@
  const merchantType = queryString('merchantType')
  form.merchantType = merchantType
  form.recommend = merchantType === '餐饮服务' || merchantType === '景区服务' ? 1 : 0
  form.merchantTypeCode = ''
  const merchantTypeCode = merchantTypeAliases[merchantType] || merchantType
  form.recommend = merchantTypeCode === 'food' || merchantTypeCode === 'scenic' ? 1 : 0
  form.sortNo = undefined
}
@@ -227,7 +256,7 @@
  ]
})
const form = reactive<MerchantPayload & { createdAt: string }>({
const form = reactive<MerchantPayload & { createdAt: string; merchantTypeCode?: string }>({
  merchantName: '',
  merchantType: queryString('merchantType', ''),
  contactMobile: '',
@@ -548,18 +577,18 @@
const businessHours = reactive<BusinessHour[]>([{ id: 1, value: [] }])
function defaultMerchantRights(): MerchantRightConfig[] {
  const currentType = queryString('merchantType', '') || normalizedMerchantType.value
  const supportsDefaultStoreConsumption = ['餐饮服务', '景区服务', '口腔治疗保健', '免费体检'].includes(currentType)
    || (currentType === '自助洗车' && form.miniProgramJumpType === 'normal')
  const isHotel = currentType === '酒店服务'
  const isCarWash = currentType === '自助洗车'
  const isHealthCheck = currentType === '免费体检'
  const isParking = currentType === '停车服务'
  const isChildcare = currentType === '子女托管'
  const isFinancial = currentType === '金融服务'
  const isMovie = currentType === '观影服务'
  const isSpouseEmployment = currentType === '配偶推荐就业'
  const isApproval = currentType === '行政审批'
  const currentType = merchantTypeCode.value
  const supportsDefaultStoreConsumption = ['food', 'scenic', 'dental', 'medical'].includes(currentType)
    || (currentType === 'carWash' && form.miniProgramJumpType === 'normal')
  const isHotel = currentType === 'hotel'
  const isCarWash = currentType === 'carWash'
  const isHealthCheck = currentType === 'medical'
  const isParking = currentType === 'parking'
  const isChildcare = currentType === 'children'
  const isFinancial = currentType === 'finance'
  const isMovie = currentType === 'cinema'
  const isSpouseEmployment = currentType === 'employment'
  const isApproval = currentType === 'approval'
  if (isHealthCheck) {
    return [
talent-card-admin/src/views/RoleFormView.vue
@@ -38,11 +38,8 @@
  {
    name: '预约管理',
    pages: [
      { code: 'appointment-management', name: '预约服务管理', actions: ['查看', '审核', '下载'] },
      { code: 'medical-reservations', name: '体检预约管理', actions: ['查看', '审核', '下载'] },
      { code: 'medical-applications', name: '就医申请管理', actions: ['查看', '审核', '下载'] },
      { code: 'parking-plates', name: '停车车牌管理', actions: ['查看', '审核', '下载'] },
      { code: 'car-wash-benefits', name: '洗车权益管理', actions: ['查看', '审核'] },
      { code: 'fuel-services', name: '加油服务管理', actions: ['查看', '审核', '下载'] },
      { code: 'spouse-employment', name: '配偶就业管理', actions: ['查看', '审核', '下载'] },
      { code: 'hotel-reservations', name: '酒店预约管理', actions: ['查看', '审核', '下载'] },
talent-card-admin/src/views/StaffManagementView.vue
@@ -48,7 +48,7 @@
    { required: true, message: "请输入登录账号", trigger: "blur" },
    {
      validator: (_rule, value, callback) => {
        if (!editId.value && value && !accountPattern.test(String(value).trim())) {
        if (value && !accountPattern.test(String(value).trim())) {
          callback(new Error("登录账号仅支持英文、数字或英文+数字"));
          return;
        }
@@ -116,8 +116,8 @@
const keepMobileDigits = () => {
  form.mobile = form.mobile.replace(/\D/g, "").slice(0, 11);
};
const normalizeAccount = () => {
  form.account = form.account.replace(/[^A-Za-z0-9]/g, "").slice(0, 32);
const normalizeAccount = (value: string) => {
  form.account = String(value || "").replace(/[^A-Za-z0-9]/g, "").slice(0, 32);
  accountTaken.value = false;
};
async function checkAccountAvailable() {
@@ -344,11 +344,11 @@
            @input="keepMobileDigits" /></el-form-item
        ><el-form-item label="登录账号" prop="account" required :error="accountTaken ? '登录账号已存在,请更换' : ''"
          ><el-input
            v-model="form.account"
            :model-value="form.account"
            :disabled="Boolean(editId)"
            maxlength="32"
            placeholder="仅支持英文和数字"
            @input="normalizeAccount"
            @update:model-value="normalizeAccount"
            @blur="checkAccountAvailable" /></el-form-item
        ><el-form-item label="登录密码" prop="password" :required="!editId"
          ><el-input
@@ -357,7 +357,7 @@
            :placeholder="
              editId ? '留空则不修改' : '至少 6 位'
            " /></el-form-item
        ><el-form-item v-if="!editId || form.password" label="确认密码" prop="confirmPassword" :required="!editId || Boolean(form.password)"
        ><el-form-item label="确认密码" prop="confirmPassword" :required="!editId || Boolean(form.password)"
          ><el-input
            v-model="form.confirmPassword"
            type="password"