diff --git a/src/App.vue b/src/App.vue index 163a9af..01d21eb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,10 @@ diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 62acd5b..b6cb3ab 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -56,6 +56,7 @@ declare global { const getToken: typeof import('./utils/storage')['getToken'] const getUserInfo: typeof import('./utils/storage')['getUserInfo'] const getWebPathForNotification: typeof import('./composables/uni-router')['getWebPathForNotification'] + const getWebPathsForNotification: typeof import('./composables/uni-router')['getWebPathsForNotification'] const h: typeof import('vue')['h'] const handlePosterRenderMergeDone: typeof import('./utils/posterRenderMergeBridge')['handlePosterRenderMergeDone'] const handlePosterRenderMergeFailed: typeof import('./utils/posterRenderMergeBridge')['handlePosterRenderMergeFailed'] @@ -250,6 +251,7 @@ declare global { const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] const useGamepad: typeof import('@vueuse/core')['useGamepad'] const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] + const useGlobalNotification: typeof import('./composables/useGlobalNotification.js')['useGlobalNotification'] const useHotUpdate: typeof import('./composables/useHotUpdate')['useHotUpdate'] const useHttp: typeof import('./composables/useHttp.js')['useHttp'] const useId: typeof import('vue')['useId'] @@ -449,6 +451,7 @@ declare module 'vue' { readonly getToken: UnwrapRef readonly getUserInfo: UnwrapRef readonly getWebPathForNotification: UnwrapRef + readonly getWebPathsForNotification: UnwrapRef readonly h: UnwrapRef readonly handlePosterRenderMergeDone: UnwrapRef readonly handlePosterRenderMergeFailed: UnwrapRef @@ -640,6 +643,7 @@ declare module 'vue' { readonly useFullscreen: UnwrapRef readonly useGamepad: UnwrapRef readonly useGeolocation: UnwrapRef + readonly useGlobalNotification: UnwrapRef readonly useHotUpdate: UnwrapRef readonly useId: UnwrapRef readonly useIdle: UnwrapRef diff --git a/src/composables/uni-router.ts b/src/composables/uni-router.ts index 2503509..7cd2bba 100644 --- a/src/composables/uni-router.ts +++ b/src/composables/uni-router.ts @@ -155,52 +155,67 @@ export function getLayoutPageTitle(): string { } /** - * 与 webview vue-router path 对齐,用于全局通知 notificationPage 匹配 + * 与 webview vue-router path 对齐,用于全局通知 notificationPage 匹配。 + * 一个 uni 页面可对应多个 web 路径(如 webview 中 /withdraw 和 /agent/withdraw 是同一页面)。 */ -const UNI_TO_WEB_NOTIFY_PATH: Record = { - 'pages/index': '/', - 'pages/agent': '/agent', - 'pages/me': '/me', - 'pages/promote': '/agent/promote', - 'pages/history-query': '/historyQuery', - 'pages/help': '/help', - 'pages/help-detail': '/help/detail', - 'pages/help-guide': '/help/guide', - 'pages/withdraw': '/withdraw', - 'pages/report-result-webview': '/app/report', - 'pages/report-example-webview': '/app/example', - 'pages/privacy-policy': '/privacyPolicy', - 'pages/user-agreement': '/userAgreement', - 'pages/agent-manage-agreement': '/agentManageAgreement', - 'pages/agent-service-agreement': '/agentSerivceAgreement', - 'pages/authorization': '/authorization', - 'pages/payment-result': '/payment/result', - 'pages/inquire': '/inquire', - 'pages/login': '/login', - 'pages/invitation': '/agent/invitation', - 'pages/agent-promote-details': '/agent/promoteDetails', - 'pages/agent-rewards-details': '/agent/rewardsDetails', - 'pages/agent-vip': '/agent/agentVip', - 'pages/agent-vip-apply': '/agent/vipApply', - 'pages/agent-vip-config': '/agent/vipConfig', - 'pages/withdraw-details': '/agent/withdrawDetails', - 'pages/subordinate-list': '/agent/subordinateList', +const UNI_TO_WEB_NOTIFY_PATHS: Record = { + 'pages/index': ['/'], + 'pages/agent': ['/agent'], + 'pages/me': ['/me'], + 'pages/promote': ['/agent/promote'], + 'pages/history-query': ['/historyQuery'], + 'pages/help': ['/help'], + 'pages/help-detail': ['/help/detail'], + 'pages/help-guide': ['/help/guide'], + 'pages/withdraw': ['/withdraw', '/agent/withdraw'], + 'pages/report-result-webview': ['/app/report', '/report'], + 'pages/report-example-webview': ['/app/example', '/example'], + 'pages/privacy-policy': ['/privacyPolicy'], + 'pages/user-agreement': ['/userAgreement'], + 'pages/agent-manage-agreement': ['/agentManageAgreement'], + 'pages/agent-service-agreement': ['/agentSerivceAgreement'], + 'pages/authorization': ['/authorization'], + 'pages/payment-result': ['/payment/result'], + 'pages/inquire': ['/inquire'], + 'pages/login': ['/login'], + 'pages/invitation': ['/agent/invitation'], + 'pages/agent-promote-details': ['/agent/promoteDetails'], + 'pages/agent-rewards-details': ['/agent/rewardsDetails'], + 'pages/agent-vip': ['/agent/agentVip'], + 'pages/agent-vip-apply': ['/agent/vipApply'], + 'pages/agent-vip-config': ['/agent/vipConfig'], + 'pages/withdraw-details': ['/agent/withdrawDetails'], + 'pages/subordinate-list': ['/agent/subordinateList'], + 'pages/cancel-account': ['/cancelAccount'], + 'pages/subordinate-detail': ['/agent/subordinateDetail'], + 'pages/invitation-agent-apply': ['/agent/invitationAgentApply'], + 'pages/report-share': ['/report/share'], } -export function getWebPathForNotification(): string { +/** + * 获取当前 uni 页面在 web 端可能的所有路径(用于通知匹配) + */ +export function getWebPathsForNotification(): string[] { const r = getCurrentUniRoute() const pages = getCurrentPages() const page = pages[pages.length - 1] as any const q: Record = { ...(page?.options || {}) } + + // 动态路由:带参数的路径 if (r === 'pages/inquire' && q.feature) - return `/inquire/${q.feature}` + return [`/inquire/${q.feature}`] if (r === 'pages/subordinate-detail' && q.id) - return `/agent/subordinateDetail/${q.id}` + return [`/agent/subordinateDetail/${q.id}`] if (r === 'pages/invitation-agent-apply' && q.linkIdentifier) - return `/agent/invitationAgentApply/${q.linkIdentifier}` + return [`/agent/invitationAgentApply/${q.linkIdentifier}`] if (r === 'pages/report-share' && q.linkIdentifier) - return `/report/share/${q.linkIdentifier}` - return UNI_TO_WEB_NOTIFY_PATH[r] || '/' + return [`/report/share/${q.linkIdentifier}`] + + return UNI_TO_WEB_NOTIFY_PATHS[r] || ['/'] +} + +export function getWebPathForNotification(): string { + return getWebPathsForNotification()[0] ?? '/' } const uniRouteToName: Record = { diff --git a/src/composables/useApiFetch.ts b/src/composables/useApiFetch.ts index e4a5aab..28544e3 100644 --- a/src/composables/useApiFetch.ts +++ b/src/composables/useApiFetch.ts @@ -94,7 +94,7 @@ function uniRequest( method: 'GET' | 'POST' | 'PUT' | 'DELETE', url: string, data?: unknown, -): Promise<{ statusCode: number, data: ApiEnvelope }> { +): Promise<{ statusCode: number, data: ApiEnvelope, header: Record }> { return new Promise((resolve, reject) => { if (!hasAcceptedPrivacyPolicy()) { reject(new Error('用户未同意隐私政策,禁止请求')) @@ -114,6 +114,7 @@ function uniRequest( resolve({ statusCode: res.statusCode || 0, data: res.data as ApiEnvelope, + header: (res.header || {}) as Record, }) }, fail: (err) => { @@ -134,10 +135,24 @@ async function executeJson( if (!silent) showLoading() try { - const { statusCode, data: body } = await uniRequest(method, url, data) + const { statusCode, data: body, header: respHeader } = await uniRequest(method, url, data) if (!silent) hideLoading() + // 检测会员过期响应头,自动刷新代理信息 + console.log('[MembershipExpired] respHeader:', JSON.stringify(respHeader)) + if (respHeader && (respHeader['X-Membership-Expired'] === 'true' || respHeader['x-membership-expired'] === 'true')) { + console.log('[MembershipExpired] 检测到过期响应头') + const agentStore = useAgentStore() + console.log('[MembershipExpired] 当前 level:', agentStore.level) + if (agentStore.level !== 'normal') { + console.log('[MembershipExpired] 触发 fetchAgentStatus') + agentStore.fetchAgentStatus() + } else { + console.log('[MembershipExpired] 已是 normal,跳过刷新') + } + } + if (statusCode === 401) { clearAuthStorage() navigateLogin() diff --git a/src/composables/useGlobalNotification.js b/src/composables/useGlobalNotification.js new file mode 100644 index 0000000..dc9db04 --- /dev/null +++ b/src/composables/useGlobalNotification.js @@ -0,0 +1,174 @@ +import { ref } from 'vue' +import { getWebPathForNotification, getWebPathsForNotification } from '@/composables/uni-router' +import useApiFetch from '@/composables/useApiFetch' + +const SESSION_KEY = 'bdrp_app_shown_notifications' + +const showPopup = ref(false) +const currentNotify = ref(null) +const notify = ref([]) +const pendingNotifyQueue = ref([]) +const shownNotificationKeys = ref(new Set()) + +function buildNotificationKey(n) { + return [ + n.title ?? '', + n.content ?? '', + n.notificationPage ?? '', + n.startDate ?? '', + n.endDate ?? '', + n.startTime ?? '', + n.endTime ?? '', + ].join('|') +} + +function loadShownNotificationKeys() { + try { + const raw = uni.getStorageSync(SESSION_KEY) + if (raw) { + const parsed = JSON.parse(raw) + if (Array.isArray(parsed)) + shownNotificationKeys.value = new Set(parsed) + } + } + catch { + shownNotificationKeys.value = new Set() + } +} + +function saveShownNotificationKeys() { + uni.setStorageSync(SESSION_KEY, JSON.stringify([...shownNotificationKeys.value])) +} + +function hasShownNotification(n) { + return shownNotificationKeys.value.has(buildNotificationKey(n)) +} + +function markNotificationShown(n) { + shownNotificationKeys.value.add(buildNotificationKey(n)) + saveShownNotificationKeys() +} + +function showNextNotification() { + const next = pendingNotifyQueue.value.shift() + if (!next) { + currentNotify.value = null + showPopup.value = false + console.log('[通知DEBUG] showNextNotification: 队列为空,关闭弹窗') + return + } + currentNotify.value = next + showPopup.value = true + markNotificationShown(next) + console.log('[通知DEBUG] showNextNotification: 展示通知, title=', next.title) +} + +function isWithinTimeRange(startTime, endTime) { + const s = (startTime || '').trim() + const e = (endTime || '').trim() + if (!s && !e) + return true + if (s === e) + return true + + const now = new Date() + const currentMinutes = now.getHours() * 60 + now.getMinutes() + const toMinutes = (t) => { + const parts = t.split(':').map(Number) + return (parts[0] ?? 0) * 60 + (parts[1] ?? 0) + } + const startMinutes = toMinutes(s) + const endMinutes = toMinutes(e) + + if (endMinutes < startMinutes) + return currentMinutes >= startMinutes || currentMinutes <= endMinutes + return currentMinutes >= startMinutes && currentMinutes <= endMinutes +} + +function matchesNotificationPage(page) { + const p = (page || '').trim() + const paths = getWebPathsForNotification() + // 当前页面的所有可能 web 路径中,有一个与配置路径匹配即可 + for (const cur of paths) { + if (p === cur) + return true + if (p === '/' && (cur === '/' || cur === '')) + return true + // 兼容:/report 与 /app/report + if (cur.startsWith('/app/') && p === cur.replace('/app/', '/')) + return true + } + return false +} + +function checkNotification() { + if (showPopup.value) { + console.log('[通知DEBUG] checkNotification: 弹窗已显示,跳过') + return + } + + const curPath = getWebPathForNotification() + console.log('[通知DEBUG] checkNotification: 当前页面路径=', curPath, '通知数量=', notify.value.length) + + const matched = [] + for (const notification of notify.value) { + const isTimeValid = isWithinTimeRange(notification.startTime, notification.endTime) + const isPageValid = matchesNotificationPage(notification.notificationPage) + const isShown = hasShownNotification(notification) + console.log('[通知DEBUG] 通知项:', JSON.stringify({ + title: notification.title, + page: notification.notificationPage, + curPath, + startTime: notification.startTime, + endTime: notification.endTime, + isTimeValid, + isPageValid, + isShown, + })) + + if (isTimeValid && isPageValid && !isShown) + matched.push(notification) + } + + console.log('[通知DEBUG] 匹配到的通知数量:', matched.length) + pendingNotifyQueue.value = matched + showNextNotification() +} + +async function getGlobalNotify() { + console.log('[通知DEBUG] getGlobalNotify 开始请求') + const { data, error } = await useApiFetch('/notification/list', { silent: true }).get().json() + console.log('[通知DEBUG] 请求完成, data:', JSON.stringify(data.value), 'error:', error.value) + if (!data.value || error.value) { + console.log('[通知DEBUG] 数据为空或出错,退出') + return + } + if (data.value.code !== 200) { + console.log('[通知DEBUG] code 不为 200,code:', data.value.code) + return + } + const raw = data.value.data + console.log('[通知DEBUG] raw data:', JSON.stringify(raw)) + const list = raw && typeof raw === 'object' && 'notifications' in raw + ? raw.notifications + : [] + notify.value = Array.isArray(list) ? list : [] + console.log('[通知DEBUG] notify list 长度:', notify.value.length, '内容:', JSON.stringify(notify.value)) + checkNotification() +} + +function onClosePopup() { + showNextNotification() +} + +export function useGlobalNotification() { + return { + showPopup, + currentNotify, + notify, + loadShownNotificationKeys, + getGlobalNotify, + checkNotification, + onClosePopup, + } +} diff --git a/src/layouts/default.vue b/src/layouts/default.vue index 6fc7828..7539b1b 100644 --- a/src/layouts/default.vue +++ b/src/layouts/default.vue @@ -4,21 +4,26 @@ import { computed, onMounted, onUnmounted, ref } from 'vue' import { getCurrentUniRoute, getLayoutPageTitle, - getWebPathForNotification, useRouter, } from '@/composables/uni-router' -import useApiFetch from '@/composables/useApiFetch' import { ensurePageAccessByUrl } from '@/composables/useNavigationAuthGuard' +import { useGlobalNotification } from '@/composables/useGlobalNotification' const router = useRouter() -const showPopup = ref(false) -const notify = ref([]) -const currentNotify = ref(null) const pageTitle = ref('赤眉') const currentRoute = ref('') const safeAreaTop = ref(0) const immersiveNavbarSolid = ref(false) +const { + showPopup, + currentNotify, + loadShownNotificationKeys, + getGlobalNotify, + checkNotification, + onClosePopup, +} = useGlobalNotification() + const immersiveRoutes = new Set([ 'pages/inquire', ]) @@ -63,55 +68,8 @@ function getSafeAreaTop() { }) } -onMounted(() => { - getSafeAreaTop() - syncTitle() - getGlobalNotify() -}) - -onShow(() => { - syncTitle() - ensureAuthIfNeeded() - checkNotification() -}) - -async function getGlobalNotify() { - const { data, error } = await useApiFetch('/notification/list', { silent: true }).get().json() - if (!data.value || error.value) - return - if (data.value.code !== 200) - return - const raw = data.value.data - const list = raw && typeof raw === 'object' && 'notifications' in raw - ? raw.notifications - : [] - notify.value = Array.isArray(list) ? list : [] - checkNotification() -} - -function isWithinTimeRange(startTime, endTime) { - const now = new Date() - const currentMinutes = now.getHours() * 60 + now.getMinutes() - const startParts = startTime.split(':').map(Number) - const endParts = endTime.split(':').map(Number) - const startMinutes = startParts[0] * 60 + startParts[1] - const endMinutes = endParts[0] * 60 + endParts[1] - if (endMinutes < startMinutes) - return currentMinutes >= startMinutes || currentMinutes < endMinutes - return currentMinutes >= startMinutes && currentMinutes <= endMinutes -} - -function checkNotification() { - const webPath = getWebPathForNotification() - showPopup.value = false - for (const notification of notify.value) { - const isTimeValid = isWithinTimeRange(notification.startTime, notification.endTime) - if (isTimeValid && notification.notificationPage === webPath) { - currentNotify.value = notification - showPopup.value = true - break - } - } +function onClickOverlay() { + onClosePopup() } function onClickLeft() { @@ -124,6 +82,21 @@ function onImmersiveNavbarChange(payload) { immersiveNavbarSolid.value = Boolean(payload.solid) } +onMounted(() => { + getSafeAreaTop() + syncTitle() + loadShownNotificationKeys() + console.log('[通知DEBUG] default layout onMounted, 即将请求通知') + getGlobalNotify() +}) + +onShow(() => { + syncTitle() + ensureAuthIfNeeded() + console.log('[通知DEBUG] default layout onShow') + checkNotification() +}) + onMounted(() => { uni.$on('immersive-navbar-change', onImmersiveNavbarChange) }) @@ -150,11 +123,14 @@ onUnmounted(() => { /> - - + + + + {{ currentNotify.title }} + - - + + 关闭 @@ -189,6 +165,7 @@ onUnmounted(() => { } .popup-content { - max-width: 90vw; + border-radius: 16px; + overflow: hidden; } diff --git a/src/layouts/home.vue b/src/layouts/home.vue index 5d4a893..3470010 100644 --- a/src/layouts/home.vue +++ b/src/layouts/home.vue @@ -5,6 +5,16 @@ import { getCurrentUniRoute } from '@/composables/uni-router' import { openCustomerService } from '@/composables/useCustomerService' import { ensurePageAccessByUrl } from '@/composables/useNavigationAuthGuard' import { getPrivacyConsentPageUrl } from '@/composables/usePrivacyConsent' +import { useGlobalNotification } from '@/composables/useGlobalNotification' + +const { + showPopup: showNotifyPopup, + currentNotify, + loadShownNotificationKeys, + getGlobalNotify, + checkNotification, + onClosePopup, +} = useGlobalNotification() const tabbar = ref('index') const safeAreaTop = ref(0) @@ -43,6 +53,11 @@ function getSafeAreaTop() { onMounted(getSafeAreaTop) onMounted(syncTabbar) +onMounted(() => { + loadShownNotificationKeys() + console.log('[通知DEBUG] home layout onMounted, 即将请求通知') + getGlobalNotify() +}) function buildCurrentPageUrl() { const pages = getCurrentPages() @@ -62,6 +77,8 @@ function ensureAuthIfNeeded() { onShow(() => { syncTabbar() ensureAuthIfNeeded() + console.log('[通知DEBUG] home layout onShow') + checkNotification() }) function tabChange(payload) { @@ -122,6 +139,20 @@ function clearCacheForPrivacyTest() { 清除缓存 --> + + + + + {{ currentNotify.title }} + + + + + 关闭 + + + + @@ -192,4 +223,9 @@ function clearCacheForPrivacyTest() { background: #ffffff; margin-bottom: 50px; } + +.notify-popup-content { + border-radius: 16px; + overflow: hidden; +} diff --git a/src/pages/agent.vue b/src/pages/agent.vue index d691ce3..b0e5558 100644 --- a/src/pages/agent.vue +++ b/src/pages/agent.vue @@ -107,10 +107,10 @@ function toSubordinateList() { 累计收益:¥ {{ (data?.total_earnings || 0).toFixed(2) }} - 待结账金额:¥ {{ (data?.frozen_balance || 0).toFixed(2) }} + 冻结余额:¥ {{ (data?.frozen_balance || 0).toFixed(2) }} - 待结账金额将在订单创建24小时后自动结账 + 冻结余额将在订单创建24小时后自动结账 @@ -140,13 +140,10 @@ function toSubordinateList() { - + @click="selectedPromoteDate = item.value"> {{ item.label }} @@ -193,13 +190,10 @@ function toSubordinateList() { - + @click="selectedTeamDate = item.value"> {{ item.label }} diff --git a/src/pages/cancel-account.vue b/src/pages/cancel-account.vue index 5dfc92b..83850af 100644 --- a/src/pages/cancel-account.vue +++ b/src/pages/cancel-account.vue @@ -206,34 +206,24 @@ async function submitCancelAccount() { - + - - + + 钱包提示 - 检测到您为代理且账户仍有余额(¥{{ (revenueData?.balance ?? 0).toFixed(2) }})或待结账金额(¥{{ (revenueData?.frozen_balance ?? 0).toFixed(2) }})。注销后将无法通过本账号提现,请确认已了解风险。 + 检测到您为代理且账户仍有余额(¥{{ (revenueData?.balance ?? 0).toFixed(2) }})或冻结余额(¥{{ (revenueData?.frozen_balance ?? + 0).toFixed(2) }})。注销后将无法通过本账号提现,请确认已了解风险。 - + 会员提示 @@ -246,22 +236,10 @@ async function submitCancelAccount() { - + 退出 - + 确认注销 @@ -269,20 +247,14 @@ async function submitCancelAccount() { - + 确认注销 - 您的代理账户仍有余额或待结账金额,注销后将无法通过本账号提现,确定继续注销? + 您的代理账户仍有余额或冻结余额,注销后将无法通过本账号提现,确定继续注销? @@ -296,15 +268,8 @@ async function submitCancelAccount() { - + @@ -322,24 +287,11 @@ async function submitCancelAccount() { 验证码 - + @@ -351,13 +303,7 @@ async function submitCancelAccount() { 取消 - + 确认注销 diff --git a/src/pages/me.vue b/src/pages/me.vue index 07d756a..92a5958 100644 --- a/src/pages/me.vue +++ b/src/pages/me.vue @@ -145,7 +145,7 @@ function getDefaultAvatar() { switch (normalizedLevel.value) { case 'NORMAL': - return '/static/images/shot_nonal.png' + return '/static/images/shot_nornal.png' case 'VIP': return '/static/images/shot_vip.png' case 'SVIP': diff --git a/src/pages/subordinate-detail.vue b/src/pages/subordinate-detail.vue index 305a96b..2a54bb2 100644 --- a/src/pages/subordinate-detail.vue +++ b/src/pages/subordinate-detail.vue @@ -243,14 +243,14 @@ function getRewardTypeClass(type) { // 获取收益类型图标 function getRewardTypeIcon(type) { const iconMap = { - descendant_promotion: 'gift', - cost: 'gold-coin', - pricing: 'balance-pay', - descendant_withdraw: 'cash-back-record', - descendant_upgrade_vip: 'fire', - descendant_upgrade_svip: 'fire', + descendant_promotion: '/static/images/tgjl.svg', + cost: '/static/images/cbgx.svg', + pricing: '/static/images/djgx.svg', + descendant_withdraw: '/static/images/txsy.svg', + descendant_upgrade_vip: '/static/images/zhvipjl.svg', + descendant_upgrade_svip: '/static/images/zhsvipjl.svg', } - return iconMap[type] || 'balance-o' + return iconMap[type] || '/static/images/tgjl.svg' } // 获取收益类型描述 @@ -340,11 +340,7 @@ function formatNumber(num) { class="flex items-center rounded-lg p-2" :class="getRewardTypeClass(item.type).split(' ')[0]" > - + {{ item.description }} @@ -374,11 +370,7 @@ function formatNumber(num) { - + {{ getRewardTypeDescription(item.type) }} diff --git a/src/static/images/cbgx.svg b/src/static/images/cbgx.svg new file mode 100644 index 0000000..0a3a2e9 --- /dev/null +++ b/src/static/images/cbgx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/images/djgx.svg b/src/static/images/djgx.svg new file mode 100644 index 0000000..e476743 --- /dev/null +++ b/src/static/images/djgx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/images/tgjl.svg b/src/static/images/tgjl.svg new file mode 100644 index 0000000..984ae84 --- /dev/null +++ b/src/static/images/tgjl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/images/txsy.svg b/src/static/images/txsy.svg new file mode 100644 index 0000000..be11d76 --- /dev/null +++ b/src/static/images/txsy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/images/zhsvipjl.svg b/src/static/images/zhsvipjl.svg new file mode 100644 index 0000000..8363f38 --- /dev/null +++ b/src/static/images/zhsvipjl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/images/zhvipjl.svg b/src/static/images/zhvipjl.svg new file mode 100644 index 0000000..cb3133a --- /dev/null +++ b/src/static/images/zhvipjl.svg @@ -0,0 +1 @@ + \ No newline at end of file