master
lion 1 day ago
parent 0088fcc5d3
commit 917670101b

@ -28,5 +28,6 @@ export const routeLoaders: Record<string, () => Promise<{ default: unknown }>> =
'/settings/notifications': () => import('../views/settings/Notifications.vue'),
'/settings/system-logs': () => import('../views/settings/SystemLogs.vue'),
'/settings/dictionaries': () => import('../views/settings/Dictionaries.vue'),
'/settings/visit-stats': () => import('../views/settings/VisitStats.vue'),
'/wechat-users': () => import('../views/users/WechatUsers.vue'),
}

@ -0,0 +1,131 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { Message } from '@arco-design/web-vue'
import { http } from '../../api/http'
type VisitStats = {
actual_total: number
actual_today: number
display_total: number
display_today: number
total_offset: number
today_offset: number
}
const loading = ref(false)
const saving = ref(false)
const currentUser = ref<{ full_admin_access?: boolean } | null>(null)
const stats = ref<VisitStats | null>(null)
const form = reactive({
display_total: 0,
display_today: 0,
})
const isSuperAdmin = computed(() => currentUser.value?.full_admin_access === true)
async function loadMe() {
try {
const { data } = await http.get('/me')
currentUser.value = data
} catch {
currentUser.value = null
}
}
async function loadStats() {
loading.value = true
try {
const { data } = await http.get<VisitStats>('/system-settings/visit-stats')
stats.value = data
form.display_total = data.display_total ?? 0
form.display_today = data.display_today ?? 0
} catch (error: any) {
Message.error(error?.response?.data?.message ?? '加载访问量设置失败')
stats.value = null
} finally {
loading.value = false
}
}
async function save() {
if (!isSuperAdmin.value) {
Message.warning('仅超级管理员可修改')
return
}
saving.value = true
try {
const { data } = await http.put<VisitStats>('/system-settings/visit-stats', {
display_total: form.display_total,
display_today: form.display_today,
})
stats.value = data
form.display_total = data.display_total ?? 0
form.display_today = data.display_today ?? 0
Message.success('保存成功')
} catch (error: any) {
Message.error(error?.response?.data?.message ?? '保存失败')
} finally {
saving.value = false
}
}
onMounted(async () => {
await loadMe().catch(() => undefined)
await loadStats()
})
</script>
<template>
<a-card title="系统设置 / 访问量设置">
<a-alert type="info" style="margin-bottom: 16px">
仪表盘总访问量今日访问量展示 <strong>展示数值</strong>= 实际统计 + 调整量实际访问量仍由 H5 访问记录自动累计在此可直接修改展示数值系统会自动换算并保存调整量
</a-alert>
<a-spin :loading="loading">
<a-form layout="vertical" style="max-width: 520px">
<a-form-item label="实际总访问量(只读)">
<a-input-number :model-value="stats?.actual_total ?? 0" disabled style="width: 100%" />
</a-form-item>
<a-form-item label="展示总访问量" extra="保存后仪表盘总访问量显示此数值;后续实际访问增加时,展示值会同步增加。">
<a-input-number
v-model="form.display_total"
:min="0"
:precision="0"
:disabled="!isSuperAdmin"
style="width: 100%"
/>
</a-form-item>
<a-form-item label="总访问量调整量(只读)">
<a-input-number :model-value="stats?.total_offset ?? 0" disabled style="width: 100%" />
</a-form-item>
<a-divider />
<a-form-item label="实际今日访问量(只读)">
<a-input-number :model-value="stats?.actual_today ?? 0" disabled style="width: 100%" />
</a-form-item>
<a-form-item label="展示今日访问量" extra="保存后仪表盘今日访问量显示此数值;跨天后实际今日统计会归零,展示值 = 新实际值 + 今日调整量。">
<a-input-number
v-model="form.display_today"
:min="0"
:precision="0"
:disabled="!isSuperAdmin"
style="width: 100%"
/>
</a-form-item>
<a-form-item label="今日访问量调整量(只读)">
<a-input-number :model-value="stats?.today_offset ?? 0" disabled style="width: 100%" />
</a-form-item>
<a-space>
<a-button type="primary" :loading="saving" :disabled="!isSuperAdmin" @click="save"></a-button>
<a-button :loading="loading" @click="loadStats"></a-button>
</a-space>
<div v-if="!isSuperAdmin" style="margin-top: 12px; color: #86909c; font-size: 13px">
仅超级管理员可修改展示数值
</div>
</a-form>
</a-spin>
</a-card>
</template>

@ -8,7 +8,7 @@ import { listTableRowIndex } from '../../utils/listTableRowIndex'
import { resolvePublicMediaUrl } from '../../utils/mediaUrl'
import { adminUploadImageTooLargeMessage, ADMIN_IMAGE_RECOMMEND_LABEL } from '../../utils/adminMediaLimits'
const STUDY_TOUR_LIST_SCROLL_X = 1100
const STUDY_TOUR_LIST_SCROLL_X = 1180
type DictOption = { id: number; item_value: string; item_label: string }
@ -44,6 +44,7 @@ type StudyTour = {
implementation_html?: string
sort: number
is_on_shelf: boolean
view_count?: number
}
const loading = ref(false)
@ -822,6 +823,15 @@ function venueCount(row: StudyTour) {
return (row.venue_ids || []).length
}
function statNum(n: unknown): string {
const v = Number(n)
return Number.isFinite(v) ? String(v) : '0'
}
function formatStudyTourStatsCell(record: StudyTour) {
return '浏览 ' + statNum(record.view_count)
}
onMounted(async () => {
await loadMe().catch(() => undefined)
await loadDicts().catch(() => undefined)
@ -922,6 +932,9 @@ onMounted(async () => {
<a-table-column title="场馆数" :width="72">
<template #cell="{ record }">{{ venueCount(record) }}</template>
</a-table-column>
<a-table-column title="浏览量" :width="88">
<template #cell="{ record }">{{ formatStudyTourStatsCell(record) }}</template>
</a-table-column>
<a-table-column title="排序" data-index="sort" :width="64" />
<a-table-column title="上架" :width="80">
<template #cell="{ record }">

Loading…
Cancel
Save