diff --git a/.env.development b/.env.development index 79adbd8..1afde8e 100644 --- a/.env.development +++ b/.env.development @@ -1,7 +1,9 @@ VITE_APP_PREVIEW=true -# VITE_APP_API_BASE_URL=http://172.16.20.21:8166/web/ +# VITE_APP_API_BASE_URL=http://172.16.20.21:20001/api/web/ +# VITE_APP_API_BASE_URL=http://172.16.0.40:20001/api/web/ +VITE_APP_API_BASE_URL=https://shop.api.suwa3d.com/api/web/ # VITE_APP_API_BASE_URL=https://wechat-test.api.puabadge.com/web/ -VITE_APP_API_BASE_URL=https://wechat.api.puabadge.com/web/ +# VITE_APP_API_BASE_URL=https://wechat.api.puabadge.com/web/ # VITE_APP_API_WX_URL=https://wechat.api.puabadge.com/web/ # http://web.suwa3d.dev:28499/ VITE_HTTP_MOCK=true diff --git a/src/config/cartoon.ts b/src/config/cartoon.ts new file mode 100644 index 0000000..43dd137 --- /dev/null +++ b/src/config/cartoon.ts @@ -0,0 +1,493 @@ +// 卡通手办页面配置 +export const cartoonConfig = { + // 路由配置 + routes: { + record: '/cartoon/photoAlbum', + myOrder: '/cartoon/myOrder', + preview: '/cartoon/previewOrder', + orderDetail: '/cartoon/orderDetail', + home: '/cartoon' + }, + + // 产品类型配置 + productTypes: { + 1: { name: '立体徽章', key: 'badge_3d' }, + 2: { name: '浮雕相框', key: 'relief_frame' }, + 3: { name: '3D冰箱贴', key: 'fridge_magnet' }, + 4: { name: '3D卡通手办', key: 'cartoon_figure' }, + 6: { name: '浮雕挂饰', key: 'relief_pendant' } + }, + + // 主体类型配置 + subjectTypes: { + person: { + id: 7, + name: '人物主体', + prodIds: [7, 1, 25], // 不同产品类型对应的人物产品ID + group: 'gp', + supportSubject: 1 + }, + pet: { + id: 8, + name: '宠物主体', + prodIds: [8, 2, 19, 24], // 不同产品类型对应的宠物产品ID + group: 'pet', + supportSubject: 2 + } + }, + + // 产品ID映射配置(根据typeId和subjectId映射到实际产品ID) + productIdMapping: { + 4: { // typeId == 4 (3D卡通手办) + person: 1, + pet: 19 + }, + 6: { // typeId == 6 (浮雕挂饰) + person: 25, + pet: 24 + }, + 3: { // typeId == 3 (3D冰箱贴) + person: 7, + pet: 8 + }, + default: { // 其他类型 + person: 7, + pet: 8 + } + }, + + // 产品限制配置 + productLimits: { + // 根据产品ID配置是否需要人脸检测 + faceCheckRequired: [7, 1, 25], + // 根据产品ID配置是否支持多视角参考图 + multiViewSupported: [19], + // 根据产品ID配置是否支持风格类型 + kindListSupported: [4], + // 根据typeId配置是否显示照片示例 + photoExampleTypes: ['3D真人肖像'] + }, + + // 上传配置 + upload: { + minWidth: 300, + minHeight: 300, + maxSize: 10 * 1024 * 1024, // 10MB + maxReferCount: 3, + cropOptions: { + canScale: true, + autoCropWidth: 1024, + autoCropHeight: 1024, + ceilbutton: true, + infoTrue: true, + fixed: false, + fixedNumber: [3, 4], + fixedBox: false, + canMoveBox: true, + enlarge: 2 + } + }, + + // 图片资源路径配置(在组件中通过 import 导入) + assets: { + sheji: '@/assets/badge/sheji.png', + order: '@/assets/badge/order.png' + }, + + // 提示信息配置 + messages: { + personPhotoTip: { + single: '1', + multiple: '1-3', + unit: '人' + }, + petPhotoTip: { + count: '1-3', + unit: '只' + } + }, + + // 业务规则配置 + businessRules: { + // 禁止的产品ID组合(typeId, subjectId) + disabled: [ + { typeId: 3, subjectId: 7, message: '人物3D冰箱贴暂未开放' } + ], + // 订单类型限制(typeId, currentProdId, targetSubjectId, message) + orderRestrictions: [ + { typeId: 4, prodId: 19, targetSubjectId: 8, message: '该订单属于宠物卡通手办' }, + { typeId: 4, prodId: 1, targetSubjectId: 7, message: '该订单属于人物卡通手办' } + ] + }, + + // 样式配置 + styles: { + colors: { + primary: '#50cf54', + primaryGradient: 'linear-gradient(90deg, #D1ED8E 0%, #55E668 100%)', + textPrimary: '#222', + textSecondary: '#333', + textTertiary: '#888', + textDisabled: '#b0b0b0', + background: '#F0F2F5', + backgroundSecondary: '#F2F2F2', + border: '#E5E5E5', + success: '#15CF5F', + progressBar: '#6fdc8c' + }, + spacing: { + pagePadding: '16px', + sectionMargin: '16px', + itemGap: '8px' + } + }, + + // Preview页面配置 + preview: { + // 图片尺寸配置 + imageSize: { + previewWidth: 320, + previewHeight: 320, + compareImageSize: 120 + }, + + // 进度条配置 + progress: { + maxProgress: 90, + interval: 300, // ms + thresholds: { + stage1: 25, + stage2: 50, + stage3: 75 + }, + stages: { + stage1: '照片数据分析中', + stage2: '提炼图像关键词', + stage3: '图像生成中', + stage4: '图像后处理与优化' + }, + totalTime: 90 // 秒 + }, + + // 形状ID配置 + shapeIds: { + round: [1, 2], // 圆形形状 + specialFrame: [15, 16], // 特殊相框 + frame: [17, 18], // 相框 + topBottomText: [27, 28] // 支持上下文字的形状 + }, + + // 文字配置默认值 + textConfig: { + front: { + x: 163, + y: 222, + width: 700, + height: 700, + radius: 350, + fontSize: 72, + maxLength: 18 + }, + back: { + x: 238, + y: 297, + width: 550, + height: 550, + radius: 275, + fontSize: 60, + maxLength: 56 + }, + defaultColor: '#000000', + defaultWeight: 'normal' + }, + + // 图片缩放比例 + imageScale: 0.6, + + // 默认值配置 + defaults: { + group: 1, + imgKey: 101, + countryId: 45, + addrId: 0 + }, + + // 轮询配置 + polling: { + interval: 10000, // 10秒 + compareImageDelay: 1000 // 1秒 + }, + + // 特殊code配置(用于地址识别功能) + specialCodes: { + addressRecognition: 'ACAYOAAC3PFCPO3CD6DVM1' + }, + + // 产品名称映射(根据typeId和prodId) + productNames: { + 1: { // typeId == 1 (立体徽章) + 7: '人物立体徽章', + 8: '宠物立体徽章' + }, + 2: { // typeId == 2 (浮雕相框) + 7: '人物浮雕相框', + 8: '宠物浮雕相框' + }, + 3: { // typeId == 3 (3D冰箱贴) + 7: '人物3D冰箱贴', + 8: '宠物3D冰箱贴' + }, + 4: { // typeId == 4 (3D卡通手办) + 1: '人物卡通手办', + 19: '宠物卡通手办' + }, + 6: { // typeId == 6 (浮雕挂饰) + 25: '人物浮雕挂饰', + 24: '宠物浮雕挂饰' + } + }, + + // 提示信息 + messages: { + invalidInput: '输入的文字不能包含特殊符号!', + selectQuantity: '请先选择下单数量', + fillContactName: '请先填写收货人信息', + fillContactMobile: '请先填写收货人手机号', + selectArea: '请先选择地区', + fillAddress: '请先填写详细地址', + orderSuccess: '下单成功', + noRemainingCount: '当前次数已用完,请重新购买', + confirmRegenerate: '请确认是否重新生成。', + confirmOrder: '请再次确认是否选择这个模型下单,下单后预计需要10天完成生产发货。如对产品有疑问,可以联系客服咨询。', + addressPlaceholder: '请输入姓名、手机号、详细地址(如:张三 13800138000 广东省深圳市南山区科技园1号)', + addressRecognitionResult: '识别结果', + compareTip: '温馨提示:AI设计的徽章效果图是根据用户提供的照片机器学习生成的,多试几次就能找到你满意的徽章~', + saveImageTip: '长按图片 → 保存到相册', + designing: '设计中', + designFailed: '设计失败', + remainingExchange: '剩余兑换', + process: '工艺', + productType: '产品类型', + orderQuantity: '订购数量', + addressRecognition: '地址识别', + shippingAddress: '收货地址', + regenerate: '再次生成', + compare: '前后对比', + saveImage: '保存图片', + confirmSelect: '确认选择', + originalImage: '原图', + previewImage: '效果图', + badgePreview: '徽章预览' + } + }, + + // PhotoAlbum页面配置 + photoAlbum: { + // 分页配置 + pagination: { + pageSize: 20, + initialPage: 1 + }, + + // 状态配置 + status: { + success: 1, // 设计成功 + designing: 0, // 设计中 + failed: 2 // 设计失败 + }, + + // 图片资源路径(在组件中通过 import 导入) + assets: { + nullData: '@/assets/badge/null-data.png' + }, + + // 样式配置 + styles: { + gridColumns: 2, // 网格列数 + gap: 16, // 间距 + borderRadius: 12 // 圆角 + }, + + // 提示信息 + messages: { + title: '设计记录', + note: '注:可点击效果图进行下单打印', + noMore: '没有更多了', + loading: '加载中...', + noData: '暂无数据', + designing: '设计中', + designFailed: '设计失败', + getDataFailed: '获取数据失败' + } + }, + + // MyOrder页面配置 + myOrder: { + // 分页配置 + pagination: { + pageSize: 30, + initialPage: 1, + defaultStatus: 0 // 默认订单状态 + }, + + // 订单状态配置 + orderStatus: { + pending: 1, // 待支付 + cancelled: 2, // 已取消 + producing: 100, // 生产中 + shipping: 200, // 待收货 + completed: 300 // 已完成 + }, + + // 订单状态文本映射 + statusText: { + 1: '待支付', + 2: '已取消', + 100: '生产中', + 200: '待收货', + 300: '已完成' + }, + + // 订单状态样式配置 + statusStyles: { + processing: { + background: '#bdbdbd' // 处理中状态背景色 + }, + finished: { + background: '#8bc34a' // 完成状态背景色 + }, + default: { + background: '#bdbdbd', + color: '#fff', + opacity: 0.9 + } + }, + + // 图片资源路径(在组件中通过 import 导入) + assets: { + nullData: '@/assets/badge/null-data.png' + }, + + // 样式配置 + styles: { + gap: 8, // 间距 + itemWidth: 'calc(50% - 4px)', // 订单项宽度 + itemHeight: '43vw', // 订单项高度 + borderRadius: 12, // 圆角 + padding: { + horizontal: 16, + vertical: 24 + }, + status: { + bottom: 5, + right: 5, + padding: { + vertical: 2, + horizontal: 12 + }, + borderRadius: 12, + fontSize: 12 + }, + shadow: '0 2px 8px rgba(0,0,0,0.08)' // 阴影 + }, + + // 提示信息 + messages: { + title: '我的订单', + noData: '暂无数据' + } + }, + + // OrderDetail页面配置 + orderDetail: { + // 订单类型配置 + orderTypes: { + shipping: 2 // 需要收货地址的订单类型 + }, + + // 产品类型配置(需要特殊显示的产品类型) + productTypes: { + reliefPendant: 6 // 浮雕挂饰(需要显示双面) + }, + + // 图片尺寸配置 + imageSize: { + width: 320, + height: 320, + borderRadius: 10 + }, + + // 图片缩放比例 + imageScale: 0.6, + + // 样式配置 + styles: { + padding: { + horizontal: 16, + vertical: 0 + }, + statusInfo: { + padding: { + horizontal: 16, + vertical: 0 + }, + title: { + fontSize: 18, + fontWeight: 'bold', + color: '#222', + marginBottom: 8 + }, + desc: { + fontSize: 10, + color: '#888' + } + }, + productInfo: { + padding: { + horizontal: 16, + vertical: 0 + }, + title: { + fontSize: 15, + fontWeight: 'bold', + color: '#111', + marginBottom: 10 + }, + details: { + gap: 8 + }, + label: { + color: '#bdbdbd', + fontSize: 14 + }, + value: { + color: '#222', + fontSize: 14 + } + }, + avatar: { + gap: 10, + borderRadius: 10 + }, + bottomSpacing: 30 + }, + + // 提示信息 + messages: { + title: '订单详情', + statusDesc: '下单后预计需要10天完成生产发货。如对产品有疑问,可以联系客服咨询。', + shippingInfo: '收货信息', + contactInfo: '收货人信息', + shippingAddress: '收货地址', + productInfo: '产品信息', + productName: '产品名称', + size: '尺寸', + shape: '形状', + quantity: '购买数量', + orderInfo: '订单信息', + orderId: '购买订单id', + orderTime: '购买时间' + } + } +} + diff --git a/src/main.ts b/src/main.ts index d766720..59588f1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { createApp, onMounted } from 'vue' +import { createApp } from 'vue' import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import { createRouterScroller } from 'vue-router-better-scroller' @@ -105,13 +105,34 @@ app.use(createRouterScroller({ }, })) -onMounted(() => { - window.addEventListener('beforeunload', handleBeforeUnload); -}); - +// 处理页面隐藏事件(可以区分刷新和关闭) +function handlePageHide(event: PageTransitionEvent) { + // event.persisted 为 true 表示页面被缓存(刷新),保留 code + // event.persisted 为 false 表示页面未被缓存(关闭),清除 code + if (!event.persisted) { + // 页面关闭(非刷新),清除 code 和 userId,避免下一个用户使用上一个用户的 code + localStorage.remove('code') + localStorage.remove('userId') + console.log('页面关闭,已清除 code 和 userId') + } else { + // 页面刷新(persisted === true),保留 code + console.log('页面刷新,保留 code') + } +} + +// 处理页面卸载事件(作为备用方案,某些浏览器可能不支持 pagehide) function handleBeforeUnload(_event: BeforeUnloadEvent) { - localStorage.remove('code') - localStorage.remove('trialCode') + // 注意:beforeunload 无法区分刷新和关闭 + // 主要依赖 pagehide 事件来处理 + // 这里不做任何操作,避免误清除刷新时的 code } +// 在应用挂载前设置事件监听器 +// 优先使用 pagehide 事件(可以区分刷新和关闭) +// 在支持的浏览器中,这是最可靠的方法 +window.addEventListener('pagehide', handlePageHide) + +// 监听 beforeunload 作为备用(某些旧浏览器可能不支持 pagehide) +window.addEventListener('beforeunload', handleBeforeUnload) + app.mount('#app') diff --git a/src/router/cartoon.ts b/src/router/cartoon.ts new file mode 100644 index 0000000..380aa5c --- /dev/null +++ b/src/router/cartoon.ts @@ -0,0 +1,60 @@ +// https://router.vuejs.org/zh/ +import 'nprogress/nprogress.css' + +import cartoon from '@/views/cartoon/index.vue' +import previewOrder from '@/views/cartoon/previewOrder.vue' +import photoAlbum from '@/views/cartoon/photoAlbum.vue' +import myOrder from '@/views/cartoon/myOrder.vue' +import orderDetail from '@/views/cartoon/orderDetail.vue' +// 定义路由,每个路由都需要映射到一个组件 +export const routes = [ + { + path: '/cartoon', + name: 'cartoon', + component: cartoon, + meta: { + needGuard: true, + title: '卡通手办', + }, + }, + { + path: '/cartoon/previewOrder', + name: 'previewOrder', + component: previewOrder, + meta: { + needGuard: true, + title: '预览效果图', + }, + }, + { + path: '/cartoon/photoAlbum', + name: 'photoAlbum', + component: photoAlbum, + meta: { + needGuard: true, + title: '设计记录', + }, + }, + { + path: '/cartoon/myOrder', + name: 'myOrder', + component: myOrder, + meta: { + needGuard: true, + title: '我的订单', + }, + }, + { + path: '/cartoon/orderDetail', + name: 'orderDetail', + component: orderDetail, + meta: { + needGuard: true, + title: '订单详情', + }, + } +] + +export function mergeRoutes(allRoutes: any[]) { + allRoutes.push(...routes) +} diff --git a/src/router/index.ts b/src/router/index.ts index a2f80d3..a97153d 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -3,8 +3,10 @@ import { createRouter, createWebHashHistory } from 'vue-router' import NProgress from 'nprogress' import 'nprogress/nprogress.css' // 导入路由组件 -import * as badgeRouter from './badge' -import badge from '@/views/badge/index.vue' +// import * as badgeRouter from './badge' +import * as cartoonRouter from './cartoon' +// import badge from '@/views/badge/index.vue' +import cartoon from '@/views/cartoon/index.vue' import {useStore} from '@/stores' import { config } from '@/config/config' import { localStorage } from '@/utils/local-storage' @@ -15,8 +17,8 @@ NProgress.configure({ showSpinner: true, parent: '#app' }) const routes = [ { path: '/', - name: 'badge', - component: badge, + name: 'cartoon', + component: cartoon, meta: { needGuard: true, title: '首页', @@ -24,7 +26,8 @@ const routes = [ }, ] -badgeRouter.mergeRoutes(routes) +// badgeRouter.mergeRoutes(routes) +cartoonRouter.mergeRoutes(routes) // 创建路由实例并传递 `routes` 配置 const router = createRouter({ history: createWebHashHistory(process.env.VUE_APP_PUBLIC_PATH), @@ -35,18 +38,25 @@ router.beforeEach((_to, _from, next) => { // const tokenStore = useTokenStore() const store = useStore() const code = _to.query.code - if (code) { - localStorage.set('code', code as string); + + // 如果 URL 中有 code 参数,保存到 localStorage(用于刷新时保留) + if (code && typeof code === 'string') { + localStorage.set('code', code) + console.log('保存 code 到 localStorage:', code) } - const trialCode = _to.query.trialCode - if (trialCode) { - localStorage.set('trialCode', trialCode as string); + + // 如果 URL 中没有 code,尝试从 localStorage 读取(用于刷新时恢复) + const savedCode = localStorage.get('code') + if (!code && savedCode) { + console.log('从 localStorage 恢复 code:', savedCode) } - console.log('code===>', code); - if (code || trialCode) { + + console.log('当前 code===>', code || savedCode) + + if (code) { let url = store.redirectUrl() - if (_to.path !== '/badge') { + if (_to.path !== '/cartoon') { if (url) { store.setRedirect('') next(url) diff --git a/src/utils/request.ts b/src/utils/request.ts index f206914..f6c6560 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -50,18 +50,18 @@ const errorHandler = (error: RequestError): Promise => { // 请求拦截器 const requestHandler = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig | Promise => { - // const savedToken = localStorage.get(STORAGE_TOKEN_KEY) - // 如果 token 存在 + // 从 localStorage 读取 code(刷新时也能获取到) const code = localStorage.get('code') - const trialCode = localStorage.get('trialCode') const userId = localStorage.get('userId') + // 让每个请求携带自定义 token, 请根据实际情况修改 - if (code) + if (code) { config.headers['Authorization'] = code - if (trialCode) - config.headers['Authorization'] = trialCode - if (userId) + } + if (userId) { config.headers['Userid'] = userId + } + return config } diff --git a/src/views/badge/index.vue b/src/views/badge/index.vue index a6a325b..1753c99 100644 --- a/src/views/badge/index.vue +++ b/src/views/badge/index.vue @@ -710,7 +710,7 @@ const getPid = async () => { try { const uploadTasks = [ sendToOss(imgurl.value, res.url), - sendToOss(imgurl.value, res.oss_url) + // sendToOss(imgurl.value, res.oss_url) ] await Promise.all(uploadTasks) if (typeId.value == 4 && prodId.value == 19 && referPicture.value.length > 0) { @@ -898,9 +898,10 @@ function Encrypt(word: string): string { return encrypted.ciphertext.toString(); } function handleBeforeUnload(_event: BeforeUnloadEvent) { - localStorage.remove('code') - localStorage.remove('trialCode') - localStorage.remove('userId') + // code 的清除已在 main.ts 中统一处理,这里只清除 userId(如果需要) + // 注意:刷新时不应该清除 userId,只有关闭时才清除 + // 由于无法精确区分刷新和关闭,这里保留清除逻辑,但主要依赖 main.ts 的处理 + // localStorage.remove('userId') } onMounted(() => { getSundryList() diff --git a/src/views/badge/preview.vue b/src/views/badge/preview.vue index c563a64..aa17155 100644 --- a/src/views/badge/preview.vue +++ b/src/views/badge/preview.vue @@ -1216,12 +1216,11 @@ const getOrderStat = () => { } function handleBeforeUnload(_event: BeforeUnloadEvent) { - localStorage.remove('code') - localStorage.remove('trialCode') - localStorage.remove('userId') + // code 的清除已在 main.ts 中统一处理,这里只处理页面特定的清理逻辑 // 清除轮询和进度条计时器 clearInterval(timer.value) clearInterval(progressTimer.value) + // trialCode 和 userId 的清除由 main.ts 统一处理 } const pid = ref(0) diff --git a/src/views/cartoon/index.vue b/src/views/cartoon/index.vue new file mode 100644 index 0000000..131a2f1 --- /dev/null +++ b/src/views/cartoon/index.vue @@ -0,0 +1,1165 @@ + + + + + + diff --git a/src/views/cartoon/myOrder.vue b/src/views/cartoon/myOrder.vue new file mode 100644 index 0000000..c35a519 --- /dev/null +++ b/src/views/cartoon/myOrder.vue @@ -0,0 +1,156 @@ + + + + + + diff --git a/src/views/cartoon/orderDetail.vue b/src/views/cartoon/orderDetail.vue new file mode 100644 index 0000000..80edc52 --- /dev/null +++ b/src/views/cartoon/orderDetail.vue @@ -0,0 +1,271 @@ + + + + + + diff --git a/src/views/cartoon/photoAlbum.vue b/src/views/cartoon/photoAlbum.vue new file mode 100644 index 0000000..d2b58c9 --- /dev/null +++ b/src/views/cartoon/photoAlbum.vue @@ -0,0 +1,229 @@ + + + + + + diff --git a/src/views/cartoon/previewOrder.vue b/src/views/cartoon/previewOrder.vue new file mode 100644 index 0000000..e9ec086 --- /dev/null +++ b/src/views/cartoon/previewOrder.vue @@ -0,0 +1,1551 @@ + + + \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index c34a37c..909666f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -144,6 +144,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { server: { host: true, port: 80, + // https: false, https: true, proxy, // proxy: env.VITE_HTTP_MOCK === 'true'