fix: align admin management and miniapp merchant data
| | |
| | | applyMedicalRightsConfig(data) |
| | | // 普通自助洗车复用餐饮商家的团单展示;外链洗车不会进入此通用详情页。 |
| | | const isNormalCarWash = merchantType.value == 'carWash' |
| | | groupBuyingEnabled.value = isNormalCarWash |
| | | groupBuyingEnabled.value = merchantType.value == 'culture' |
| | | ? false |
| | | : isNormalCarWash |
| | | ? true |
| | | : (data == null || data.groupBuyingEnabled == null ? groupBuyingEnabled.value : data.groupBuyingEnabled === true || Number(data.groupBuyingEnabled) == 1) |
| | | if (merchantType.value == 'culture') { |
| | | const cultureStoreConsume = data != null && (data.storeConsume === true || Number(data.storeConsume) == 1) && (data.consumeEnabled === true || Number(data.consumeEnabled) == 1) |
| | | merchantServiceLabel.value = cultureStoreConsume ? '到店消费' : '' |
| | | merchantPrimaryTag.value = merchantHasCoupon.value ? '券' : '权益' |
| | | } |
| | | isHotelMerchant.value = merchantType.value == 'hotel' |
| | | merchantConsumeDiscount.value = '' |
| | | if (isHotelMerchant.value) { |
| | |
| | | <view v-if="initialLoading" class="service-empty"><view class="loading-spinner"></view><text>正在加载购物服务</text></view> |
| | | <view v-else-if="initialError" class="service-empty error-state"><text>购物服务加载失败</text><text class="retry-text" @tap="retryInitialLoad">重新加载</text></view> |
| | | <view v-else-if="hasLoaded && visibleServices.length == 0" class="service-empty"><text>暂无相关购物服务</text></view> |
| | | <view v-else-if="hasLoaded" class="service-list"><view v-for="item in visibleServices" :key="item.id" class="service-item" :class="item.available ? '' : 'is-unavailable'" @tap="openMerchant(item)"><image class="service-image" :src="item.image" mode="aspectFill" @error="replaceImage(item)"></image><view class="service-info"><text class="service-name">{{item.name}}</text><text v-if="item.discount != ''" class="service-discount">{{item.discount}}</text><text v-else-if="!item.available" class="service-discount">{{item.unavailableReason}}</text><view v-if="item.address != ''" class="address-row"><image src="/static/benefits-assets/map-pin.svg" mode="aspectFit"></image><text>{{item.address}}</text></view></view></view></view> |
| | | <view v-else-if="hasLoaded" class="service-list"><view v-for="item in visibleServices" :key="item.id" class="service-item" :class="item.available ? '' : 'is-unavailable'" @tap="openMerchant(item)"><image class="service-image" :src="item.image" mode="aspectFill" @error="replaceImage(item)"></image><view class="service-info"><text class="service-name">{{item.name}}</text><view v-if="item.tags.length > 0" class="service-tags"><text v-for="tag in item.tags" :key="tag" :class="tag == '券' ? 'coupon-tag' : 'service-tag'">{{tag}}</text></view><text v-else-if="!item.available" class="service-discount">{{item.unavailableReason}}</text><view v-if="item.address != ''" class="address-row"><image src="/static/benefits-assets/map-pin.svg" mode="aspectFit"></image><text>{{item.address}}</text></view></view></view></view> |
| | | <view v-if="loadingMore" class="loading-row"><view class="loading-spinner"></view><text>正在加载</text></view><view v-else-if="loadMoreError" class="loading-row load-error" @tap="retryLoadMore"><text>加载失败,点击重试</text></view><view v-else-if="hasLoaded && !hasMore && visibleServices.length > 0" class="no-more"><view></view><text>已经到底了</text><view></view></view> |
| | | </view><view class="bottom-space"></view> |
| | | </scroll-view> |
| | |
| | | getShoppingTopicMerchants(activeTab.value, talentId.value, currentPage.value + 1, 4, (data: any) => { |
| | | if (requestVersion != listRequestVersion.value) return |
| | | const records: any[] = data != null && data.records instanceof Array ? data.records as any[] : []; const next = append ? visibleServices.value.slice() : [] |
| | | for (let index = 0; index < records.length; index++) { const item = records[index]; next.push({ id: '' + item.id, merchantType: item.merchantType != null ? item.merchantType as string : '', name: item.merchantName != null && item.merchantName != '' ? item.merchantName as string : '商家名称未配置', discount: item.discountText != null ? item.discountText as string : '', address: item.address != null ? item.address as string : '', image: item.coverUrl != null && item.coverUrl != '' ? resolveApiUrl(item.coverUrl as string) : fallbackImage, available: item.available !== false, unavailableReason: item.unavailableReason != null && item.unavailableReason != '' ? item.unavailableReason as string : '该服务当前不可用' }) } |
| | | for (let index = 0; index < records.length; index++) { |
| | | const item = records[index] |
| | | const merchantType = item.merchantType != null ? item.merchantType as string : '' |
| | | const tags: string[] = [] |
| | | if (item.couponAvailable === true || Number(item.couponAvailable) == 1) tags.push('券') |
| | | const configuredTag = item.discountText != null ? String(item.discountText).trim() : '' |
| | | if (merchantType == 'culture' && (item.storeConsumeAvailable === true || Number(item.storeConsumeAvailable) == 1)) tags.push('到店消费') |
| | | if (merchantType != 'culture' && configuredTag != '' && tags.indexOf(configuredTag) < 0) tags.push(configuredTag) |
| | | next.push({ id: '' + item.id, merchantType: merchantType, name: item.merchantName != null && item.merchantName != '' ? item.merchantName as string : '商家名称未配置', discount: configuredTag, tags: tags, address: item.address != null ? item.address as string : '', image: item.coverUrl != null && item.coverUrl != '' ? resolveApiUrl(item.coverUrl as string) : fallbackImage, available: item.available !== false, unavailableReason: item.unavailableReason != null && item.unavailableReason != '' ? item.unavailableReason as string : '该服务当前不可用' }) |
| | | } |
| | | visibleServices.value = next; currentPage.value++; hasMore.value = data != null && data.total != null ? next.length < data.total : false; initialLoading.value = false; loadingMore.value = false; hasLoaded.value = true |
| | | }, () => { if (requestVersion != listRequestVersion.value) return; initialLoading.value = false; loadingMore.value = false; if (append) loadMoreError.value = true; else initialError.value = true }) |
| | | } |
| | |
| | | uni.navigateTo({ url: '/pages/merchant/merchant?merchantId=' + encodeURIComponent(item.id) + '&merchantType=dental&name=' + encodeURIComponent(item.name) + '&image=' + encodeURIComponent(item.image) + '&address=' + encodeURIComponent(item.address) + '&discount=' + encodeURIComponent(item.discount) }) |
| | | return |
| | | } |
| | | if (item.merchantType == 'culture') { |
| | | uni.navigateTo({ url: '/pages/merchant/merchant?merchantId=' + encodeURIComponent(item.id) + '&merchantType=culture&name=' + encodeURIComponent(item.name) + '&image=' + encodeURIComponent(item.image) + '&address=' + encodeURIComponent(item.address) + '&discount=' + encodeURIComponent(item.discount) }) |
| | | return |
| | | } |
| | | uni.navigateTo({ url: '/pages/shopping-merchant/shopping-merchant?merchantId=' + encodeURIComponent(item.id) }) |
| | | } |
| | | </script> |
| | |
| | | .page,.page-scroll { width:100%; height:100vh; background-color:#FFFFFF; }.custom-nav { position:fixed; z-index:20; left:0; right:0; top:0; padding-bottom:20rpx; }.nav-bg { position:absolute; left:0; right:0; top:0; bottom:0; opacity:0; background-image:linear-gradient(180deg,#F26A38,#EE542B); transition:opacity 280ms ease-out; }.custom-nav.is-solid .nav-bg { opacity:1; }.safe-space,.nav-row { position:relative; z-index:1; }.safe-space { height:28px; }.nav-row { display:flex; box-sizing:border-box; padding-left:24rpx; flex-direction:row; align-items:center; justify-content:space-between; }.back-button { display:flex; width:54rpx; height:54rpx; position:relative; z-index:2; border-radius:50%; background-color:rgba(45,45,45,.52); justify-content:center; align-items:center; }.custom-nav.is-solid .back-button { background-color:transparent; }.back-button image { width:38rpx; height:38rpx; }.nav-title { position:absolute; z-index:1; left:0; right:0; color:#FFFFFF; font-size:31rpx; font-weight:500; text-align:center; text-shadow:0 2rpx 8rpx rgba(0,0,0,.35); }.capsule-space { height:100%; flex-shrink:0; } |
| | | .hero { height:730rpx; position:relative; overflow:visible; background-color:#1C315E; }.hero-image { position:absolute; left:0; top:0; width:100%; height:730rpx; }.hero-top-shade { position:absolute; left:0; right:0; top:0; height:245rpx; background-image:linear-gradient(180deg,rgba(0,0,0,.62),rgba(0,0,0,0)); }.hero-bottom-shade { position:absolute; left:0; right:0; bottom:0; height:230rpx; background-image:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.42)); }.scene-name { position:absolute; z-index:3; left:38rpx; top:318rpx; color:#FFFFFF; font-family:'FoodCalligraphy'; font-size:48rpx; line-height:66rpx; text-shadow:0 3rpx 10rpx rgba(0,0,0,.55); }.scene-scroll { position:absolute; z-index:4; left:0; right:0; top:469rpx; width:100%; height:150rpx; white-space:nowrap; }.scene-row { display:flex; height:150rpx; flex-direction:row; align-items:center; }.scene-pad { width:322rpx; height:1rpx; flex-shrink:0; }.scene-item { display:flex; width:106rpx; height:106rpx; margin-right:31rpx; box-sizing:border-box; overflow:hidden; position:relative; z-index:1; border:3rpx solid rgba(255,255,255,.9); border-radius:8rpx; flex-shrink:0; transition:transform 180ms ease-out; }.scene-item image { width:100%; height:100%; }.scene-item.active { z-index:3; border-width:4rpx; border-color:#FFD329; transform:scale(1.28); box-shadow:0 8rpx 18rpx rgba(0,0,0,.28); } |
| | | .topic-error { display:flex; height:76rpx; padding:0 31rpx; background-color:#FFF8F4; flex-direction:row; justify-content:center; align-items:center; }.topic-error text { color:#A48A7A; font-size:22rpx; }.topic-error text + text { margin-left:22rpx; color:#F26A38; }.topic-summary { position:relative; z-index:4; margin-top:-66rpx; }.intro-card { margin:0 31rpx; box-sizing:border-box; padding:25rpx 31rpx; border-radius:26rpx; background-image:linear-gradient(90deg,#FF5320,#FF8A22); box-shadow:0 10rpx 22rpx rgba(255,104,28,.24); }.intro-card text { color:#FFFFFF; font-size:23rpx; line-height:34rpx; }.service-section { padding:32rpx 31rpx 0; }.tab-scroll { width:100%; height:78rpx; white-space:nowrap; }.service-tabs { display:flex; height:72rpx; flex-direction:row; align-items:flex-start; }.service-tab { display:flex; width:144rpx; margin-right:42rpx; flex-direction:column; align-items:center; flex-shrink:0; }.service-tab text { color:#8A8D92; font-size:29rpx; font-weight:600; }.service-tab.active text { color:#363636; font-weight:700; }.tab-line { width:0; height:7rpx; margin-top:12rpx; border-radius:4rpx; background-image:linear-gradient(90deg,#FF4D36,#FFBF3A); transition:width 180ms ease-out; }.service-tab.active .tab-line { width:61rpx; } |
| | | .service-list { padding-top:10rpx; }.service-item { display:flex; min-height:215rpx; margin-bottom:22rpx; flex-direction:row; align-items:flex-start; }.service-item.is-unavailable { opacity:.55; }.service-image { width:184rpx; height:184rpx; border-radius:17rpx; flex-shrink:0; background-color:#EEEEEE; }.service-info { display:flex; min-width:0; min-height:184rpx; flex:1; box-sizing:border-box; padding:4rpx 0 4rpx 24rpx; flex-direction:column; justify-content:space-between; }.service-name { color:#292929; font-size:29rpx; line-height:40rpx; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.service-discount { align-self:flex-start; max-width:400rpx; padding:6rpx 9rpx; border-radius:6rpx; background-color:#FFF5E7; color:#FF7C2B; font-size:21rpx; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.address-row { display:flex; min-width:0; box-sizing:border-box; padding:7rpx 10rpx; border-radius:7rpx; background-image:linear-gradient(90deg,rgba(239,240,242,.98),rgba(242,243,245,.76) 58%,rgba(255,255,255,0)); flex-direction:row; align-items:flex-start; }.address-row image { width:27rpx; height:27rpx; margin-right:8rpx; flex-shrink:0; }.address-row text { min-width:0; flex:1; color:#7E8490; font-size:21rpx; line-height:31rpx; max-height:62rpx; overflow:hidden; }.service-empty { display:flex; height:310rpx; justify-content:center; align-items:center; }.service-empty text { color:#A5A8AD; font-size:25rpx; }.error-state { flex-direction:column; }.error-state .retry-text { margin-top:22rpx; color:#F26A38; }.loading-row,.no-more { display:flex; height:90rpx; flex-direction:row; justify-content:center; align-items:center; }.loading-spinner { width:28rpx; height:28rpx; margin-right:13rpx; box-sizing:border-box; border:3rpx solid #FFD6C4; border-top-color:#FF6C2B; border-radius:50%; animation:shopping-loading 720ms linear infinite; }.loading-row text { color:#989DA5; font-size:22rpx; }.load-error text { color:#F26A38; }.no-more view { width:70rpx; height:1rpx; background-color:#E7E7E7; }.no-more text { margin:0 18rpx; color:#B1B3B7; font-size:21rpx; }.bottom-space { height:calc(30rpx + env(safe-area-inset-bottom)); }@keyframes shopping-loading { from { transform:rotate(0deg); } to { transform:rotate(360deg); } } |
| | | .service-list { padding-top:10rpx; }.service-item { display:flex; min-height:215rpx; margin-bottom:22rpx; flex-direction:row; align-items:flex-start; }.service-item.is-unavailable { opacity:.55; }.service-image { width:184rpx; height:184rpx; border-radius:17rpx; flex-shrink:0; background-color:#EEEEEE; }.service-info { display:flex; min-width:0; min-height:184rpx; flex:1; box-sizing:border-box; padding:4rpx 0 4rpx 24rpx; flex-direction:column; justify-content:space-between; }.service-name { color:#292929; font-size:29rpx; line-height:40rpx; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.service-discount { align-self:flex-start; max-width:400rpx; padding:6rpx 9rpx; border-radius:6rpx; background-color:#FFF5E7; color:#FF7C2B; font-size:21rpx; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.service-tags { display:flex; min-height:38rpx; flex-direction:row; align-items:center; }.service-tags text { margin-right:9rpx; padding:5rpx 9rpx; border-radius:5rpx; font-size:21rpx; line-height:28rpx; white-space:nowrap; }.coupon-tag { background-color:#FFF0EC; color:#FF5D37; }.service-tag { background-color:#FFF5E7; color:#FF7C2B; }.address-row { display:flex; min-width:0; box-sizing:border-box; padding:7rpx 10rpx; border-radius:7rpx; background-image:linear-gradient(90deg,rgba(239,240,242,.98),rgba(242,243,245,.76) 58%,rgba(255,255,255,0)); flex-direction:row; align-items:flex-start; }.address-row image { width:27rpx; height:27rpx; margin-right:8rpx; flex-shrink:0; }.address-row text { min-width:0; flex:1; color:#7E8490; font-size:21rpx; line-height:31rpx; max-height:62rpx; overflow:hidden; }.service-empty { display:flex; height:310rpx; justify-content:center; align-items:center; }.service-empty text { color:#A5A8AD; font-size:25rpx; }.error-state { flex-direction:column; }.error-state .retry-text { margin-top:22rpx; color:#F26A38; }.loading-row,.no-more { display:flex; height:90rpx; flex-direction:row; justify-content:center; align-items:center; }.loading-spinner { width:28rpx; height:28rpx; margin-right:13rpx; box-sizing:border-box; border:3rpx solid #FFD6C4; border-top-color:#FF6C2B; border-radius:50%; animation:shopping-loading 720ms linear infinite; }.loading-row text { color:#989DA5; font-size:22rpx; }.load-error text { color:#F26A38; }.no-more view { width:70rpx; height:1rpx; background-color:#E7E7E7; }.no-more text { margin:0 18rpx; color:#B1B3B7; font-size:21rpx; }.bottom-space { height:calc(30rpx + env(safe-area-inset-bottom)); }@keyframes shopping-loading { from { transform:rotate(0deg); } to { transform:rotate(360deg); } } |
| | | </style> |
| | |
| | | } |
| | | |
| | | export function getServiceApplications(params: ServiceApplicationQuery = {}) { |
| | | const requestParams: Record<string, unknown> = { ...params } |
| | | if (params.talentKeyword && !requestParams.keyword) { |
| | | requestParams.keyword = params.talentKeyword |
| | | } |
| | | delete requestParams.talentKeyword |
| | | return http.get<PageResult<ServiceApplication>, PageResult<ServiceApplication>>('/api/admin/service-appointment-applications', { |
| | | params, |
| | | params: requestParams, |
| | | }) |
| | | } |
| | | |
| | |
| | | |
| | | .parking-plate-filter-form { |
| | | display: grid; |
| | | grid-template-columns: minmax(250px, 1.05fr) minmax(250px, 1.05fr) minmax(240px, 1fr) auto; |
| | | gap: 14px 36px; |
| | | grid-template-columns: repeat(3, minmax(180px, 360px)) minmax(74px, 1fr); |
| | | gap: 14px 24px; |
| | | align-items: start; |
| | | padding-bottom: 18px; |
| | | border-bottom: 1px solid #d9dee8; |
| | |
| | | grid-row: 1 / span 2; |
| | | grid-column: 4; |
| | | display: grid; |
| | | justify-content: end; |
| | | gap: 10px; |
| | | align-content: start; |
| | | min-width: 74px; |
| | |
| | | |
| | | .movie-ticket-create-row { |
| | | display: grid; |
| | | grid-template-columns: 58px minmax(180px, 1fr) 36px 180px 86px; |
| | | grid-template-columns: 58px minmax(180px, 600px) 36px 180px 86px; |
| | | gap: 12px; |
| | | align-items: center; |
| | | color: #111827; |
| | |
| | | |
| | | .movie-ticket-benefit-row { |
| | | display: grid; |
| | | grid-template-columns: 86px 40px 56px 80px 20px 56px 90px; |
| | | grid-template-columns: 86px 40px 56px 180px 14px 56px 108px; |
| | | gap: 12px; |
| | | align-items: center; |
| | | color: #111827; |
| | |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .movie-ticket-benefit-row .el-input, |
| | | .movie-ticket-benefit-row .el-input-number, |
| | | .movie-ticket-benefit-row .el-select { |
| | | width: 100%; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .movie-ticket-benefit-row > span { |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .movie-ticket-panel-header { |
| New file |
| | |
| | | export const TALENT_CARD_LEVELS: readonly string[] = [ |
| | | '卓越人才卡', |
| | | '杰出人才卡', |
| | | '菁英人才卡', |
| | | '普通游客', |
| | | ] |
| | |
| | | import { Download, Refresh, Search } from '@element-plus/icons-vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { exportServiceApplications, getServiceApplications, updateServiceApplicationStatus } from '../api/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { saveBlob } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | |
| | | { label: '配偶就业', value: 'SPOUSE_EMPLOYMENT' }, |
| | | { label: '学生托管', value: 'CHILD_ENTRUSTMENT' }, |
| | | ] |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡', 'EXCELLENT', 'OUTSTANDING', 'ELITE'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const statusOptions = ['待处理', '已处理', '已驳回', '待审核', '审核通过', '审核不通过', '已取消'] |
| | | |
| | | function queryParams(size = pagination.size) { |
| | |
| | | <el-form-item label="预约类型"><el-select v-model="filters.applicationType" clearable placeholder="全部"><el-option v-for="item in appointmentTypes" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item> |
| | | <el-form-item label="关键词"><el-input v-model="filters.keyword" clearable placeholder="姓名、手机号、卡号、申请编号" /></el-form-item> |
| | | <el-form-item label="服务机构"><el-input v-model="filters.serviceKeyword" clearable placeholder="医院、酒店或机构名称" /></el-form-item> |
| | | <el-form-item label="卡片等级"><el-select v-model="filters.cardLevel" clearable placeholder="全部"><el-option v-for="level in cardLevels" :key="level" :label="level" :value="level" /></el-select></el-form-item> |
| | | <el-form-item label="卡片等级"><el-select v-model="filters.cardLevel" clearable placeholder="全部"><el-option label="全部" value="" /><el-option v-for="level in cardLevels" :key="level" :label="level" :value="level" /></el-select></el-form-item> |
| | | <el-form-item label="状态"><el-select v-model="filters.status" clearable placeholder="全部"><el-option v-for="status in statusOptions" :key="status" :label="status" :value="status" /></el-select></el-form-item> |
| | | <el-form-item label="提交时间"><el-date-picker v-model="filters.submittedTime" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item> |
| | | <div class="toolbar-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="resetFilters">重置</el-button><el-button v-permission="'admin.appointment-management.export'" :icon="Download" @click="download">导出</el-button></div> |
| | |
| | | updateAdminStaff, |
| | | } from '../api/admin' |
| | | import type { AdminRole, AdminStaff } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const route = useRoute() |
| | |
| | | <el-form-item v-if="isUpgrade" label="卡片等级"> |
| | | <el-select v-model="filters.cardLevel" clearable placeholder="全部"> |
| | | <el-option label="全部" value="" /> |
| | | <el-option label="卓越人才卡" value="卓越人才卡" /> |
| | | <el-option label="杰出人才卡" value="杰出人才卡" /> |
| | | <el-option label="菁英人才卡" value="菁英人才卡" /> |
| | | <el-option label="普通游客" value="普通游客" /> |
| | | <el-option v-for="level in TALENT_CARD_LEVELS" :key="level" :label="level" :value="level" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="状态"> |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getChildCardApplications } from '../api/admin' |
| | | import type { ChildCardApplication } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const route = useRoute() |
| | |
| | | <el-form-item label="卡片等级"> |
| | | <el-select v-model="filters.cardLevel" placeholder="全部" clearable> |
| | | <el-option label="全部" value="" /> |
| | | <el-option label="卓越人才卡" value="卓越人才卡" /> |
| | | <el-option label="杰出人才卡" value="杰出人才卡" /> |
| | | <el-option label="菁英人才卡" value="菁英人才卡" /> |
| | | <el-option label="普通游客" value="普通游客" /> |
| | | <el-option v-for="level in TALENT_CARD_LEVELS" :key="level" :label="level" :value="level" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="状态"> |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getFamilyCardApplications } from '../api/admin' |
| | | import type { FamilyCardApplication } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const props = defineProps<{ mode?: 'application' | 'management' }>() |
| | |
| | | ] |
| | | const statusOptions = computed(() => isManagement.value ? managementStatusOptions : applicationStatusOptions) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '普通游客'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | |
| | | function statusText(row: FamilyCardApplication) { |
| | | if (isManagement.value) { |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getServiceApplications } from '../api/admin' |
| | | import type { ServiceApplication } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const router = useRouter() |
| | | const rows = ref<ServiceApplication[]>([]) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const statusOptions = ['待处理', '已处理', '已驳回'] |
| | | const submissionTypes = ['新增', '修改'] |
| | | |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getServiceApplications } from '../api/admin' |
| | | import type { ServiceApplication } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const router = useRouter() |
| | | const rows = ref<ServiceApplication[]>([]) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const subsidyOptions = ['是', '否'] |
| | | const statusOptions = ['待处理', '已处理', '已取消'] |
| | | |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getVerificationRecords } from '../api/admin' |
| | | import type { VerificationRecord } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const rows = ref<VerificationRecord[]>([]) |
| | | |
| | | const cardLevels = ['钻石卡', '卓越人才卡', '精英人才卡', '青年人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | |
| | | const filters = reactive({ |
| | | talentKeyword: '', |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getServiceApplications } from '../api/admin' |
| | | import type { ServiceApplication } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const router = useRouter() |
| | | const rows = ref<ServiceApplication[]>([]) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const statusOptions = ['待处理', '已处理', '已取消'] |
| | | |
| | | const filters = reactive({ |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getServiceApplications } from '../api/admin' |
| | | import type { ServiceApplication } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const router = useRouter() |
| | | const rows = ref<ServiceApplication[]>([]) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const statusOptions = ['待处理', '已处理', '已取消'] |
| | | |
| | | const filters = reactive({ |
| | |
| | | ].includes(merchantTypeCode.value)) |
| | | const isSpouseEmploymentMerchant = computed(() => merchantTypeCode.value === 'employment') |
| | | const isApprovalMerchant = computed(() => merchantTypeCode.value === 'approval') |
| | | const isBookMerchant = computed(() => merchantTypeCode.value === 'culture') |
| | | const isServiceMerchant = computed(() => isFoodMerchant.value || merchantTypeCode.value === 'scenic') |
| | | const supportsStoreConsumption = computed(() => [ |
| | | 'hotel', 'scenic', 'food', 'dental', 'medical', |
| | | 'hotel', 'scenic', 'food', 'dental', 'medical', 'culture', |
| | | ].includes(merchantTypeCode.value) || (isCarWashMerchant.value && form.miniProgramJumpType === 'normal')) |
| | | const merchantAppLoginOptionalTypes = [ |
| | | 'housing', 'car', 'appliance', 'digital', 'culture', 'telecom', 'dental', 'fuel', |
| | |
| | | } |
| | | |
| | | const tabs = computed(() => { |
| | | if (isBookMerchant.value) { |
| | | return [ |
| | | { label: '商家设置', value: 'settings' }, |
| | | { label: '商家权益', value: 'rights' }, |
| | | { label: '优惠券', value: 'coupons' }, |
| | | ] |
| | | } |
| | | if (usesSettingsAndRightsOnlyTabs.value) { |
| | | return [ |
| | | { label: '商家设置', value: 'settings' }, |
| | |
| | | }], |
| | | validTime: [{ required: true, message: '请选择优惠券有效期', trigger: 'change' }], |
| | | productScope: [{ |
| | | validator: (_rule, value, callback) => value !== 'selected' || selectedCouponProductIds.value.length > 0 |
| | | validator: (_rule, value, callback) => isBookMerchant.value || value !== 'selected' || selectedCouponProductIds.value.length > 0 |
| | | ? callback() |
| | | : callback(new Error('请至少选择一个可用商品')), |
| | | trigger: 'change', |
| | |
| | | } |
| | | |
| | | async function loadCouponProducts() { |
| | | if (isBookMerchant.value) { |
| | | couponProducts.value = [] |
| | | selectedCouponProductIds.value = [] |
| | | return |
| | | } |
| | | const merchantId = currentMerchantId() |
| | | if (!merchantId) { |
| | | couponProducts.value = [] |
| | |
| | | couponEditForm.couponAmount = '' |
| | | couponEditForm.talentLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡'] |
| | | couponEditForm.validTime = [] |
| | | couponEditForm.productScope = 'selected' |
| | | couponEditForm.productScope = isBookMerchant.value ? 'all' : 'selected' |
| | | couponEditForm.storeConsume = true |
| | | couponEditForm.governmentSubsidy = true |
| | | couponEditForm.status = 1 |
| | |
| | | try { savedQuantities = typeof detail.talentLevelQuantities === 'string' ? JSON.parse(detail.talentLevelQuantities) : (detail.talentLevelQuantities || {}) } catch { savedQuantities = {} } |
| | | couponEditForm.talentLevels.forEach((level) => { couponLevelQuantities[level] = Number(savedQuantities[level] || 1) }) |
| | | couponEditForm.validTime = detail.validStart && detail.validEnd ? [detail.validStart, detail.validEnd] : [] |
| | | couponEditForm.productScope = detail.productScope === 'ALL' ? 'all' : 'selected' |
| | | couponEditForm.productScope = isBookMerchant.value || detail.productScope === 'ALL' ? 'all' : 'selected' |
| | | couponEditForm.storeConsume = detail.storeConsume === true || detail.storeConsume === 1 |
| | | couponEditForm.governmentSubsidy = detail.governmentSubsidy === true || detail.governmentSubsidy === 1 |
| | | couponEditForm.status = detail.status ?? 1 |
| | |
| | | talentLevelQuantities: Object.fromEntries(couponEditForm.talentLevels.map((level) => [level, Number(couponLevelQuantities[level] || 1)])), |
| | | validStart: couponEditForm.validTime?.[0], |
| | | validEnd: couponEditForm.validTime?.[1], |
| | | productScope: couponEditForm.productScope === 'all' ? 'ALL' : 'SELECTED', |
| | | productIds: couponEditForm.productScope === 'all' ? [] : selectedCouponProductIds.value, |
| | | storeConsume: couponEditForm.storeConsume, |
| | | productScope: isBookMerchant.value || couponEditForm.productScope === 'all' ? 'ALL' : 'SELECTED', |
| | | productIds: isBookMerchant.value || couponEditForm.productScope === 'all' ? [] : selectedCouponProductIds.value, |
| | | storeConsume: isBookMerchant.value || couponEditForm.storeConsume, |
| | | governmentSubsidy: couponEditForm.governmentSubsidy, |
| | | status: couponEditForm.status, |
| | | } |
| | |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item class="coupon-edit-row coupon-product-scope-row" label="可用商品" prop="productScope" :required="couponEditForm.productScope === 'selected'"> |
| | | <div v-if="isBookMerchant" class="coupon-edit-row coupon-switch-row"> |
| | | <label>适用范围</label> |
| | | <span>到店消费</span> |
| | | </div> |
| | | |
| | | <el-form-item v-else class="coupon-edit-row coupon-product-scope-row" label="可用商品" prop="productScope" :required="couponEditForm.productScope === 'selected'"> |
| | | <el-radio-group v-model="couponEditForm.productScope"> |
| | | <el-radio label="all">不限</el-radio> |
| | | <el-radio label="selected">选择</el-radio> |
| | |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | |
| | | <div class="coupon-product-select-table"> |
| | | <div v-if="!isBookMerchant" class="coupon-product-select-table"> |
| | | <el-table :data="couponProducts" border class="coupon-edit-product-table" :show-header="true"> |
| | | <el-table-column width="98" align="center"> |
| | | <template #default="{ row }"> |
| | |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="coupon-edit-row coupon-switch-row"> |
| | | <div v-if="!isBookMerchant" class="coupon-edit-row coupon-switch-row"> |
| | | <label>是否到店消费可用</label> |
| | | <el-switch v-model="couponEditForm.storeConsume" /> |
| | | </div> |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getMovieTicketRecords } from '../api/admin' |
| | | import type { MovieTicketRecord } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const rows = ref<MovieTicketRecord[]>([]) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | |
| | | const filters = reactive({ |
| | | talentKeyword: '', |
| | |
| | | updateTicketPackageStatus, |
| | | voidTicketPackageCode, |
| | | } from '../api/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | type ViewMode = 'list' | 'claims' | 'form' | 'codes' |
| | |
| | | const activePackage = ref<Record<string, any> | null>(null) |
| | | const selectedPackage = ref<Record<string, any> | null>(null) |
| | | |
| | | const cardLevels = ['卓越人才卡', '菁英人才卡', '杰出人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const ticketRuleLevels = TALENT_CARD_LEVELS.filter(level => level !== '普通游客') |
| | | const resetCycles = ['每天', '每周', '每月', '每年'] |
| | | const packageStatuses = [{ label: '全部', value: '' }, { label: '正常', value: 1 }, { label: '禁用', value: 0 }] |
| | | const codeStatuses = [ |
| | |
| | | usageText: '', |
| | | status: 1, |
| | | sortNo: 0, |
| | | levelRules: cardLevels.map(level => ({ talentLevel: level, enabled: 1, claimableCount: undefined as number | undefined, resetCycle: '每天' })), |
| | | levelRules: ticketRuleLevels.map(level => ({ talentLevel: level, enabled: 1, claimableCount: undefined as number | undefined, resetCycle: '每天' })), |
| | | }) |
| | | |
| | | const codeForm = reactive({ batchNo: '', codesText: '', remark: '' }) |
| | |
| | | sortNo: Number(row?.sortNo || 0), |
| | | }) |
| | | const existingRules = Array.isArray(row?.levelRules) ? row.levelRules : [] |
| | | form.levelRules = cardLevels.map(level => { |
| | | form.levelRules = ticketRuleLevels.map(level => { |
| | | const found = existingRules.find((item: Record<string, any>) => item.talentLevel === level) |
| | | return { |
| | | talentLevel: level, |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getOrders } from '../api/admin' |
| | | import type { Order } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | |
| | | const orders = ref<Order[]>([]) |
| | | const router = useRouter() |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '普通游客'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | |
| | | const filters = reactive({ |
| | | talentKeyword: '', |
| | |
| | | import { useRouter } from 'vue-router' |
| | | import { Download, Refresh, Search } from '@element-plus/icons-vue' |
| | | import { getParkingPlateSubmissions } from '../api/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const router = useRouter() |
| | | const rows = ref<Record<string, any>[]>([]) |
| | | const filters = reactive({ keyword: '', serviceKeyword: '', plateNumber: '', cardLevel: '', status: '', submissionType: '', submittedTime: [] as string[] }) |
| | | const filters = reactive({ keyword: '', serviceKeyword: '', cardLevel: '', status: '', submissionType: '', submittedTime: [] as string[] }) |
| | | const pagination = reactive({ page: 1, size: 10, total: 0 }) |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡', 'EXCELLENT', 'OUTSTANDING', 'ELITE'] |
| | | const statusOptions = ['待处理', '已处理', '已驳回', '已提交商家', '已录入'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const statusOptions = ['待处理', '已处理', '已驳回'] |
| | | const submissionTypes = ['新增', '修改'] |
| | | function text(value: unknown) { return value === undefined || value === null || value === '' ? '-' : String(value) } |
| | | function formatDate(value: unknown) { return text(value).replace('T', ' ') } |
| | | function statusText(value: unknown) { |
| | | const label = text(value) |
| | | return label === '已拒绝' ? '已驳回' : label |
| | | const label = text(value).toUpperCase() |
| | | if (['SUBMITTED', 'PENDING', '待处理', '待审核'].includes(label)) return '待处理' |
| | | if (['ACTIVE', 'PROCESSED', 'PASSED', 'APPROVED', '已处理', '处理完成', '通过', '已通过'].includes(label)) return '已处理' |
| | | if (['REJECTED', 'REFUSED', 'DENIED', '已拒绝', '拒绝', '驳回', '已驳回'].includes(label)) return '已驳回' |
| | | if (['CANCELLED', 'CANCELED', '已取消', '取消'].includes(label)) return '已取消' |
| | | return text(value) |
| | | } |
| | | function queryParams(size = pagination.size) { return { page: pagination.page, size, keyword: filters.keyword || undefined, serviceKeyword: filters.serviceKeyword || undefined, plateNumber: filters.plateNumber || undefined, cardLevel: filters.cardLevel || undefined, status: filters.status || undefined, submissionType: filters.submissionType || undefined, submittedStart: filters.submittedTime?.[0], submittedEnd: filters.submittedTime?.[1] } } |
| | | function queryParams(size = pagination.size) { return { page: pagination.page, size, keyword: filters.keyword || undefined, serviceKeyword: filters.serviceKeyword || undefined, cardLevel: filters.cardLevel || undefined, status: filters.status || undefined, submissionType: filters.submissionType || undefined, submittedStart: filters.submittedTime?.[0], submittedEnd: filters.submittedTime?.[1] } } |
| | | async function loadData() { loading.value = true; try { const result = await getParkingPlateSubmissions(queryParams()); rows.value = result.records || []; pagination.total = result.total || 0 } finally { loading.value = false } } |
| | | function search() { pagination.page = 1; loadData() } |
| | | function resetFilters() { Object.assign(filters, { keyword: '', serviceKeyword: '', plateNumber: '', cardLevel: '', status: '', submissionType: '', submittedTime: [] }); search() } |
| | | function resetFilters() { Object.assign(filters, { keyword: '', serviceKeyword: '', cardLevel: '', status: '', submissionType: '', submittedTime: [] }); search() } |
| | | function viewDetail(row: Record<string, any>) { router.push(`/parking-plates/${row.id}`) } |
| | | async function download() { const result = await getParkingPlateSubmissions({ ...queryParams(1000), page: 1 }); downloadExcel('停车车牌管理', [{ title: '提交单号', value: row => text(row.submissionNo || row.applicationNo) }, { title: '持卡人', value: row => text(row.talentName) }, { title: '手机号', value: row => text(row.talentMobile) }, { title: '车牌号', value: row => text(row.plateNumber || row.plateNo) }, { title: '停车场', value: row => text(row.parkingLotName || row.parkingLot || row.serviceName) }, { title: '提交类型', value: row => text(row.submissionType) }, { title: '提交时间', value: row => formatDate(row.submittedAt || row.createdAt) }, { title: '状态', value: row => statusText(row.statusName || row.status) }], result.records || []) } |
| | | async function download() { const result = await getParkingPlateSubmissions({ ...queryParams(1000), page: 1 }); downloadExcel('停车车牌管理', [{ title: '提交单号', value: row => text(row.registrationNo || row.submissionNo || row.applicationNo) }, { title: '持卡人名称', value: row => text(row.talentName) }, { title: '持卡人电话', value: row => text(row.talentMobile) }, { title: '人才等级', value: row => text(row.cardLevel || row.talentLevel) }, { title: '车牌号', value: row => text(row.plateNumber || row.plateNo) }, { title: '停车场', value: row => text(row.parkingLotName || row.parkingLot || row.serviceName) }, { title: '提交时间', value: row => formatDate(row.submittedAt || row.createdAt) }, { title: '提交类型', value: row => text(row.submissionType) }, { title: '状态', value: row => statusText(row.statusName || row.status) }], result.records || []) } |
| | | onMounted(loadData) |
| | | </script> |
| | | <template> |
| | | <div class="page-stack prototype-page"> |
| | | <div class="toolbar"><div><h2>停车车牌管理</h2><p>查看人才提交的车牌号,支持筛选、处理和导出给商家录入。</p></div><el-button :icon="Refresh" :loading="loading" @click="loadData">刷新</el-button></div> |
| | | <div class="page-stack prototype-page parking-plate-page"> |
| | | <section class="panel"> |
| | | <el-form class="filter-form" :model="filters" label-width="84px"> |
| | | <el-form-item label="关键词"><el-input v-model="filters.keyword" clearable placeholder="持卡人、手机号、卡号、提交单号" /></el-form-item> |
| | | <el-form-item label="车牌号"><el-input v-model="filters.plateNumber" clearable placeholder="请输入车牌号" /></el-form-item> |
| | | <el-form-item label="停车场"><el-input v-model="filters.serviceKeyword" clearable placeholder="商家或停车场" /></el-form-item> |
| | | <el-form-item label="卡片等级"><el-select v-model="filters.cardLevel" clearable placeholder="全部"><el-option v-for="level in cardLevels" :key="level" :label="level" :value="level" /></el-select></el-form-item> |
| | | <el-form-item label="提交类型"><el-select v-model="filters.submissionType" clearable placeholder="全部"><el-option v-for="item in submissionTypes" :key="item" :label="item" :value="item" /></el-select></el-form-item> |
| | | <el-form-item label="状态"><el-select v-model="filters.status" clearable placeholder="全部"><el-option v-for="status in statusOptions" :key="status" :label="status" :value="status" /></el-select></el-form-item> |
| | | <el-form-item label="提交时间"><el-date-picker v-model="filters.submittedTime" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item> |
| | | <div class="toolbar-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="resetFilters">重置</el-button><el-button v-permission="'admin.parking-plates.export'" :icon="Download" @click="download">导出</el-button></div> |
| | | <div class="panel-header"> |
| | | <h3>查询表格</h3> |
| | | </div> |
| | | |
| | | <el-form class="parking-plate-filter-form" :model="filters" label-width="72px"> |
| | | <el-form-item label="持卡人"><el-input v-model="filters.keyword" clearable placeholder="持卡人姓名、手机号、卡号" /></el-form-item> |
| | | <el-form-item label="停车场"><el-input v-model="filters.serviceKeyword" clearable placeholder="停车场名称" /></el-form-item> |
| | | <el-form-item label="卡片等级"><el-select v-model="filters.cardLevel" clearable placeholder="全部"><el-option label="全部" value="" /><el-option v-for="level in cardLevels" :key="level" :label="level" :value="level" /></el-select></el-form-item> |
| | | <el-form-item label="提交时间"><el-date-picker v-model="filters.submittedTime" type="daterange" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item> |
| | | <el-form-item label="状态"><el-select v-model="filters.status" clearable placeholder="全部"><el-option label="全部" value="" /><el-option v-for="status in statusOptions" :key="status" :label="status" :value="status" /></el-select></el-form-item> |
| | | <el-form-item label="提交类型"><el-select v-model="filters.submissionType" clearable placeholder="全部"><el-option label="全部" value="" /><el-option v-for="item in submissionTypes" :key="item" :label="item" :value="item" /></el-select></el-form-item> |
| | | <div class="parking-plate-filter-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="resetFilters">重置</el-button></div> |
| | | </el-form> |
| | | <el-table v-loading="loading" :data="rows" border empty-text="暂无车牌提交数据"> |
| | | <el-table-column label="提交单号" min-width="130"><template #default="{ row }">{{ text(row.submissionNo || row.applicationNo) }}</template></el-table-column> |
| | | <el-table-column prop="talentName" label="持卡人" min-width="110" /> |
| | | <el-table-column prop="talentMobile" label="手机号" min-width="120" /> |
| | | <el-table-column label="车牌号" min-width="120"><template #default="{ row }">{{ text(row.plateNumber || row.plateNo) }}</template></el-table-column> |
| | | <el-table-column label="停车场" min-width="160" show-overflow-tooltip><template #default="{ row }">{{ text(row.parkingLotName || row.parkingLot || row.serviceName) }}</template></el-table-column> |
| | | <el-table-column prop="submissionType" label="提交类型" min-width="100" /> |
| | | <el-table-column label="提交时间" min-width="170"><template #default="{ row }">{{ formatDate(row.submittedAt || row.createdAt) }}</template></el-table-column> |
| | | <el-table-column label="状态" min-width="110"><template #default="{ row }">{{ statusText(row.statusName || row.status) }}</template></el-table-column> |
| | | <el-table-column label="操作" width="100" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="viewDetail(row)">查看</el-button></template></el-table-column> |
| | | |
| | | <div class="parking-plate-table-tools"> |
| | | <el-button v-permission="'admin.parking-plates.export'" :icon="Download" @click="download">下载</el-button> |
| | | </div> |
| | | |
| | | <el-table v-loading="loading" :data="rows" border class="parking-plate-table" empty-text="暂无车牌提交数据"> |
| | | <el-table-column label="提交单号" min-width="116" align="center"><template #default="{ row }">{{ text(row.registrationNo || row.submissionNo || row.applicationNo) }}</template></el-table-column> |
| | | <el-table-column prop="talentName" label="持卡人名称" min-width="106" align="center" /> |
| | | <el-table-column prop="talentMobile" label="持卡人电话" min-width="118" align="center" /> |
| | | <el-table-column label="人才等级" min-width="106" align="center"><template #default="{ row }">{{ text(row.cardLevel || row.talentLevel) }}</template></el-table-column> |
| | | <el-table-column label="车牌号" min-width="104" align="center"><template #default="{ row }">{{ text(row.plateNumber || row.plateNo) }}</template></el-table-column> |
| | | <el-table-column label="停车场" min-width="144" align="center" show-overflow-tooltip><template #default="{ row }">{{ text(row.parkingLotName || row.parkingLot || row.serviceName) }}</template></el-table-column> |
| | | <el-table-column label="提交时间" min-width="134" align="center"><template #default="{ row }">{{ formatDate(row.submittedAt || row.createdAt) }}</template></el-table-column> |
| | | <el-table-column prop="submissionType" label="提交类型" min-width="96" align="center" /> |
| | | <el-table-column label="状态" min-width="90" align="center"><template #default="{ row }">{{ statusText(row.statusName || row.status) }}</template></el-table-column> |
| | | <el-table-column label="操作" width="86" fixed="right" align="center"><template #default="{ row }"><el-button link type="primary" @click="viewDetail(row)">查看</el-button></template></el-table-column> |
| | | </el-table> |
| | | <div class="pagination-row"><el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.size" background layout="total, prev, pager, next" :total="pagination.total" @change="loadData" /></div> |
| | | <div class="pagination-row"><el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.size" background layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]" :pager-count="5" :total="pagination.total" @change="loadData" /></div> |
| | | </section> |
| | | </div> |
| | | </template> |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getRefundRecords } from '../api/admin' |
| | | import type { RefundRecord } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const activeTab = ref<'GROUP' | 'STORE'>('GROUP') |
| | |
| | | const loading = ref(false) |
| | | const rows = ref<RefundRecord[]>([]) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '普通游客'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const itemNameLabel = computed(() => (activeTab.value === 'GROUP' ? '团购名称' : '消费项目')) |
| | | |
| | | const filters = reactive({ |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { getServiceApplications } from '../api/admin' |
| | | import type { ServiceApplication } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | import { downloadExcel } from '../utils/excel' |
| | | |
| | | const loading = ref(false) |
| | | const router = useRouter() |
| | | const rows = ref<ServiceApplication[]>([]) |
| | | |
| | | const cardLevels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '青年人才卡'] |
| | | const cardLevels = TALENT_CARD_LEVELS |
| | | const statusOptions = ['待处理', '已处理', '已驳回'] |
| | | |
| | | const filters = reactive({ |
| | |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { downloadTalentImportTemplate, exportTalents, getTalents, importTalents, updateTalentStatus } from '../api/admin' |
| | | import type { Talent, TalentImportResult } from '../types/admin' |
| | | import { TALENT_CARD_LEVELS } from '../utils/cardLevels' |
| | | |
| | | const router = useRouter() |
| | | const loading = ref(false) |
| | |
| | | const importResult = ref<TalentImportResult>() |
| | | const talents = ref<Talent[]>([]) |
| | | |
| | | const levels = ['卓越人才卡', '杰出人才卡', '菁英人才卡', '普通游客'] |
| | | const levels = TALENT_CARD_LEVELS |
| | | |
| | | const filters = reactive({ |
| | | keyword: '', |
| | |
| | | List<Map<String, Object>> rights = merchantRightsConfigs(row.get("merchantRightsConfig")); |
| | | Map<String, Object> configuredRight = matchingMerchantRight(rights, cardLevel, sourceAreaType, true); |
| | | Map<String, Object> levelRight = matchingMerchantRight(rights, cardLevel, sourceAreaType, false); |
| | | Long merchantId = row.get("id") instanceof Number number ? number.longValue() : null; |
| | | item.put("couponAvailable", merchantId != null && hasActiveMerchantCoupon(merchantId)); |
| | | item.put("storeConsumeAvailable", configuredRight != null |
| | | && booleanValue(configuredRight.get("storeConsume")) |
| | | && booleanValue(configuredRight.get("consumeEnabled"))); |
| | | if (configuredRight != null) { |
| | | item.put("discountText", textOrDefault(configuredRight.get("tag"), textOrDefault(item.get("serviceLabel"), ""))); |
| | | item.put("rightsAvailable", true); |
| | |
| | | result.put("appointmentTime", first(application.getExpectedTime(), "以专员通知为准")); |
| | | result.put("applicantName", participantName(application)); |
| | | result.put("applicantMobile", sensitiveDataCrypto.decrypt(first(application.getApplicantMobile(), application.getSpouseMobile(), application.getTalentMobile()))); |
| | | if (ServiceAppointmentApplicationService.TYPE_SPOUSE.equals(application.getApplicationType())) { |
| | | result.put("spouseName", first(application.getSpouseName(), application.getApplicantName())); |
| | | result.put("spouseMobile", sensitiveDataCrypto.decrypt(first(application.getSpouseMobile(), application.getApplicantMobile()))); |
| | | } |
| | | result.put("submittedAt", application.getSubmittedAt()); |
| | | result.put("handledAt", application.getHandledAt()); |
| | | result.put("handlerName", application.getHandlerName()); |
| | |
| | | spouse.put("inInstitution", first(application.getSpouseInInstitution())); |
| | | spouse.put("workExperience", first(application.getSpouseWorkExperience())); |
| | | result.put("spouse", spouse); |
| | | result.put("spouseIdCardNo", spouse.get("idCardNo")); |
| | | result.put("spousePoliticalStatus", spouse.get("politicalStatus")); |
| | | result.put("spouseEducation", spouse.get("education")); |
| | | result.put("spouseSchoolMajor", spouse.get("schoolMajor")); |
| | | result.put("spouseWorkUnit", spouse.get("workUnit")); |
| | | result.put("spouseInInstitution", spouse.get("inInstitution")); |
| | | result.put("spouseWorkExperience", spouse.get("workExperience")); |
| | | |
| | | Map<String, Object> child = new LinkedHashMap<>(); |
| | | child.put("id", application.getChildId()); |