From 0168214e0df68de95cf2bd71772dd58b39d3d82c Mon Sep 17 00:00:00 2001 From: weizong song Date: Fri, 19 Jun 2026 15:41:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=8A=A5=E5=90=8D=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7=E6=B8=A0=E9=81=93=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/admin/types.ts | 3 ++ src/styles/login-page-overrides.css | 21 ++++++++++++++ src/views/ApplyFormView.vue | 29 +++++++++++++++++++ src/views/LoginView.vue | 4 +++ .../admin/competition/CompetitionFormView.vue | 11 +++++++ 5 files changed, 68 insertions(+) diff --git a/src/api/admin/types.ts b/src/api/admin/types.ts index 8789a2a..f0e18c8 100644 --- a/src/api/admin/types.ts +++ b/src/api/admin/types.ts @@ -119,6 +119,9 @@ export interface AdminApplicationRow { submitted_review_count: number team_sum: number | null team_avg: number | null + public_source_channel: { id: number; source_code: string; source_name: string } | null + public_source_code: string | null + public_source_attributed_at: string | null } export interface AdminApplicationFileRow { diff --git a/src/styles/login-page-overrides.css b/src/styles/login-page-overrides.css index 281d319..22a4a43 100644 --- a/src/styles/login-page-overrides.css +++ b/src/styles/login-page-overrides.css @@ -79,3 +79,24 @@ body.login-page-wls .login-page-wls__input.login-page-wls__input--error:focus { body.login-page-wls .login-page-wls__slogan.login-page-wls__slogan--error { color: #052d62; } + +body.login-page-wls .login-page-wls__contact { + display: grid; + gap: 0.45rem; + margin-top: 0.95rem; + padding-top: 0.9rem; + border-top: 1px solid rgba(5, 45, 98, 0.14); + color: #314158; + font-size: 0.92rem; + line-height: 1.35; +} + +body.login-page-wls .login-page-wls__contact a { + color: inherit; + text-decoration: none; +} + +body.login-page-wls .login-page-wls__contact a:hover { + color: #052d62; + text-decoration: underline; +} diff --git a/src/views/ApplyFormView.vue b/src/views/ApplyFormView.vue index 6e3aa5e..bd7352f 100644 --- a/src/views/ApplyFormView.vue +++ b/src/views/ApplyFormView.vue @@ -1367,6 +1367,10 @@ onMounted(() => { {{ applicationStatusBody }} +
+ 参赛咨询:17665307139 + 技术咨询:18626298655 +
{ color: #42664f; } +.apply-contact-panel { + display: flex; + flex-wrap: wrap; + gap: 0.45rem 1rem; + align-items: center; + margin-bottom: 1rem; + padding: 0.72rem 0.9rem; + border: 1px solid #e4e9ef; + border-radius: 8px; + background: #fbfcfe; + color: #425466; + font-size: 0.9rem; + line-height: 1.4; +} + +.apply-contact-panel a { + color: inherit; + text-decoration: none; +} + +.apply-contact-panel a:hover { + color: #0d6efd; + text-decoration: underline; +} + .track-selected-note { display: block; margin-top: 0.35rem; diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index 20c5f84..b8086ae 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -464,6 +464,10 @@ onUnmounted(() => { +

{{ apiHint }}

diff --git a/src/views/admin/competition/CompetitionFormView.vue b/src/views/admin/competition/CompetitionFormView.vue index 2e37351..c95cd3c 100644 --- a/src/views/admin/competition/CompetitionFormView.vue +++ b/src/views/admin/competition/CompetitionFormView.vue @@ -1359,6 +1359,17 @@ onMounted(() => { + + + From f9e367b0c6282cbeace47886af49d877f0e85757 Mon Sep 17 00:00:00 2001 From: weizong song Date: Fri, 19 Jun 2026 16:19:11 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E7=89=A9=E7=9A=84=E7=9B=AE=E7=9A=84=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E6=8A=BD=E7=A6=BB=E5=88=B0=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 8 ++++- .gitignore | 2 ++ vite.config.ts | 87 +++++++++++++++++++++++++++++--------------------- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/.env.example b/.env.example index c49561a..d754c1e 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,10 @@ -# 复制为 .env 或 .env.local 后生效(须以 VITE_ 开头才会注入构建) +# 复制为 .env.local 后生效;.env.local 已加入 .gitignore,适合放本机个性化配置。 +# 注意:只有 VITE_ 开头的变量会注入浏览器;FRONTEND_BUILD_OUT_DIR 只供 vite.config.ts 读取。 +# +# 管理端生产构建输出目录;可写相对 frontend/ 的路径或绝对路径。 +# 不设置时默认输出到 frontend/dist/admin,避免误写到某个固定 backend 仓库。 +# 例:FRONTEND_BUILD_OUT_DIR=../backend/public/admin +# FRONTEND_BUILD_OUT_DIR= # # 留空则开发时走当前站点同源 + Vite 代理 /api(推荐) # VITE_API_BASE= diff --git a/.gitignore b/.gitignore index a547bf3..5cbc626 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ lerna-debug.log* node_modules dist dist-ssr +.env.local +.env.*.local *.local # Editor directories and files diff --git a/vite.config.ts b/vite.config.ts index 90eb773..9c724fa 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,13 +2,23 @@ import history from 'connect-history-api-fallback' import path from 'node:path' import { fileURLToPath } from 'node:url' -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' import { viteStaticCopy } from 'vite-plugin-static-copy' import type { Plugin } from 'vite' const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const laravelPublicAdmin = path.resolve(__dirname, '../xxfcyds2026-backend/public/admin') + +function resolveBuildOutDir(rawPath: string | undefined): string { + const configured = rawPath?.trim() + if (!configured) { + return path.resolve(__dirname, 'dist/admin') + } + + return path.isAbsolute(configured) + ? configured + : path.resolve(__dirname, configured) +} function spaHistoryFallback(): Plugin { const middleware = history({ index: '/index.html' }) @@ -28,42 +38,47 @@ function spaHistoryFallback(): Plugin { } // https://vite.dev/config/ -export default defineConfig(({ mode }) => ({ - base: mode === 'production' ? '/admin/' : '/', - build: { - outDir: laravelPublicAdmin, - emptyOutDir: true, - }, - plugins: [ - vue(), - spaHistoryFallback(), - /** 勿在 dev 对 /frontend/prototype 挂 sirv:会与 import prototype.css 冲突(返回 text/css 导致动态 import .vue 失败) */ - viteStaticCopy({ - targets: [ - { - src: 'frontend/prototype/**/*', - dest: 'frontend/prototype', - rename: { stripBase: 2 }, +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, __dirname, '') + const adminOutDir = resolveBuildOutDir(env.FRONTEND_BUILD_OUT_DIR) + + return { + base: mode === 'production' ? '/admin/' : '/', + build: { + outDir: adminOutDir, + emptyOutDir: true, + }, + plugins: [ + vue(), + spaHistoryFallback(), + /** 勿在 dev 对 /frontend/prototype 挂 sirv:会与 import prototype.css 冲突(返回 text/css 导致动态 import .vue 失败) */ + viteStaticCopy({ + targets: [ + { + src: 'frontend/prototype/**/*', + dest: 'frontend/prototype', + rename: { stripBase: 2 }, + }, + ], + }), + ], + server: { + open: '/', + proxy: { + '/api': { + // cxxfds-service;与本机 php artisan serve 端口一致(8000 常被其它 Laravel 占用时改用 8001) + target: 'http://127.0.0.1:8002', + changeOrigin: true, }, - ], - }), - ], - server: { - open: '/', - proxy: { - '/api': { - // cxxfds-service;与本机 php artisan serve 端口一致(8000 常被其它 Laravel 占用时改用 8001) - target: 'http://127.0.0.1:8002', - changeOrigin: true, }, }, - }, - preview: { - proxy: { - '/api': { - target: 'http://127.0.0.1:8002', - changeOrigin: true, + preview: { + proxy: { + '/api': { + target: 'http://127.0.0.1:8002', + changeOrigin: true, + }, }, }, - }, -})) + } +}) From e1e09a671f6c67167fd4291ace80f50786e1bd25 Mon Sep 17 00:00:00 2001 From: weizong song Date: Fri, 19 Jun 2026 16:49:54 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=8A=A5=E5=90=8D=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7=E6=B8=A0=E9=81=93=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 2 +- src/api/admin/types.ts | 1 + src/main.ts | 2 + .../admin/competition/CompetitionFormView.vue | 50 +++++++++++++++++-- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/App.vue b/src/App.vue index a78fb6f..14949a0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,4 +4,4 @@ import { RouterView } from 'vue-router' + \ No newline at end of file diff --git a/src/api/admin/types.ts b/src/api/admin/types.ts index a969789..2406dd9 100644 --- a/src/api/admin/types.ts +++ b/src/api/admin/types.ts @@ -102,6 +102,7 @@ export interface AdminApplicationListParams { track?: string keyword?: string signup_channel_id?: number + public_source_channel_id?: number } export type AdminApplicationExportMode = 'xlsx' | 'selected' | 'all' diff --git a/src/main.ts b/src/main.ts index 6db4961..03df983 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,4 +18,6 @@ router.afterEach(() => { void capturePublicSourceFromLocation() }) +document.documentElement.dataset.adminBuildMarker = '2026-06-19-frontend-rebuild' + createApp(App).use(pinia).use(router).use(ElementPlus, { locale: zhCn }).mount('#app') diff --git a/src/views/admin/competition/CompetitionFormView.vue b/src/views/admin/competition/CompetitionFormView.vue index 09dbefd..070a9b4 100644 --- a/src/views/admin/competition/CompetitionFormView.vue +++ b/src/views/admin/competition/CompetitionFormView.vue @@ -20,6 +20,7 @@ import { listSignupChannels, updateSignupChannel, } from '../../../api/admin/signupChannels' +import { listPublicSourceChannels } from '../../../api/admin/publicSourceChannels' import { downloadAdminApplicationFile, exportAdminApplications, @@ -37,6 +38,7 @@ import type { CompetitionTrackPayload, CompetitionTrackRow, FormSchemaRow, + PublicSourceChannelRow, SignupChannelPayload, SignupChannelRow, } from '../../../api/admin/types' @@ -99,6 +101,8 @@ const tracks = ref([]) const tracksLoading = ref(false) const channels = ref([]) const channelsLoading = ref(false) +const publicSourceChannels = ref([]) +const publicSourceChannelsLoading = ref(false) const applications = ref([]) const selectedApplications = ref([]) const applicationsLoading = ref(false) @@ -117,6 +121,7 @@ const applicationFilters = ref({ status: 'submitted', track: '', signup_channel_id: undefined as number | undefined, + public_source_channel_id: undefined as number | undefined, }) const trackDialogVisible = ref(false) @@ -289,6 +294,7 @@ watch(tabKey, (t) => { if (t === 'channels') void refreshChannels() if (t === 'applications') { void refreshChannels() + void refreshPublicSourceChannels() void refreshApplications() } if (t === 'signupForm') void refreshSignupSchemas() @@ -637,6 +643,18 @@ async function refreshChannels() { } } +async function refreshPublicSourceChannels() { + publicSourceChannelsLoading.value = true + try { + publicSourceChannels.value = await listPublicSourceChannels() + } catch (e) { + publicSourceChannels.value = [] + ElMessage.error(e instanceof Error ? e.message : '加载用户渠道列表失败') + } finally { + publicSourceChannelsLoading.value = false + } +} + function applicationStatusLabel(status: string): string { if (status === 'submitted') return '已提交' if (status === 'draft') return '草稿' @@ -714,6 +732,7 @@ async function refreshApplications(page = applicationPager.value.page) { status: applicationFilters.value.status || undefined, track: applicationFilters.value.track || undefined, signup_channel_id: applicationFilters.value.signup_channel_id || undefined, + public_source_channel_id: applicationFilters.value.public_source_channel_id || undefined, }) applications.value = res.data applicationPager.value = { @@ -730,7 +749,13 @@ async function refreshApplications(page = applicationPager.value.page) { } function resetApplicationFilters() { - applicationFilters.value = { keyword: '', status: 'submitted', track: '', signup_channel_id: undefined } + applicationFilters.value = { + keyword: '', + status: 'submitted', + track: '', + signup_channel_id: undefined, + public_source_channel_id: undefined, + } void refreshApplications(1) } @@ -752,6 +777,7 @@ async function exportApplications(mode: AdminApplicationExportMode) { status: applicationFilters.value.status || undefined, track: applicationFilters.value.track || undefined, signup_channel_id: applicationFilters.value.signup_channel_id || undefined, + public_source_channel_id: applicationFilters.value.public_source_channel_id || undefined, application_ids: mode === 'selected' ? selectedApplications.value.map((row) => row.id) : undefined, }, @@ -1430,7 +1456,7 @@ onMounted(() => { { :value="channel.id" /> + + +
@@ -1499,7 +1541,7 @@ onMounted(() => { - +