重构项目,方便开发

This commit is contained in:
super
2026-03-19 13:15:47 +08:00
parent fb2e5ccea5
commit 1e63ab291b
38 changed files with 5940 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
import { requestDeleteJson, requestGetJson, requestPostJson, requestPutJson } from '@/shared/api/http'
export interface AdminUserItem {
id: number
username?: string
role?: 'super_admin' | 'admin' | 'normal' | string
creator_username?: string
created_at?: string
}
export interface AdminSimpleUser {
id: number
username: string
}
export interface AdminUsersResponse {
success: boolean
current_user_role?: 'super_admin' | 'admin' | 'normal' | string
admins?: AdminSimpleUser[]
items?: AdminUserItem[]
total?: number
page?: number
page_size?: number
error?: string
}
export interface AdminMutationResponse {
success: boolean
msg?: string
error?: string
}
export interface AdminHistoryItem {
id: number
username?: string
panel_type?: string
created_at?: string
long_image_url?: string | null
result_urls?: string[]
}
export interface AdminHistoryResponse {
success: boolean
items?: AdminHistoryItem[]
total?: number
page?: number
page_size?: number
error?: string
}
export interface AdminColumnPermissionItem {
column_key?: string
}
export interface AdminColumnPermissionResponse {
success: boolean
items?: AdminColumnPermissionItem[]
error?: string
}
export function getAdminUsers(query = '') {
const suffix = query ? `?${query}` : ''
return requestGetJson<AdminUsersResponse>(`/api/admin/users${suffix}`)
}
export function createAdminUser(payload: {
username: string
password: string
role: string
created_by_id?: number
}) {
return requestPostJson<AdminMutationResponse>('/api/admin/user', payload)
}
export function updateAdminUser(
userId: number | string,
payload: {
password?: string
role?: string
},
) {
return requestPutJson<AdminMutationResponse>(`/api/admin/user/${userId}`, payload)
}
export function deleteAdminUser(userId: number | string) {
return requestDeleteJson<AdminMutationResponse>(`/api/admin/user/${userId}`)
}
export function getAdminHistory(query = '') {
const suffix = query ? `?${query}` : ''
return requestGetJson<AdminHistoryResponse>(`/api/admin/history${suffix}`)
}
export function getUserColumnPermissions(userId: string | number) {
return requestGetJson<AdminColumnPermissionResponse>(`/api/admin/user/${userId}/column-permissions`)
}

View File

@@ -0,0 +1,38 @@
import { requestGetJson, requestPostJson } from '@/shared/api/http'
export interface LoginResponse {
success: boolean
redirect?: string
error?: string
}
export interface AuthCheckResponse {
logged_in: boolean
redirect?: string
}
export async function checkAuth() {
return requestGetJson<AuthCheckResponse>('/api/auth/check')
}
export async function login(username: string, password: string) {
const formData = new FormData()
formData.set('username', username)
formData.set('password', password)
return requestPostJson<LoginResponse>('/login', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
}
export async function logout() {
try {
await requestGetJson('/logout', {
validateStatus: () => true,
})
} catch {
// logout should still continue on the client even if backend responds abnormally
}
}

View File

@@ -0,0 +1,88 @@
import { requestDeleteJson, requestGetJson, requestPostJson } from '@/shared/api/http'
export interface BrandExpandFolderResponse {
success: boolean
paths?: string[]
error?: string
}
export interface BrandTaskResultPaths {
zip_url?: string
}
export interface BrandTaskItem {
id: number | string
status?: 'pending' | 'running' | 'success' | 'failed' | 'cancelled' | string
desc?: string
file_paths?: string[]
created_at?: string
progress_total?: number
progress_current?: number
result_paths?: BrandTaskResultPaths
}
export interface BrandTaskListResponse {
success: boolean
items?: BrandTaskItem[]
error?: string
}
export interface BrandTaskDetailResponse {
success: boolean
task?: BrandTaskItem
error?: string
}
export interface BrandTaskMutationResponse {
success: boolean
task_id?: string
error?: string
}
export function getBrandTaskEventsUrl(taskId: string | number) {
return `/api/brand/tasks/${taskId}/events`
}
export function getBrandTaskDownloadUrl(taskId: string | number) {
return `/api/brand/download/${taskId}`
}
export function getBrandTemplateXlsxUrl() {
return '/static/品牌文档格式_模板.xlsx'
}
export function getBrandTemplateZipUrl() {
return '/static/模板2-以文件夹方式上传.zip'
}
export function expandBrandFolder(folder: string) {
return requestPostJson<BrandExpandFolderResponse>('/api/brand/expand-folder', { folder })
}
export function runBrandNow(paths: string[], strategy: string) {
return requestPostJson<BrandTaskMutationResponse>('/api/brand/run', { paths, strategy })
}
export function createBrandTask(paths: string[], strategy: string) {
return requestPostJson<BrandTaskMutationResponse>('/api/brand/tasks', { paths, strategy })
}
export function getBrandTasks() {
return requestGetJson<BrandTaskListResponse>('/api/brand/tasks')
}
export function getBrandTask(taskId: string | number) {
return requestGetJson<BrandTaskDetailResponse>(`/api/brand/tasks/${taskId}`)
}
export function cancelBrandTask(taskId: string | number) {
return requestPostJson<BrandTaskMutationResponse>(`/api/brand/tasks/${taskId}/cancel`)
}
export function deleteBrandTask(taskId: string | number) {
return requestDeleteJson<BrandTaskMutationResponse>(`/api/brand/tasks/${taskId}`)
}
export function createBrandTaskEvents(taskId: string | number) {
return new EventSource(getBrandTaskEventsUrl(taskId))
}

View File

@@ -0,0 +1,107 @@
import axios, {
AxiosError,
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
type InternalAxiosRequestConfig,
} from 'axios'
export interface ApiSuccess<T> {
success: true
msg?: string
data?: T
}
export interface ApiFailure {
success: false
error?: string
}
export type ApiResponse<T> = ApiSuccess<T> | ApiFailure
export type RequestOptions<D = unknown> = AxiosRequestConfig<D>
function extractErrorMessage(error: unknown) {
if (error instanceof AxiosError) {
const data = error.response?.data
if (data && typeof data === 'object' && 'error' in data) {
return String(data.error)
}
if (typeof data === 'string' && data.trim()) {
return data
}
return error.message || '请求失败'
}
return error instanceof Error ? error.message : '请求失败'
}
function createHttpClient(): AxiosInstance {
const instance = axios.create({
withCredentials: true,
timeout: 30000,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
})
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
return config
})
instance.interceptors.response.use(
(response: AxiosResponse) => response,
(error: unknown) => Promise.reject(new Error(extractErrorMessage(error))),
)
return instance
}
export const http = createHttpClient()
export async function request<T = unknown, D = unknown>(config: RequestOptions<D>): Promise<T> {
const response = await http.request<T, AxiosResponse<T>, D>(config)
return response.data
}
export function get<T = unknown>(url: string, config?: RequestOptions) {
return request<T>({
url,
method: 'GET',
...config,
})
}
export function post<T = unknown, D = unknown>(url: string, data?: D, config?: RequestOptions<D>) {
return request<T, D>({
url,
method: 'POST',
data,
...config,
})
}
export function put<T = unknown, D = unknown>(url: string, data?: D, config?: RequestOptions<D>) {
return request<T, D>({
url,
method: 'PUT',
data,
...config,
})
}
export function del<T = unknown>(url: string, config?: RequestOptions) {
return request<T>({
url,
method: 'DELETE',
...config,
})
}
export const requestJson = request
export const requestGetJson = get
export const requestPostJson = post
export const requestPutJson = put
export const requestDeleteJson = del

