|
|
|
@ -10,6 +10,7 @@ import { useUnsavedChangesGuard } from '../../composables/useUnsavedChangesGuard
|
|
|
|
import { adminUploadImageTooLargeMessage, ADMIN_IMAGE_RECOMMEND_LABEL } from '../../utils/adminMediaLimits'
|
|
|
|
import { adminUploadImageTooLargeMessage, ADMIN_IMAGE_RECOMMEND_LABEL } from '../../utils/adminMediaLimits'
|
|
|
|
import { listTableRowIndex } from '../../utils/listTableRowIndex'
|
|
|
|
import { listTableRowIndex } from '../../utils/listTableRowIndex'
|
|
|
|
import { downloadActivityListXlsx } from '../../utils/exportActivityListXlsx'
|
|
|
|
import { downloadActivityListXlsx } from '../../utils/exportActivityListXlsx'
|
|
|
|
|
|
|
|
import { ymdFromDateValue } from '../../utils/datetime'
|
|
|
|
import { reverseMapGeocode, searchMapPlaces } from '../../utils/mapGeo'
|
|
|
|
import { reverseMapGeocode, searchMapPlaces } from '../../utils/mapGeo'
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
bindTiandituMapClick,
|
|
|
|
bindTiandituMapClick,
|
|
|
|
@ -35,6 +36,7 @@ type Venue = {
|
|
|
|
open_time?: string
|
|
|
|
open_time?: string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
type CurrentUser = { id?: number; role: string; venues: Venue[]; full_admin_access?: boolean }
|
|
|
|
type CurrentUser = { id?: number; role: string; venues: Venue[]; full_admin_access?: boolean }
|
|
|
|
|
|
|
|
type DictOption = { item_value: string; item_label: string }
|
|
|
|
type Activity = {
|
|
|
|
type Activity = {
|
|
|
|
id: number
|
|
|
|
id: number
|
|
|
|
venue_id: number
|
|
|
|
venue_id: number
|
|
|
|
@ -83,6 +85,7 @@ type Activity = {
|
|
|
|
offline_reservation_method?: string | null
|
|
|
|
offline_reservation_method?: string | null
|
|
|
|
booking_method_note?: string | null
|
|
|
|
booking_method_note?: string | null
|
|
|
|
ticket_fee_note?: string | null
|
|
|
|
ticket_fee_note?: string | null
|
|
|
|
|
|
|
|
age_group?: string | null
|
|
|
|
external_url?: string | null
|
|
|
|
external_url?: string | null
|
|
|
|
/** 线上:已报名人数;H5/统计用 */
|
|
|
|
/** 线上:已报名人数;H5/统计用 */
|
|
|
|
registered_count?: number
|
|
|
|
registered_count?: number
|
|
|
|
@ -96,6 +99,7 @@ type Activity = {
|
|
|
|
|
|
|
|
|
|
|
|
const rows = ref<Activity[]>([])
|
|
|
|
const rows = ref<Activity[]>([])
|
|
|
|
const venues = ref<Venue[]>([])
|
|
|
|
const venues = ref<Venue[]>([])
|
|
|
|
|
|
|
|
const ageGroupOptions = ref<DictOption[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
/** 活动举办场馆等引用场景:待审/退回时展示已通过快照中的名称 */
|
|
|
|
/** 活动举办场馆等引用场景:待审/退回时展示已通过快照中的名称 */
|
|
|
|
function venueReferenceDisplayName(v: Pick<Venue, 'name' | 'audit_status' | 'last_approved_snapshot'> | null | undefined): string {
|
|
|
|
function venueReferenceDisplayName(v: Pick<Venue, 'name' | 'audit_status' | 'last_approved_snapshot'> | null | undefined): string {
|
|
|
|
@ -300,19 +304,6 @@ const DEFAULT_CENTER = { lat: 31.299379, lng: 120.585315 }
|
|
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
|
|
|
|
|
|
/** 接口 ISO8601 → 东八区日历日 YYYY-MM-DD(与后端 APP_TIMEZONE 一致)。勿对 ISO 用 slice(0,10),UTC 日界会导致与后端差一天。 */
|
|
|
|
|
|
|
|
function formatYmdInShanghai(iso: string | undefined | null): string {
|
|
|
|
|
|
|
|
if (!iso) return ''
|
|
|
|
|
|
|
|
const d = new Date(String(iso))
|
|
|
|
|
|
|
|
if (Number.isNaN(d.getTime())) return String(iso).slice(0, 10)
|
|
|
|
|
|
|
|
return new Intl.DateTimeFormat('en-CA', {
|
|
|
|
|
|
|
|
timeZone: 'Asia/Shanghai',
|
|
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
|
|
|
}).format(d)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseShanghaiLocalToMs(datetimeYmdHmss: string): number {
|
|
|
|
function parseShanghaiLocalToMs(datetimeYmdHmss: string): number {
|
|
|
|
let s = String(datetimeYmdHmss || '').trim().replace(' ', 'T')
|
|
|
|
let s = String(datetimeYmdHmss || '').trim().replace(' ', 'T')
|
|
|
|
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(s)) s += ':00'
|
|
|
|
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(s)) s += ':00'
|
|
|
|
@ -336,8 +327,8 @@ function reservationTypeSupportsSessionSettings(rt?: string | null): boolean {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatActivityTableDateRange(record: Activity): string {
|
|
|
|
function formatActivityTableDateRange(record: Activity): string {
|
|
|
|
const s = record.start_at ? formatYmdInShanghai(String(record.start_at)) : ''
|
|
|
|
const s = record.start_at ? ymdFromDateValue(String(record.start_at)) : ''
|
|
|
|
const e = record.end_at ? formatYmdInShanghai(String(record.end_at)) : ''
|
|
|
|
const e = record.end_at ? ymdFromDateValue(String(record.end_at)) : ''
|
|
|
|
if (!s && !e) return '-'
|
|
|
|
if (!s && !e) return '-'
|
|
|
|
if (s && e) {
|
|
|
|
if (s && e) {
|
|
|
|
if (s === e) return s
|
|
|
|
if (s === e) return s
|
|
|
|
@ -396,6 +387,7 @@ const form = reactive({
|
|
|
|
is_hot: false,
|
|
|
|
is_hot: false,
|
|
|
|
booking_method_note: '',
|
|
|
|
booking_method_note: '',
|
|
|
|
fee_note: '',
|
|
|
|
fee_note: '',
|
|
|
|
|
|
|
|
age_group: undefined as string | undefined,
|
|
|
|
location: '',
|
|
|
|
location: '',
|
|
|
|
check_in_meeting_point: '',
|
|
|
|
check_in_meeting_point: '',
|
|
|
|
lat: undefined as number | undefined,
|
|
|
|
lat: undefined as number | undefined,
|
|
|
|
@ -1559,6 +1551,15 @@ function formatActivityStatsCell(record: Activity) {
|
|
|
|
return '浏览 ' + statNum(record.view_count)
|
|
|
|
return '浏览 ' + statNum(record.view_count)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function loadAgeGroupOptions() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const { data } = await http.get('/dict-items', { params: { dict_type: 'age_groups', active_only: 1 } })
|
|
|
|
|
|
|
|
ageGroupOptions.value = Array.isArray(data) ? data : []
|
|
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
|
|
ageGroupOptions.value = []
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadAll() {
|
|
|
|
async function loadAll() {
|
|
|
|
loading.value = true
|
|
|
|
loading.value = true
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
@ -1567,6 +1568,7 @@ async function loadAll() {
|
|
|
|
params: buildActivitiesListParams(pagination.current, pagination.pageSize),
|
|
|
|
params: buildActivitiesListParams(pagination.current, pagination.pageSize),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
http.get('/venues'),
|
|
|
|
http.get('/venues'),
|
|
|
|
|
|
|
|
loadAgeGroupOptions(),
|
|
|
|
])
|
|
|
|
])
|
|
|
|
rows.value = aRes.data.data
|
|
|
|
rows.value = aRes.data.data
|
|
|
|
pagination.total = aRes.data.total
|
|
|
|
pagination.total = aRes.data.total
|
|
|
|
@ -1598,6 +1600,7 @@ function openCreate() {
|
|
|
|
form.reservation_type = 'online'
|
|
|
|
form.reservation_type = 'online'
|
|
|
|
form.booking_method_note = '平台预约'
|
|
|
|
form.booking_method_note = '平台预约'
|
|
|
|
form.fee_note = '免费'
|
|
|
|
form.fee_note = '免费'
|
|
|
|
|
|
|
|
form.age_group = undefined
|
|
|
|
form.location = ''
|
|
|
|
form.location = ''
|
|
|
|
form.check_in_meeting_point = ''
|
|
|
|
form.check_in_meeting_point = ''
|
|
|
|
form.lat = undefined
|
|
|
|
form.lat = undefined
|
|
|
|
@ -1657,6 +1660,7 @@ function openEdit(row: Activity) {
|
|
|
|
(['online', 'none'].includes(reservationKindEffective(form.reservation_type))
|
|
|
|
(['online', 'none'].includes(reservationKindEffective(form.reservation_type))
|
|
|
|
? '免费'
|
|
|
|
? '免费'
|
|
|
|
: '')
|
|
|
|
: '')
|
|
|
|
|
|
|
|
form.age_group = row.age_group || undefined
|
|
|
|
form.location = row.location || ''
|
|
|
|
form.location = row.location || ''
|
|
|
|
form.check_in_meeting_point = row.check_in_meeting_point || ''
|
|
|
|
form.check_in_meeting_point = row.check_in_meeting_point || ''
|
|
|
|
form.lat = parseCoord(row.lat)
|
|
|
|
form.lat = parseCoord(row.lat)
|
|
|
|
@ -1665,8 +1669,8 @@ function openEdit(row: Activity) {
|
|
|
|
form.title = row.title
|
|
|
|
form.title = row.title
|
|
|
|
form.contact_name = row.contact_name ?? ''
|
|
|
|
form.contact_name = row.contact_name ?? ''
|
|
|
|
form.contact_phone = row.contact_phone ?? ''
|
|
|
|
form.contact_phone = row.contact_phone ?? ''
|
|
|
|
form.start_at = row.start_at ? formatYmdInShanghai(row.start_at) : ''
|
|
|
|
form.start_at = row.start_at ? ymdFromDateValue(row.start_at) : ''
|
|
|
|
form.end_at = row.end_at ? formatYmdInShanghai(row.end_at) : ''
|
|
|
|
form.end_at = row.end_at ? ymdFromDateValue(row.end_at) : ''
|
|
|
|
form.detail_html = row.detail_html || ''
|
|
|
|
form.detail_html = row.detail_html || ''
|
|
|
|
form.cover_image = row.cover_image || ''
|
|
|
|
form.cover_image = row.cover_image || ''
|
|
|
|
form.gallery_media = Array.isArray(row.gallery_media) ? [...row.gallery_media] : []
|
|
|
|
form.gallery_media = Array.isArray(row.gallery_media) ? [...row.gallery_media] : []
|
|
|
|
@ -1893,6 +1897,7 @@ async function executeUnifiedActivitySave(): Promise<void> {
|
|
|
|
tags: form.tags,
|
|
|
|
tags: form.tags,
|
|
|
|
reservation_notice: null,
|
|
|
|
reservation_notice: null,
|
|
|
|
open_time: null,
|
|
|
|
open_time: null,
|
|
|
|
|
|
|
|
age_group: form.age_group || null,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isUnifiedNature) {
|
|
|
|
if (isUnifiedNature) {
|
|
|
|
payload.offline_reservation_method = null
|
|
|
|
payload.offline_reservation_method = null
|
|
|
|
@ -2570,6 +2575,21 @@ async function removeActivity(row: Activity) {
|
|
|
|
<span style="color: #f53f3f;">{{ formErrors.fee_note }}</span>
|
|
|
|
<span style="color: #f53f3f;">{{ formErrors.fee_note }}</span>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</a-form-item>
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
<a-form-item label="适合年龄段">
|
|
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
|
|
v-model="form.age_group"
|
|
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
|
|
allow-search
|
|
|
|
|
|
|
|
placeholder="请选择适合年龄段(选填)"
|
|
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<a-option
|
|
|
|
|
|
|
|
v-for="opt in ageGroupOptions"
|
|
|
|
|
|
|
|
:key="opt.item_value"
|
|
|
|
|
|
|
|
:value="opt.item_value"
|
|
|
|
|
|
|
|
>{{ opt.item_label }}</a-option>
|
|
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
|
|
</a-form-item>
|
|
|
|
<a-row :gutter="16" class="admin-modal-form__full">
|
|
|
|
<a-row :gutter="16" class="admin-modal-form__full">
|
|
|
|
<a-col :xs="24" :md="10">
|
|
|
|
<a-col :xs="24" :md="10">
|
|
|
|
<a-form-item
|
|
|
|
<a-form-item
|
|
|
|
|