|
|
|
|
@ -1,14 +1,24 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { nextTick, onMounted, reactive, ref } from 'vue'
|
|
|
|
|
import { Message } from '@arco-design/web-vue'
|
|
|
|
|
import { nextTick, onMounted, reactive, ref, computed } from 'vue'
|
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
|
import { Message, Modal } from '@arco-design/web-vue'
|
|
|
|
|
import { http } from '../../api/http'
|
|
|
|
|
import RichEditorField from '../../components/RichEditorField.vue'
|
|
|
|
|
import { adminUploadImageTooLargeMessage, ADMIN_IMAGE_RECOMMEND_LABEL } from '../../utils/adminMediaLimits'
|
|
|
|
|
import { listTableRowIndex } from '../../utils/listTableRowIndex'
|
|
|
|
|
import { resolvePublicMediaUrl } from '../../utils/mediaUrl'
|
|
|
|
|
import { reverseMapGeocode, searchMapPlaces } from '../../utils/mapGeo'
|
|
|
|
|
import {
|
|
|
|
|
bindTiandituMapClick,
|
|
|
|
|
clearTiandituOverlay,
|
|
|
|
|
createTiandituMap,
|
|
|
|
|
loadTiandituApi,
|
|
|
|
|
refreshTiandituViewport,
|
|
|
|
|
setTiandituPickMarker,
|
|
|
|
|
} from '../../utils/tiandituMap'
|
|
|
|
|
|
|
|
|
|
/** 主列表横向滚动宽度(与各列宽之和大致对齐) */
|
|
|
|
|
const VENUE_LIST_SCROLL_X = 2060
|
|
|
|
|
const VENUE_LIST_SCROLL_X = 2130
|
|
|
|
|
|
|
|
|
|
type DictItem = {
|
|
|
|
|
id: number
|
|
|
|
|
@ -48,6 +58,7 @@ type Venue = {
|
|
|
|
|
is_included_in_stats?: boolean
|
|
|
|
|
audit_status?: 'approved' | 'pending' | 'rejected'
|
|
|
|
|
audit_remark?: string | null
|
|
|
|
|
last_approved_snapshot?: Record<string, unknown> | null
|
|
|
|
|
}
|
|
|
|
|
type CurrentUser = {
|
|
|
|
|
role: string
|
|
|
|
|
@ -76,11 +87,12 @@ const editorRenderKey = ref(0)
|
|
|
|
|
const mediaPreviewVisible = ref(false)
|
|
|
|
|
const mediaPreviewType = ref<'image' | 'video'>('image')
|
|
|
|
|
const mediaPreviewUrl = ref('')
|
|
|
|
|
let mapInstance: any = null
|
|
|
|
|
let markerLayer: any = null
|
|
|
|
|
let mapInstance: ReturnType<typeof createTiandituMap> | null = null
|
|
|
|
|
const pickMarkerHolder = { overlay: null as unknown }
|
|
|
|
|
const pickedPoint = ref<{ lat: number; lng: number; address: string } | null>(null)
|
|
|
|
|
const DEFAULT_CENTER = { lat: 31.299379, lng: 120.585315 } // 苏州市人民政府
|
|
|
|
|
const modalBodyStyle = { maxHeight: '70vh', overflow: 'auto' }
|
|
|
|
|
const venueAuditModalBodyStyle = { height: '70vh', overflow: 'auto' as const }
|
|
|
|
|
|
|
|
|
|
// 表单验证错误信息
|
|
|
|
|
const formErrors = reactive<Record<string, string>>({
|
|
|
|
|
@ -122,6 +134,7 @@ const filters = reactive({
|
|
|
|
|
/** 预约方式(富文本)是否已填写:all=不限 */
|
|
|
|
|
is_active: '' as '' | '1' | '0',
|
|
|
|
|
is_included_in_stats: '' as '' | '1' | '0',
|
|
|
|
|
audit_status: '' as '' | 'pending' | 'approved' | 'rejected',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const pagination = reactive({
|
|
|
|
|
@ -130,10 +143,114 @@ const pagination = reactive({
|
|
|
|
|
total: 0,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
|
|
|
|
const rejectVenueId = ref<number | null>(null)
|
|
|
|
|
const rejectVenueRemark = ref('')
|
|
|
|
|
const rejectVenueVisible = ref(false)
|
|
|
|
|
|
|
|
|
|
type VenueAuditLogRow = {
|
|
|
|
|
id: number
|
|
|
|
|
action: string
|
|
|
|
|
remark?: string | null
|
|
|
|
|
created_at?: string
|
|
|
|
|
admin_user?: { name?: string; username?: string } | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const venueAuditVisible = ref(false)
|
|
|
|
|
const venueAuditModalMode = ref<'audit' | 'view'>('view')
|
|
|
|
|
const venueAuditLoading = ref(false)
|
|
|
|
|
const venueAuditVenue = ref<Venue | null>(null)
|
|
|
|
|
const venueAuditLogs = ref<VenueAuditLogRow[]>([])
|
|
|
|
|
const venueAuditChanges = ref<Array<{ field: string }>>([])
|
|
|
|
|
|
|
|
|
|
const venueAuditChangedFieldSet = computed(
|
|
|
|
|
() => new Set(venueAuditChanges.value.map((c) => c.field)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
function venueAuditFieldChanged(field: string): boolean {
|
|
|
|
|
return venueAuditChangedFieldSet.value.has(field)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function venueAuditShowChanged(field: string): boolean {
|
|
|
|
|
return venueAuditModalMode.value === 'audit' && venueAuditFieldChanged(field)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const venueAuditModalTitle = computed(() => (
|
|
|
|
|
venueAuditModalMode.value === 'audit' ? '场馆审核' : '查看场馆'
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
function stripRichPlain(v: unknown): string {
|
|
|
|
|
if (v === null || v === undefined) return '—'
|
|
|
|
|
let s = String(v)
|
|
|
|
|
s = s.replace(/<[^>]+>/g, ' ')
|
|
|
|
|
s = s.replace(/ /gi, ' ')
|
|
|
|
|
s = s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
|
|
|
s = s.replace(/\s+/g, ' ').trim()
|
|
|
|
|
return s || '—'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function venueAuditBoolText(v: unknown): string {
|
|
|
|
|
return v === true || v === 1 || v === '1' ? '是' : '否'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function venueAuditThemeText(v: Partial<Venue> | Venue | null | undefined): string {
|
|
|
|
|
if (!v) return '—'
|
|
|
|
|
const list = venueTypeValues(v as Venue)
|
|
|
|
|
if (!list.length) return '—'
|
|
|
|
|
return list.map((tv) => optionLabel(venueTypeOptions.value, tv)).join('、')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function venueAuditPlainText(v: unknown): string {
|
|
|
|
|
if (v === null || v === undefined) return '—'
|
|
|
|
|
const s = String(v).trim()
|
|
|
|
|
return s || '—'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 列表展示:待审/退回且有快照时显示已通过的原值 */
|
|
|
|
|
function venueListRowDisplay(record: Venue): Venue {
|
|
|
|
|
const snap = record.last_approved_snapshot
|
|
|
|
|
if (
|
|
|
|
|
(record.audit_status === 'pending' || record.audit_status === 'rejected')
|
|
|
|
|
&& snap
|
|
|
|
|
&& typeof snap === 'object'
|
|
|
|
|
&& !Array.isArray(snap)
|
|
|
|
|
) {
|
|
|
|
|
return {
|
|
|
|
|
...record,
|
|
|
|
|
...(snap as Partial<Venue>),
|
|
|
|
|
id: record.id,
|
|
|
|
|
audit_status: record.audit_status,
|
|
|
|
|
audit_remark: record.audit_remark,
|
|
|
|
|
last_approved_snapshot: record.last_approved_snapshot,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return record
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 查看模式展示已通过版本;审核模式展示待审内容 */
|
|
|
|
|
const venueAuditDisplayVenue = computed((): Venue | null => {
|
|
|
|
|
const v = venueAuditVenue.value
|
|
|
|
|
if (!v) return null
|
|
|
|
|
if (venueAuditModalMode.value === 'audit') return v
|
|
|
|
|
return venueListRowDisplay(v)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function venueAuditPendingDisplay(field: string, asRich = false): string {
|
|
|
|
|
const v = venueAuditDisplayVenue.value
|
|
|
|
|
if (!v) return '—'
|
|
|
|
|
if (field === 'venue_types') return venueAuditThemeText(v)
|
|
|
|
|
if (field === 'district') return optionLabel(districtOptions.value, v.district) || '—'
|
|
|
|
|
if (field === 'ticket_type') return optionLabel(ticketTypeOptions.value, v.ticket_type) || '—'
|
|
|
|
|
if (field === 'booking_mode') return optionLabel(bookingModeOptions.value, v.booking_mode) || '—'
|
|
|
|
|
if (field === 'open_mode') return optionLabel(openModeOptions.value, v.open_mode) || '—'
|
|
|
|
|
if (field === 'is_active') return venueAuditBoolText(v.is_active)
|
|
|
|
|
if (field === 'is_included_in_stats') return venueAuditBoolText(v.is_included_in_stats)
|
|
|
|
|
const raw = (v as Record<string, unknown>)[field]
|
|
|
|
|
if (asRich) return stripRichPlain(raw)
|
|
|
|
|
return venueAuditPlainText(raw)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseCoord(v: unknown): number | undefined {
|
|
|
|
|
if (v === null || v === undefined || v === '') return undefined
|
|
|
|
|
const n = typeof v === 'number' ? v : parseFloat(String(v).trim())
|
|
|
|
|
@ -250,10 +367,51 @@ function isSuperAdmin() {
|
|
|
|
|
return currentUser.value?.full_admin_access === true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function approveVenue(row: Venue) {
|
|
|
|
|
function auditStatusLabel(status?: string) {
|
|
|
|
|
if (status === 'pending') return '待审核'
|
|
|
|
|
if (status === 'rejected') return '已退回'
|
|
|
|
|
if (status === 'approved') return '已通过'
|
|
|
|
|
return status || '—'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function auditLogActionLabel(action: string) {
|
|
|
|
|
if (action === 'approve') return '通过'
|
|
|
|
|
if (action === 'reject') return '退回'
|
|
|
|
|
if (action === 'edit_submit') return '提交审核'
|
|
|
|
|
return action
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function openVenueAudit(row: Venue, mode: 'audit' | 'view' = 'view') {
|
|
|
|
|
const effectiveMode =
|
|
|
|
|
mode === 'audit' && row.audit_status === 'pending' && isSuperAdmin() ? 'audit' : 'view'
|
|
|
|
|
venueAuditModalMode.value = effectiveMode
|
|
|
|
|
venueAuditVisible.value = true
|
|
|
|
|
venueAuditLoading.value = true
|
|
|
|
|
venueAuditVenue.value = row
|
|
|
|
|
venueAuditLogs.value = []
|
|
|
|
|
venueAuditChanges.value = []
|
|
|
|
|
try {
|
|
|
|
|
const [{ data: detail }, { data: logs }] = await Promise.all([
|
|
|
|
|
http.get(`/venues/${row.id}/audit-detail`),
|
|
|
|
|
http.get(`/venues/${row.id}/audit-logs`),
|
|
|
|
|
])
|
|
|
|
|
venueAuditVenue.value = detail.venue ?? row
|
|
|
|
|
venueAuditChanges.value = Array.isArray(detail.changes) ? detail.changes : []
|
|
|
|
|
venueAuditLogs.value = Array.isArray(logs) ? logs : []
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
Message.error(error?.response?.data?.message ?? '加载场馆详情失败')
|
|
|
|
|
} finally {
|
|
|
|
|
venueAuditLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function approveVenueFromAudit() {
|
|
|
|
|
const row = venueAuditVenue.value
|
|
|
|
|
if (!row) return
|
|
|
|
|
try {
|
|
|
|
|
await http.post(`/venues/${row.id}/audit/approve`)
|
|
|
|
|
Message.success('已通过审核')
|
|
|
|
|
venueAuditVisible.value = false
|
|
|
|
|
await loadRows()
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
Message.error(error?.response?.data?.message ?? '操作失败')
|
|
|
|
|
@ -356,10 +514,6 @@ function venueTypeValues(record: Venue): string[] {
|
|
|
|
|
return record.venue_type ? [String(record.venue_type)] : []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mapJsKey() {
|
|
|
|
|
return (import.meta.env.VITE_TENCENT_MAP_KEY as string) || ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeMediaUrl(rawUrl?: string, rawPath?: string) {
|
|
|
|
|
const urlText = String(rawUrl || '').trim()
|
|
|
|
|
if (urlText) {
|
|
|
|
|
@ -436,6 +590,7 @@ async function loadRows() {
|
|
|
|
|
appointment_type: filters.appointment_type || undefined,
|
|
|
|
|
is_active: filters.is_active || undefined,
|
|
|
|
|
is_included_in_stats: filters.is_included_in_stats || undefined,
|
|
|
|
|
audit_status: filters.audit_status || undefined,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
rows.value = data
|
|
|
|
|
@ -676,54 +831,16 @@ function onPageChange(p: number) {
|
|
|
|
|
pagination.current = p
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ensureMapSdkLoaded() {
|
|
|
|
|
const w = window as any
|
|
|
|
|
if (w.TMap) return
|
|
|
|
|
const key = mapJsKey()
|
|
|
|
|
if (!key) {
|
|
|
|
|
throw new Error('请先配置 VITE_TENCENT_MAP_KEY')
|
|
|
|
|
}
|
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
|
|
|
const script = document.createElement('script')
|
|
|
|
|
script.src = `https://map.qq.com/api/gljs?v=1.exp&key=${key}`
|
|
|
|
|
script.async = true
|
|
|
|
|
script.onload = () => resolve()
|
|
|
|
|
script.onerror = () => reject(new Error('腾讯地图SDK加载失败'))
|
|
|
|
|
document.head.appendChild(script)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderMarker(lat: number, lng: number) {
|
|
|
|
|
const TMap = (window as any).TMap
|
|
|
|
|
if (!mapInstance) return
|
|
|
|
|
if (markerLayer) markerLayer.setMap(null)
|
|
|
|
|
markerLayer = new TMap.MultiMarker({
|
|
|
|
|
map: mapInstance,
|
|
|
|
|
styles: {
|
|
|
|
|
marker: new TMap.MarkerStyle({ width: 24, height: 35 }),
|
|
|
|
|
},
|
|
|
|
|
geometries: [{ id: 'picked', styleId: 'marker', position: new TMap.LatLng(lat, lng) }],
|
|
|
|
|
})
|
|
|
|
|
mapInstance.setCenter(new TMap.LatLng(lat, lng))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshMapViewport(lat: number, lng: number) {
|
|
|
|
|
const TMap = (window as any).TMap
|
|
|
|
|
if (!mapInstance || !TMap) return
|
|
|
|
|
const center = new TMap.LatLng(lat, lng)
|
|
|
|
|
// 弹窗场景下首帧可能未完成布局,强制刷新两次可避免灰底无底图
|
|
|
|
|
mapInstance.resize?.()
|
|
|
|
|
mapInstance.setCenter(center)
|
|
|
|
|
mapInstance.setZoom(13)
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
mapInstance.resize?.()
|
|
|
|
|
mapInstance.setCenter(center)
|
|
|
|
|
}, 120)
|
|
|
|
|
setTiandituPickMarker(mapInstance, pickMarkerHolder, lat, lng)
|
|
|
|
|
refreshTiandituViewport(mapInstance, lng, lat, 13)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function reverseGeocode(lat: number, lng: number) {
|
|
|
|
|
const { data } = await http.get('/map/reverse-geocode', { params: { lat, lng } })
|
|
|
|
|
pickedPoint.value = { lat, lng, address: data.address || '' }
|
|
|
|
|
const fallback = pickedPoint.value?.address || ''
|
|
|
|
|
const data = await reverseMapGeocode(lat, lng)
|
|
|
|
|
pickedPoint.value = { lat, lng, address: (data.address || '').trim() || fallback }
|
|
|
|
|
if (data.district) {
|
|
|
|
|
const exists = districtOptions.value.some((d) => d.item_value === data.district)
|
|
|
|
|
if (exists) form.district = data.district
|
|
|
|
|
@ -731,41 +848,36 @@ async function reverseGeocode(lat: number, lng: number) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function initMapPicker() {
|
|
|
|
|
await ensureMapSdkLoaded()
|
|
|
|
|
const TMap = (window as any).TMap
|
|
|
|
|
await loadTiandituApi()
|
|
|
|
|
const lat = typeof form.lat === 'number' ? form.lat : DEFAULT_CENTER.lat
|
|
|
|
|
const lng = typeof form.lng === 'number' ? form.lng : DEFAULT_CENTER.lng
|
|
|
|
|
const center = new TMap.LatLng(lat, lng)
|
|
|
|
|
if (!mapContainerRef.value) return
|
|
|
|
|
if (!mapInstance) {
|
|
|
|
|
mapInstance = new TMap.Map(mapContainerRef.value, { center, zoom: 13 })
|
|
|
|
|
mapInstance.on('click', async (evt: any) => {
|
|
|
|
|
const lat = Number(evt.latLng.getLat().toFixed(6))
|
|
|
|
|
const lng = Number(evt.latLng.getLng().toFixed(6))
|
|
|
|
|
renderMarker(lat, lng)
|
|
|
|
|
mapInstance = createTiandituMap(mapContainerRef.value, lng, lat, 13)
|
|
|
|
|
bindTiandituMapClick(mapInstance, async (clickLat, clickLng) => {
|
|
|
|
|
renderMarker(clickLat, clickLng)
|
|
|
|
|
pickedPoint.value = { lat: clickLat, lng: clickLng, address: '' }
|
|
|
|
|
try {
|
|
|
|
|
await reverseGeocode(lat, lng)
|
|
|
|
|
await reverseGeocode(clickLat, clickLng)
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
Message.error(error?.response?.data?.message ?? '逆地理编码失败')
|
|
|
|
|
Message.error(error?.message ?? error?.response?.data?.message ?? '逆地理编码失败')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
mapInstance.setCenter(center)
|
|
|
|
|
refreshTiandituViewport(mapInstance, lng, lat, 13)
|
|
|
|
|
}
|
|
|
|
|
refreshMapViewport(lat, lng)
|
|
|
|
|
refreshTiandituViewport(mapInstance, lng, lat, 13)
|
|
|
|
|
if (typeof form.lat !== 'undefined' && typeof form.lng !== 'undefined') {
|
|
|
|
|
renderMarker(form.lat, form.lng)
|
|
|
|
|
pickedPoint.value = { lat: form.lat, lng: form.lng, address: form.address || '' }
|
|
|
|
|
} else {
|
|
|
|
|
if (markerLayer) {
|
|
|
|
|
markerLayer.setMap(null)
|
|
|
|
|
markerLayer = null
|
|
|
|
|
}
|
|
|
|
|
clearTiandituOverlay(mapInstance, pickMarkerHolder.overlay as never)
|
|
|
|
|
pickMarkerHolder.overlay = null
|
|
|
|
|
pickedPoint.value = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function openMapPicker() {
|
|
|
|
|
if (!isCreate.value) return
|
|
|
|
|
mapVisible.value = true
|
|
|
|
|
mapKeyword.value = ''
|
|
|
|
|
mapResults.value = []
|
|
|
|
|
@ -793,10 +905,10 @@ async function searchMapKeyword() {
|
|
|
|
|
}
|
|
|
|
|
mapLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const { data } = await http.get('/map/search', { params: { keyword: mapKeyword.value, region: '苏州' } })
|
|
|
|
|
mapResults.value = data
|
|
|
|
|
if (!mapInstance) throw new Error('地图未初始化')
|
|
|
|
|
mapResults.value = await searchMapPlaces(mapInstance, mapKeyword.value)
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
Message.error(error?.response?.data?.message ?? '地图搜索失败')
|
|
|
|
|
Message.error(error?.message ?? error?.response?.data?.message ?? '地图搜索失败')
|
|
|
|
|
} finally {
|
|
|
|
|
mapLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
@ -804,11 +916,12 @@ async function searchMapKeyword() {
|
|
|
|
|
|
|
|
|
|
async function pickSearchResult(item: { title: string; address: string; lat: number; lng: number }) {
|
|
|
|
|
renderMarker(item.lat, item.lng)
|
|
|
|
|
pickedPoint.value = { lat: item.lat, lng: item.lng, address: item.address || '' }
|
|
|
|
|
const addr = [item.title, item.address].filter(Boolean).join(' · ')
|
|
|
|
|
pickedPoint.value = { lat: item.lat, lng: item.lng, address: addr }
|
|
|
|
|
try {
|
|
|
|
|
await reverseGeocode(item.lat, item.lng)
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
// 保留搜索结果中的地址
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -932,8 +1045,12 @@ async function submit(): Promise<boolean> {
|
|
|
|
|
await http.post('/venues', payload)
|
|
|
|
|
Message.success('创建场馆成功')
|
|
|
|
|
} else if (editId.value) {
|
|
|
|
|
await http.put(`/venues/${editId.value}`, payload)
|
|
|
|
|
Message.success('更新场馆成功')
|
|
|
|
|
const { data } = await http.put(`/venues/${editId.value}`, payload)
|
|
|
|
|
if (!isSuperAdmin() && data?.audit_status === 'pending') {
|
|
|
|
|
Message.success('信息已保存,请等待管理员审核')
|
|
|
|
|
} else {
|
|
|
|
|
Message.success('更新场馆成功')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await loadRows()
|
|
|
|
|
return true
|
|
|
|
|
@ -946,10 +1063,34 @@ async function submit(): Promise<boolean> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onBeforeOk() {
|
|
|
|
|
if (!validateForm()) {
|
|
|
|
|
Message.warning('请填写所有必填项')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if (!isSuperAdmin() && !isCreate.value) {
|
|
|
|
|
return new Promise<boolean>((resolve) => {
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
title: '提交确认',
|
|
|
|
|
content: '请核对本次修改内容信息准确,再确认提交审核。',
|
|
|
|
|
okText: '确认提交',
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
async onBeforeOk() {
|
|
|
|
|
const ok = await submit()
|
|
|
|
|
resolve(ok)
|
|
|
|
|
return ok
|
|
|
|
|
},
|
|
|
|
|
onCancel: () => resolve(false),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return await submit()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
const qAudit = typeof route.query.audit_status === 'string' ? route.query.audit_status : ''
|
|
|
|
|
if (qAudit === 'pending' || qAudit === 'approved' || qAudit === 'rejected') {
|
|
|
|
|
filters.audit_status = qAudit
|
|
|
|
|
}
|
|
|
|
|
await loadMe()
|
|
|
|
|
await Promise.all([
|
|
|
|
|
loadRows(),
|
|
|
|
|
@ -991,6 +1132,11 @@ onMounted(async () => {
|
|
|
|
|
<a-option value="1">上架</a-option>
|
|
|
|
|
<a-option value="0">下架</a-option>
|
|
|
|
|
</a-select>
|
|
|
|
|
<a-select v-if="isSuperAdmin()" v-model="filters.audit_status" allow-clear placeholder="审核状态" style="width: 130px">
|
|
|
|
|
<a-option value="pending">待审核</a-option>
|
|
|
|
|
<a-option value="approved">已通过</a-option>
|
|
|
|
|
<a-option value="rejected">已退回</a-option>
|
|
|
|
|
</a-select>
|
|
|
|
|
<a-button type="primary" @click="onSearch">查询</a-button>
|
|
|
|
|
<a-button type="primary" @click="openCreate">新增场馆</a-button>
|
|
|
|
|
<a-button v-if="isSuperAdmin()" :loading="venueExporting" @click="exportVenues">导出场馆</a-button>
|
|
|
|
|
@ -1007,17 +1153,19 @@ onMounted(async () => {
|
|
|
|
|
@page-change="onPageChange"
|
|
|
|
|
>
|
|
|
|
|
<template #columns>
|
|
|
|
|
<a-table-column title="" :width="50" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<a-table-column title="" :width="72" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<template #cell="{ rowIndex }">{{
|
|
|
|
|
listTableRowIndex(rowIndex, pagination.current, pagination.pageSize)
|
|
|
|
|
}}</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="场馆名称" data-index="name" :width="220" :min-width="160" :ellipsis="true" :tooltip="true" />
|
|
|
|
|
<a-table-column title="场馆名称" :width="220" :min-width="160" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<template #cell="{ record }">{{ venueListRowDisplay(record as Venue).name }}</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="主题" :width="130">
|
|
|
|
|
<template #cell="{ record }">
|
|
|
|
|
<a-space v-if="venueTypeValues(record).length" wrap :size="4">
|
|
|
|
|
<a-space v-if="venueTypeValues(venueListRowDisplay(record as Venue)).length" wrap :size="4">
|
|
|
|
|
<a-tag
|
|
|
|
|
v-for="(v, i) in venueTypeValues(record)"
|
|
|
|
|
v-for="(v, i) in venueTypeValues(venueListRowDisplay(record as Venue))"
|
|
|
|
|
:key="record.id + '-vt-' + i"
|
|
|
|
|
:color="optionColor(venueTypeOptions, v, 'arcoblue')"
|
|
|
|
|
>
|
|
|
|
|
@ -1027,43 +1175,67 @@ onMounted(async () => {
|
|
|
|
|
<span v-else>-</span>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="行政区" data-index="district" :width="120" :ellipsis="true" :tooltip="true" />
|
|
|
|
|
<a-table-column title="所属单位" data-index="unit_name" :width="200" :min-width="140" :ellipsis="true" :tooltip="true" />
|
|
|
|
|
<a-table-column title="行政区" :width="120" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<template #cell="{ record }">{{ optionLabel(districtOptions, venueListRowDisplay(record as Venue).district) }}</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="所属单位" :width="200" :min-width="140" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<template #cell="{ record }">{{ venueListRowDisplay(record as Venue).unit_name || '—' }}</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="门票类型" :width="120">
|
|
|
|
|
<template #cell="{ record }">
|
|
|
|
|
<a-tag :color="optionColor(ticketTypeOptions, record.ticket_type, 'green')">
|
|
|
|
|
{{ optionLabel(ticketTypeOptions, record.ticket_type) }}
|
|
|
|
|
<a-tag :color="optionColor(ticketTypeOptions, venueListRowDisplay(record as Venue).ticket_type, 'green')">
|
|
|
|
|
{{ optionLabel(ticketTypeOptions, venueListRowDisplay(record as Venue).ticket_type) }}
|
|
|
|
|
</a-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="预约模式" :width="180">
|
|
|
|
|
<template #cell="{ record }">
|
|
|
|
|
<span>{{ optionLabel(bookingModeOptions, record.booking_mode) }}</span>
|
|
|
|
|
<span>{{ optionLabel(bookingModeOptions, venueListRowDisplay(record as Venue).booking_mode) }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="开放模式" :width="150">
|
|
|
|
|
<template #cell="{ record }">
|
|
|
|
|
<span>{{ optionLabel(openModeOptions, record.open_mode) }}</span>
|
|
|
|
|
<span>{{ optionLabel(openModeOptions, venueListRowDisplay(record as Venue).open_mode) }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="开放时间" :width="220" :min-width="160" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<template #cell="{ record }">{{ listCellOneLine(record.open_time) }}</template>
|
|
|
|
|
<template #cell="{ record }">{{ listCellOneLine(venueListRowDisplay(record as Venue).open_time) }}</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="地址" :width="280" :min-width="200" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<template #cell="{ record }">{{ venueListRowDisplay(record as Venue).address || '—' }}</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="排序" :width="90" :ellipsis="true" :tooltip="true">
|
|
|
|
|
<template #cell="{ record }">{{ venueListRowDisplay(record as Venue).sort ?? 0 }}</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="地址" data-index="address" :width="280" :min-width="200" :ellipsis="true" :tooltip="true" />
|
|
|
|
|
<a-table-column title="排序" data-index="sort" :width="90" :ellipsis="true" :tooltip="true" />
|
|
|
|
|
<a-table-column title="上架状态" :width="100">
|
|
|
|
|
<template #cell="{ record }">
|
|
|
|
|
<a-tag :color="record.is_active ? 'green' : 'gray'">{{ record.is_active ? '上架' : '下架' }}</a-tag>
|
|
|
|
|
<a-tag :color="venueListRowDisplay(record as Venue).is_active ? 'green' : 'gray'">{{
|
|
|
|
|
venueListRowDisplay(record as Venue).is_active ? '上架' : '下架'
|
|
|
|
|
}}</a-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column v-if="isSuperAdmin()" title="审核状态" :width="100">
|
|
|
|
|
<template #cell="{ record }">
|
|
|
|
|
<a-tag :color="record.audit_status === 'approved' ? 'green' : record.audit_status === 'pending' ? 'orange' : 'red'">
|
|
|
|
|
{{ auditStatusLabel(record.audit_status) }}
|
|
|
|
|
</a-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table-column>
|
|
|
|
|
<a-table-column title="操作" :width="170" fixed="right" align="left">
|
|
|
|
|
<a-table-column title="操作" :width="320" fixed="right" align="left">
|
|
|
|
|
<template #cell="{ record }">
|
|
|
|
|
<a-space wrap justify="start">
|
|
|
|
|
<a-button type="text" @click="openEdit(record)">编辑</a-button>
|
|
|
|
|
<template v-if="isSuperAdmin() && (record.audit_status === 'pending' || record.audit_status === 'rejected')">
|
|
|
|
|
<a-button type="text" status="success" @click="approveVenue(record)">通过</a-button>
|
|
|
|
|
<a-button type="text" status="danger" @click="openRejectVenue(record)">退回</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
<a-button
|
|
|
|
|
v-if="isSuperAdmin() && record.audit_status === 'pending'"
|
|
|
|
|
type="text"
|
|
|
|
|
status="success"
|
|
|
|
|
@click="openVenueAudit(record, 'audit')"
|
|
|
|
|
>审核</a-button>
|
|
|
|
|
<a-button
|
|
|
|
|
v-else
|
|
|
|
|
type="text"
|
|
|
|
|
@click="openVenueAudit(record, 'view')"
|
|
|
|
|
>查看</a-button>
|
|
|
|
|
<a-popconfirm
|
|
|
|
|
v-if="isSuperAdmin()"
|
|
|
|
|
content="删除后该场馆关联的活动、预约等数据将一并删除,且不可恢复,确认删除?"
|
|
|
|
|
@ -1082,6 +1254,172 @@ onMounted(async () => {
|
|
|
|
|
<a-textarea v-model="rejectVenueRemark" placeholder="退回说明(选填)" :auto-size="{ minRows: 3, maxRows: 8 }" />
|
|
|
|
|
</a-modal>
|
|
|
|
|
|
|
|
|
|
<a-modal v-model:visible="venueAuditVisible" :title="venueAuditModalTitle" width="70%" :body-style="venueAuditModalBodyStyle">
|
|
|
|
|
<a-spin :loading="venueAuditLoading">
|
|
|
|
|
<div v-if="venueAuditVenue" class="venue-audit-panel">
|
|
|
|
|
<a-form layout="vertical" class="admin-modal-form venue-audit-form">
|
|
|
|
|
<a-form-item label="场馆名称">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('name') }">{{ venueAuditPendingDisplay('name') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="主题(可多选)">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('venue_types') }">{{ venueAuditPendingDisplay('venue_types') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="行政区">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('district') }">{{ venueAuditPendingDisplay('district') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="所属单位">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('unit_name') }">{{ venueAuditPendingDisplay('unit_name') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="门票类型">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('ticket_type') }">{{ venueAuditPendingDisplay('ticket_type') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="预约模式">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('booking_mode') }">{{ venueAuditPendingDisplay('booking_mode') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="开放模式">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('open_mode') }">{{ venueAuditPendingDisplay('open_mode') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="参观形式">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('visit_form') }">{{ venueAuditPendingDisplay('visit_form') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="开放时间">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('open_time') }">{{ venueAuditPendingDisplay('open_time') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="咨询时间">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('consultation_hours') }">{{ venueAuditPendingDisplay('consultation_hours') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="咨询电话">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('contact_phone') }">{{ venueAuditPendingDisplay('contact_phone') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="排序">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('sort') }">{{ venueAuditPendingDisplay('sort') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item label="上架状态">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('is_active') }">{{ venueAuditPendingDisplay('is_active') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="12">
|
|
|
|
|
<a-form-item label="纳入市科协人数统计系统">
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('is_included_in_stats') }">{{ venueAuditPendingDisplay('is_included_in_stats') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="预约方式与预约二维码" class="admin-modal-form__full">
|
|
|
|
|
<div style="display: flex; gap: 16px; width: 100%">
|
|
|
|
|
<div style="flex: 1 1 50%; min-width: 0">
|
|
|
|
|
<div class="venue-form-split-label">预约方式</div>
|
|
|
|
|
<div class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('booking_method') }">{{ venueAuditPendingDisplay('booking_method') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="flex: 1 1 50%; min-width: 0">
|
|
|
|
|
<div class="venue-form-split-label">预约二维码</div>
|
|
|
|
|
<div
|
|
|
|
|
v-if="venueAuditDisplayVenue?.booking_qr_media?.length"
|
|
|
|
|
class="venue-gallery-grid"
|
|
|
|
|
:class="{ 'venue-audit-media--changed': venueAuditShowChanged('booking_qr_media') }"
|
|
|
|
|
>
|
|
|
|
|
<img
|
|
|
|
|
v-for="(m, i) in venueAuditDisplayVenue.booking_qr_media"
|
|
|
|
|
:key="`qr-${i}`"
|
|
|
|
|
:src="resolvePublicMediaUrl(m.url)"
|
|
|
|
|
class="venue-gallery-thumb"
|
|
|
|
|
alt=""
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('booking_qr_media') }">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="门票说明" class="admin-modal-form__full">
|
|
|
|
|
<div class="venue-audit-value-box venue-audit-value-box--multiline" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('ticket_content') }">{{ venueAuditPendingDisplay('ticket_content') }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="场馆地址与经纬度" class="admin-modal-form__full">
|
|
|
|
|
<div class="venue-address-coord-row venue-audit-address-row">
|
|
|
|
|
<div class="venue-audit-value-box venue-address-coord-row__address" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('address') }">{{ venueAuditPendingDisplay('address') }}</div>
|
|
|
|
|
<div class="venue-audit-value-box venue-address-coord-row__lng" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('lng') }">{{ venueAuditPlainText(venueAuditDisplayVenue?.lng) }}</div>
|
|
|
|
|
<div class="venue-audit-value-box venue-address-coord-row__lat" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('lat') }">{{ venueAuditPlainText(venueAuditDisplayVenue?.lat) }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="科普场馆图片" class="admin-modal-form__full">
|
|
|
|
|
<div class="venue-cover-carousel-wrap">
|
|
|
|
|
<div class="venue-cover-carousel-row__col">
|
|
|
|
|
<div class="venue-cover-carousel-row__sub">科普场馆主图</div>
|
|
|
|
|
<img
|
|
|
|
|
v-if="venueAuditDisplayVenue?.cover_image"
|
|
|
|
|
:src="resolvePublicMediaUrl(venueAuditDisplayVenue.cover_image)"
|
|
|
|
|
class="venue-cover-thumb"
|
|
|
|
|
:class="{ 'venue-audit-media--changed': venueAuditShowChanged('cover_image') }"
|
|
|
|
|
alt=""
|
|
|
|
|
/>
|
|
|
|
|
<div v-else class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('cover_image') }">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="venue-cover-carousel-row__col">
|
|
|
|
|
<div class="venue-cover-carousel-row__sub">科普场馆展示图片</div>
|
|
|
|
|
<div
|
|
|
|
|
v-if="venueAuditDisplayVenue?.gallery_media?.length"
|
|
|
|
|
class="venue-gallery-grid"
|
|
|
|
|
:class="{ 'venue-audit-media--changed': venueAuditShowChanged('gallery_media') }"
|
|
|
|
|
>
|
|
|
|
|
<template v-for="(item, idx) in venueAuditDisplayVenue.gallery_media" :key="item.url + idx">
|
|
|
|
|
<img v-if="item.type === 'image'" :src="resolvePublicMediaUrl(item.url)" class="venue-gallery-thumb" alt="" />
|
|
|
|
|
<video v-else :src="resolvePublicMediaUrl(item.url)" controls class="venue-gallery-thumb venue-gallery-thumb--video" />
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="venue-audit-value-box" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('gallery_media') }">—</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="预约须知" class="admin-modal-form__full">
|
|
|
|
|
<div class="venue-audit-value-box venue-audit-value-box--multiline" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('reservation_notice') }">{{ venueAuditPendingDisplay('reservation_notice', true) }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
<a-form-item label="场馆简介" class="admin-modal-form__full">
|
|
|
|
|
<div class="venue-audit-value-box venue-audit-value-box--multiline" :class="{ 'venue-audit-value-box--changed': venueAuditShowChanged('detail_html') }">{{ venueAuditPendingDisplay('detail_html', true) }}</div>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-form>
|
|
|
|
|
|
|
|
|
|
<div style="margin: 16px 0 8px; color: var(--color-text-2)">审核记录</div>
|
|
|
|
|
<a-timeline v-if="venueAuditLogs.length">
|
|
|
|
|
<a-timeline-item v-for="log in venueAuditLogs" :key="log.id">
|
|
|
|
|
<a-tag :color="log.action === 'approve' ? 'green' : log.action === 'reject' ? 'red' : 'arcoblue'">
|
|
|
|
|
{{ auditLogActionLabel(log.action) }}
|
|
|
|
|
</a-tag>
|
|
|
|
|
<span v-if="log.admin_user?.name" style="margin-left: 8px">{{ log.admin_user.name }}</span>
|
|
|
|
|
<div v-if="log.remark" style="margin-top: 4px; color: var(--color-text-3)">{{ log.remark }}</div>
|
|
|
|
|
</a-timeline-item>
|
|
|
|
|
</a-timeline>
|
|
|
|
|
<span v-else style="color: var(--color-text-3)">暂无审核记录</span>
|
|
|
|
|
</div>
|
|
|
|
|
</a-spin>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<a-space>
|
|
|
|
|
<a-button @click="venueAuditVisible = false">关闭</a-button>
|
|
|
|
|
<template v-if="venueAuditModalMode === 'audit'">
|
|
|
|
|
<a-button type="primary" @click="approveVenueFromAudit">通过</a-button>
|
|
|
|
|
<a-button status="danger" @click="venueAuditVenue && openRejectVenue(venueAuditVenue)">退回</a-button>
|
|
|
|
|
</template>
|
|
|
|
|
</a-space>
|
|
|
|
|
</template>
|
|
|
|
|
</a-modal>
|
|
|
|
|
|
|
|
|
|
<a-modal
|
|
|
|
|
v-model:visible="visible"
|
|
|
|
|
:title="isCreate ? '新增场馆' : '编辑场馆'"
|
|
|
|
|
@ -1172,7 +1510,7 @@ onMounted(async () => {
|
|
|
|
|
</template>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
<a-form-item label="咨询电话" required :help="formErrors.contact_phone">
|
|
|
|
|
<a-input v-model="form.contact_phone" placeholder="前台可点击拨打" />
|
|
|
|
|
<a-input v-model="form.contact_phone" placeholder="多个电话请用、分隔" />
|
|
|
|
|
<template v-if="formErrors.contact_phone" #help>
|
|
|
|
|
<span style="color: #f53f3f;">{{ formErrors.contact_phone }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
@ -1257,7 +1595,7 @@ onMounted(async () => {
|
|
|
|
|
hide-button
|
|
|
|
|
disabled
|
|
|
|
|
/>
|
|
|
|
|
<a-button v-if="isCreate" type="primary" class="venue-address-coord-row__map" @click="openMapPicker">地图选点</a-button>
|
|
|
|
|
<a-button type="primary" class="venue-address-coord-row__map" @click="openMapPicker">地图选点</a-button>
|
|
|
|
|
</div>
|
|
|
|
|
<template #extra>经纬度不可手动编辑,请使用地图选点自动填充。</template>
|
|
|
|
|
<template #help>
|
|
|
|
|
@ -1374,7 +1712,7 @@ onMounted(async () => {
|
|
|
|
|
|
|
|
|
|
<a-modal
|
|
|
|
|
v-model:visible="mapVisible"
|
|
|
|
|
title="地图选点(腾讯地图)"
|
|
|
|
|
title="地图选点(天地图)"
|
|
|
|
|
width="70%"
|
|
|
|
|
:body-style="modalBodyStyle"
|
|
|
|
|
:on-before-ok="confirmMapPick"
|
|
|
|
|
@ -1472,4 +1810,54 @@ onMounted(async () => {
|
|
|
|
|
.venue-gallery-thumb--video {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-cover-thumb {
|
|
|
|
|
width: 120px;
|
|
|
|
|
height: 75px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border: 1px solid #e5e6eb;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-audit-form :deep(.arco-form-item) {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-audit-value-box {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
min-height: 32px;
|
|
|
|
|
background: var(--color-fill-2);
|
|
|
|
|
border: 1px solid var(--color-border-2);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
color: var(--color-text-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-audit-value-box--multiline {
|
|
|
|
|
min-height: 80px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-audit-value-box--changed {
|
|
|
|
|
color: #f53f3f;
|
|
|
|
|
border-color: rgba(245, 63, 63, 0.35);
|
|
|
|
|
background: rgba(245, 63, 63, 0.04);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-audit-media--changed :deep(.venue-gallery-thumb),
|
|
|
|
|
img.venue-cover-thumb.venue-audit-media--changed {
|
|
|
|
|
outline: 2px solid #f53f3f;
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-audit-address-row {
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venue-audit-address-row .venue-audit-value-box {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|