diff --git a/src/app.config.ts b/src/app.config.ts index e41d37d..864e59d 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -2,6 +2,7 @@ export default defineAppConfig({ pages: [ 'pages/index/index', 'pages/brochure/index', + 'pages/brochure/viewer', 'pages/cart/cart', 'pages/find/find', 'pages/user/user' diff --git a/src/pages/brochure/pdf.ts b/src/pages/brochure/pdf.ts new file mode 100644 index 0000000..91f68e4 --- /dev/null +++ b/src/pages/brochure/pdf.ts @@ -0,0 +1,30 @@ +export const DEFAULT_CATALOG_URL = 'https://file.websoft.top/nnlc.pdf' + +const safeDecodeUrl = (value: string) => { + let nextValue = value + + for (let index = 0; index < 2; index += 1) { + try { + const decodedValue = decodeURIComponent(nextValue) + if (decodedValue === nextValue) { + break + } + nextValue = decodedValue + } catch { + break + } + } + + return nextValue +} + +export const normalizePdfUrl = (value?: string) => { + const trimmedValue = (value || '').trim() + if (!trimmedValue) { + return DEFAULT_CATALOG_URL + } + + return safeDecodeUrl(trimmedValue) +} + +export const isPdfUrl = (value?: string) => /\.pdf(\?|#|$)/i.test((value || '').trim()) diff --git a/src/pages/brochure/viewer.config.ts b/src/pages/brochure/viewer.config.ts new file mode 100644 index 0000000..b1502e9 --- /dev/null +++ b/src/pages/brochure/viewer.config.ts @@ -0,0 +1,5 @@ +export default definePageConfig({ + navigationBarTitleText: '品牌画册', + navigationBarBackgroundColor: '#ffffff', + navigationBarTextStyle: 'black' +}) diff --git a/src/pages/brochure/viewer.scss b/src/pages/brochure/viewer.scss new file mode 100644 index 0000000..d555b46 --- /dev/null +++ b/src/pages/brochure/viewer.scss @@ -0,0 +1,19 @@ +.brochure-viewer { + min-height: 100vh; + padding: 0; + box-sizing: border-box; + background: #ffffff; + display: flex; + flex-direction: column; + + &--loading { + display: flex; + align-items: center; + justify-content: center; + } + + &__image { + width: 100%; + display: block; + } +} diff --git a/src/pages/brochure/viewer.tsx b/src/pages/brochure/viewer.tsx new file mode 100644 index 0000000..1895b0f --- /dev/null +++ b/src/pages/brochure/viewer.tsx @@ -0,0 +1,140 @@ +import {useEffect, useState} from 'react' +import Taro from '@tarojs/taro' +import {View, Image} from '@tarojs/components' +import {Loading} from '@nutui/nutui-react-taro' +import './viewer.scss' + +const PREVIEW_TITLE = '品牌画册' +const BROCHURE_API_BASE = 'https://cms-api.websoft.top/api' + +interface BrochureGoods { + goodsId?: number; + name?: string; + goodsName?: string; + files?: string; +} + +const parseFiles = (files?: string): string[] => { + if (!files) { + return [] + } + + try { + const parsed = JSON.parse(files) + if (Array.isArray(parsed)) { + return parsed.reduce((result, item: any) => { + if (typeof item === 'string') { + result.push(item) + return result + } + + if (item && typeof item.url === 'string') { + result.push(item.url) + } + + return result + }, []) + } + + if (typeof parsed === 'string') { + return [parsed] + } + + if (parsed && typeof parsed.url === 'string') { + return [parsed.url] + } + } catch (error) { + if (/^https?:\/\//.test(files) || files.startsWith('/')) { + return [files] + } + + console.error('解析画册图片失败:', error) + return [] + } + + return [] +} + +function BrochureViewer() { + const [loading, setLoading] = useState(true) + const [images, setImages] = useState([]) + const [errorMessage, setErrorMessage] = useState('') + + useEffect(() => { + const loadBrochure = async () => { + try { + const response = await Taro.request({ + url: `${BROCHURE_API_BASE}/shop/shop-goods`, + method: 'GET', + header: { + 'Content-Type': 'application/json', + 'Tenantid': '10582' + } + }) + + const responseData: any = response.data + const goodsList: BrochureGoods[] = Array.isArray(responseData) + ? responseData + : (Array.isArray(responseData?.data) ? responseData.data : []) + + if (!goodsList.length) { + throw new Error('画册加载失败,请稍后重试') + } + const nextTitle = goodsList[0]?.goodsName || goodsList[0]?.name || PREVIEW_TITLE + const nextImages: string[] = [] + + goodsList.forEach((item) => { + const parsedImages = parseFiles(item.files) + if (parsedImages.length) { + nextImages.push(...parsedImages) + } + }) + + if (!nextImages.length) { + throw new Error('当前商品未配置画册图片') + } + + setImages(nextImages) + Taro.setNavigationBarTitle({ + title: nextTitle + }) + } catch (error: any) { + const nextMessage = error?.message || error?.errMsg || '画册加载失败,请稍后重试' + setErrorMessage(nextMessage) + Taro.showToast({ + title: nextMessage, + icon: 'none' + }) + } finally { + setLoading(false) + } + } + + loadBrochure() + }, []) + + if (loading) { + return ( + + 加载中 + + ) + } + + return ( + + {errorMessage ? null : images.map((item, index) => ( + + ))} + + ) +} + +export default BrochureViewer diff --git a/src/pages/index/CatalogShowcase.tsx b/src/pages/index/CatalogShowcase.tsx index e235e30..339db2f 100644 --- a/src/pages/index/CatalogShowcase.tsx +++ b/src/pages/index/CatalogShowcase.tsx @@ -2,31 +2,12 @@ import { View, Text } from '@tarojs/components' import Taro from '@tarojs/taro' import { ArrowRight } from '@nutui/icons-react-taro' import './CatalogShowcase.scss' - -const CATALOG_URL = 'https://book.yunzhan365.com/mdfy/tjcs/mobile/index.html' +const BROCHURE_GOODS_ID = 333 function CatalogShowcase() { const handleViewCatalog = () => { - Taro.setClipboardData({ - data: CATALOG_URL, - success: () => { - Taro.showToast({ - title: '链接已复制', - icon: 'success', - duration: 2000 - }) - setTimeout(() => { - Taro.showModal({ - title: '提示', - content: '链接已复制到剪贴板,请前往浏览器打开查看品牌画册', - showCancel: false, - confirmText: '知道了' - }) - }, 2100) - }, - fail: () => { - Taro.showToast({ title: '复制失败,请重试', icon: 'none' }) - } + Taro.navigateTo({ + url: `/pages/brochure/viewer?id=${BROCHURE_GOODS_ID}` }) } diff --git a/src/utils/request.ts b/src/utils/request.ts index d515bf9..a24c4ee 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -53,7 +53,49 @@ const DEFAULT_CONFIG = { showError: true }; -let baseUrl = Taro.getStorageSync('ApiUrl') || BaseUrl; +const getStorageSyncSafe = (key: string): T | undefined => { + try { + if (typeof Taro.getStorageSync === 'function') { + return Taro.getStorageSync(key) as T; + } + } catch (error) { + console.warn(`读取存储失败: ${key}`, error); + } + + if (typeof window !== 'undefined' && window.localStorage) { + const rawValue = window.localStorage.getItem(key); + if (rawValue === null) { + return undefined; + } + + try { + return JSON.parse(rawValue) as T; + } catch { + return rawValue as T; + } + } + + return undefined; +}; + +const removeStorageSyncSafe = (key: string) => { + try { + if (typeof Taro.removeStorageSync === 'function') { + Taro.removeStorageSync(key); + return; + } + } catch (error) { + console.warn(`删除存储失败: ${key}`, error); + } + + if (typeof window !== 'undefined' && window.localStorage) { + window.localStorage.removeItem(key); + } +}; + +const getBaseUrl = (): string => { + return getStorageSyncSafe('ApiUrl') || BaseUrl; +}; // 开发环境配置 if (process.env.NODE_ENV === 'development') { @@ -63,8 +105,8 @@ if (process.env.NODE_ENV === 'development') { // 请求拦截器 const requestInterceptor = (config: RequestConfig): RequestConfig => { // 添加认证token - const token = Taro.getStorageSync('access_token'); - const tenantId = Taro.getStorageSync('TenantId') || TenantId; + const token = getStorageSyncSafe('access_token'); + const tenantId = getStorageSyncSafe('TenantId') || TenantId; const defaultHeaders: Record = { 'Content-Type': 'application/json', @@ -156,11 +198,11 @@ const responseInterceptor = (response: any, config: RequestConfig): T => { const handleAuthError = () => { // 清除本地存储的认证信息 try { - Taro.removeStorageSync('access_token'); - Taro.removeStorageSync('User'); - Taro.removeStorageSync('UserId'); - Taro.removeStorageSync('TenantId'); - Taro.removeStorageSync('Phone'); + removeStorageSyncSafe('access_token'); + removeStorageSyncSafe('User'); + removeStorageSyncSafe('UserId'); + removeStorageSyncSafe('TenantId'); + removeStorageSyncSafe('Phone'); } catch (error) { console.error('清除认证信息失败:', error); } @@ -293,7 +335,7 @@ export async function request(options: RequestConfig): Promise { // 构建完整URL const buildUrl = (url: string): string => { if (url.indexOf('http') === -1) { - return baseUrl + url; + return getBaseUrl() + url; } return url; };