feat(web): 品牌检测/图片生成服务端化——URL 统一 https+域名,入口文件带 hash 防长缓存失效

- brand/image 本地执行 API 服务端化(主机A shufu-web-api:15126,nginx 转发 /api/brand/* 等)
- 前端撤销本地回连逻辑(local-http 删除,http.ts/brand.ts 还原为域名相对路径)
- 版本更新改桌面桥 do_update_app(原 Flask /api/update/do 桥化,Web 无更新入口)
- 品牌检测页 Web 可用(hasBridge 恢复 blanket 判定:文件选择走浏览器降级、任务/SSE 走域名)
- vite 入口文件带内容 hash(/assets 配了 immutable 长缓存,无 hash 入口导致更新永不生效)
- public/logo.jpg:Web 版 /logo.jpg 素材(原 app_client/logo.jpg 随桌面瘦身移除)
This commit is contained in:
2026-09-07 17:11:33 +08:00
parent 30b8c910e8
commit c086d402a7
8 changed files with 1576 additions and 26 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -148,8 +148,9 @@ const runMode = ref<'immediate' | 'queue'>('immediate')
const submitting = ref(false)
const tasks = ref<BrandTaskItem[]>([])
// 品牌检测由桌面客户端本地执行:以"文件选择"桥(Web 降级桥不提供)判定桌面运行时,浏览器下仅可预览
const hasBridge = computed(() => Boolean(getPywebviewApi()?.select_brand_folder))
// 品牌检测已服务端化(/api/brand/* 走域名):浏览器降级桥提供文件选择Web 端可用;
// 文件夹选择/模板桥等降级桥未实现的方法在点击时提示"仅桌面端"
const hasBridge = computed(() => Boolean(getPywebviewApi()))
const hasUid = computed(() => {
const raw = typeof window === 'undefined' ? '' : window.localStorage.getItem('uid') || ''
const numeric = Number(raw.trim())
+18 -16
View File
@@ -76,10 +76,10 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { requestGetJson } from '@/shared/api/http'
import { buildJavaUrl } from '@/shared/api/url'
import { isDesktopRuntime } from '@/shared/bridges/pywebview'
import { getPywebviewApi, isDesktopRuntime } from '@/shared/bridges/pywebview'
import { resolvePageHref } from '@/shared/page-prefix'
const username = ref('')
@@ -90,10 +90,8 @@ const updateReady = ref(false)
const updateFileUrl = ref('')
const toastText = ref('')
// 桌面端 Flask /logout(清 cookie 302 登录页);Web 独立部署 / dev 跳登录页并清本地 tokenlogin.html?logout=1
const logoutHref = computed(() =>
isDesktopRuntime.value ? '/logout' : `${resolvePageHref('/new_web_source/login.html')}?logout=1`,
)
// 桌面端 Flask /logout 已随瘦身下线:统一跳登录页并清本地 tokenlogin.html?logout=1
const logoutHref = computed(() => `${resolvePageHref('/new_web_source/login.html')}?logout=1`)
type PermissionState = { amazon: boolean; video: boolean; image: boolean }
const permissionState = ref<PermissionState>({ amazon: false, video: false, image: false })
@@ -252,14 +250,12 @@ async function doUpdate() {
updateHint.value = '正在下载并准备更新,程序将自动退出...'
updateReady.value = false
try {
const resp = await window.fetch('/api/update/do', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_url: updateFileUrl.value }),
})
const result = await resp.json().catch(() => ({}))
updateHint.value = result.success ? '更新已启动,程序即将退出...' : (result.error || '更新启动失败')
// 版本更新是桌面客户端专属动作:经浏览器桥调用本地 update 下载/启动(服务端化后无 /api/update/do
const bridge = getPywebviewApi()
const result = bridge?.do_update_app ? await bridge.do_update_app(updateFileUrl.value) : undefined
updateHint.value = result?.success
? '更新已启动,程序即将退出...'
: (result?.error || '当前环境不支持自动更新')
} catch {
updateHint.value = '请求更新失败,请重试'
}
@@ -268,10 +264,16 @@ async function doUpdate() {
onMounted(() => {
void loadCurrentUser()
void loadPermissions()
// 版本检测/更新桌面客户端 Flask 提供,Web 版不发起
if (isDesktopRuntime.value) {
// 版本检测/更新桌面客户端(本地 Flask image 蓝图)提供;桌面桥注入后立即拉取
watch(
isDesktopRuntime,
(runtime) => {
if (runtime) {
void fetchVersion().catch(() => undefined)
}
},
{ immediate: true },
)
})
</script>
File diff suppressed because it is too large Load Diff
+11
View File
@@ -60,6 +60,17 @@ function createHttpClient(): AxiosInstance {
})
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
// 携带本地登录令牌:桌面端同源 cookie 仍可用;dev(5173) 无 cookie 时靠 Bearer 过 Java 鉴权
try {
if (config.url && config.url.indexOf('/newApi') === 0 && typeof window !== 'undefined') {
const token = window.localStorage.getItem('aiimage_auth_token') || ''
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
} catch {
/* 忽略读取令牌异常 */
}
return config
})
+5 -4
View File
@@ -1,6 +1,5 @@
import { ref } from 'vue';
import { webFallbackApi } from './web-fallback';
import { isDesktopRuntime } from '@/shared/runtime-env';
export interface UploadedJavaFile {
fileKey: string;
@@ -122,6 +121,8 @@ export interface PywebviewApi {
not_found?: boolean;
error?: string;
}>;
/** 版本更新(桌面专属):下载更新包并启动 update.exe 后退出主程序(原 Flask /api/update/do 桥化) */
do_update_app?: (fileUrl: string) => Promise<{ success: boolean; error?: string }>;
launch_yaoayanui?: () => Promise<{
success: boolean;
path?: string;
@@ -197,8 +198,8 @@ declare global {
let cachedPywebviewApi: PywebviewApi | undefined;
let pywebviewReadyBound = false;
/** 当前是否运行在桌面客户端(pywebview)运行时;Web 独立部署下恒为 false */
export const isDesktopRuntime = ref(false);
/** 当前是否运行在桌面客户端(pywebview)运行时;Web 独立部署下恒为 false(见 runtime-env.ts 注释) */
export { isDesktopRuntime };
function syncPywebviewApi() {
cachedPywebviewApi = window.pywebview?.api;
+14
View File
@@ -0,0 +1,14 @@
/**
* 桌面运行时环境标志
*
* 独立模块避免共享模块之间循环依赖(pywebview.ts 写入、各页面读取)。
* 桌面客户端窗口加载 Web 版 URL 后 pywebview 桥注入完成即置 true
* 初始为 false,页面在 pywebviewready 之后的交互/挂载阶段读取时已为 true。
*
* 注意:前端 URL 已全部统一走 https+域名(品牌检测/图片生成已服务端化),
* 本地不再有任何 Flask API 回连,无需 LOCAL_API_BASE。
*/
import { ref } from 'vue'
/** 桌面客户端(pywebview 桥已注入)运行时标志;Web 独立部署下恒为 false */
export const isDesktopRuntime = ref(false)
+4 -2
View File
@@ -71,13 +71,15 @@ export default defineConfig({
'variant-collection': resolve(__dirname, 'variant-collection.html'),
brand: resolve(__dirname, 'brand.html'),
'image-video': resolve(__dirname, 'image-video.html'),
image: resolve(__dirname, 'image.html'),
'amazon-console': resolve(__dirname, 'amazon-console.html'),
home: resolve(__dirname, 'home.html'),
login: resolve(__dirname, 'login.html'),
},
output: {
entryFileNames: (chunkInfo) =>
chunkInfo.name === 'image-video' ? 'assets/[name]-[hash].js' : 'assets/[name].js',
// 入口文件同样带内容 hash/assets/ 静态服务配了 immutable 长缓存,
// 无 hash 的入口文件会导致前端更新对用户永不生效(旧文件名被永久缓存)
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]',
},