diff --git a/iconfont.json b/iconfont.json index 143a62b..d0e744f 100644 --- a/iconfont.json +++ b/iconfont.json @@ -1,5 +1,5 @@ { - "symbol_url": "//at.alicdn.com/t/c/font_3786318_isejgadt4t.js", + "symbol_url": "//at.alicdn.com/t/c/font_3786318_sqfeq7399ga.js", "save_dir": "./src/components/iconfont", "use_typescript": false, "use_rpx": true, diff --git a/src/api/material.ts b/src/api/material.ts index 6c7c40a..5a97c51 100644 --- a/src/api/material.ts +++ b/src/api/material.ts @@ -49,7 +49,6 @@ export const GetClassList = () => { */ export const GetProductDetailApi = () => { return useRequest({ - // url: '/v1/mall/product', url: '/v3/mallCherry/product', method: 'get', }) diff --git a/src/api/search.ts b/src/api/search.ts index 174cace..b5dd532 100644 --- a/src/api/search.ts +++ b/src/api/search.ts @@ -28,4 +28,44 @@ export const AddSearchHistoryApi = () => { url: '/v1/mall/searchHistory', method: 'post', }) -} +} + +/** + * 获取标签组列表 + */ +export const GetLabelGroupListApi = () => { + return useRequest({ + url: '/v1/mall/product/label/group/list', + method: 'get', + }) +} + +/** + * 首页跳转入口 + */ +export const HomePageJumpApi = () => { + return useRequest({ + url: '/v1/mall/product/label/page', + method: 'get', + }) +} + +/** + * 获取面料列表(首页进入) + */ +export const GetLabelProductsApi = () => { + return useRequest({ + url: '/v1/mall/product/label/products', + method: 'get', + }) +} + +/** + * [枚举]首页跳转分类的枚举 + */ +export const EnumLabelPageJumpApi = () => { + return useRequest({ + url: '/v1/mall/product/label/page_jump', + method: 'get', + }) +} diff --git a/src/app.config.ts b/src/app.config.ts index d2213c1..22920f0 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -41,6 +41,12 @@ export default { borderStyle: 'white', }, subPackages: [ + { + root: 'pages/category', + pages: [ + 'index', + ], + }, { root: 'pages/search', pages: [ diff --git a/src/common/constant.ts b/src/common/constant.ts index 3f4895b..6223306 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -1,11 +1,11 @@ -// export const BASE_URL = CURRENT_BASE_URL +export const BASE_URL = CURRENT_BASE_URL // export const BASE_URL = `http://192.168.0.75:50001/lymarket` // export const BASE_URL = `http://192.168.0.89:50001/lymarket` // export const BASE_URL = `http://10.0.0.5:50001/lymarket` // export const BASE_URL = `http://192.168.0.89:40001/lymarket` // export const BASE_URL = `http://192.168.1.165:40001/lymarket` // 王霞 // export const BASE_URL = 'https://test.zzfzyc.com/lymarket' // 测试环境 -export const BASE_URL = 'https://pre.zzfzyc.com/lymarket' // 预发布 +// export const BASE_URL = 'https://pre.zzfzyc.com/lymarket' // 预发布 // export const BASE_URL = `http://192.168.1.9:40001/lymarket` // 发 // export const BASE_URL = `http://192.168.1.9:50005/lymarket` // 发 // export const BASE_URL = `http://192.168.1.30:50001/lymarket` // 发 @@ -13,7 +13,7 @@ export const BASE_URL = 'https://pre.zzfzyc.com/lymarket' // 预发布 // export const BASE_URL = 'https://www.zzfzyc.com/lymarket' // 正式环境 // export const BASE_URL = `http://192.168.1.5:40001/lymarket` // 王霞 // export const BASE_URL = 'http://192.168.1.7:50002/lymarket' // 添 -// export const BASE_URL = 'http://192.168.1.28:50001/lymarket' // 婷 +// export const BASE_URL = 'http://192.168.1.28:50002/lymarket' // 婷 // export const BASE_URL = 'http://192.168.1.42:50002/lymarket' // 杰 // CDN @@ -49,6 +49,10 @@ export const BANk_WX_APPID = 'wx65934ee32a88d726' // 支付码单跳转链接 export const PAY_H5_CODE_URL = CURRENT_PAY_H5_CODE_URL +// 首页三张图片 +export const HOME_ITEM1 = getCDNSource('/mall/home_item1.png') +export const HOME_ITEM2 = getCDNSource('/mall/home_item2.png') +export const HOME_ITEM3 = getCDNSource('/mall/home_item3.png') // 场景值 export const SCENE = { diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index 3c0f3d1..7eca285 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -33,7 +33,7 @@ const Banner = (props: params) => { } const showDot = useMemo(() => { - return list.length > 1 + return list?.length > 1 }, [list]) useEffect(() => { getData() diff --git a/src/components/cell/index.module.scss b/src/components/cell/index.module.scss new file mode 100644 index 0000000..4f4cb32 --- /dev/null +++ b/src/components/cell/index.module.scss @@ -0,0 +1,27 @@ +.cell { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + font-size: 28px; + padding: 12px 0; + + .title { + min-width: 140px; + flex: none; + color: #a9a9a9; + } + + .desc { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-end; + flex: 1 1 auto; + color: $color_font_one; + } +} + +.descText { + @include common_ellipsis(2); +} diff --git a/src/components/cell/index.tsx b/src/components/cell/index.tsx new file mode 100644 index 0000000..0431b89 --- /dev/null +++ b/src/components/cell/index.tsx @@ -0,0 +1,46 @@ +import { Text, View } from '@tarojs/components' +import classNames from 'classnames' +import type { FC } from 'react' +import { useCallback, useMemo, useRef } from 'react' +import type { IconNames } from '../iconfont/iconfont' +import IconFont from '../iconfont/iconfont' +import styles from './index.module.scss' + +interface CellPropsType { + title: string | React.ReactNode + desc: string | React.ReactNode + isLink?: boolean + onClick?: () => void + customStyle?: React.CSSProperties + customClassName?: string + customDescClassName?: string + iconName?: IconNames +} +const Cell: FC = (props) => { + const { title, desc, isLink = false, customClassName, customStyle, onClick, iconName, customDescClassName } = props + + const onClickRef = useRef(onClick) + onClickRef.current = onClick + // 防止函数的引用改变 + const handleClick = useCallback(() => { + return isLink && onClickRef.current?.() + }, [isLink]) + + const CellElement = useMemo(() => { + return ( + + + {iconName && } + {title} + + + {desc} + {isLink && } + + + ) + }, [title, desc, handleClick, customStyle]) + + return <>{CellElement} +} +export default Cell diff --git a/src/components/dropDown-item/index.module.scss b/src/components/dropDown-item/index.module.scss new file mode 100644 index 0000000..b770d80 --- /dev/null +++ b/src/components/dropDown-item/index.module.scss @@ -0,0 +1,22 @@ +.dropDownItem { + width: 100%; + + &--title { + position: relative; + background-color: white; + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + padding: 24px 0; + color: #8c8c8c; + font-size: 28px; + &--text { + display: block; + margin-right: 12px; + } + } + &Options { + padding: 24px 40px; + } +} diff --git a/src/components/dropDown-item/index.tsx b/src/components/dropDown-item/index.tsx new file mode 100644 index 0000000..b2f3d51 --- /dev/null +++ b/src/components/dropDown-item/index.tsx @@ -0,0 +1,160 @@ +/** + * 注意:需要在父节点设置 position: relative; + */ + +import { Text, View } from '@tarojs/components' +import Taro from '@tarojs/taro' +import type { Ref } from 'react' +import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react' +import classNames from 'classnames' +import Iconfont from '../iconfont/iconfont' +import Popup from '../popup' +import Cell from '../cell' +import styles from './index.module.scss' +// 弹窗选择向上弹窗还是向下弹窗 +type Direction = 'up' | 'down' +// 配置 菜单可选项 +export interface DropDownOptions { + text: string + value: number +} + +interface DropDownEvent { + change?: (value: DropDownOptions['value']) => void // value 变化时触发 + onCloseOverlay?: () => void +} + +export interface DropDownItemRef { + show: boolean + closePopup: () => void + showPopup: () => void +} + +interface PropsType extends DropDownEvent { + direction?: Direction + options?: DropDownOptions[] + title?: string + value?: number | string // 当前选中的值 + children?: React.ReactNode + activeColor?: string + showOverlay?: boolean + customClassName?: string + customStyle?: React.CSSProperties + hasBottomBtn?: boolean // 底部有按钮不允许点击蒙层关闭 + titleSlot?: React.ReactNode +} +const DropDownItem = (props: PropsType, ref: Ref) => { + const { children, titleSlot, direction = 'down', title = '', value, options = [], change, activeColor, showOverlay = true, hasBottomBtn = false, customClassName, customStyle, onCloseOverlay } = props + + const [showPopup, setShowPopup] = useState(false) + + useImperativeHandle(ref, () => ({ + show: showPopup, + // 暴露出方法给有需要的用 + closePopup() { + setShowPopup(false) + }, + showPopup() { + setShowPopup(true) + }, + }), [showPopup]) + + const handleClickOption = (value: DropDownOptions['value']) => { + change?.(value) + } + + const [text, setText] = useState(options?.[0]?.text || '') + + const defaultOptions = useMemo(() => { + const currentValue = value + return options?.map(({ text, value }, key) => { + currentValue === value && setText(text) + return handleClickOption(value)}> + }) + }, [value]) + + const getIconName = () => { + if (direction === 'up') { + return showPopup ? 'icon-zhankai1' : 'icon-shouqi1' + } + // down + return showPopup ? 'icon-shouqi1' : 'icon-zhankai1' + } + + const handleClickTitle = () => { + console.log('handleClickTitle', showPopup) + onCloseOverlay?.() + setShowPopup(prev => !prev) + } + + const handleClosePopup = () => { + if (hasBottomBtn) { return } + setShowPopup(false) + onCloseOverlay?.() + } + + const [overlayOffsetTop, setOverlayOffsetTop] = useState('unset') + // 获取遮罩层的样式 + const getOverlayStyle = (): React.CSSProperties => { + return { position: !showOverlay ? 'fixed' : 'absolute', top: 0 } + } + + // 获取整个页面的完整高度 + const [scrollHeight, setScrollHeight] = useState(0) + + useEffect(() => { + const query = Taro.createSelectorQuery() + query.select('#DropDownItem').boundingClientRect() + query.selectViewport().scrollOffset() + query.exec((res) => { + console.log('res==>', res) + setScrollHeight(res[1].scrollHeight) + if (direction === 'down') { + setOverlayOffsetTop(`${res[0].bottom}px`) + } + else { + setOverlayOffsetTop(`${res[0].top}px`) + } + }) + }, []) + + // 获取样式 + const getCustomStyle: React.CSSProperties = useMemo(() => { + if (direction === 'up') { + return { position: 'absolute', top: 0, height: overlayOffsetTop } + } + else { + return { position: 'absolute', top: overlayOffsetTop, height: `calc(${`${scrollHeight}px`} - ${overlayOffsetTop})` } + } + }, [overlayOffsetTop]) + + return ( + + + { + titleSlot || <> + + + {title || text} + + + + } + + + {/* showPopup 为true才显示children 是因为真机环境下 children内的字体有穿透的情况 */} + {showPopup && (children || {defaultOptions})} + + + ) +} +export default forwardRef(DropDownItem) diff --git a/src/components/filter/index.module.scss b/src/components/filter/index.module.scss index 7cc0f3f..8ed68f8 100644 --- a/src/components/filter/index.module.scss +++ b/src/components/filter/index.module.scss @@ -1,34 +1,34 @@ .popup_main { - width: 608px; - height: 100vh; + height: 70vh; padding: 20px; + padding-bottom: 0; box-sizing: border-box; display: flex; - flex-direction: column; + flex-flow: column nowrap; + overflow: hidden; .popup_title { font-size: $font_size; font-weight: 700; text-align: center; padding: 20px 0; } - .scroll { - flex: 1; - height: 0; - } .popup_filter { - padding-bottom: 100px; + flex: 1 1 auto; + overflow: scroll; } .popup_filter_item { margin-bottom: 20px; .title { - font-size: $font_size; + font-size: 28px; color: $color_font_one; font-weight: 700; padding: 20px 0; } .btn_list { display: grid; - grid-template-columns: repeat(3, 165.75px); + grid-template-columns: 1fr 1fr 1fr; + grid-row-gap: 16px; + grid-column-gap: 16px; justify-content: space-between; .btn_item { height: 69.2px; @@ -38,7 +38,8 @@ text-align: center; font-size: $font_size_medium; color: $color_font_one; - margin-bottom: 20px; + border: 2px solid transparent; + box-sizing: border-box; @include common_ellipsis(); } .select_btn_item { @@ -65,11 +66,6 @@ font-size: $font_size_medium; } } - .unit { - color: $color_font_one; - font-size: $font_size; - margin-left: 20px; - } text { color: #ccc; padding: 0 20px; @@ -82,43 +78,28 @@ border-radius: 30px; padding: 20px; box-sizing: border-box; - textarea { - width: 100%; - height: 126px; - font-size: $font_size_medium; - } } } .btns_con { width: 100%; - position: fixed; - bottom: 0; - padding-bottom: constant(safe-area-inset-bottom); - padding-bottom: env(safe-area-inset-bottom); - .btns_two { - display: flex; - width: 552px; - height: 82px; - border: 2px solid #cde5ff; - box-sizing: border-box; - font-size: $font_size_big; - border-radius: 40px; - margin-bottom: 20px; - .rest_btn { - flex: 1; - border-radius: 0px 40px 40px 0px; - text-align: center; - line-height: 82px; - color: $color_main; - } - .verify_btn { - flex: 1; - border-radius: 0px 40px 40px 0px; - background: $color_main; - text-align: center; - line-height: 82px; - color: #fff; - } + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + padding: 32px 0; + // .btns_two { + // display: flex; + // height: 82px; + // border: 2px solid #cde5ff; + // box-sizing: border-box; + // font-size: $font_size_big; + // border-radius: 40px; + // margin-bottom: 20px; + .rest_btn { + width: 48%; } + .verify_btn { + width: 48%; + } + // } } } diff --git a/src/components/filter/index.tsx b/src/components/filter/index.tsx index a93bd23..7966d98 100644 --- a/src/components/filter/index.tsx +++ b/src/components/filter/index.tsx @@ -1,149 +1,209 @@ import { Input, ScrollView, Text, Textarea, View } from '@tarojs/components' -import { useDidShow } from '@tarojs/taro' +import { useDidShow, useReady, useUnload } from '@tarojs/taro' import classnames from 'classnames' -import { memo, useEffect, useRef, useState } from 'react' +import type { Ref } from 'react' +import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' +import { nextTick } from '@tarojs/runtime' +import DropDownItem from '../dropDown-item' +import type { DropDownItemRef } from '../dropDown-item' +import NormalButton from '../normalButton' +import IconFont from '../iconfont/iconfont' import styles from './index.module.scss' -import Popup from '@/components/popup' -import type { Params as PopuParams } from '@/components/popup' -import { GetProductKindListApi } from '@/api/material' +import type { Params as PopupParams } from '@/components/popup' +import { GetLabelGroupListApi } from '@/api/search' -type params = { - onFiltr?: (val: object) => void // 确定搜索 - onRest?: (val: Object) => void // 重置 -} & PopuParams -const Filter = ({ onClose, onFiltr, show = false, onRest }: params) => { +interface params extends PopupParams { + defaultValue?: FilterOptions['label_ids'] + onFilter?: (val: object) => void // 确定搜索 + onReset?: (val: Object) => void // 重置 +} + +interface FilterOptions { + label_ids: number[] + width: string + weight_density: string + component: string +} + +const Filter = (props: params) => { + const { onFilter, onReset, defaultValue = [] } = props + console.log('rerender') // 搜索条件 - const [filterObj, setFilterObj] = useState({ - seriesName: '', - seriesId: '', - width: '', - weight: '', - element: '', + const [filterObj, setFilterObj] = useState({ + label_ids: defaultValue, + width: '', // 幅宽 + weight_density: '', // 克重 + component: '', // 成分 }) - const selectFieldValue = useRef({ width: '幅宽', weight: '克重', element: '成分', seriesName: '系列' }) + useEffect(() => { + console.log('defaultValue', defaultValue) + setFilterObj((prev) => { + return { + ...prev, + label_ids: defaultValue, + } + }) + }, [defaultValue]) + + const dropDownRef = useRef(null) // 获取系列 - const { fetchData: kindFetchData } = GetProductKindListApi() + const { fetchData: kindFetchData } = GetLabelGroupListApi() + const [kindList, setKindList] = useState([]) + const getCategoryList = async() => { const { data } = await kindFetchData() setKindList(data.list) } useEffect(() => { - show && getCategoryList() - }, [show]) + getCategoryList() + }, []) // 切换系列 - const changeKind = (e) => { - setFilterObj({ ...filterObj, seriesId: e.id, seriesName: e.name }) - } - - const onCloseEven = () => { - onClose?.() + const changeKind = (id: number) => { + setFilterObj((prev) => { + return { + ...prev, + label_ids: (() => { + if (prev.label_ids.includes(id)) { + const index = prev.label_ids.findIndex(item => item === id) + prev.label_ids.splice(index, 1) + return prev.label_ids + } + else { + return [...prev.label_ids, id] + } + })(), + } + }) } // 重置数据 const onRestEven = () => { const res = { - seriesName: '', - seriesId: '', + label_ids: [], width: '', - weight: '', - element: '', + weight_density: '', + component: '', } setFilterObj(res) - onFiltr?.(filterObj) - onClose?.() + onFilter?.(res) + onReset?.(res) + dropDownRef.current?.closePopup() } // 提交搜索 const onVerify = () => { - onFiltr?.({ data: filterObj, field: selectFieldValue.current }) - onClose?.() + onFilter?.(filterObj) + dropDownRef.current?.closePopup() } // 获取幅宽或克重输入值或成分 - const setFieldData = (e, field) => { + const setFieldData = (e, field: keyof FilterOptions) => { filterObj[field] = e.detail.value setFilterObj({ ...filterObj }) } - return ( - onCloseEven()} showIconButton> - - 全部筛选 - - - - 系列 - - {kindList.map(item => ( - changeKind(item)} - className={classnames(styles.btn_item, filterObj.seriesId == item.id && styles.select_btn_item)} - > - {item.name} - - ))} - - - - 幅宽 - - - setFieldData(e, 'width')} - placeholder="请输入幅宽" - placeholderStyle="font-size: 26rpx" - /> - - cm - - - - 克重 - - - setFieldData(e, 'weight')} - placeholder="请输入克重" - placeholderStyle="font-size: 26rpx" - /> - - kg - - - - 成分 - -