View File

@@ -0,0 +1,22 @@
import { requestGetJson, requestPostJson } from '@/shared/api/http'
export interface VersionResponse {
version?: string
has_update?: boolean
latest_version?: string
desc?: string
file_url?: string
}
export interface UpdateStartResponse {
success: boolean
error?: string
}
export function fetchVersion() {
return requestGetJson<VersionResponse>('/api/version')
}
export function startUpdate(fileUrl: string) {
return requestPostJson<UpdateStartResponse>('/api/update/do', { file_url: fileUrl })
}

View File

@@ -0,0 +1,24 @@
export interface PywebviewApi {
select_brand_xlsx_files?: () => Promise<string[]>
select_brand_folder?: () => Promise<string | null>
select_folder?: () => Promise<string | null>
save_template_xlsx?: () => Promise<{ success: boolean; path?: string; error?: string }>
save_template_zip?: () => Promise<{ success: boolean; path?: string; error?: string }>
save_file_from_url?: (url: string, filename: string) => Promise<{ success: boolean; path?: string; error?: string }>
}
declare global {
interface Window {
pywebview?: {
api?: PywebviewApi
}
}
}
export function getPywebviewApi() {
return window.pywebview?.api
}
export function hasPywebview() {
return Boolean(getPywebviewApi())
}

View File

@@ -0,0 +1,71 @@
import { ref } from 'vue'
import { fetchVersion, startUpdate } from '@/shared/api/update'
export function useUpdateCheck() {
const loading = ref(false)
const version = ref('1.0.0')
const hint = ref('')
const downloadVisible = ref(false)
const downloadLoading = ref(false)
const fileUrl = ref('')
const check = async () => {
loading.value = true
hint.value = '正在检测更新...'
downloadVisible.value = false
try {
const result = await fetchVersion()
version.value = (result.version || version.value).replace(/^v/i, '')
if (result.has_update && result.latest_version) {
hint.value = `发现新版本 v${result.latest_version}${result.desc ? `${result.desc}` : ''}`
fileUrl.value = result.file_url || ''
downloadVisible.value = true
return
}
hint.value = '已是最新版本'
} catch (error) {
hint.value = error instanceof Error ? error.message : '检测更新失败'
} finally {
loading.value = false
}
}
const runUpdate = async () => {
if (!fileUrl.value) {
hint.value = '暂无下载地址,请关注官方渠道。'
return false
}
downloadLoading.value = true
hint.value = '正在下载并准备更新,程序将自动退出...'
try {
const result = await startUpdate(fileUrl.value)
if (result.success) {
hint.value = '更新已启动,程序即将退出...'
return true
}
hint.value = result.error || '更新启动失败'
return false
} catch (error) {
hint.value = error instanceof Error ? error.message : '请求更新失败,请重试'
return false
} finally {
downloadLoading.value = false
}
}
return {
loading,
version,
hint,
downloadVisible,
downloadLoading,
check,
runUpdate,
}
}

View File

@@ -0,0 +1,16 @@
export interface BootstrapState {
username?: string
userId?: string
isAdmin?: boolean
error?: string
}
declare global {
interface Window {
__BOOTSTRAP__?: BootstrapState
}
}
export function getBootstrapState(): BootstrapState {
return window.__BOOTSTRAP__ ?? {}
}