Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
basic-uniapp-v3
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
1
合并请求
1
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Basic
basic-uniapp-v3
Commits
69e1168c
提交
69e1168c
authored
3月 31, 2026
作者:
廖在望
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 登录页面、供应需求、采购需求、地区组件、相关详情页面重构&问题修复。
上级
b5983f10
隐藏空白字符变更
内嵌
并排
正在显示
10 个修改的文件
包含
1760 行增加
和
175 行删除
+1760
-175
index.vue
src/components/AreaPicker/index.vue
+1
-1
chanxiao.vue
src/pages/chanxiao/chanxiao.vue
+110
-26
purchaseXuQiu.vue
src/pages/chanxiao/purchaseXuQiu.vue
+307
-37
supplyXuQiu.vue
src/pages/chanxiao/supplyXuQiu.vue
+301
-38
login.vue
src/pages/login/login.vue
+6
-3
register.vue
src/pages/login/register.vue
+5
-3
nongchang.vue
src/pages/nongchang/nongchang.vue
+64
-12
farm-form.vue
src/pages/nongjifuwu/farm-form.vue
+413
-14
machine-form.vue
src/pages/nongjifuwu/machine-form.vue
+522
-11
area.ts
src/utils/dict/area.ts
+31
-30
没有找到文件。
src/components/AreaPicker/index.vue
浏览文件 @
69e1168c
...
...
@@ -93,7 +93,7 @@
border-radius
:
32
rpx
32
rpx
0
0
;
display
:
flex
;
flex-direction
:
column
;
height
:
8
0vh
;
height
:
6
0vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
...
...
src/pages/chanxiao/chanxiao.vue
浏览文件 @
69e1168c
<
script
setup
lang=
"ts"
>
import
{
reactive
}
from
'vue'
import
{
reactive
,
onMounted
}
from
'vue'
import
{
onPullDownRefresh
,
onReachBottom
,
onShow
}
from
'@dcloudio/uni-app'
import
PriceDialog
from
'./components/price-dialog.vue'
import
Navigate
from
'@/utils/page/navigate'
import
*
as
ChanxiaoAPI
from
'@/api/model/chanxiao'
import
{
getText
}
from
'@/utils/dict/area'
import
{
getDictData
,
getText
}
from
'@/utils/dict/area'
onMounted
(()
=>
{
getDictData
()
})
// 下拉刷新
onPullDownRefresh
(()
=>
{
...
...
@@ -26,13 +30,13 @@
},
1000
)
})
onShow
(()
=>
{
pageData
.
search
.
pageNo
=
1
if
(
pageData
.
currentTransactionTab
===
1
)
{
pageData
.
s
upplyInfos
=
[]
// 只有当列表为空时才重新获取数据,避免每次返回页面都全量刷新
if
(
pageData
.
currentTransactionTab
===
1
&&
pageData
.
supplyInfos
.
length
===
0
)
{
pageData
.
s
earch
.
pageNo
=
1
fetchSupplyInfos
()
}
if
(
pageData
.
currentTransactionTab
===
2
)
{
pageData
.
purchaseDemands
=
[]
if
(
pageData
.
currentTransactionTab
===
2
&&
pageData
.
purchaseDemands
.
length
===
0
)
{
pageData
.
search
.
pageNo
=
1
fetchPurchaseDemands
()
}
})
...
...
@@ -151,11 +155,16 @@
ChanxiaoAPI
.
purchaseList
(
pageData
.
search
)
.
then
((
res
)
=>
{
const
{
records
,
total
}
=
res
pageData
.
purchaseDemands
=
[...
pageData
.
purchaseDemands
,
...
records
]
pageData
.
purchaseDemands
=
pageData
.
purchaseDemands
.
map
((
item
)
=>
({
...
item
,
location
:
getText
(
`
${
item
.
province
}
,
${
item
.
city
}
,
${
item
.
country
}
`
,
' / '
),
}))
const
mappedRecords
=
records
.
map
((
item
)
=>
{
const
nameParts
=
[
item
.
cityName
,
item
.
countryName
].
filter
(
Boolean
)
return
{
...
item
,
location
:
nameParts
.
length
>
0
?
nameParts
.
join
(
''
)
:
getText
(
`
${
item
.
city
}
,
${
item
.
country
}
`
,
''
),
}
})
pageData
.
purchaseDemands
=
[...
pageData
.
purchaseDemands
,
...
mappedRecords
]
pageData
.
total
=
total
})
.
finally
(()
=>
{
...
...
@@ -171,11 +180,16 @@
ChanxiaoAPI
.
supplyList
(
params
)
.
then
((
res
)
=>
{
const
{
records
,
total
}
=
res
pageData
.
supplyInfos
=
[...
pageData
.
supplyInfos
,
...
records
]
pageData
.
supplyInfos
=
pageData
.
supplyInfos
.
map
((
item
)
=>
({
...
item
,
location
:
getText
(
`
${
item
.
province
}
,
${
item
.
city
}
,
${
item
.
country
}
`
,
' / '
),
}))
const
mappedRecords
=
records
.
map
((
item
)
=>
{
const
nameParts
=
[
item
.
cityName
,
item
.
districtName
].
filter
(
Boolean
)
return
{
...
item
,
location
:
nameParts
.
length
>
0
?
nameParts
.
join
(
''
)
:
getText
(
`
${
item
.
city
}
,
${
item
.
district
}
`
,
''
),
}
})
pageData
.
supplyInfos
=
[...
pageData
.
supplyInfos
,
...
mappedRecords
]
pageData
.
total
=
total
})
.
finally
(()
=>
{
...
...
@@ -388,6 +402,12 @@
<!-- 采购需求列表 -->
<view
v-if=
"pageData.currentTransactionTab === 2"
>
<view
v-if=
"pageData.purchaseDemands.length === 0"
style=
"height: 528rpx"
>
<fui-empty
src=
"/static/images/no-data.png"
title=
"暂无数据"
/>
</view>
<view
v-for=
"(demand, index) in pageData.purchaseDemands"
:key=
"demand.id"
class=
"product-card purchase"
...
...
@@ -424,22 +444,86 @@
</view>
</view>
</view>
<fui-fab
position=
"right"
distance=
"30"
bottom=
"150"
width=
"96"
@
click=
"handlePublish"
>
<view
v-show=
"pageData.currentTransactionTab === 1"
class=
"text-white text-center"
>
<view
class=
"fab-icon"
/>
<view
style=
"font-size: 24rpx"
>
发布
</view>
</view>
<view
v-show=
"pageData.currentTransactionTab === 2"
class=
"text-white text-center"
>
<view
class=
"fab-icon"
/>
<view
style=
"font-size: 24rpx"
>
发布
</view>
<!-- 发布悬浮按钮 -->
<view
class=
"fab-container"
@
click=
"handlePublish"
>
<view
class=
"ripple"
:class=
"
{ 'ripple-purchase': pageData.currentTransactionTab === 2 }">
</view>
<view
class=
"ripple ripple-2"
:class=
"
{ 'ripple-purchase': pageData.currentTransactionTab === 2 }">
</view>
<view
class=
"fab-entry"
:class=
"
{ 'fab-purchase': pageData.currentTransactionTab === 2 }">
<fui-icon
name=
"plus"
:size=
"48"
color=
"#fff"
bold
></fui-icon>
<text>
发布
</text>
</view>
</
fui-fab
>
</
view
>
<PriceDialog
ref=
"priceDialogRef"
/>
<fui-loading
isFixed
v-if=
"pageData.loading"
backgroundColor=
"rgba(0, 0, 0, 0.4)"
/>
</
template
>
<
style
scoped
lang=
"scss"
>
.fab-container
{
position
:
fixed
;
right
:
30
rpx
;
bottom
:
150
rpx
;
width
:
110
rpx
;
height
:
110
rpx
;
z-index
:
100
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
.ripple
{
position
:
absolute
;
width
:
100%
;
height
:
100%
;
background-color
:
#5db66f
;
border-radius
:
50%
;
opacity
:
0.4
;
animation
:
ripple
2s
infinite
ease-out
;
&.ripple-purchase
{
background-color
:
#fa8c16
;
}
}
.ripple-2
{
animation-delay
:
1s
;
}
.fab-entry
{
position
:
relative
;
width
:
110
rpx
;
height
:
110
rpx
;
background
:
linear-gradient
(
135deg
,
#5db66f
0%
,
#4caf50
100%
);
border-radius
:
50%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
box-shadow
:
0
4
rpx
16
rpx
rgba
(
93
,
182
,
111
,
0.3
);
&.fab-purchase
{
background
:
linear-gradient
(
135deg
,
#ffbb96
0%
,
#fa8c16
100%
);
box-shadow
:
0
4
rpx
16
rpx
rgba
(
250
,
140
,
22
,
0.3
);
}
text
{
color
:
#fff
;
font-size
:
20
rpx
;
font-weight
:
bold
;
margin-top
:
-4
rpx
;
}
}
}
@keyframes
ripple
{
0
%
{
transform
:
scale
(
1
);
opacity
:
0.4
;
}
100
%
{
transform
:
scale
(
1.6
);
opacity
:
0
;
}
}
.ml-7
{
margin-left
:
14
rpx
;
}
...
...
src/pages/chanxiao/purchaseXuQiu.vue
浏览文件 @
69e1168c
...
...
@@ -5,7 +5,6 @@
import
{
useGlobSetting
}
from
'/@/hooks/setting'
import
*
as
ChanxiaoAPI
from
'@/api/model/chanxiao'
import
*
as
UserInfoAPI
from
'@/api/model/userInfo'
import
{
getCodeByText
}
from
'@/utils/areaData'
import
{
useDictStore
}
from
'@/store/modules/dict'
import
{
getText
}
from
'@/utils/dict/area'
import
AreaPicker
from
'@/components/AreaPicker/index.vue'
...
...
@@ -60,10 +59,9 @@
classify
:
''
,
classifyText
:
''
,
inputTextArea
:
''
,
image
:
null
,
imageObj
:
null
,
image
:
''
,
imageObj
:
null
as
any
,
},
position
:
[],
rules
:
[
{
name
:
'classify'
,
rule
:
[
'required'
],
msg
:
[
'请选择采购类别'
]
},
{
name
:
'title'
,
rule
:
[
'required'
],
msg
:
[
'请输入采购标题'
]
},
...
...
@@ -75,14 +73,25 @@
{
name
:
'address'
,
rule
:
[
'required'
],
msg
:
[
'请选择收货地区'
]
},
{
name
:
'image'
,
rule
:
[
'required'
],
msg
:
[
'请上传参考图片'
]
},
],
agree
:
true
})
const
{
show
,
options
,
form
}
=
toRefs
(
pageData
)
async
function
initDict
()
{
pageData
.
options
.
classify
=
dictStore
.
getDictList
.
classify
.
map
((
item
)
=>
{
return
{
value
:
item
.
value
,
text
:
item
.
text
}
})
pageData
.
options
.
classify
=
[
{
value
:
1
,
text
:
'蔬菜'
},
{
value
:
2
,
text
:
'水果'
},
{
value
:
3
,
text
:
'粮食'
},
{
value
:
4
,
text
:
'畜牧'
},
{
value
:
0
,
text
:
'其他'
},
]
// 如果有已选分类,需要回显文字
if
(
pageData
.
form
.
classify
&&
pageData
.
options
.
classify
.
length
)
{
const
item
=
pageData
.
options
.
classify
.
find
(
i
=>
i
.
value
==
pageData
.
form
.
classify
)
if
(
item
)
pageData
.
form
.
classifyText
=
item
.
text
}
}
function
handleAreaConfirm
(
e
)
{
...
...
@@ -90,28 +99,44 @@
pageData
.
form
.
province
=
e
.
text
[
0
]
pageData
.
form
.
city
=
e
.
text
[
1
]
pageData
.
form
.
country
=
e
.
text
[
2
]
// @ts-ignore
pageData
.
form
.
areaText
=
e
.
fullText
}
function
getCurrentAddressInfo
()
{
const
loc
=
uni
.
getStorageSync
(
'location'
)
if
(
!
loc
)
return
pageData
.
position
=
[
loc
.
lon
,
loc
.
lat
]
UserInfoAPI
.
location
({
lon
:
loc
.
lon
,
lat
:
loc
.
lat
}).
then
((
res
)
=>
{
pageData
.
form
.
province
=
res
.
province
pageData
.
form
.
city
=
res
.
city
pageData
.
form
.
country
=
res
.
country
pageData
.
form
.
address
=
`
${
res
.
province
}
,
${
res
.
city
}
,
${
res
.
country
}
`
pageData
.
form
.
areaText
=
`
${
res
.
province
}
/
${
res
.
city
}
/
${
res
.
country
}
`
// @ts-ignore
const
areaCodes
=
[
res
.
province
,
res
.
city
,
res
.
country
].
filter
(
Boolean
).
join
(
','
)
pageData
.
form
.
areaText
=
getText
(
areaCodes
,
' / '
)
})
}
function
getDetails
(
id
)
{
pageData
.
loading
=
true
ChanxiaoAPI
.
purchaseSellDetails
({
id
}).
then
((
res
)
=>
{
ChanxiaoAPI
.
purchaseSellDetails
({
id
}).
then
(
async
(
res
)
=>
{
pageData
.
form
=
res
// 格式化地区显示
pageData
.
form
.
areaText
=
`
${
res
.
province
||
''
}
/
${
res
.
city
||
''
}
/
${
res
.
country
||
''
}
`
.
replace
(
/
\/
+$/
,
''
)
// 确保字典已加载
if
(
!
pageData
.
options
.
classify
.
length
)
{
await
initDict
()
}
// 优先用接口返回的中文名,getText 作为兜底
const
nameParts
=
[
res
.
cityName
,
res
.
countryName
].
filter
(
Boolean
)
if
(
nameParts
.
length
>
0
)
{
// @ts-ignore
pageData
.
form
.
areaText
=
nameParts
.
join
(
''
)
}
else
{
const
areaCodes
=
[
res
.
city
,
res
.
country
].
filter
(
Boolean
).
join
(
','
)
// @ts-ignore
pageData
.
form
.
areaText
=
getText
(
areaCodes
,
''
)
}
if
(
res
.
classify
&&
pageData
.
options
.
classify
.
length
)
{
const
item
=
pageData
.
options
.
classify
.
find
(
i
=>
i
.
value
==
res
.
classify
)
...
...
@@ -149,6 +174,10 @@
}
const
formRef
=
ref
()
function
submit
()
{
if
(
!
pageData
.
agree
)
{
toastRef
.
value
.
show
({
text
:
'请先同意发布协议'
})
return
}
formRef
.
value
.
validator
(
pageData
.
form
,
pageData
.
rules
,
true
).
then
((
res
)
=>
{
if
(
res
.
isPassed
)
{
ChanxiaoAPI
.
purchaseSellAdd
(
pageData
.
form
).
then
(()
=>
{
...
...
@@ -166,34 +195,96 @@
<
template
>
<view
class=
"page"
>
<!-- 发布/编辑模式 -->
<view
v-if=
"isSave"
class=
"formBox"
>
<fui-form
ref=
"formRef"
label-weight=
"auto"
top=
"60"
>
<view
class=
"mt20"
>
<fui-input
disabled
required
label=
"采购类别"
v-model=
"form.classifyText"
@
click=
"show.classify = true"
labelSize=
"28"
label-width=
"180"
/>
<fui-input
required
label=
"招聘标题"
placeholder=
"请输入标题"
v-model=
"form.title"
labelSize=
"28"
label-width=
"180"
/>
<view
v-if=
"isSave"
class=
"form-container"
>
<view
class=
"header-card"
>
<image
class=
"header-bg"
src=
"/static/images/login/farm_base_bg.png"
mode=
"aspectFill"
/>
<view
class=
"header-overlay"
></view>
<view
class=
"header-content"
>
<view
class=
"title"
>
发布采购需求
</view>
<view
class=
"subtitle"
>
精准对接供货商,高效满足您的采购需求
</view>
<view
class=
"stepper-box"
>
<view
class=
"step-item active"
>
<view
class=
"step-num"
>
1
</view>
<view
class=
"step-text"
>
提交信息
</view>
</view>
<view
class=
"step-line"
></view>
<view
class=
"step-item"
>
<view
class=
"step-num"
>
2
</view>
<view
class=
"step-text"
>
后台审核
</view>
</view>
<view
class=
"step-line"
></view>
<view
class=
"step-item"
>
<view
class=
"step-num"
>
3
</view>
<view
class=
"step-text"
>
完成
</view>
</view>
</view>
</view>
<view
class=
"mt20"
>
<!-- 装饰图标 -->
<image
class=
"deco-icon"
src=
"/static/images/nongchang/device2.png"
mode=
"aspectFit"
/>
</view>
<fui-form
ref=
"formRef"
label-weight=
"auto"
>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
基本信息
</view>
<fui-input
disabled
required
label=
"采购分类"
v-model=
"form.classifyText"
@
click=
"show.classify = true"
placeholder=
"请选择类别"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
>
<template
#
right
>
<fui-icon
name=
"arrowright"
:size=
"32"
color=
"#ccc"
></fui-icon>
</
template
>
</fui-input>
<fui-input
required
label=
"采购标题"
placeholder=
"例如:长期大量采购红富士苹果"
v-model=
"form.title"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
/>
</view>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
预算与数量
</view>
<view
class=
"form-item required"
>
<text
class=
"label"
>
预算区间
</text>
<view
class=
"price-range"
>
<input
type=
"number"
class=
"price-input"
v-model=
"form.priceStart"
placeholder=
"最低价"
/>
<text
class=
"sep"
>
-
</text>
<input
type=
"number"
class=
"price-input"
v-model=
"form.priceEnd"
placeholder=
"最高价"
/>
<view
class=
"price-range-box"
>
<input
type=
"number"
class=
"price-input"
v-model=
"form.priceStart"
placeholder=
"最低价"
placeholder-class=
"ph-style"
/>
<text
class=
"sep"
>
至
</text>
<input
type=
"number"
class=
"price-input"
v-model=
"form.priceEnd"
placeholder=
"最高价"
placeholder-class=
"ph-style"
/>
<text
class=
"unit-text"
>
元
</text>
</view>
</view>
<fui-input
required
type=
"number"
label=
"采购数量"
v-model=
"form.count"
labelSize=
"28"
label-width=
"180
"
/>
<fui-input
required
label=
"
单位"
v-model=
"form.unit"
labelSize=
"28"
label-width=
"180
"
/>
<fui-input
required
type=
"number"
label=
"采购数量"
placeholder=
"请输入计划采购数量"
v-model=
"form.count"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']
"
/>
<fui-input
required
label=
"
计价单位"
placeholder=
"如:斤、箱、吨"
v-model=
"form.unit"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']
"
/>
</view>
<view
class=
"mt20"
>
<fui-input
disabled
required
label=
"收货地区"
v-model=
"form.areaText"
@
click=
"show.address = true"
labelSize=
"28"
label-width=
"180"
/>
<fui-input
required
label=
"截止日期"
v-model=
"form.deadLine"
@
click=
"show.time = true"
disabled
labelSize=
"28"
label-width=
"180"
/>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
时效与地区
</view>
<fui-input
disabled
required
label=
"收货地区"
v-model=
"form.areaText"
@
click=
"show.address = true"
placeholder=
"请选择收货地"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
>
<
template
#
right
>
<fui-icon
name=
"arrowright"
:size=
"32"
color=
"#ccc"
></fui-icon>
</
template
>
</fui-input>
<fui-input
required
label=
"截止日期"
v-model=
"form.deadLine"
@
click=
"show.time = true"
disabled
placeholder=
"请选择截止日期"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
>
<
template
#
right
>
<fui-icon
name=
"arrowright"
:size=
"32"
color=
"#ccc"
></fui-icon>
</
template
>
</fui-input>
</view>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
图片展示
</view>
<view
class=
"upload-section"
>
<view
class=
"upload-tip"
><span
class=
"required-star"
>
*
</span>
上传参考图片,帮助供货商更准确报价
</view>
<uni-file-picker
:value=
"form.imageObj"
limit=
"1"
@
select=
"handleUpload"
@
delete=
"form.image = ''"
/>
</view>
</view>
<view
class=
"bg-white mt20"
style=
"padding: 30rpx"
>
<view
class=
"mb-1"
style=
"font-size: 28rpx"
><span
style=
"color: red"
>
*
</span>
上传参考图片
</view>
<uni-file-picker
:value=
"form.imageObj"
limit=
"1"
@
select=
"handleUpload"
@
delete=
"form.image = null"
/>
<view
class=
"agreement-row"
>
<checkbox-group
@
change=
"pageData.agree = !pageData.agree"
>
<label
class=
"checkbox-label"
>
<checkbox
:checked=
"pageData.agree"
color=
"#fa8c16"
style=
"transform:scale(0.7)"
/>
<text
class=
"agreement-text"
>
我已阅读并同意
<text
class=
"link"
>
《湘农数智农服发布协议》
</text></text>
</label>
</checkbox-group>
</view>
<view
class=
"fui-btn__box"
>
<fui-button
text=
"立即发布"
bold
radius=
"100rpx"
@
click=
"submit"
/>
<view
class=
"submit-btn-box"
>
<fui-button
text=
"立即发布"
bold
radius=
"100rpx"
background=
"#fa8c16"
@
click=
"submit"
/>
</view>
</fui-form>
</view>
...
...
@@ -257,11 +348,187 @@
</template>
<
style
lang=
"scss"
scoped
>
.page
{
background-color
:
#f7f8fa
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
.formBox
{
padding
:
24
rpx
;
.mt20
{
background
:
#fff
;
border-radius
:
20
rpx
;
padding
:
10
rpx
24
rpx
;
margin-bottom
:
24
rpx
;
}
}
.price-range
{
display
:
flex
;
align-items
:
center
;
flex
:
1
;
margin-left
:
20
rpx
;
.price-input
{
flex
:
1
;
text-align
:
center
;
font-size
:
28
rpx
;
}
.sep
{
margin
:
0
10
rpx
;
color
:
#ccc
;
}
}
.form-item
{
padding
:
30
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
180
rpx
;
.red
{
color
:
#ff4d4f
;
}
}
}
.page
{
background-color
:
#f7f9fb
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
.form-container
{
padding-bottom
:
40
rpx
;
.header-card
{
position
:
relative
;
height
:
320
rpx
;
overflow
:
hidden
;
display
:
flex
;
align-items
:
center
;
.header-bg
{
position
:
absolute
;
width
:
100%
;
height
:
100%
;
z-index
:
1
;
}
.header-overlay
{
position
:
absolute
;
width
:
100%
;
height
:
100%
;
background
:
linear-gradient
(
135deg
,
rgba
(
250
,
140
,
22
,
0.9
)
0%
,
rgba
(
255
,
187
,
150
,
0.8
)
100%
);
z-index
:
2
;
}
.header-content
{
position
:
relative
;
z-index
:
3
;
width
:
100%
;
padding
:
0
40
rpx
;
margin-top
:
-20
rpx
;
}
.title
{
font-size
:
38
rpx
;
font-weight
:
bold
;
color
:
#fff
;
margin-bottom
:
6
rpx
;
text-shadow
:
0
2
rpx
4
rpx
rgba
(
0
,
0
,
0
,
0.1
);
}
.subtitle
{
font-size
:
24
rpx
;
color
:
rgba
(
255
,
255
,
255
,
0.9
);
}
.deco-icon
{
position
:
absolute
;
right
:
-20
rpx
;
bottom
:
20
rpx
;
width
:
200
rpx
;
height
:
200
rpx
;
opacity
:
0.15
;
z-index
:
2
;
transform
:
rotate
(
-15deg
);
}
}
.stepper-box
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-top
:
30
rpx
;
padding
:
0
20
rpx
;
.step-item
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
opacity
:
0.7
;
position
:
relative
;
z-index
:
2
;
&.active
{
opacity
:
1
;
.step-num
{
background
:
#fff
;
color
:
#fa8c16
;
border-color
:
#fff
;
box-shadow
:
0
4
rpx
10
rpx
rgba
(
0
,
0
,
0
,
0.1
);
}
.step-text
{
font-weight
:
bold
;
}
}
.step-num
{
width
:
36
rpx
;
height
:
36
rpx
;
border
:
2
rpx
solid
rgba
(
255
,
255
,
255
,
0.8
);
border-radius
:
50%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
22
rpx
;
font-weight
:
bold
;
margin-bottom
:
8
rpx
;
color
:
#fff
;
transition
:
all
0.3s
;
}
.step-text
{
font-size
:
20
rpx
;
color
:
#fff
;
}
}
.step-line
{
flex
:
1
;
height
:
2
rpx
;
background
:
rgba
(
255
,
255
,
255
,
0.4
);
margin
:
0
15
rpx
;
margin-top
:
-30
rpx
;
}
}
.form-group
{
background
:
#fff
;
margin
:
20
rpx
24
rpx
0
;
border-radius
:
20
rpx
;
padding
:
24
rpx
28
rpx
;
box-shadow
:
0
4
rpx
16
rpx
rgba
(
0
,
0
,
0
,
0.02
);
.group-title
{
font-size
:
28
rpx
;
font-weight
:
bold
;
color
:
#333
;
margin-bottom
:
20
rpx
;
display
:
flex
;
align-items
:
center
;
&::before
{
content
:
''
;
width
:
6
rpx
;
height
:
28
rpx
;
background
:
#fa8c16
;
border-radius
:
4
rpx
;
margin-right
:
12
rpx
;
}
}
}
}
.form-item
{
padding
:
20
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
160
rpx
;
}
}
.required-star
{
color
:
#ff4d4f
;
margin-right
:
4
rpx
;
}
.price-range-box
{
display
:
flex
;
align-items
:
center
;
flex
:
1
;
.price-input
{
flex
:
1
;
height
:
72
rpx
;
background
:
#f5f7f9
;
border-radius
:
12
rpx
;
padding
:
0
20
rpx
;
font-size
:
28
rpx
;
text-align
:
center
;
}
.sep
{
margin
:
0
16
rpx
;
color
:
#999
;
font-size
:
24
rpx
;
}
.unit-text
{
margin-left
:
12
rpx
;
color
:
#666
;
font-size
:
28
rpx
;
}
}
.upload-section
{
.upload-tip
{
font-size
:
24
rpx
;
color
:
#666
;
margin-bottom
:
24
rpx
;
}
}
.agreement-row
{
padding
:
20
rpx
40
rpx
;
.agreement-text
{
font-size
:
24
rpx
;
color
:
#999
;
}
.link
{
color
:
#fa8c16
;
}
}
.submit-btn-box
{
padding
:
40
rpx
;
}
/* 采购详情样式 */
.product-detail
{
.detail-banner
{
width
:
750
rpx
;
height
:
500
rpx
;
background-color
:
#fff
;
position
:
relative
;
.banner-img
{
width
:
100%
;
height
:
100%
;
}
.banner-placeholder
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background-color
:
#f1f1f1
;
}
.status-tag
{
position
:
absolute
;
right
:
30
rpx
;
top
:
30
rpx
;
background-color
:
rgba
(
93
,
182
,
111
,
0.9
);
color
:
#fff
;
padding
:
8
rpx
20
rpx
;
border-radius
:
30
rpx
;
font-size
:
24
rpx
;
}
}
...
...
@@ -270,5 +537,8 @@
.bottom-bar
{
position
:
fixed
;
bottom
:
0
;
left
:
0
;
right
:
0
;
height
:
110
rpx
;
background-color
:
#fff
;
padding
:
0
30
rpx
;
display
:
flex
;
align-items
:
center
;
box-shadow
:
0
-4
rpx
16
rpx
rgba
(
0
,
0
,
0
,
0.05
);
z-index
:
100
;
.action-btns
{
flex
:
1
;
.contact-btn
{
height
:
80
rpx
;
background-color
:
#fa8c16
;
color
:
#fff
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
40
rpx
;
font-size
:
28
rpx
;
font-weight
:
bold
;
}
}
}
}
:deep
(
.fui-input__label
)
{
font-family
:
'DingTalk Sans'
!important
;
}
:deep
(
.fui-input__label
)
{
font-family
:
'DingTalk Sans'
!important
;
font-size
:
28
rpx
!important
;
}
:deep
(
.fui-input__content
)
{
font-size
:
28
rpx
!important
;
}
:deep
(
.fui-input__placeholder
)
{
font-size
:
26
rpx
!important
;
}
.ph-style
{
font-size
:
26
rpx
!important
;
color
:
#ccc
;
}
</
style
>
src/pages/chanxiao/supplyXuQiu.vue
浏览文件 @
69e1168c
...
...
@@ -62,8 +62,8 @@
province
:
''
,
city
:
''
,
country
:
''
,
image
:
null
,
imageObj
:
null
,
image
:
''
,
imageObj
:
null
as
any
,
},
rules
:
[
{
name
:
'classify'
,
rule
:
[
'required'
],
msg
:
[
'请选择分类'
]
},
...
...
@@ -77,14 +77,25 @@
{
name
:
'detailedAddress'
,
rule
:
[
'required'
],
msg
:
[
'请输入详细地址'
]
},
{
name
:
'image'
,
rule
:
[
'required'
],
msg
:
[
'请上传图片'
]
},
],
agree
:
true
})
const
{
show
,
options
,
form
}
=
toRefs
(
pageData
)
async
function
initDict
()
{
pageData
.
options
.
classify
=
dictStore
.
getDictList
.
classify
.
map
((
item
)
=>
{
return
{
value
:
item
.
value
,
text
:
item
.
text
}
})
pageData
.
options
.
classify
=
[
{
value
:
1
,
text
:
'蔬菜'
},
{
value
:
2
,
text
:
'水果'
},
{
value
:
3
,
text
:
'粮食'
},
{
value
:
4
,
text
:
'畜牧'
},
{
value
:
0
,
text
:
'其他'
},
]
// 如果有已选分类,需要回显文字
if
(
pageData
.
form
.
classify
&&
pageData
.
options
.
classify
.
length
)
{
const
item
=
pageData
.
options
.
classify
.
find
(
i
=>
i
.
value
==
pageData
.
form
.
classify
)
if
(
item
)
pageData
.
form
.
classifyText
=
item
.
text
}
}
function
handleAreaConfirm
(
e
)
{
...
...
@@ -92,6 +103,8 @@
pageData
.
form
.
province
=
e
.
text
[
0
]
pageData
.
form
.
city
=
e
.
text
[
1
]
pageData
.
form
.
country
=
e
.
text
[
2
]
// @ts-ignore
pageData
.
form
.
areaText
=
e
.
fullText
}
function
getCurrentAddressInfo
()
{
...
...
@@ -102,15 +115,26 @@
pageData
.
form
.
city
=
res
.
city
pageData
.
form
.
country
=
res
.
country
pageData
.
form
.
address
=
`
${
res
.
province
}
,
${
res
.
city
}
,
${
res
.
country
}
`
// @ts-ignore
const
areaCodes
=
[
res
.
province
,
res
.
city
,
res
.
country
].
filter
(
Boolean
).
join
(
','
)
pageData
.
form
.
areaText
=
getText
(
areaCodes
,
' / '
)
})
}
function
getDetails
(
id
)
{
pageData
.
loading
=
true
ChanxiaoAPI
.
supplyDetails
({
id
}).
then
((
res
)
=>
{
ChanxiaoAPI
.
supplyDetails
({
id
}).
then
(
async
(
res
)
=>
{
pageData
.
form
=
res
// 确保字典已加载
if
(
!
pageData
.
options
.
classify
.
length
)
{
await
initDict
()
}
// 格式化地区显示
pageData
.
form
.
areaText
=
`
${
res
.
province
||
''
}
/
${
res
.
city
||
''
}
/
${
res
.
country
||
''
}
`
.
replace
(
/
\/
+$/
,
''
)
const
areaCodes
=
[
res
.
province
,
res
.
city
,
res
.
country
].
filter
(
Boolean
).
join
(
','
)
// @ts-ignore
pageData
.
form
.
areaText
=
getText
(
areaCodes
,
' / '
)
if
(
res
.
classify
)
{
const
item
=
pageData
.
options
.
classify
.
find
(
i
=>
i
.
value
==
res
.
classify
)
...
...
@@ -145,7 +169,7 @@
success
:
(
res
)
=>
{
const
data
=
JSON
.
parse
(
res
.
data
)
if
(
data
.
code
===
200
||
data
.
code
===
0
)
{
toastRef
.
show
({
type
:
'success'
,
text
:
'上传成功'
})
toastRef
.
value
.
show
({
type
:
'success'
,
text
:
'上传成功'
})
pageData
.
form
.
image
=
data
.
message
}
}
...
...
@@ -153,10 +177,14 @@
}
const
formRef
=
ref
()
function
submit
()
{
if
(
!
pageData
.
agree
)
{
toastRef
.
value
.
show
({
text
:
'请先同意发布协议'
})
return
}
formRef
.
value
.
validator
(
pageData
.
form
,
pageData
.
rules
,
true
).
then
((
res
)
=>
{
if
(
res
.
isPassed
)
{
ChanxiaoAPI
.
supplyAdd
(
pageData
.
form
).
then
(()
=>
{
toastRef
.
show
({
type
:
'success'
,
text
:
'发布成功'
})
toastRef
.
value
.
show
({
type
:
'success'
,
text
:
'发布成功'
})
setTimeout
(()
=>
uni
.
navigateBack
(),
1500
)
})
}
...
...
@@ -170,36 +198,94 @@
<
template
>
<view
class=
"page"
>
<!-- 发布/编辑模式 -->
<view
v-if=
"isSave"
class=
"formBox"
>
<fui-form
ref=
"formRef"
label-weight=
"auto"
top=
"60"
>
<view
class=
"mt20"
>
<fui-input
required
label=
"标题"
placeholder=
"请输入标题"
v-model=
"form.title"
labelSize=
"28"
label-width=
"180"
/>
<fui-input
label=
"规格说明"
placeholder=
"请输入规格说明"
v-model=
"form.productSpecs"
labelSize=
"28"
label-width=
"180"
/>
<view
v-if=
"isSave"
class=
"form-container"
>
<view
class=
"header-card"
>
<image
class=
"header-bg"
src=
"/static/images/login/farm_base_bg.png"
mode=
"aspectFill"
/>
<view
class=
"header-overlay"
></view>
<view
class=
"header-content"
>
<view
class=
"title"
>
发布供应需求
</view>
<view
class=
"subtitle"
>
让精准买家更快速地找到您的优质货源
</view>
<view
class=
"stepper-box"
>
<view
class=
"step-item active"
>
<view
class=
"step-num"
>
1
</view>
<view
class=
"step-text"
>
提交信息
</view>
</view>
<view
class=
"step-line"
></view>
<view
class=
"step-item"
>
<view
class=
"step-num"
>
2
</view>
<view
class=
"step-text"
>
后台审核
</view>
</view>
<view
class=
"step-line"
></view>
<view
class=
"step-item"
>
<view
class=
"step-num"
>
3
</view>
<view
class=
"step-text"
>
完成
</view>
</view>
</view>
</view>
<view
class=
"mt20"
>
<!-- 装饰图标 -->
<image
class=
"deco-icon"
src=
"/static/images/nongchang/device1.png"
mode=
"aspectFit"
/>
</view>
<fui-form
ref=
"formRef"
label-weight=
"auto"
>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
基本信息
</view>
<fui-input
required
label=
"供应标题"
placeholder=
"例如:优质红富士苹果"
v-model=
"form.title"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
/>
<fui-input
label=
"规格说明"
placeholder=
"例如:一级果,直径80mm以上"
v-model=
"form.productSpecs"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
/>
<fui-input
disabled
required
label=
"所属分类"
v-model=
"form.classifyText"
@
click=
"show.classify = true"
placeholder=
"请选择分类"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
>
<template
#
right
>
<fui-icon
name=
"arrowright"
:size=
"32"
color=
"#ccc"
></fui-icon>
</
template
>
</fui-input>
</view>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
价格与库存
</view>
<view
class=
"form-item required"
>
<text
class=
"label"
>
价格区间
</text>
<view
class=
"price-range"
>
<input
type=
"number"
class=
"price-input"
v-model=
"form.minPrice"
placeholder=
"最低价"
/>
<text
class=
"sep"
>
-
</text>
<input
type=
"number"
class=
"price-input"
v-model=
"form.maxPrice"
placeholder=
"最高价"
/>
<view
class=
"price-range-box"
>
<input
type=
"number"
class=
"price-input"
v-model=
"form.minPrice"
placeholder=
"最低价"
placeholder-class=
"ph-style"
/>
<text
class=
"sep"
>
至
</text>
<input
type=
"number"
class=
"price-input"
v-model=
"form.maxPrice"
placeholder=
"最高价"
placeholder-class=
"ph-style"
/>
<text
class=
"unit-text"
>
元
</text>
</view>
</view>
<fui-input
required
label=
"
单位"
placeholder=
"请输入"
v-model=
"form.unit"
labelSize=
"28"
label-width=
"180
"
/>
<fui-input
required
type=
"number"
label=
"供应
数量"
v-model=
"form.supplyQuantity"
labelSize=
"28"
label-width=
"180
"
/>
<fui-input
required
type=
"number"
label=
"起订量"
v-model=
"form.minOrderQuantity"
labelSize=
"28"
label-width=
"180
"
/>
<fui-input
required
label=
"
计价单位"
placeholder=
"如:斤、箱、吨"
v-model=
"form.unit"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']
"
/>
<fui-input
required
type=
"number"
label=
"供应
总量"
placeholder=
"输入可供总量"
v-model=
"form.supplyQuantity"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']
"
/>
<fui-input
required
type=
"number"
label=
"起订量"
placeholder=
"输入最低起订数量"
v-model=
"form.minOrderQuantity"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']
"
/>
</view>
<view
class=
"mt20"
>
<fui-input
disabled
required
label=
"所在地区"
v-model=
"form.address"
@
click=
"show.address = true"
labelSize=
"28"
label-width=
"180"
/>
<fui-input
required
label=
"详细地址"
v-model=
"form.detailedAddress"
labelSize=
"28"
label-width=
"180"
/>
<fui-input
disabled
required
label=
"分类"
v-model=
"form.classifyText"
@
click=
"show.classify = true"
labelSize=
"28"
label-width=
"180"
/>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
发货信息
</view>
<fui-input
disabled
required
label=
"所在地区"
v-model=
"form.areaText"
@
click=
"show.address = true"
placeholder=
"请选择发货地"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
>
<
template
#
right
>
<fui-icon
name=
"arrowright"
:size=
"32"
color=
"#ccc"
></fui-icon>
</
template
>
</fui-input>
<fui-input
required
label=
"详细地址"
placeholder=
"街道、门牌号等"
v-model=
"form.detailedAddress"
labelSize=
"28"
label-width=
"160"
size=
"28"
:padding=
"['16rpx', '0']"
/>
</view>
<view
class=
"form-group"
>
<view
class=
"group-title"
>
图片展示
</view>
<view
class=
"upload-section"
>
<view
class=
"upload-tip"
><span
class=
"required-star"
>
*
</span>
上传实拍图片,成交率提升3倍
</view>
<uni-file-picker
:value=
"form.imageObj"
limit=
"1"
@
select=
"handleUpload"
@
delete=
"form.image = ''"
/>
</view>
</view>
<view
class=
"bg-white mt20"
style=
"padding: 30rpx"
>
<view
class=
"mb-1"
style=
"font-size: 28rpx"
><span
style=
"color: red"
>
*
</span>
上传图片
</view>
<uni-file-picker
:value=
"form.imageObj"
limit=
"1"
@
select=
"handleUpload"
@
delete=
"form.image = null"
/>
<view
class=
"agreement-row"
>
<checkbox-group
@
change=
"pageData.agree = !pageData.agree"
>
<label
class=
"checkbox-label"
>
<checkbox
:checked=
"pageData.agree"
color=
"#5db66f"
style=
"transform:scale(0.7)"
/>
<text
class=
"agreement-text"
>
我已阅读并同意
<text
class=
"link"
>
《湘农数智农服发布协议》
</text></text>
</label>
</checkbox-group>
</view>
<view
class=
"fui-btn__box"
>
<fui-button
text=
"立即发布"
bold
radius=
"100rpx"
@
click=
"submit"
/>
<view
class=
"submit-btn-box"
>
<fui-button
text=
"立即发布"
bold
radius=
"100rpx"
background=
"#5db66f"
@
click=
"submit"
/>
</view>
</fui-form>
</view>
...
...
@@ -266,18 +352,192 @@
<AreaPicker
v-model:show=
"show.address"
:layer=
"3"
@
confirm=
"handleAreaConfirm"
/>
<fui-picker
:show=
"show.classify"
:options=
"options.classify"
@
change=
"handleChangeClassify"
@
cancel=
"show.classify = false"
/>
<fui-date-picker
:show=
"show.time1"
type=
"3"
@
change=
"handleChangeTime1"
@
cancel=
"show.time1 = false"
/>
<fui-date-picker
:show=
"show.time2"
type=
"3"
@
change=
"handleChangeTime2"
@
cancel=
"show.time2 = false"
/>
<fui-toast
ref=
"toastRef"
/>
</view>
</template>
<
style
lang=
"scss"
scoped
>
.page
{
background-color
:
#f7f8fa
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
.formBox
{
padding
:
24
rpx
;
.mt20
{
background
:
#fff
;
border-radius
:
20
rpx
;
padding
:
10
rpx
24
rpx
;
margin-bottom
:
24
rpx
;
}
}
.price-range
{
display
:
flex
;
align-items
:
center
;
flex
:
1
;
margin-left
:
20
rpx
;
.price-input
{
flex
:
1
;
text-align
:
center
;
font-size
:
28
rpx
;
}
.sep
{
margin
:
0
10
rpx
;
color
:
#ccc
;
}
}
.form-item
{
padding
:
30
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
180
rpx
;
.red
{
color
:
#ff4d4f
;
}
}
}
.page
{
background-color
:
#f7f9fb
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
.form-container
{
padding-bottom
:
40
rpx
;
.header-card
{
position
:
relative
;
height
:
320
rpx
;
overflow
:
hidden
;
display
:
flex
;
align-items
:
center
;
.header-bg
{
position
:
absolute
;
width
:
100%
;
height
:
100%
;
z-index
:
1
;
}
.header-overlay
{
position
:
absolute
;
width
:
100%
;
height
:
100%
;
background
:
linear-gradient
(
135deg
,
rgba
(
93
,
182
,
111
,
0.9
)
0%
,
rgba
(
76
,
175
,
80
,
0.8
)
100%
);
z-index
:
2
;
}
.header-content
{
position
:
relative
;
z-index
:
3
;
width
:
100%
;
padding
:
0
40
rpx
;
margin-top
:
-20
rpx
;
}
.title
{
font-size
:
38
rpx
;
font-weight
:
bold
;
color
:
#fff
;
margin-bottom
:
6
rpx
;
text-shadow
:
0
2
rpx
4
rpx
rgba
(
0
,
0
,
0
,
0.1
);
}
.subtitle
{
font-size
:
24
rpx
;
color
:
rgba
(
255
,
255
,
255
,
0.9
);
}
.deco-icon
{
position
:
absolute
;
right
:
-20
rpx
;
bottom
:
20
rpx
;
width
:
200
rpx
;
height
:
200
rpx
;
opacity
:
0.15
;
z-index
:
2
;
transform
:
rotate
(
-15deg
);
}
}
.stepper-box
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-top
:
30
rpx
;
padding
:
0
20
rpx
;
.step-item
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
opacity
:
0.7
;
position
:
relative
;
z-index
:
2
;
&.active
{
opacity
:
1
;
.step-num
{
background
:
#fff
;
color
:
#5db66f
;
border-color
:
#fff
;
box-shadow
:
0
4
rpx
10
rpx
rgba
(
0
,
0
,
0
,
0.1
);
}
.step-text
{
font-weight
:
bold
;
}
}
.step-num
{
width
:
36
rpx
;
height
:
36
rpx
;
border
:
2
rpx
solid
rgba
(
255
,
255
,
255
,
0.8
);
border-radius
:
50%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
22
rpx
;
font-weight
:
bold
;
margin-bottom
:
8
rpx
;
color
:
#fff
;
transition
:
all
0.3s
;
}
.step-text
{
font-size
:
20
rpx
;
color
:
#fff
;
}
}
.step-line
{
flex
:
1
;
height
:
2
rpx
;
background
:
rgba
(
255
,
255
,
255
,
0.4
);
margin
:
0
15
rpx
;
margin-top
:
-30
rpx
;
}
}
.form-group
{
background
:
#fff
;
margin
:
20
rpx
24
rpx
0
;
border-radius
:
20
rpx
;
padding
:
24
rpx
28
rpx
;
box-shadow
:
0
4
rpx
16
rpx
rgba
(
0
,
0
,
0
,
0.02
);
.group-title
{
font-size
:
28
rpx
;
font-weight
:
bold
;
color
:
#333
;
margin-bottom
:
20
rpx
;
display
:
flex
;
align-items
:
center
;
&::before
{
content
:
''
;
width
:
6
rpx
;
height
:
28
rpx
;
background
:
#5db66f
;
border-radius
:
4
rpx
;
margin-right
:
12
rpx
;
}
}
}
}
.form-item
{
padding
:
20
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
160
rpx
;
}
}
.required-star
{
color
:
#ff4d4f
;
margin-right
:
4
rpx
;
}
.price-range-box
{
display
:
flex
;
align-items
:
center
;
flex
:
1
;
.price-input
{
flex
:
1
;
height
:
72
rpx
;
background
:
#f5f7f9
;
border-radius
:
12
rpx
;
padding
:
0
20
rpx
;
font-size
:
28
rpx
;
text-align
:
center
;
}
.sep
{
margin
:
0
16
rpx
;
color
:
#999
;
font-size
:
24
rpx
;
}
.unit-text
{
margin-left
:
12
rpx
;
color
:
#666
;
font-size
:
28
rpx
;
}
}
.upload-section
{
.upload-tip
{
font-size
:
24
rpx
;
color
:
#666
;
margin-bottom
:
24
rpx
;
}
}
.agreement-row
{
padding
:
20
rpx
40
rpx
;
.agreement-text
{
font-size
:
24
rpx
;
color
:
#999
;
}
.link
{
color
:
#5db66f
;
}
}
.submit-btn-box
{
padding
:
40
rpx
;
}
/* 商品详情模式样式 */
.product-detail
{
.detail-banner
{
width
:
750
rpx
;
height
:
600
rpx
;
background-color
:
#fff
;
.banner-img
{
width
:
100%
;
height
:
100%
;
}
.banner-placeholder
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background-color
:
#f1f1f1
;
}
}
...
...
@@ -286,5 +546,8 @@
.bottom-bar
{
position
:
fixed
;
bottom
:
0
;
left
:
0
;
right
:
0
;
height
:
110
rpx
;
background-color
:
#fff
;
padding
:
0
30
rpx
;
display
:
flex
;
align-items
:
center
;
box-shadow
:
0
-4
rpx
16
rpx
rgba
(
0
,
0
,
0
,
0.05
);
z-index
:
100
;
.action-btns
{
flex
:
1
;
.contact-btn
{
height
:
80
rpx
;
background-color
:
#5db66f
;
color
:
#fff
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
40
rpx
;
font-size
:
28
rpx
;
font-weight
:
bold
;
}
}
}
}
:deep
(
.fui-input__label
)
{
font-family
:
'DingTalk Sans'
!important
;
}
:deep
(
.fui-input__label
)
{
font-family
:
'DingTalk Sans'
!important
;
font-size
:
28
rpx
!important
;
}
:deep
(
.fui-input__content
)
{
font-size
:
28
rpx
!important
;
}
:deep
(
.fui-input__placeholder
)
{
font-size
:
26
rpx
!important
;
}
.ph-style
{
font-size
:
26
rpx
!important
;
color
:
#ccc
;
}
</
style
>
src/pages/login/login.vue
浏览文件 @
69e1168c
...
...
@@ -141,6 +141,9 @@
function
onSlideSuccess
()
{
if
(
slideType
.
value
===
'sms'
)
{
// 立即开启倒计时(乐观更新),接口报错也不停止,防止恶意连点
startCountdown
()
// 发送验证码
const
params
=
{
mobile
:
model
.
form
.
data
.
username
,
...
...
@@ -149,10 +152,10 @@
API
.
sysSms
(
params
)
.
then
(
async
()
=>
{
Message
.
toast
(
'验证码已发送'
)
startCountdown
()
})
.
catch
(()
=>
{
Message
.
toast
(
'验证码发送失败'
)
.
catch
((
err
)
=>
{
// 即使接口返回 code: 130 等错误,也不干扰已启动的倒计时
console
.
error
(
'短信发送业务异常:'
,
err
)
})
}
else
{
// 执行登录
...
...
src/pages/login/register.vue
浏览文件 @
69e1168c
...
...
@@ -93,6 +93,9 @@
function
onSlideSuccess
()
{
if
(
slideType
.
value
===
'sms'
)
{
// 开启乐观倒计时,不论接口成败,60s内不准重发
startCountdown
()
const
params
=
{
mobile
:
model
.
form
.
data
.
phone
,
smsmode
:
2
,
// 2-注册
...
...
@@ -100,10 +103,9 @@
API
.
sysSms
(
params
)
.
then
(
async
()
=>
{
Message
.
toast
(
'验证码已发送'
)
startCountdown
()
})
.
catch
(()
=>
{
Message
.
toast
(
'验证码发送失败'
)
.
catch
((
err
)
=>
{
console
.
error
(
'注册短信发送失败:'
,
err
)
})
}
else
{
doRegister
()
...
...
src/pages/nongchang/nongchang.vue
浏览文件 @
69e1168c
...
...
@@ -747,22 +747,74 @@
</view>
</view>
<fui-fab
v-if=
"!userStore.isAuditMode"
position=
"right"
distance=
"10"
bottom=
"240"
width=
"96"
@
click=
"handlePublish"
>
<view
class=
"text-white text-center"
>
<image
style=
"width: 52rpx; height: 52rpx"
src=
"/static/images/nongchang/work_icon.png"
/>
<view
style=
"font-size: 18rpx; margin-top: -16rpx"
>
找人干活
</view>
<!-- 找人干活悬浮按钮 -->
<view
class=
"fab-container"
v-if=
"!userStore.isAuditMode"
@
click=
"handlePublish"
>
<view
class=
"ripple"
></view>
<view
class=
"ripple ripple-2"
></view>
<view
class=
"fab-entry"
>
<image
style=
"width: 48rpx; height: 48rpx"
src=
"/static/images/nongchang/work_icon.png"
/>
<text>
找人干活
</text>
</view>
</
fui-fab
>
</
view
>
</
template
>
<
style
scoped
lang=
"scss"
>
.fab-container
{
position
:
fixed
;
right
:
30
rpx
;
bottom
:
240
rpx
;
width
:
110
rpx
;
height
:
110
rpx
;
z-index
:
100
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
.ripple
{
position
:
absolute
;
width
:
100%
;
height
:
100%
;
background-color
:
#5db66f
;
border-radius
:
50%
;
opacity
:
0.4
;
animation
:
ripple
2s
infinite
ease-out
;
}
.ripple-2
{
animation-delay
:
1s
;
}
.fab-entry
{
position
:
relative
;
width
:
110
rpx
;
height
:
110
rpx
;
background
:
linear-gradient
(
135deg
,
#a5d63f
0%
,
#2e8b57
100%
);
border-radius
:
50%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
box-shadow
:
0
4
rpx
16
rpx
rgba
(
46
,
139
,
87
,
0.3
);
text
{
color
:
#fff
;
font-size
:
18
rpx
;
font-weight
:
bold
;
margin-top
:
-4
rpx
;
}
}
}
@keyframes
ripple
{
0
%
{
transform
:
scale
(
1
);
opacity
:
0.4
;
}
100
%
{
transform
:
scale
(
1.6
);
opacity
:
0
;
}
}
.mt-19
{
margin-top
:
38
rpx
;
}
...
...
src/pages/nongjifuwu/farm-form.vue
浏览文件 @
69e1168c
...
...
@@ -7,13 +7,14 @@
import
*
as
nongjifuwu
from
'@/api/model/nongjifuwu'
import
{
getText
}
from
'@/utils/dict/area'
import
AreaPicker
from
'@/components/AreaPicker/index.vue'
import
ApplyDialog
from
'@/pages/nongjifuwu/components/apply-dialog.vue'
const
userStore
=
useUserStore
()
const
globSetting
=
useGlobSetting
()
onLoad
((
option
)
=>
{
pageData
.
form
.
type
=
option
.
type
if
(
option
.
id
)
{
pageData
.
title
=
'
查看
农活详情'
pageData
.
title
=
'
干
农活详情'
getDetails
(
option
.
id
)
}
else
{
pageData
.
title
=
'新增农活作业'
...
...
@@ -66,7 +67,13 @@
nongjifuwu
.
farmMachineDetails
({
id
}).
then
((
res
)
=>
{
pageData
.
form
=
res
pageData
.
form
.
pictureObj
=
res
.
picture
&&
parseUrlInfo
(
res
.
picture
)
if
(
res
.
scope
)
pageData
.
optionsValText
=
getText
(
res
.
scope
,
' / '
)
// 优先用接口返回的中文名拼接,getText 作为兜底
const
nameParts
=
[
res
.
cityName
,
res
.
districtName
].
filter
(
Boolean
)
if
(
nameParts
.
length
>
0
)
{
pageData
.
optionsValText
=
nameParts
.
join
(
''
)
}
else
if
(
res
.
scope
)
{
pageData
.
optionsValText
=
getText
(
res
.
scope
,
''
)
}
}).
finally
(()
=>
pageData
.
loading
=
false
)
}
...
...
@@ -112,6 +119,14 @@
const
d
=
new
Date
()
return
`
${
d
.
getFullYear
()}
-
${
d
.
getMonth
()
+
1
}
-
${
d
.
getDate
()}
`
}
const
applyDialogRef
=
ref
()
function
handleApply
()
{
applyDialogRef
.
value
.
open
({
id
:
pageData
.
form
.
id
,
serviceType
:
pageData
.
form
.
type
,
})
}
</
script
>
<
template
>
...
...
@@ -155,15 +170,121 @@
</fui-form>
</view>
<!-- 详情模式... (已在之前步骤重构) -->
<view
v-else
class=
"product-detail"
>
<!-- 详情 UI 内容 -->
<view
class=
"detail-banner"
>
<image
v-if=
"form.picture"
:src=
"form.picture"
mode=
"aspectFill"
class=
"banner-img"
/>
<view
v-else
class=
"banner-placeholder"
><fui-icon
name=
"picture"
:size=
"100"
color=
"#ddd"
></fui-icon></view>
<!-- 详情模式 -->
<view
v-else
class=
"detail-page"
>
<!-- 顶部封面图区域 -->
<view
class=
"detail-hero"
>
<image
v-if=
"form.picture"
:src=
"form.picture"
mode=
"aspectFill"
class=
"hero-img"
/>
<view
v-else
class=
"hero-placeholder"
>
<fui-icon
name=
"picture"
:size=
"120"
color=
"rgba(255,255,255,0.4)"
></fui-icon>
<text
class=
"hero-placeholder-text"
>
暂无图片
</text>
</view>
<!-- 顶部渐变遮罩 -->
<view
class=
"hero-overlay"
></view>
<!-- 状态标签 -->
<view
class=
"hero-badge"
>
<view
class=
"badge-dot"
></view>
<text
class=
"badge-text"
>
招募中
</text>
</view>
</view>
<view
class=
"info-card"
>
<text
class=
"product-title"
>
{{
form
.
name
}}
</text>
<!-- 主信息卡片 -->
<view
class=
"main-card"
>
<text
class=
"service-title"
>
{{
form
.
name
}}
</text>
<!-- 价格/关键信息行 -->
<view
class=
"key-info-row"
>
<view
class=
"key-tag"
>
<fui-icon
name=
"time"
:size=
"26"
color=
"#5db66f"
></fui-icon>
<text
class=
"key-tag-text"
>
{{
form
.
startTime
||
'--'
}}
至
{{
form
.
endTime
||
'--'
}}
</text>
</view>
</view>
</view>
<!-- 详细信息 section -->
<view
class=
"info-section"
>
<view
class=
"section-header"
>
<view
class=
"section-bar"
></view>
<text
class=
"section-title"
>
作业信息
</text>
</view>
<view
class=
"info-grid"
>
<view
class=
"info-row"
>
<view
class=
"info-icon-wrap"
>
<fui-icon
name=
"location"
:size=
"32"
color=
"#5db66f"
></fui-icon>
</view>
<view
class=
"info-content"
>
<text
class=
"info-label"
>
服务范围
</text>
<text
class=
"info-value"
>
{{
pageData
.
optionsValText
||
form
.
scope
||
'暂未填写'
}}
</text>
</view>
</view>
<view
class=
"info-divider"
></view>
<view
class=
"info-row"
>
<view
class=
"info-icon-wrap"
>
<fui-icon
name=
"map"
:size=
"32"
color=
"#5db66f"
></fui-icon>
</view>
<view
class=
"info-content"
>
<text
class=
"info-label"
>
详细地址
</text>
<text
class=
"info-value"
>
{{
form
.
address
||
'暂未填写'
}}
</text>
</view>
</view>
<view
class=
"info-divider"
></view>
<view
class=
"info-row"
>
<view
class=
"info-icon-wrap"
>
<fui-icon
name=
"mobile"
:size=
"32"
color=
"#5db66f"
></fui-icon>
</view>
<view
class=
"info-content"
>
<text
class=
"info-label"
>
联系方式
</text>
<text
class=
"info-value"
>
{{
form
.
phone
||
'暂未填写'
}}
</text>
</view>
</view>
<view
v-if=
"form.startTime || form.endTime"
class=
"info-divider"
></view>
<view
v-if=
"form.startTime || form.endTime"
class=
"info-row"
>
<view
class=
"info-icon-wrap"
>
<fui-icon
name=
"time"
:size=
"32"
color=
"#5db66f"
></fui-icon>
</view>
<view
class=
"info-content"
>
<text
class=
"info-label"
>
作业时间
</text>
<text
class=
"info-value"
>
{{
form
.
startTime
}}
至
{{
form
.
endTime
}}
</text>
</view>
</view>
</view>
</view>
<!-- 作业需求 section -->
<view
class=
"info-section"
v-if=
"form.demand"
>
<view
class=
"section-header"
>
<view
class=
"section-bar"
></view>
<text
class=
"section-title"
>
作业需求
</text>
</view>
<view
class=
"demand-box"
>
<text
class=
"demand-text"
>
{{
form
.
demand
}}
</text>
</view>
</view>
<!-- 底部安全高度 -->
<view
style=
"height: 180rpx;"
></view>
<!-- 固定底部栏 -->
<view
class=
"detail-footer"
>
<view
class=
"footer-hint"
>
<text
class=
"hint-title"
>
有意接单?立即报名
</text>
<text
class=
"hint-sub"
>
平台将为您推送给发布方
</text>
</view>
<view
class=
"footer-apply-btn"
@
tap=
"handleApply"
>
<text
class=
"apply-btn-text"
>
立即报名
</text>
</view>
</view>
</view>
...
...
@@ -172,14 +293,292 @@
<fui-date-picker
:show=
"show.time2"
type=
"3"
@
change=
"handleChangeTime2"
@
cancel=
"show.time2 = false"
:min-date=
"getCurrentDate()"
/>
<fui-toast
ref=
"toastRef"
/>
<fui-loading
isFixed
v-if=
"pageData.loading"
/>
<ApplyDialog
ref=
"applyDialogRef"
/>
</view>
</
template
>
<
style
lang=
"scss"
scoped
>
.page
{
background-color
:
#f7f8fa
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
.formBox
{
padding
:
24
rpx
;
.mt20
{
background
:
#fff
;
border-radius
:
20
rpx
;
padding
:
10
rpx
24
rpx
;
margin-bottom
:
24
rpx
;
}
}
.form-item
{
padding
:
30
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
180
rpx
;
.red
{
color
:
#ff4d4f
;
}
}
}
.time-range
{
display
:
flex
;
align-items
:
center
;
flex
:
1
;
margin-left
:
20
rpx
;
.time-input
{
flex
:
1
;
text-align
:
center
;
}
.sep
{
margin
:
0
10
rpx
;
color
:
#ccc
;
}
}
.page
{
background-color
:
#f8fafc
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
/* ===== 发布模式 ===== */
.formBox
{
padding
:
24
rpx
;
.mt20
{
background
:
#fff
;
border-radius
:
20
rpx
;
padding
:
10
rpx
24
rpx
;
margin-bottom
:
24
rpx
;
}
}
.form-item
{
padding
:
30
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
180
rpx
;
}
}
.time-range
{
display
:
flex
;
align-items
:
center
;
flex
:
1
;
margin-left
:
20
rpx
;
.time-input
{
flex
:
1
;
text-align
:
center
;
}
.sep
{
margin
:
0
10
rpx
;
color
:
#ccc
;
}
}
.select-text
{
font-size
:
28
rpx
;
&.placeholder
{
color
:
#ccc
;
}
}
:deep
(
.fui-input__label
)
{
font-family
:
'DingTalk Sans'
!important
;
}
/* ===== 详情模式 ===== */
.detail-page
{
background-color
:
#f8fafc
;
}
.detail-hero
{
position
:
relative
;
width
:
100%
;
height
:
520
rpx
;
background
:
linear-gradient
(
135deg
,
#3a944c
0%
,
#5db66f
100%
);
overflow
:
hidden
;
.hero-img
{
width
:
100%
;
height
:
100%
;
display
:
block
;
}
.hero-placeholder
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
.hero-placeholder-text
{
font-size
:
26
rpx
;
color
:
rgba
(
255
,
255
,
255
,
0.5
);
margin-top
:
20
rpx
;
}
}
.hero-overlay
{
position
:
absolute
;
bottom
:
0
;
left
:
0
;
right
:
0
;
height
:
240
rpx
;
background
:
linear-gradient
(
to
top
,
rgba
(
0
,
0
,
0
,
0.45
)
0%
,
transparent
100%
);
}
.hero-badge
{
position
:
absolute
;
top
:
30
rpx
;
right
:
30
rpx
;
display
:
flex
;
align-items
:
center
;
background-color
:
rgba
(
255
,
255
,
255
,
0.18
);
backdrop-filter
:
blur
(
8px
);
border
:
1
rpx
solid
rgba
(
255
,
255
,
255
,
0.3
);
padding
:
10
rpx
22
rpx
;
border-radius
:
40
rpx
;
.badge-dot
{
width
:
14
rpx
;
height
:
14
rpx
;
border-radius
:
50%
;
background-color
:
#52c41a
;
margin-right
:
10
rpx
;
box-shadow
:
0
0
0
4
rpx
rgba
(
82
,
196
,
26
,
0.3
);
}
.badge-text
{
font-size
:
24
rpx
;
color
:
#fff
;
font-weight
:
500
;
}
}
}
.main-card
{
background-color
:
#fff
;
margin
:
-40
rpx
24
rpx
20
rpx
;
border-radius
:
28
rpx
;
padding
:
36
rpx
32
rpx
28
rpx
;
box-shadow
:
0
8
rpx
32
rpx
rgba
(
0
,
0
,
0
,
0.08
);
position
:
relative
;
z-index
:
10
;
.service-title
{
font-size
:
40
rpx
;
font-weight
:
bold
;
color
:
#1a1a1a
;
line-height
:
1.35
;
display
:
block
;
margin-bottom
:
24
rpx
;
letter-spacing
:
1
rpx
;
}
.key-info-row
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
16
rpx
;
.key-tag
{
display
:
flex
;
align-items
:
center
;
background-color
:
#f0faf2
;
border-radius
:
10
rpx
;
padding
:
10
rpx
20
rpx
;
.key-tag-text
{
font-size
:
26
rpx
;
color
:
#3a944c
;
margin-left
:
8
rpx
;
font-weight
:
500
;
}
}
}
}
.info-section
{
background-color
:
#fff
;
margin
:
0
24
rpx
20
rpx
;
border-radius
:
24
rpx
;
padding
:
32
rpx
;
box-shadow
:
0
2
rpx
12
rpx
rgba
(
0
,
0
,
0
,
0.04
);
.section-header
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
28
rpx
;
.section-bar
{
width
:
8
rpx
;
height
:
36
rpx
;
background
:
linear-gradient
(
to
bottom
,
#5db66f
,
#3a944c
);
border-radius
:
4
rpx
;
margin-right
:
16
rpx
;
}
.section-title
{
font-size
:
32
rpx
;
font-weight
:
bold
;
color
:
#1a1a1a
;
}
}
.info-grid
{
.info-row
{
display
:
flex
;
align-items
:
flex-start
;
padding
:
4
rpx
0
;
.info-icon-wrap
{
width
:
56
rpx
;
height
:
56
rpx
;
background-color
:
#f0faf2
;
border-radius
:
14
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
.info-content
{
flex
:
1
;
margin-left
:
20
rpx
;
padding
:
4
rpx
0
;
.info-label
{
font-size
:
24
rpx
;
color
:
#94a3b8
;
display
:
block
;
margin-bottom
:
6
rpx
;
}
.info-value
{
font-size
:
30
rpx
;
color
:
#1e293b
;
line-height
:
1.4
;
font-weight
:
500
;
}
}
}
.info-divider
{
height
:
1
rpx
;
background-color
:
#f1f5f9
;
margin
:
24
rpx
0
;
}
}
.demand-box
{
background-color
:
#f8fafc
;
border-radius
:
16
rpx
;
padding
:
28
rpx
;
border-left
:
6
rpx
solid
#5db66f
;
.demand-text
{
font-size
:
28
rpx
;
color
:
#475569
;
line-height
:
1.7
;
}
}
}
/* ===== 底部操作栏 ===== */
.detail-footer
{
position
:
fixed
;
bottom
:
0
;
left
:
0
;
right
:
0
;
background-color
:
rgba
(
255
,
255
,
255
,
0.97
);
backdrop-filter
:
blur
(
10px
);
padding
:
20
rpx
32
rpx
calc
(
20
rpx
+
env
(
safe-area-inset-bottom
));
box-shadow
:
0
-8
rpx
32
rpx
rgba
(
0
,
0
,
0
,
0.06
);
z-index
:
100
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
.footer-hint
{
display
:
flex
;
flex-direction
:
column
;
.hint-title
{
font-size
:
30
rpx
;
font-weight
:
bold
;
color
:
#1e293b
;
}
.hint-sub
{
font-size
:
22
rpx
;
color
:
#94a3b8
;
margin-top
:
4
rpx
;
}
}
.footer-apply-btn
{
background
:
linear-gradient
(
135deg
,
#5db66f
0%
,
#3a944c
100%
);
height
:
88
rpx
;
padding
:
0
52
rpx
;
border-radius
:
44
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
box-shadow
:
0
8
rpx
24
rpx
rgba
(
93
,
182
,
111
,
0.35
);
&:active
{
opacity
:
0.9
;
transform
:
scale
(
0.97
);
}
.apply-btn-text
{
font-size
:
30
rpx
;
color
:
#fff
;
font-weight
:
bold
;
letter-spacing
:
2
rpx
;
}
}
}
</
style
>
src/pages/nongjifuwu/machine-form.vue
浏览文件 @
69e1168c
...
...
@@ -4,14 +4,16 @@
import
{
useUserStore
}
from
'@/store/modules/user'
import
{
useGlobSetting
}
from
'/@/hooks/setting'
import
*
as
nongjifuwu
from
'@/api/model/nongjifuwu'
import
{
getText
}
from
'@/utils/dict/area'
import
AreaPicker
from
'@/components/AreaPicker/index.vue'
import
ApplyDialog
from
'@/pages/nongjifuwu/components/apply-dialog.vue'
const
userStore
=
useUserStore
()
const
globSetting
=
useGlobSetting
()
onLoad
((
option
)
=>
{
pageData
.
form
.
type
=
option
.
type
if
(
option
.
id
)
{
pageData
.
title
=
'
查看农机
详情'
pageData
.
title
=
'
农机服务
详情'
getDetails
(
option
.
id
)
}
else
{
pageData
.
title
=
'新增农机作业'
...
...
@@ -57,6 +59,13 @@
nongjifuwu
.
farmMachineDetails
({
id
}).
then
((
res
)
=>
{
pageData
.
form
=
res
pageData
.
form
.
pictureObj
=
res
.
picture
&&
parseUrlInfo
(
res
.
picture
)
// 优先用接口返回的中文名拼接,getText 作为兜底
const
nameParts
=
[
res
.
cityName
,
res
.
districtName
].
filter
(
Boolean
)
if
(
nameParts
.
length
>
0
)
{
pageData
.
optionsValText
=
nameParts
.
join
(
''
)
}
else
if
(
res
.
scope
)
{
pageData
.
optionsValText
=
getText
(
res
.
scope
,
''
)
}
}).
finally
(()
=>
pageData
.
loading
=
false
)
}
...
...
@@ -66,7 +75,6 @@
}
const
toastRef
=
ref
()
const
uploadRef
=
ref
()
function
handleUpload
(
file
)
{
uni
.
uploadFile
({
url
:
`
${
globSetting
.
apiUrl
+
globSetting
.
urlPrefix
}
/sys/common/upload`
,
...
...
@@ -97,12 +105,21 @@
}
})
}
const
applyDialogRef
=
ref
()
function
handleApply
()
{
applyDialogRef
.
value
.
open
({
id
:
pageData
.
form
.
id
,
serviceType
:
pageData
.
form
.
type
,
})
}
</
script
>
<
template
>
<view
class=
"page"
>
<view
class=
"formBox"
>
<fui-form
ref=
"formRef"
label-weight=
"auto"
top=
"60"
:disabled=
"form.id ? true : false"
>
<!-- 发布模式 -->
<view
v-if=
"!form.id"
class=
"formBox"
>
<fui-form
ref=
"formRef"
label-weight=
"auto"
top=
"60"
>
<view
class=
"mt20"
>
<fui-input
required
label=
"作业标题"
placeholder=
"请输入"
v-model=
"form.name"
labelSize=
"28"
label-width=
"180"
/>
<fui-input
required
label=
"联系方式"
type=
"number"
placeholder=
"请输入手机号"
v-model=
"form.phone"
labelSize=
"28"
label-width=
"180"
/>
...
...
@@ -124,23 +141,517 @@
<uni-file-picker
:value=
"form.pictureObj"
limit=
"1"
@
select=
"handleUpload"
@
delete=
"handleDelete"
/>
</view>
<view
class=
"fui-btn__box"
v-if=
"!form.id"
>
<view
class=
"fui-btn__box"
>
<fui-button
text=
"提交发布"
bold
radius=
"100rpx"
@
click=
"submit"
/>
</view>
</fui-form>
</view>
<!-- 详情模式 -->
<view
v-else
class=
"detail-page"
>
<!-- 顶部封面图 -->
<view
class=
"detail-hero"
>
<image
v-if=
"form.picture"
:src=
"form.picture"
mode=
"aspectFill"
class=
"hero-img"
/>
<view
v-else
class=
"hero-placeholder"
>
<fui-icon
name=
"picture"
:size=
"120"
color=
"rgba(255,255,255,0.4)"
></fui-icon>
<text
class=
"hero-placeholder-text"
>
暂无图片
</text>
</view>
<view
class=
"hero-overlay"
></view>
<!-- 价格浮层 -->
<view
class=
"hero-price-float"
>
<text
class=
"price-symbol"
>
¥
</text>
<text
class=
"price-num"
>
{{ form.price }}
</text>
<text
class=
"price-unit"
>
/亩
</text>
</view>
<!-- 服务状态 -->
<view
class=
"hero-badge"
>
<view
class=
"badge-dot"
></view>
<text
class=
"badge-text"
>
服务中
</text>
</view>
</view>
<!-- 主信息卡 -->
<view
class=
"main-card"
>
<text
class=
"service-title"
>
{{ form.name }}
</text>
<view
class=
"price-highlight-row"
>
<view
class=
"price-wrap"
>
<text
class=
"price-label"
>
作业收费
</text>
<view
class=
"price-main"
>
<text
class=
"ph-symbol"
>
¥
</text>
<text
class=
"ph-value"
>
{{ form.price }}
</text>
<text
class=
"ph-unit"
>
/亩
</text>
</view>
</view>
<view
class=
"divider-v"
></view>
<view
class=
"scope-wrap"
>
<text
class=
"scope-label"
>
服务范围
</text>
<text
class=
"scope-value"
>
{{ pageData.optionsValText || '全国' }}
</text>
</view>
</view>
</view>
<!-- 联系信息 section -->
<view
class=
"info-section"
>
<view
class=
"section-header"
>
<view
class=
"section-bar"
></view>
<text
class=
"section-title"
>
服务信息
</text>
</view>
<AreaPicker
v-model:show=
"show.address"
:layer=
"3"
title=
"选择服务范围"
@
confirm=
"handleAreaConfirm"
/>
<fui-toast
ref=
"toastRef"
/>
<fui-loading
isFixed
v-if=
"pageData.loading"
/>
<view
class=
"info-grid"
>
<view
class=
"info-row"
>
<view
class=
"info-icon-wrap orange"
>
<fui-icon
name=
"mobile"
:size=
"30"
color=
"#fa8c16"
></fui-icon>
</view>
<view
class=
"info-content"
>
<text
class=
"info-label"
>
联系方式
</text>
<text
class=
"info-value"
>
{{ form.phone || '暂未填写' }}
</text>
</view>
</view>
<view
class=
"info-divider"
></view>
<view
class=
"info-row"
>
<view
class=
"info-icon-wrap orange"
>
<fui-icon
name=
"location"
:size=
"30"
color=
"#fa8c16"
></fui-icon>
</view>
<view
class=
"info-content"
>
<text
class=
"info-label"
>
服务区域
</text>
<text
class=
"info-value"
>
{{ pageData.optionsValText || form.scope || '全国范围' }}
</text>
</view>
</view>
</view>
</view>
<!-- 服务说明区域(如有更多字段可展开) -->
<view
class=
"tips-section"
>
<view
class=
"tips-row"
>
<view
class=
"tip-item"
>
<view
class=
"tip-icon"
>
<fui-icon
name=
"shield"
:size=
"36"
color=
"#5db66f"
></fui-icon>
</view>
<text
class=
"tip-text"
>
平台认证
</text>
</view>
<view
class=
"tip-divider"
></view>
<view
class=
"tip-item"
>
<view
class=
"tip-icon"
>
<fui-icon
name=
"notice"
:size=
"36"
color=
"#5db66f"
></fui-icon>
</view>
<text
class=
"tip-text"
>
专业团队
</text>
</view>
<view
class=
"tip-divider"
></view>
<view
class=
"tip-item"
>
<view
class=
"tip-icon"
>
<fui-icon
name=
"tag"
:size=
"36"
color=
"#5db66f"
></fui-icon>
</view>
<text
class=
"tip-text"
>
按亩计价
</text>
</view>
</view>
</view>
<!-- 底部安全高度 -->
<view
style=
"height: 180rpx;"
></view>
<!-- 固定底部栏 -->
<view
class=
"detail-footer"
>
<view
class=
"footer-price-area"
>
<text
class=
"footer-price-label"
>
作业价格
</text>
<view
class=
"footer-price-main"
>
<text
class=
"fp-symbol"
>
¥
</text>
<text
class=
"fp-value"
>
{{ form.price }}
</text>
<text
class=
"fp-unit"
>
/亩起
</text>
</view>
</view>
<view
class=
"footer-apply-btn"
@
tap=
"handleApply"
>
<text
class=
"apply-btn-text"
>
立即预约
</text>
</view>
</view>
</view>
<AreaPicker
v-model:show=
"show.address"
:layer=
"3"
title=
"选择服务范围"
@
confirm=
"handleAreaConfirm"
/>
<fui-toast
ref=
"toastRef"
/>
<fui-loading
isFixed
v-if=
"pageData.loading"
/>
<ApplyDialog
ref=
"applyDialogRef"
/>
</view>
</template>
<
style
lang=
"scss"
scoped
>
.page
{
background-color
:
#f7f8fa
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
.formBox
{
padding
:
24
rpx
;
.mt20
{
background
:
#fff
;
border-radius
:
20
rpx
;
padding
:
10
rpx
24
rpx
;
margin-bottom
:
24
rpx
;
}
}
.form-item
{
padding
:
30
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
180
rpx
;
.red
{
color
:
#ff4d4f
;
}
}
}
.page
{
background-color
:
#f8fafc
;
min-height
:
100vh
;
font-family
:
'DingTalk Sans'
,
sans-serif
;
}
/* ===== 发布模式 ===== */
.formBox
{
padding
:
24
rpx
;
.mt20
{
background
:
#fff
;
border-radius
:
20
rpx
;
padding
:
10
rpx
24
rpx
;
margin-bottom
:
24
rpx
;
}
}
.form-item
{
padding
:
30
rpx
0
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
1
rpx
solid
#f8f8f8
;
.label
{
font-size
:
28
rpx
;
color
:
#333
;
width
:
180
rpx
;
}
}
.select-text
{
font-size
:
28
rpx
;
&.placeholder
{
color
:
#ccc
;
}
}
.unit-slot
{
font-size
:
28
rpx
;
color
:
#999
;
margin-left
:
12
rpx
;
}
:deep
(
.fui-input__label
)
{
font-family
:
'DingTalk Sans'
!important
;
}
/* ===== 详情模式 ===== */
.detail-page
{
background-color
:
#f8fafc
;
}
.detail-hero
{
position
:
relative
;
width
:
100%
;
height
:
520
rpx
;
background
:
linear-gradient
(
135deg
,
#e65c00
0%
,
#fa8c16
50%
,
#f9af31
100%
);
overflow
:
hidden
;
.hero-img
{
width
:
100%
;
height
:
100%
;
display
:
block
;
}
.hero-placeholder
{
width
:
100%
;
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
.hero-placeholder-text
{
font-size
:
26
rpx
;
color
:
rgba
(
255
,
255
,
255
,
0.5
);
margin-top
:
20
rpx
;
}
}
.hero-overlay
{
position
:
absolute
;
bottom
:
0
;
left
:
0
;
right
:
0
;
height
:
280
rpx
;
background
:
linear-gradient
(
to
top
,
rgba
(
0
,
0
,
0
,
0.5
)
0%
,
transparent
100%
);
}
/* 左下角价格浮层 */
.hero-price-float
{
position
:
absolute
;
bottom
:
60
rpx
;
left
:
32
rpx
;
display
:
flex
;
align-items
:
baseline
;
.price-symbol
{
font-size
:
30
rpx
;
color
:
#fff
;
font-weight
:
bold
;
opacity
:
0.9
;
}
.price-num
{
font-size
:
72
rpx
;
color
:
#fff
;
font-weight
:
bold
;
line-height
:
1
;
margin-left
:
4
rpx
;
text-shadow
:
0
2
rpx
10
rpx
rgba
(
0
,
0
,
0
,
0.3
);
}
.price-unit
{
font-size
:
28
rpx
;
color
:
rgba
(
255
,
255
,
255
,
0.8
);
margin-left
:
8
rpx
;
}
}
.hero-badge
{
position
:
absolute
;
top
:
30
rpx
;
right
:
30
rpx
;
display
:
flex
;
align-items
:
center
;
background-color
:
rgba
(
255
,
255
,
255
,
0.18
);
backdrop-filter
:
blur
(
8px
);
border
:
1
rpx
solid
rgba
(
255
,
255
,
255
,
0.3
);
padding
:
10
rpx
22
rpx
;
border-radius
:
40
rpx
;
.badge-dot
{
width
:
14
rpx
;
height
:
14
rpx
;
border-radius
:
50%
;
background-color
:
#52c41a
;
margin-right
:
10
rpx
;
box-shadow
:
0
0
0
4
rpx
rgba
(
82
,
196
,
26
,
0.3
);
}
.badge-text
{
font-size
:
24
rpx
;
color
:
#fff
;
font-weight
:
500
;
}
}
}
.main-card
{
background-color
:
#fff
;
margin
:
-40
rpx
24
rpx
20
rpx
;
border-radius
:
28
rpx
;
padding
:
36
rpx
32
rpx
28
rpx
;
box-shadow
:
0
8
rpx
32
rpx
rgba
(
0
,
0
,
0
,
0.08
);
position
:
relative
;
z-index
:
10
;
.service-title
{
font-size
:
40
rpx
;
font-weight
:
bold
;
color
:
#1a1a1a
;
line-height
:
1.35
;
display
:
block
;
margin-bottom
:
28
rpx
;
letter-spacing
:
1
rpx
;
}
.price-highlight-row
{
display
:
flex
;
align-items
:
center
;
background-color
:
#fff8f0
;
border-radius
:
16
rpx
;
padding
:
24
rpx
;
.price-wrap
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
.price-label
{
font-size
:
22
rpx
;
color
:
#94a3b8
;
margin-bottom
:
8
rpx
;
}
.price-main
{
display
:
flex
;
align-items
:
baseline
;
.ph-symbol
{
font-size
:
24
rpx
;
color
:
#fa541c
;
font-weight
:
bold
;
}
.ph-value
{
font-size
:
52
rpx
;
color
:
#fa541c
;
font-weight
:
bold
;
margin-left
:
2
rpx
;
line-height
:
1
;
}
.ph-unit
{
font-size
:
22
rpx
;
color
:
#fa8c16
;
margin-left
:
4
rpx
;
}
}
}
.divider-v
{
width
:
1
rpx
;
height
:
60
rpx
;
background-color
:
#ffd591
;
margin
:
0
28
rpx
;
}
.scope-wrap
{
flex
:
1.2
;
display
:
flex
;
flex-direction
:
column
;
.scope-label
{
font-size
:
22
rpx
;
color
:
#94a3b8
;
margin-bottom
:
8
rpx
;
}
.scope-value
{
font-size
:
28
rpx
;
color
:
#1e293b
;
font-weight
:
600
;
line-height
:
1.3
;
}
}
}
}
.info-section
{
background-color
:
#fff
;
margin
:
0
24
rpx
20
rpx
;
border-radius
:
24
rpx
;
padding
:
32
rpx
;
box-shadow
:
0
2
rpx
12
rpx
rgba
(
0
,
0
,
0
,
0.04
);
.section-header
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
28
rpx
;
.section-bar
{
width
:
8
rpx
;
height
:
36
rpx
;
background
:
linear-gradient
(
to
bottom
,
#fa8c16
,
#e65c00
);
border-radius
:
4
rpx
;
margin-right
:
16
rpx
;
}
.section-title
{
font-size
:
32
rpx
;
font-weight
:
bold
;
color
:
#1a1a1a
;
}
}
.info-grid
{
.info-row
{
display
:
flex
;
align-items
:
flex-start
;
padding
:
4
rpx
0
;
.info-icon-wrap
{
width
:
56
rpx
;
height
:
56
rpx
;
background-color
:
#f0faf2
;
border-radius
:
14
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
&.orange
{
background-color
:
#fff8f0
;
}
}
.info-content
{
flex
:
1
;
margin-left
:
20
rpx
;
padding
:
4
rpx
0
;
.info-label
{
font-size
:
24
rpx
;
color
:
#94a3b8
;
display
:
block
;
margin-bottom
:
6
rpx
;
}
.info-value
{
font-size
:
30
rpx
;
color
:
#1e293b
;
line-height
:
1.4
;
font-weight
:
500
;
}
}
}
.info-divider
{
height
:
1
rpx
;
background-color
:
#f1f5f9
;
margin
:
24
rpx
0
;
}
}
}
/* 服务保障小图标行 */
.tips-section
{
background-color
:
#fff
;
margin
:
0
24
rpx
20
rpx
;
border-radius
:
24
rpx
;
padding
:
32
rpx
;
box-shadow
:
0
2
rpx
12
rpx
rgba
(
0
,
0
,
0
,
0.04
);
.tips-row
{
display
:
flex
;
align-items
:
center
;
.tip-item
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
.tip-icon
{
width
:
72
rpx
;
height
:
72
rpx
;
background-color
:
#f0faf2
;
border-radius
:
50%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
margin-bottom
:
12
rpx
;
}
.tip-text
{
font-size
:
24
rpx
;
color
:
#475569
;
font-weight
:
500
;
}
}
.tip-divider
{
width
:
1
rpx
;
height
:
56
rpx
;
background-color
:
#f1f5f9
;
}
}
}
/* ===== 底部操作栏 ===== */
.detail-footer
{
position
:
fixed
;
bottom
:
0
;
left
:
0
;
right
:
0
;
background-color
:
rgba
(
255
,
255
,
255
,
0.97
);
backdrop-filter
:
blur
(
10px
);
padding
:
20
rpx
32
rpx
calc
(
20
rpx
+
env
(
safe-area-inset-bottom
));
box-shadow
:
0
-8
rpx
32
rpx
rgba
(
0
,
0
,
0
,
0.06
);
z-index
:
100
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
.footer-price-area
{
display
:
flex
;
flex-direction
:
column
;
.footer-price-label
{
font-size
:
22
rpx
;
color
:
#94a3b8
;
margin-bottom
:
4
rpx
;
}
.footer-price-main
{
display
:
flex
;
align-items
:
baseline
;
.fp-symbol
{
font-size
:
22
rpx
;
color
:
#fa541c
;
font-weight
:
bold
;
}
.fp-value
{
font-size
:
44
rpx
;
color
:
#fa541c
;
font-weight
:
bold
;
margin-left
:
2
rpx
;
line-height
:
1
;
}
.fp-unit
{
font-size
:
22
rpx
;
color
:
#fa8c16
;
margin-left
:
4
rpx
;
}
}
}
.footer-apply-btn
{
background
:
linear-gradient
(
135deg
,
#fa8c16
0%
,
#fa541c
100%
);
height
:
88
rpx
;
padding
:
0
52
rpx
;
border-radius
:
44
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
box-shadow
:
0
8
rpx
24
rpx
rgba
(
250
,
140
,
22
,
0.35
);
&:active
{
opacity
:
0.9
;
transform
:
scale
(
0.97
);
}
.apply-btn-text
{
font-size
:
30
rpx
;
color
:
#fff
;
font-weight
:
bold
;
letter-spacing
:
2
rpx
;
}
}
}
</
style
>
src/utils/dict/area.ts
浏览文件 @
69e1168c
...
...
@@ -3,6 +3,21 @@ import { cascaderHn } from '/@/api/model/dict'
const
storageKey
=
'app_dict_data_area_cascaderHn'
let
areaOptions
=
[]
let
areaMap
=
new
Map
()
/**
* 构建扁平化字典Map,用于高效查询
*/
function
buildAreaMap
(
nodes
)
{
if
(
!
nodes
)
return
for
(
const
node
of
nodes
)
{
areaMap
.
set
(
node
.
value
,
node
.
text
)
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
buildAreaMap
(
node
.
children
)
}
}
}
export
async
function
getDictData
()
{
// 先从本地加载数据
let
dictData
=
getLocalDict
()
...
...
@@ -15,6 +30,9 @@ export async function getDictData() {
}
areaOptions
=
dictData
// 构建查询Map
areaMap
.
clear
()
buildAreaMap
(
areaOptions
)
return
dictData
}
...
...
@@ -24,7 +42,11 @@ export async function getDictData() {
export
function
getLocalDict
()
{
const
data
=
uni
.
getStorageSync
(
storageKey
)
if
(
data
)
{
return
data
?
JSON
.
parse
(
data
)
:
null
const
dict
=
data
?
JSON
.
parse
(
data
)
:
null
if
(
dict
&&
areaMap
.
size
===
0
)
{
buildAreaMap
(
dict
)
}
return
dict
}
return
null
}
...
...
@@ -40,37 +62,16 @@ export function refreshDictData() {
}
export
function
getText
(
scope
:
string
,
spliced
:
string
)
{
if
(
!
scope
||
!
areaOptions
||
areaOptions
.
length
===
0
)
{
return
''
if
(
!
scope
)
return
''
// 如果Map尚未初始化,则回退到原始递归方法或返回空
if
(
areaMap
.
size
===
0
)
{
const
dict
=
getLocalDict
()
if
(
!
dict
)
return
''
}
const
values
=
scope
.
split
(
','
)
const
labels
=
[]
// 递归查找label
const
findLabel
=
(
nodes
,
value
)
=>
{
for
(
const
node
of
nodes
)
{
if
(
node
.
value
===
value
)
{
return
node
.
text
}
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
const
found
=
findLabel
(
node
.
children
,
value
)
if
(
found
)
{
return
found
}
}
}
return
null
// 如果没找到,返回原始value
}
const
labels
=
values
.
map
(
val
=>
areaMap
.
get
(
val
.
trim
())
||
val
.
trim
())
for
(
const
value
of
values
)
{
const
text
=
findLabel
(
areaOptions
,
value
.
trim
())
labels
.
push
(
text
)
}
if
(
spliced
)
{
return
labels
?
labels
.
join
(
spliced
)
:
''
}
else
{
return
labels
?
labels
.
join
(
''
)
:
''
}
return
labels
.
join
(
spliced
||
''
)
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论