From c76797b68140dcf5b36099820443dcdbbd66c9a6 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Thu, 14 May 2026 14:07:42 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=BB=E5=86=8C?= =?UTF-8?q?=E8=B0=83=E7=94=A8goods=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.config.ts | 1 + src/pages/brochure/pdf.ts | 30 ++++++ src/pages/brochure/viewer.config.ts | 5 + src/pages/brochure/viewer.scss | 19 ++++ src/pages/brochure/viewer.tsx | 140 ++++++++++++++++++++++++++++ src/pages/index/CatalogShowcase.tsx | 25 +---- src/utils/request.ts | 60 ++++++++++-- 7 files changed, 249 insertions(+), 31 deletions(-) create mode 100644 src/pages/brochure/pdf.ts create mode 100644 src/pages/brochure/viewer.config.ts create mode 100644 src/pages/brochure/viewer.scss create mode 100644 src/pages/brochure/viewer.tsx 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; }; From 20f77ff88620f618ddb843ee0797da35526fca98 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Thu, 14 May 2026 18:18:11 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E7=9A=84=E7=94=BB=E5=86=8C=E4=B8=BAswiper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/brochure/shared.ts | 97 ++++++++++++++++++++ src/pages/brochure/viewer.tsx | 92 ++----------------- src/pages/index/CatalogShowcase.scss | 93 +++++++++++++++++++ src/pages/index/CatalogShowcase.tsx | 131 ++++++++++++++++++++------- 4 files changed, 294 insertions(+), 119 deletions(-) create mode 100644 src/pages/brochure/shared.ts diff --git a/src/pages/brochure/shared.ts b/src/pages/brochure/shared.ts new file mode 100644 index 0000000..a14915e --- /dev/null +++ b/src/pages/brochure/shared.ts @@ -0,0 +1,97 @@ +import Taro from '@tarojs/taro' + +const PREVIEW_TITLE = '品牌画册' +const BROCHURE_API_BASE = 'https://cms-api.websoft.top/api' + +interface BrochureGoods { + goodsId?: number + name?: string + goodsName?: string + files?: string +} + +export interface BrochurePayload { + title: string + images: 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 [] +} + +export const getBrochurePayload = async (): Promise => { + 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 title = goodsList[0]?.goodsName || goodsList[0]?.name || PREVIEW_TITLE + const images: string[] = [] + + goodsList.forEach((item) => { + const parsedImages = parseFiles(item.files) + if (parsedImages.length) { + images.push(...parsedImages) + } + }) + + if (!images.length) { + throw new Error('当前商品未配置画册图片') + } + + return { + title, + images + } +} diff --git a/src/pages/brochure/viewer.tsx b/src/pages/brochure/viewer.tsx index 1895b0f..19e5689 100644 --- a/src/pages/brochure/viewer.tsx +++ b/src/pages/brochure/viewer.tsx @@ -1,60 +1,10 @@ -import {useEffect, useState} from 'react' +import { useEffect, useState } from 'react' import Taro from '@tarojs/taro' -import {View, Image} from '@tarojs/components' -import {Loading} from '@nutui/nutui-react-taro' +import { View, Image } from '@tarojs/components' +import { Loading } from '@nutui/nutui-react-taro' +import { getBrochurePayload } from './shared' 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([]) @@ -63,40 +13,10 @@ function BrochureViewer() { 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('当前商品未配置画册图片') - } - + const { title, images: nextImages } = await getBrochurePayload() setImages(nextImages) Taro.setNavigationBarTitle({ - title: nextTitle + title }) } catch (error: any) { const nextMessage = error?.message || error?.errMsg || '画册加载失败,请稍后重试' diff --git a/src/pages/index/CatalogShowcase.scss b/src/pages/index/CatalogShowcase.scss index 0d3f590..a04b08a 100644 --- a/src/pages/index/CatalogShowcase.scss +++ b/src/pages/index/CatalogShowcase.scss @@ -108,3 +108,96 @@ &:nth-child(3) { height: 32px; } } } + +.catalog-swiper { + margin: 24px 16px 8px; + + &__header { + display: flex; + align-items: flex-end; + justify-content: space-between; + padding: 0 4px 12px; + gap: 12px; + } + + &__title-wrap { + display: flex; + flex-direction: column; + gap: 4px; + } + + &__eyebrow { + font-size: 20px; + line-height: 1; + letter-spacing: 3px; + color: #94a3b8; + } + + &__title { + font-size: 36px; + line-height: 1.2; + font-weight: 700; + color: #0f172a; + } + + &__cta { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 10px 14px; + border-radius: 999px; + background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%); + flex-shrink: 0; + } + + &__cta-text { + font-size: 24px; + line-height: 1; + color: #ffffff; + font-weight: 600; + } + + &__body { + border-radius: 20px; + overflow: hidden; + background: linear-gradient(135deg, #1e293b 0%, #2563eb 100%); + box-shadow: 0 12px 32px rgba(37, 99, 235, 0.18); + } + + &__slide { + width: 100%; + //height: 220px; + overflow: hidden; + } + + &__image { + width: 100%; + //height: 220px; + display: block; + } + + &__placeholder { + //height: 220px; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + } + + &__placeholder-text { + font-size: 28px; + color: rgba(255, 255, 255, 0.88); + } + + .nut-swiper { + //height: 220px !important; + } + + .nut-swiper-item { + //height: 220px !important; + } + + .nut-swiper-item__inner { + height: 100%; + } +} diff --git a/src/pages/index/CatalogShowcase.tsx b/src/pages/index/CatalogShowcase.tsx index 339db2f..e22023f 100644 --- a/src/pages/index/CatalogShowcase.tsx +++ b/src/pages/index/CatalogShowcase.tsx @@ -1,50 +1,115 @@ -import { View, Text } from '@tarojs/components' +import { useEffect, useState } from 'react' +import { View, Text, Image } from '@tarojs/components' import Taro from '@tarojs/taro' +import { Swiper } from '@nutui/nutui-react-taro' import { ArrowRight } from '@nutui/icons-react-taro' +import { getBrochurePayload } from '@/pages/brochure/shared' import './CatalogShowcase.scss' -const BROCHURE_GOODS_ID = 333 function CatalogShowcase() { - const handleViewCatalog = () => { - Taro.navigateTo({ - url: `/pages/brochure/viewer?id=${BROCHURE_GOODS_ID}` + const [images, setImages] = useState([]) + const [current, setCurrent] = useState(0) + + useEffect(() => { + getBrochurePayload() + .then(({ images: nextImages }) => { + setImages(nextImages) + }) + .catch(() => undefined) + }, []) + + const handlePreviewCurrent = () => { + if (!images.length) { + Taro.showToast({ + title: '画册加载中', + icon: 'none' + }) + return + } + + Taro.previewImage({ + current: images[current] || images[0], + urls: images }) } return ( - - {/* 装饰性背景元素 */} - - + <> + {/* 旧版品牌画册卡片保留,按需求先注释,不直接删除 + + + - - - BRAND CATALOG - 品牌画册 - - 了解南南佐顿门窗的完整产品线与定制方案 - - - 点击查看画册 - - - - - - - - - - - + + + BRAND CATALOG + 品牌画册 + + 了解南南佐顿门窗的完整产品线与定制方案 + + + 点击查看画册 + + + + + + + + + + + + + 2026 - 2026 - + */} + + + + + BRAND CATALOG + 品牌画册 + + {/**/} + {/* 点击预览当前图片*/} + {/* */} + {/**/} + + + + {images.length ? ( + setCurrent(event.detail.current)} + > + {images.map((item, index) => ( + + + + + + ))} + + ) : ( + + 画册加载中... + + )} + + + ) }