文件一般写法
import { getById } from "@/api";
import { Toast } from "antd-mobile";
import { useEffect, useState, useMemo } from "react";
import { useSearchParams, useLocation } from "react-router-dom";
import Styles from './index.module.less'
import '@/layout/navbar/index.less'
import PvForm from "@/components/PvForm";
import {searchRoute} from "@/router/utils/guard.jsx";
import {rootRouter} from "@/router/index.jsx";
const Station = (props) => {const { pathname } = useLocation()const route = searchRoute(pathname, rootRouter)const { nav } = route.metaconst [scrollTop, setScrollTop] = useState(0);const [searchParams, setSearchParams] = useSearchParams();const [ tdInfo, setTdInfo] = useState({title: '土地资质',list: [{ name: 'projectReportImage',label: '项目可研报告', type: 'text', value: '', },],})const getDetail = (params, routeParams = { mode: 'YX'}) => {getById(params).then(res => {const { success, result, error} = resif (success) {judgeBuild(result); } else {Toast.show({content: error || '信息获取失败'});}})}const judgeBuild = (res) => {const { projectType } = resconst isTdInfo = tabList.some(item => item.label === 'tdInfo')if (['PUB_BUILD'].includes(projectType) && !isTdInfo) {const index = tabList.findIndex(item => item.label === 'information')tabList.splice(index+1, 0, {name: '资质',label: 'tdInfo'})setTabList(tabList)const { list } = tdInfoconst { stationCode } = resconst projectReportImage = res.projectReportImage ? `报告-${stationCode}` : '无'const landImage = res.landImage ? `性质-${stationCode}` : '无'const landContractImage = res.landContractImage ? `公示-${stationCode}` : '无'const noImpactImage = res.noImpactImage ? `证明-${stationCode}` : '无'const loadReportImage = res.loadReportImage ? `报告-${stationCode}` : '无'Object.assign(commObj, { projectReportImage, landImage, landContractImage, noImpactImage, loadReportImage })list.forEach(item => {item.value = commObj[item.name]})setTdInfo({...projectInfo, list})}}const handleScroll = (e) => {if(e && e.target.scrollTop) {setScrollTop(e.target.scrollTop)}}useEffect(() => {const { stationId } = propsconst routeParams = searchParams.get("mode");getDetail({stationId},routeParams)window.addEventListener("scroll", handleScroll, true);handleScroll()return () => window.removeEventListener("scroll", handleScroll, false);}, []);
const projectInfoDom = useMemo(() => <PvForm tdInfo={tdInfo}/>, [tdInfo]);return (<><div className={`${Styles.content} w-full`}>{scrollTop < 15 && <div className={`${Styles.head} w-full navbar ${nav}`}></div>}<div className={`${Styles.bottom}`} style={{padding: '0 12px'}}>{projectInfoDom}<div style={{marginTop: '10px'}}>{projectInfoDom}</div></div></div></>)
}export default Station
.navbar {border-bottom: 0;
}
.content {background: #F7F8FA;min-height: calc(100vh - 45px);padding-bottom: 48px;.head {height: 36px;position: absolute;top: 40px;border-bottom: 0;}.bottom {position: relative;z-index: 1;}
}
组件一般写法
import { useEffect, useState } from "react";
import PvImageViewer from "@/components/PvForm/PvImageViewer"
import PvConfTable from "@/components/PvForm/PvConfTable"
import PvTimeLine from "@/components/PvForm/PvTimeLine"
import "./index.less"
import noData from "@/assets/img/noData.png"const PvForm = (props) => {const [ tab, setTab ] = useState({index: 0,prop: props?.default || props?.prop })const chooseTab = (index, item) => {setTab({index, prop: props[item.label] || {list:[]} })props.communication(index, item)}const isEmpty = (a) => {if (['', null, undefined].includes(a)) return true if (Array.prototype.isPrototypeOf(a) && a.length === 0 ) return true; if (Object.prototype.isPrototypeOf(a) && Object.keys(a).length === 0 ) return true; return false;}return (<><div className="content w-full">{ props.tabList && props.tabList.length && <div className='tab-list flex'>{props.tabList.map((item, index) => {return <div className={`tab-item ${tab.index === index ? 'tab-item-active' : ''}`} key={item.name} onClick={() => chooseTab(index, item)}>{item.name}</div>})}</div>}{tab.prop.title && <div className="title">{tab.prop.title}</div>}{tab.prop.list && tab.prop.list.every(item => isEmpty(item.value)) && <div className="flex flex-col items-center w-full"><img style={{width: '200px', height: '200px'}} src={noData}/><div style={{color: "#999999"}}>暂无信息</div></div>}<div className="items">{tab.prop.list.map((item, index) => {if (!isEmpty(item.value)) {if (['text'].includes(item.type)) {return <div className="form-item flex" key={item.name}><div className='item-label'>{item.label}</div><div className="item-value">{item.value}</div><div className="item-unit">{item.unit}</div></div>}if (['upload'].includes(item.type)) {return <div className="form-item" key={item.name}><div className='item-label'>{item.label}</div><div className="item-value"><PvImageViewer list={item.value}></PvImageViewer></div><div className="item-bottom-line"></div></div>}if (['uploads'].includes(item.type)) {return item.value.map(it => {return <div className="form-item" key={it.name}><div className='item-label'>{it.label}</div><div className="item-value"><PvImageViewer list={it.value}></PvImageViewer></div></div>})}if (['table'].includes(item.type)) {return <div key={index}>{item.label && <div className="title">{ item.label }</div>}{item.value.map((it, ind) => {return <PvConfTable list={it} key={ind}/>})}</div>}if (['timeline'].includes(item.type)) {return <div key={index}>{item.label && <div className="title">{ item.label }</div>}<PvTimeLine list={item.value}></PvTimeLine></div>}}})}</div></div></>)
}export default PvForm
.content {background: #ffffff;border-radius: 8px;padding: 16px 0 20px 0;// font-size: var(--adm-font-size-7);.tab-list {margin: 17px 15px 16px 15px;.tab-item {flex: 1;height: 32px;line-height: 32px;background: #2283e21a;font-size: 12px;color: #2283E2;border: 1px solid #2283e21a;text-align: center;&:nth-child(1) {border-radius: 4px 0px 0px 4px;}&:nth-last-child(1) {border-radius: 0px 4px 4px 0px;}}.tab-item-active {background: #2283E2;border: 1px solid #2283E2;color: #ffffff;}}.title {font-size: 14px;font-weight: bold;color: #323233;padding: 0 0 12px 0;margin: 0 15px 0 15px;border-bottom: 1px solid #EBEDF0;&:nth-child(n+2) {margin-top: 16px;}}.form-item {margin-top: 12px;padding: 0 15px;font-size: 14px;color: #C8C9CC;.item-label {width: 90px;}.item-bottom-line {margin-top: 12px;height: 1px;background: #EBEDF0;}.item-value {color: #323233;flex: 1;}}
}
图片preview 组件 写法示例
import { useEffect, useState } from "react";
import { ImageViewer } from "antd-mobile";
import "./index.less"const PvImageViewer = (props) => {const [visible, setVisible] = useState(false)const [imageSrc, setImageSrc] = useState(null)const imageView = (src) => {setImageSrc(src)setVisible(true)}return (<><div className="image-view flex flex-wrap items-baseline">{props.list &&props.list.length &&props.list.map((item, index) => {return <div className="image-item flex flex-col align-center justify-center" style={{width: '30%'}} onClick={ () => imageView(item.imgSrc) } key={index}><img className="w-full" style={{ objectFit: 'cover', height: '26vw' }} src={item.imgSrc + '?x-oss-process=image/resize,m_lfit,h_200,w_200'} alt="" /><div className="image-name">{item.imgName}</div></div>})}<ImageViewerimage={imageSrc}visible={visible}onClose={() => {setVisible(false)}}/></div></>)
}export default PvImageViewer
.image-view {.image-item {margin-top: 8px;&:nth-child(3n+2) {margin-left: 15px;}&:nth-child(3n+3) {margin-left: 15px;}.image-name {font-size: 10px;margin-top: 6px;color: #969799;text-align: center;display: block;}}
}