文件一般写法
// 引入统一封装api请求
import { getById } from "@/api";
// 引入ui组件库
import { Toast } from "antd-mobile";
// useEffect 类似vue中watch,或者moundted生命周期,视第二参数数据而定
// useState 是vue2的data、是vue3的ref或reactive,总之是动态绑定数据,用于视图使用
// useMemo 用于防止子组件随父组件变动而实时刷新,尤其是无畏刷新
import { useEffect, useState, useMemo } from "react";
// useSearchParams,查找路由后面?跟的数据
// useLocation,获取路由pathname
import { useSearchParams, useLocation } from "react-router-dom";
// 引入样式,如果以module方式命名,类似于vue中的scss scoped
import Styles from './index.module.less'
import '@/layout/navbar/index.less'
// 自用组件引入
import PvForm from "@/components/PvForm";
// searchRoute搜索路由总体参数(包含meta等)
import {searchRoute} from "@/router/utils/guard.jsx";
// rootRouter函数式路由 汇总
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: '',
},
],
})
// api接口获取信息处理
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) => {
// 监听div内滚动条距离顶部距离
if(e && e.target.scrollTop) {
setScrollTop(e.target.scrollTop)
}
}
// * 渲染后加载 useEffect
useEffect(() => {
// 作接口请求
const { stationId } = props
const routeParams = searchParams.get("mode");
getDetail({stationId},routeParams)
// 监听滚动事件
window.addEventListener("scroll", handleScroll, true);
handleScroll()
// 销毁滚动监听事件 return中的执行 (同 vue2 beforeDestroy、vue3 onBeforeUnmount)
return () => window.removeEventListener("scroll", handleScroll, false);
}, []); // useEffect 第二参数,确定其重复次数 为空数组时,仅执行一次(同 vue2 mounted vue3 onMounted)
// 若无参数,则每次重绘都会执行(同 vue2 updated、vue3 onUpdated),若有参数则,参数变化才会变(同vue的watch监听)
// 使用useMemo 对子组件进行防止无用更新处理。第二个参数 是参数变换 才进行重新渲染标志
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) => {
// console.log('pvForm', props)
const [ tab, setTab ] = useState({
index: 0,
prop: props?.default || props?.prop // 适配tab项,如果没有tab 默认获取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;
}
}
}
查看公众号信息:

博客介绍了文件和组件的一般写法,还给出了图片preview组件的写法示例,主要围绕前端开发中React相关内容。
176

被折叠的 条评论
为什么被折叠?



