文件一般写法
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.meta
const [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} = res
if (success) {
judgeBuild(result);
} else {
Toast.show({
content: error || '信息获取失败'
});
}
})
}
const judgeBuild = (res) => {
const { projectType } = res
const 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 } = tdInfo
const { stationCode } = res
const 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 } = props
const 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>
})
}
<ImageViewer
image={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;
}
}
}