diff --git a/app/resources/cardsoon.config.json b/app/resources/cardsoon.config.json index c2b0c14..79b732d 100644 --- a/app/resources/cardsoon.config.json +++ b/app/resources/cardsoon.config.json @@ -1,3 +1,6 @@ { - "designAppPath": "D:\\SoonProject\\SoonDesign\\build\\win-unpacked\\SoonDesign.exe" + "designAppPath": "D:\\SoonProject\\SoonDesign\\build\\win-unpacked\\SoonDesign.exe", + "sharedDir": "C:\\PrintTasks", + "logLevel": 3, + "cleanTaskFile": 0 } diff --git a/app/src/main/index.ts b/app/src/main/index.ts index 5ced19e..ebc80af 100644 --- a/app/src/main/index.ts +++ b/app/src/main/index.ts @@ -15,7 +15,7 @@ if (!gotSingleInstanceLock) { } import { suppressKnownDllStderr } from './utils/suppress-dll-stderr' -import { loadAppFileConfig } from './services/app-config' +import { loadAppFileConfig, applyFileConfigToStore } from './services/app-config' import { migrateTraceConfig, setTraceWebContents } from './utils/trace-bridge' suppressKnownDllStderr() @@ -133,6 +133,7 @@ app.whenReady().then(async () => { try { migrateTraceConfig() loadAppFileConfig() + applyFileConfigToStore() log.info('app startup', { packaged: app.isPackaged, execPath: process.execPath }) registerIpcHandlers() try { diff --git a/app/src/main/services/app-config.ts b/app/src/main/services/app-config.ts index 2f654c6..8bdec4e 100644 --- a/app/src/main/services/app-config.ts +++ b/app/src/main/services/app-config.ts @@ -2,20 +2,59 @@ import { app } from 'electron' import fs from 'fs' import path from 'path' import log from 'electron-log' +import { LOG_FATAL_FLAG } from '../constants' +import { configStore } from './config-store' import { getProcessExecDir } from './native-path' export const APP_CONFIG_FILENAME = 'cardsoon.config.json' +const DEFAULT_SHARED_DIR = path.join('C:', 'PrintTasks') + export interface AppFileConfig { designAppPath: string + sharedDir: string + logLevel: number + cleanTaskFile: boolean } const defaults: AppFileConfig = { - designAppPath: '' + designAppPath: '', + sharedDir: DEFAULT_SHARED_DIR, + logLevel: LOG_FATAL_FLAG, + cleanTaskFile: false } let cached: AppFileConfig | null = null +function pickRaw(raw: Record, ...keys: string[]): unknown { + for (const key of keys) { + const hit = Object.entries(raw).find(([name]) => name.toLowerCase() === key.toLowerCase()) + if (hit && hit[1] != null && String(hit[1]).trim() !== '') return hit[1] + } + return undefined +} + +function parseCleanTaskFile(raw: unknown): boolean { + if (raw === undefined || raw === null) return defaults.cleanTaskFile + if (typeof raw === 'boolean') return raw + if (typeof raw === 'number') return raw !== 0 + const s = String(raw).trim().toLowerCase() + if (s === '0' || s === 'false') return false + if (s === '1' || s === 'true') return true + return defaults.cleanTaskFile +} + +function parseLogLevel(raw: unknown): number { + if (raw === undefined || raw === null) return defaults.logLevel + const n = Number(raw) + return Number.isFinite(n) ? Math.round(n) : defaults.logLevel +} + +function parseSharedDir(raw: unknown): string { + const s = String(raw ?? '').trim() + return s || DEFAULT_SHARED_DIR +} + function bundledConfigPath(): string { if (app.isPackaged) { return path.join(process.resourcesPath, APP_CONFIG_FILENAME) @@ -33,7 +72,10 @@ function configSearchPaths(): string[] { function parseConfigFile(filePath: string): AppFileConfig { const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Record return { - designAppPath: String(raw.designAppPath ?? '').trim() + designAppPath: String(pickRaw(raw, 'designAppPath') ?? '').trim(), + sharedDir: parseSharedDir(pickRaw(raw, 'sharedDir', 'shareddir')), + logLevel: parseLogLevel(pickRaw(raw, 'logLevel', 'loglevel')), + cleanTaskFile: parseCleanTaskFile(pickRaw(raw, 'cleanTaskFile', 'cleantaskfile')) } } @@ -44,7 +86,11 @@ export function loadAppFileConfig(): AppFileConfig { if (!fs.existsSync(filePath)) continue try { cached = parseConfigFile(filePath) - log.info(`Loaded ${APP_CONFIG_FILENAME} from ${filePath}`) + log.info(`Loaded ${APP_CONFIG_FILENAME} from ${filePath}`, { + sharedDir: cached.sharedDir, + logLevel: cached.logLevel, + cleanTaskFile: cached.cleanTaskFile + }) return cached } catch (e) { log.warn(`Skip invalid ${APP_CONFIG_FILENAME}: ${filePath}`, e) @@ -58,6 +104,11 @@ export function loadAppFileConfig(): AppFileConfig { return cached } +export function applyFileConfigToStore(): void { + const cfg = loadAppFileConfig() + configStore.set('sharedDir', cfg.sharedDir) +} + export function getDesignAppPath(): string { return loadAppFileConfig().designAppPath } diff --git a/app/src/main/services/dll-bootstrap.ts b/app/src/main/services/dll-bootstrap.ts index 9541093..707d1e4 100644 --- a/app/src/main/services/dll-bootstrap.ts +++ b/app/src/main/services/dll-bootstrap.ts @@ -3,6 +3,7 @@ import log from 'electron-log' import { CS_OK } from '../constants' import { mainAppState } from './app-state' import { configStore } from './config-store' +import { loadAppFileConfig } from './app-config' import { loadDllModule } from './dll-loader' import type { InitParams } from './work-dll.service' @@ -18,8 +19,9 @@ export async function ensureDllInitialized( if (dllInitAttempted) { return { code: CS_OK } } + const fileCfg = loadAppFileConfig() const sharedDir = - params?.sharedDir || (configStore.get('sharedDir') as string) || 'C:\\PrintTasks' + params?.sharedDir?.trim() || fileCfg.sharedDir || (configStore.get('sharedDir') as string) try { const dll = await loadDllModule() fs.mkdirSync(sharedDir, { recursive: true }) @@ -27,10 +29,10 @@ export async function ensureDllInitialized( sharedDir, keepCombinedImage: params?.keepCombinedImage, stopOnFailure: params?.stopOnFailure, - cleanTaskFile: params?.cleanTaskFile, + cleanTaskFile: params?.cleanTaskFile ?? fileCfg.cleanTaskFile, autoRetryTimes: params?.autoRetryTimes, rejectConfig: params?.rejectConfig, - logLevel: params?.logLevel, + logLevel: params?.logLevel ?? fileCfg.logLevel, outBack: params?.outBack }) dllInitAttempted = true diff --git a/app/src/main/services/work-dll.service.ts b/app/src/main/services/work-dll.service.ts index 023d745..d242484 100644 --- a/app/src/main/services/work-dll.service.ts +++ b/app/src/main/services/work-dll.service.ts @@ -239,7 +239,7 @@ export function dllInit(params: InitParams): number { sharedDir: params.sharedDir, keepCombinedImage: params.keepCombinedImage ?? true, stopOnFailure: params.stopOnFailure ?? false, - cleanTaskFile: params.cleanTaskFile ?? true, + cleanTaskFile: params.cleanTaskFile ?? false, autoRetryTimes: params.autoRetryTimes ?? 0, rejectConfig: params.rejectConfig ?? false, logLevel: params.logLevel ?? LOG_FATAL_FLAG, @@ -251,7 +251,7 @@ export function dllInit(params: InitParams): number { params.sharedDir, params.keepCombinedImage ?? true, params.stopOnFailure ?? false, - params.cleanTaskFile ?? true, + params.cleanTaskFile ?? false, params.autoRetryTimes ?? 0, params.rejectConfig ?? false, params.logLevel ?? LOG_FATAL_FLAG, diff --git a/app/src/main/utils/parse-soon.ts b/app/src/main/utils/parse-soon.ts index 07d0c0b..721876a 100644 --- a/app/src/main/utils/parse-soon.ts +++ b/app/src/main/utils/parse-soon.ts @@ -5,6 +5,8 @@ export interface TemplateFieldRow { label: string value: string originName: string + /** soon frontData/backData 的 type:1=图片,3/4/5=文本可编辑 */ + fieldType: number } export interface ParsedSoonTemplate { @@ -80,7 +82,7 @@ function parseSoonWorkerDisk(soonPath: string, raw: Record): Pa const name = String(o.name ?? '').trim() if (!name) continue const value = o.DefaultText == null ? '' : String(o.DefaultText) - fields.push({ label: `${name}[${side}]`, value, originName: name }) + fields.push({ label: `${name}[${side}]`, value, originName: name, fieldType: type }) } } @@ -115,7 +117,7 @@ function parseSoonLegacy(soonPath: string, raw: Record): Parsed if (side === 'front') { if (!frontImageUrl) frontImageUrl = url const name = pickStr(item, ['name', 'field', 'key']) || 'IMAGE' - fields.push({ label: toFieldLabel(name, 'front'), value: fileRef, originName: name }) + fields.push({ label: toFieldLabel(name, 'front'), value: fileRef, originName: name, fieldType: 1 }) } else if (!backImageUrl) { backImageUrl = url } @@ -127,7 +129,7 @@ function parseSoonLegacy(soonPath: string, raw: Record): Parsed const value = pickStr(item, ['value', 'text', 'default', 'content', 'data']) let side = sideOf(item) if (!side) side = /image|img|front/i.test(name) ? 'front' : 'back' - fields.push({ label: toFieldLabel(name, side), value, originName: name }) + fields.push({ label: toFieldLabel(name, side), value, originName: name, fieldType: 5 }) }) return { frontImageUrl, backImageUrl, fields, printFlag: readSoonPrintFlag(raw) } diff --git a/app/src/renderer/src/api/cardsoon.ts b/app/src/renderer/src/api/cardsoon.ts index d945c6b..add713b 100644 --- a/app/src/renderer/src/api/cardsoon.ts +++ b/app/src/renderer/src/api/cardsoon.ts @@ -103,6 +103,12 @@ export async function dialogOpenSoon(): Promise> { > } +export async function dialogOpenImage(): Promise> { + return api().invoke('dialog:open-file', [ + { name: 'Image', extensions: ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'webp'] } + ]) as Promise> +} + export async function fsPathExists(paths: string[]): Promise> { return api().invoke('fs:path-exists', paths) as Promise> } @@ -126,7 +132,7 @@ export async function fsParseSoon(filePath: string): Promise< IpcResult<{ frontImageUrl: string backImageUrl: string - fields: { label: string; value: string; originName: string }[] + fields: { label: string; value: string; originName: string; fieldType: number }[] printFlag: number }> > { @@ -134,7 +140,7 @@ export async function fsParseSoon(filePath: string): Promise< IpcResult<{ frontImageUrl: string backImageUrl: string - fields: { label: string; value: string; originName: string }[] + fields: { label: string; value: string; originName: string; fieldType: number }[] printFlag: number }> > diff --git a/app/src/renderer/src/stores/distributeForm.ts b/app/src/renderer/src/stores/distributeForm.ts index c799413..4243c87 100644 --- a/app/src/renderer/src/stores/distributeForm.ts +++ b/app/src/renderer/src/stores/distributeForm.ts @@ -4,6 +4,7 @@ export interface TemplateFieldRow { label: string value: string originName: string + fieldType: number } export interface TemplatePreview { diff --git a/app/src/renderer/src/styles/pages/page4.css b/app/src/renderer/src/styles/pages/page4.css index 6c26733..da3b3c0 100644 --- a/app/src/renderer/src/styles/pages/page4.css +++ b/app/src/renderer/src/styles/pages/page4.css @@ -566,6 +566,43 @@ min-width: 0; } +.c-data-table-mini .c-field-input { + width: 100%; + height: 16px; + border: none; + background: transparent; + font-size: inherit; + font-weight: inherit; + color: inherit; + outline: none; + min-width: 0; + padding: 0; +} + +.c-field-pick { + flex-shrink: 0; + border: 1px solid #dcdfe6; + border-radius: 3px; + background: #fff; + font-size: 8px; + font-weight: 700; + color: #495057; + padding: 0 6px; + height: 18px; + cursor: pointer; +} + +.c-field-pick:hover { + background: #f1f3f5; +} + +.c-field-value { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .c-path-cell__text { flex: 1; min-width: 0; diff --git a/app/src/renderer/src/views/DistributeConfigView.vue b/app/src/renderer/src/views/DistributeConfigView.vue index 691fa82..efb525f 100644 --- a/app/src/renderer/src/views/DistributeConfigView.vue +++ b/app/src/renderer/src/views/DistributeConfigView.vue @@ -117,7 +117,21 @@ - + + @@ -148,8 +162,10 @@ import { useAppStore } from '@/stores/app' import { validateJobPreflight } from '@/utils/validateJobPreflight' import { createDistributeJob } from '@/utils/createDistributeJob' import { formatBytesAsGb, formatBytesCompact } from '@/utils/formatBytes' +import type { TemplateFieldRow } from '@/stores/distributeForm' import { dialogOpenDirectory, + dialogOpenImage, dialogOpenSoon, dllJobCancel, fsDirSize, @@ -185,6 +201,36 @@ const loadProgressText = computed(() => { return `已加载: ${loadedGb} GB / ${CARD_CAPACITY_GB} GB (${loadPercent.value}%)` }) +function isTextField(row: TemplateFieldRow): boolean { + return row.fieldType === 3 || row.fieldType === 4 || row.fieldType === 5 +} + +function isImageField(row: TemplateFieldRow): boolean { + return row.fieldType === 1 +} + +function imageFieldLabel(value: string): string { + const v = value.trim() + if (!v) return '未选择图片' + const parts = v.replace(/\\/g, '/').split('/') + return parts[parts.length - 1] || v +} + +async function pickFieldImage(idx: number): Promise { + const preview = formStore.templatePreview + if (!preview) return + const row = preview.fields[idx] + if (!row || !isImageField(row)) return + const r = await dialogOpenImage() + if (!r.ok) { + notify.error(r.message || '打开图片选择失败') + return + } + const picked = r.data?.path?.trim() + if (!picked) return + row.value = picked +} + function onClear(): void { formStore.reset() notify.info('已清空,已恢复初始状态') @@ -341,11 +387,4 @@ async function onSubmit(): Promise { .c-card-small--back { background: #f8f9fa; } - -.c-field-value { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -}
{{ row.label }} + + {{ + imageFieldLabel(row.value) + }} + + + + {{ row.value || '—' }}