提交 b4b30b49 作者: 廖在望

feat: 页面功能重构

上级 1c87240e
# API 接口地址 # API 接口地址
# VITE_GLOB_API_URL=http://111.22.182.169:49600 # VITE_GLOB_API_URL=http://111.22.182.169:49600
# VITE_GLOB_API_URL=http://36.133.16.81:42111 # VITE_GLOB_API_URL=http://36.133.16.81:42111
VITE_GLOB_API_URL=http://123.207.47.17 VITE_GLOB_API_URL=http://localhost:8080
# API 接口地址前缀 # API 接口地址前缀
# VITE_GLOB_API_URL_PREFIX=/jeecgboot # VITE_GLOB_API_URL_PREFIX=/jeecgboot
......
const fs = require('fs');
['src/pages/chanxiao/supplyXuQiu.vue', 'src/pages/chanxiao/purchaseXuQiu.vue'].forEach(filePath => {
let file = fs.readFileSync(filePath, 'utf8');
// Replace ' required\n' or ' required ' inside tags
file = file.replace(/\s+required(\s*[\n>])/g, ' :required="isSave"$1');
file = file.replace(/class="form-item required flex align-center"/g, ':class="[\'form-item\', \'flex\', \'align-center\', isSave ? \'required\' : \'\']"');
file = file.replace(/placeholder="([^"]+)"/g, ':placeholder="isSave ? \'$1\' : \'\'"');
fs.writeFileSync(filePath, file);
});
const fs = require('fs');
['src/pages/chanxiao/supplyXuQiu.vue', 'src/pages/chanxiao/purchaseXuQiu.vue'].forEach(filePath => {
let file = fs.readFileSync(filePath, 'utf8');
// Fix ::placeholder back to :placeholder
file = file.replace(/::placeholder/g, ':placeholder');
// Also hide default text "请选择" if !isSave
file = file.replace(/\|\| '请选择(.*?)'/g, "|| (isSave ? '请选择$1' : '')");
fs.writeFileSync(filePath, file);
});
const fs = require('fs');
['src/pages/chanxiao/supplyXuQiu.vue', 'src/pages/chanxiao/purchaseXuQiu.vue'].forEach(filePath => {
let file = fs.readFileSync(filePath, 'utf8');
// Make wrapper clicks conditional
file = file.replace(/@click="show\.address = true"/g, '@click="isSave && (show.address = true)"');
file = file.replace(/@click="show\.time1 = true"/g, '@click="isSave && (show.time1 = true)"');
file = file.replace(/@click="show\.time2 = true"/g, '@click="isSave && (show.time2 = true)"');
file = file.replace(/@click="show\.classify = true"/g, '@click="isSave && (show.classify = true)"');
file = file.replace(/@click="show\.time = true"/g, '@click="isSave && (show.time = true)"');
// Remove `isSave ? '\`...`' : ''` syntax error
// From `:placeholder="isSave ? '\`请输入${pageText}标题\`' : ''"`
// To `:placeholder="isSave ? \`请输入${pageText}标题\` : ''"`
file = file.replace(/:placeholder="isSave \? '\\`(.*?)\\`' : ''"/g, ':placeholder="isSave ? \\`$1\\` : \'\'"');
fs.writeFileSync(filePath, file);
});
...@@ -4,7 +4,8 @@ import { otherHttp } from '/@/utils/http/axios' ...@@ -4,7 +4,8 @@ import { otherHttp } from '/@/utils/http/axios'
enum Api { enum Api {
purchaseList = '/purchaseSell/bizPurchase/list', // 采购需求列表 purchaseList = '/purchaseSell/bizPurchase/list', // 采购需求列表
supplyList = '/purchaseSell/bizSupply/list', // 供应需求列表 supplyList = '/purchaseSell/bizSupply/list', // 供应需求列表
purchaseSellDetails = '/purchaseSell/bizPurchase/queryById', // 产销详情 purchaseSellDetails = '/purchaseSell/bizPurchase/queryById', // 采购详情
supplyDetails = '/purchaseSell/bizSupply/queryById', // 供应详情
purchaseSell = '/purchaseSell/bizPurchase/add', // 发布采购需求 purchaseSell = '/purchaseSell/bizPurchase/add', // 发布采购需求
supplyAdd = '/purchaseSell/bizSupply/add', // 发布供应需求 supplyAdd = '/purchaseSell/bizSupply/add', // 发布供应需求
bizPurchaseSupplyRecord = '/purchaseSell/bizPurchaseSupplyRecord/add', // 报价 bizPurchaseSupplyRecord = '/purchaseSell/bizPurchaseSupplyRecord/add', // 报价
...@@ -34,7 +35,7 @@ export function supplyList(params = {}) { ...@@ -34,7 +35,7 @@ export function supplyList(params = {}) {
/** /**
* @param params 请求参数 * @param params 请求参数
* @description: 采购需求详情 * @description: 采购详情
*/ */
export function purchaseSellDetails(params = {}) { export function purchaseSellDetails(params = {}) {
return otherHttp.get({ return otherHttp.get({
...@@ -48,6 +49,20 @@ export function purchaseSellDetails(params = {}) { ...@@ -48,6 +49,20 @@ export function purchaseSellDetails(params = {}) {
/** /**
* @param params 请求参数 * @param params 请求参数
* @description: 供应详情
*/
export function supplyDetails(params = {}) {
return otherHttp.get({
url: Api.supplyDetails,
params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
}
/**
* @param params 请求参数
* @description: 发布采购需求 * @description: 发布采购需求
*/ */
export function purchaseSellAdd(params = {}) { export function purchaseSellAdd(params = {}) {
......
import { otherHttp } from '/@/utils/http/axios' import { otherHttp } from '/@/utils/http/axios'
enum Api { // enum Api {
bizCommonFileList = '/mine/bizCommonFile/list', // 文件列表 // bizCommonFileList = '/mine/bizCommonFile/list', // 文件列表
} // }
//
/** // export function bizCommonFileList(params = {}) {
* @param params 请求参数 // return otherHttp.get({
* @description: 文件列表 // url: Api.bizCommonFileList,
*/ // params,
export function bizCommonFileList(params = {}) { // })
return otherHttp.get({ // }
url: Api.bizCommonFileList, // export function getResourceList(params = {}) {
params, // }
}) // export function addResource(params = {}) {
} // }
/** // export function downloadResource(params = {}) {
* 资源列表 // }
* @param params
* @returns
*/
export function getResourceList(params = {}) {
return otherHttp.get({
url: '/resource/list',
params,
})
}
/**
* 添加资源
* @param params
* @returns
*/
export function addResource(params = {}) {
return otherHttp.post({
url: '/resource/add',
params,
})
}
/**
* 下载资源
* @param params
* @returns
*/
export function downloadResource(params = {}) {
return otherHttp.get({
url: '/resource/download',
params,
})
}
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
const props = defineProps({
show: { type: Boolean, default: false },
title: { type: String, default: '选择所在地区' },
layer: { type: Number, default: 3 }, // 1-5 层
rootCode: { type: [String, Number], default: 0 }, // 湖南省代码是 43,默认 0 为全国
})
const emit = defineEmits(['update:show', 'confirm', 'cancel'])
const cascaderRef = ref(null)
const state = reactive({
options: [],
value: [],
})
// 监听显示状态
watch(() => props.show, (val) => {
if (val && state.options.length === 0) {
fetchSubData(props.rootCode, null)
}
})
function fetchSubData(parentCode, e) {
LinghuoyonggongAPI.queryConditions({ parentCode }).then((res) => {
if (res && res.length) {
const dataArr = res.map(i => ({ text: i.name, value: i.code }))
if (!state.options.length) {
state.options = dataArr
} else {
cascaderRef.value.setRequestData(dataArr, e.layer)
}
} else {
if (cascaderRef.value) cascaderRef.value.end()
}
})
}
function handleChange(e) {
// e.layer 从 1 开始
if (e.layer < props.layer) {
fetchSubData(e.value, e)
} else {
if (cascaderRef.value) cascaderRef.value.end()
}
}
function handleComplete(e) {
emit('confirm', {
text: e.text,
value: e.value,
fullText: e.text.join(' / '),
fullCode: e.value.join(',')
})
emit('update:show', false)
}
function handleClose() {
emit('update:show', false)
emit('cancel')
}
</script>
<template>
<fui-bottom-popup :show="show" @close="handleClose">
<view class="area-picker-wrap">
<view class="area-header">
<text class="area-title">{{ title }}</text>
<view class="close-icon" @click="handleClose">
<fui-icon name="close" :size="44" color="#ccc"></fui-icon>
</view>
</view>
<view class="area-body">
<fui-cascader
ref="cascaderRef"
:value="state.value"
stepLoading
@change="handleChange"
@complete="handleComplete"
:options="state.options"
/>
</view>
</view>
</fui-bottom-popup>
</template>
<style lang="scss" scoped>
.area-picker-wrap {
background-color: #fff;
border-radius: 32rpx 32rpx 0 0;
display: flex;
flex-direction: column;
height: 80vh;
font-family: 'DingTalk Sans', sans-serif;
}
.area-header {
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx 30rpx;
position: relative;
border-bottom: 1rpx solid #f5f5f5;
.area-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.close-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
padding: 10rpx;
}
}
.area-body {
flex: 1;
overflow: hidden;
}
/* 统一字体穿透 */
:deep(.fui-cascader__item) {
font-family: 'DingTalk Sans' !important;
}
</style>
...@@ -80,6 +80,8 @@ export function useMapbox<T extends MapboxInstance, P extends MapboxConfig>( ...@@ -80,6 +80,8 @@ export function useMapbox<T extends MapboxInstance, P extends MapboxConfig>(
getInstance()?.setLayoutProperty(layerId, name, value), getInstance()?.setLayoutProperty(layerId, name, value),
setFilter: (layerId: string, filter: any[]) => getInstance()?.setFilter(layerId, filter), setFilter: (layerId: string, filter: any[]) => getInstance()?.setFilter(layerId, filter),
flyTo: (options: mapboxgl.FlyToOptions) => getInstance()?.flyTo(options), flyTo: (options: mapboxgl.FlyToOptions) => getInstance()?.flyTo(options),
fitBounds: (bounds: [[number, number], [number, number]], options?: mapboxgl.FitBoundsOptions) =>
getInstance()?.fitBounds(bounds, options),
addMarker: ( addMarker: (
id: string, id: string,
lnglat: [number, number], lnglat: [number, number],
......
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
flyToOptions: undefined, flyToOptions: undefined,
regionOptions: undefined, regionOptions: undefined,
addMarkerOptions: undefined, addMarkerOptions: undefined,
fitBoundsOptions: undefined,
// change options 锁,结合 event,配合数组实现先进先出队列控制 // change options 锁,结合 event,配合数组实现先进先出队列控制
changeLock: false, changeLock: false,
...@@ -372,6 +373,18 @@ ...@@ -372,6 +373,18 @@
this.tryTriggerChange() this.tryTriggerChange()
}, },
fitBounds(bounds, options) {
// 加入到 change 事件队列中
this.changeOptionsQueue.push({
fn: 'fitBounds',
bounds,
options,
})
// 尝试触发 change 事件
this.tryTriggerChange()
},
addMarker(id, lngLat, popup, popupDefaultOpen, imageUrl, iconSize) { addMarker(id, lngLat, popup, popupDefaultOpen, imageUrl, iconSize) {
// 加入到 change 事件队列中 // 加入到 change 事件队列中
this.changeOptionsQueue.push({ this.changeOptionsQueue.push({
...@@ -460,6 +473,8 @@ ...@@ -460,6 +473,8 @@
:change:regionOptions="mapbox.changeRegionOptions" :change:regionOptions="mapbox.changeRegionOptions"
:addMarkerOptions="addMarkerOptions" :addMarkerOptions="addMarkerOptions"
:change:addMarkerOptions="mapbox.changeAddMarkerOptions" :change:addMarkerOptions="mapbox.changeAddMarkerOptions"
:fitBoundsOptions="fitBoundsOptions"
:change:fitBoundsOptions="mapbox.changeFitBoundsOptions"
/> />
<!-- #endif --> <!-- #endif -->
<!-- #ifndef APP-PLUS || H5 --> <!-- #ifndef APP-PLUS || H5 -->
......
...@@ -410,6 +410,11 @@ export default { ...@@ -410,6 +410,11 @@ export default {
this.map.flyTo(options) this.map.flyTo(options)
} }
}, },
changeFitBoundsOptions(options) {
if (this.checkOnChangeValidity(options)) {
this.map.fitBounds(options.bounds, options.options)
}
},
changeAddSourceOptions(options) { changeAddSourceOptions(options) {
if (this.checkOnChangeValidity(options)) { if (this.checkOnChangeValidity(options)) {
if (this.map.getSource(options.id)) { if (this.map.getSource(options.id)) {
......
...@@ -293,20 +293,7 @@ ...@@ -293,20 +293,7 @@
"navigationBarBackgroundColor": "#5DB66F", "navigationBarBackgroundColor": "#5DB66F",
"navigationBarTextStyle": "white", "navigationBarTextStyle": "white",
"backgroundColorBottom": "#F2F2F2", "backgroundColorBottom": "#F2F2F2",
"onReachBottomDistance": 50, "onReachBottomDistance": 50
"app-plus": {
"titleNView": {
"titleSize": "20",
"buttons": [
{
"text": "申请入驻",
"color": "#fff",
"fontSize": "28rpx",
"width": "auto"
}
]
}
}
} }
}, },
{ {
...@@ -357,21 +344,7 @@ ...@@ -357,21 +344,7 @@
"navigationBarBackgroundColor": "#5DB66F", "navigationBarBackgroundColor": "#5DB66F",
"navigationBarTextStyle": "white", "navigationBarTextStyle": "white",
"backgroundColorBottom": "#F2F2F2", "backgroundColorBottom": "#F2F2F2",
"onReachBottomDistance": 50, "onReachBottomDistance": 50
"app-plus": {
"titleNView": {
"buttons": [
{
"text": "\uE674 地图模式",
"fontSrc": "/static/fonts/tihuan.ttf",
"color": "#fff",
"fontSize": "26rpx",
"width": "auto",
"icon": "map"
}
]
}
}
} }
}, },
{ {
...@@ -382,21 +355,7 @@ ...@@ -382,21 +355,7 @@
"navigationBarBackgroundColor": "#5DB66F", "navigationBarBackgroundColor": "#5DB66F",
"navigationBarTextStyle": "white", "navigationBarTextStyle": "white",
"backgroundColorBottom": "#F2F2F2", "backgroundColorBottom": "#F2F2F2",
"onReachBottomDistance": 50, "onReachBottomDistance": 50
"app-plus": {
"titleNView": {
"buttons": [
{
"text": "\uE674 列表模式",
"fontSrc": "/static/fonts/tihuan.ttf",
"color": "#fff",
"fontSize": "26rpx",
"width": "auto",
"icon": "map"
}
]
}
}
} }
}, },
{ {
...@@ -591,29 +550,6 @@ ...@@ -591,29 +550,6 @@
} }
}, },
{ {
"path": "pages/resource/resource",
"style": {
"navigationBarTitleText": "资源库管理",
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#5DB66F",
"navigationBarTextStyle": "white",
"backgroundColorBottom": "#F2F2F2",
"app-plus": {
"titleNView": {
"buttons": [
{
"text": "+ 添加资源",
"fontSrc": "/static/uni.ttf",
"color": "#fff",
"fontSize": "26rpx",
"width": "auto"
}
]
}
}
}
},
{
"path": "pages/nongchang/detail/index", "path": "pages/nongchang/detail/index",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
...@@ -689,19 +625,7 @@ ...@@ -689,19 +625,7 @@
"path" : "pages/kexinnongzi/kexinnongzi", "path" : "pages/kexinnongzi/kexinnongzi",
"style" : "style" :
{ {
"navigationBarTitleText" : "可信农资", "navigationBarTitleText" : "可信农资"
"app-plus": {
"titleNView": {
"buttons": [
{
"text": "申请入驻",
"color": "#fff",
"fontSize": "26rpx",
"width": "auto"
}
]
}
}
} }
}, },
{ {
......
...@@ -10,27 +10,30 @@ ...@@ -10,27 +10,30 @@
onPullDownRefresh(() => { onPullDownRefresh(() => {
pageData.search.pageNo = 1 pageData.search.pageNo = 1
if (pageData.currentTransactionTab === 1) { if (pageData.currentTransactionTab === 1) {
pageData.purchaseDemands = [] pageData.supplyInfos = []
getPurchaseList() fetchSupplyInfos()
} }
if (pageData.currentTransactionTab === 2) { if (pageData.currentTransactionTab === 2) {
pageData.supplyInfos = [] pageData.purchaseDemands = []
getSupplyList() fetchPurchaseDemands()
} }
setTimeout(function () { setTimeout(function () {
uni.stopPullDownRefresh() uni.stopPullDownRefresh()
Message.toast('刷新成功') uni.showToast({
title: '刷新成功',
icon: 'none',
})
}, 1000) }, 1000)
}) })
onShow(() => { onShow(() => {
pageData.search.pageNo = 1 pageData.search.pageNo = 1
if (pageData.currentTransactionTab === 1) { if (pageData.currentTransactionTab === 1) {
pageData.purchaseDemands = [] pageData.supplyInfos = []
getPurchaseList() fetchSupplyInfos()
} }
if (pageData.currentTransactionTab === 2) { if (pageData.currentTransactionTab === 2) {
pageData.supplyInfos = [] pageData.purchaseDemands = []
getSupplyList() fetchPurchaseDemands()
} }
}) })
...@@ -143,7 +146,7 @@ ...@@ -143,7 +146,7 @@
], ],
}) })
// 获取采购需求列表 // 获取采购需求列表
function getSupplyList() { function fetchPurchaseDemands() {
pageData.loading = true pageData.loading = true
ChanxiaoAPI.purchaseList(pageData.search) ChanxiaoAPI.purchaseList(pageData.search)
.then((res) => { .then((res) => {
...@@ -160,9 +163,12 @@ ...@@ -160,9 +163,12 @@
}) })
} }
// 获取供应信息列表 // 获取供应信息列表
function getPurchaseList() { function fetchSupplyInfos() {
pageData.loading = true pageData.loading = true
ChanxiaoAPI.supplyList(pageData.search) const params = { ...pageData.search }
delete params.classify // 后端 BizSupply 无 classify 字段,移除以防报错
ChanxiaoAPI.supplyList(params)
.then((res) => { .then((res) => {
const { records, total } = res const { records, total } = res
pageData.supplyInfos = [...pageData.supplyInfos, ...records] pageData.supplyInfos = [...pageData.supplyInfos, ...records]
...@@ -179,7 +185,6 @@ ...@@ -179,7 +185,6 @@
// 分类标签点击事件 // 分类标签点击事件
function onCategoryTabClick(tab: any) { function onCategoryTabClick(tab: any) {
// 在这里添加具体的分类标签点击逻辑
if (tab.id === -1) { if (tab.id === -1) {
pageData.search.classify = null pageData.search.classify = null
} else { } else {
...@@ -188,23 +193,18 @@ ...@@ -188,23 +193,18 @@
pageData.search.pageNo = 1 pageData.search.pageNo = 1
pageData.purchaseDemands = [] pageData.purchaseDemands = []
pageData.supplyInfos = [] pageData.supplyInfos = []
if (pageData.currentTransactionTab === 1) if (pageData.currentTransactionTab === 1) fetchSupplyInfos()
getPurchaseList() if (pageData.currentTransactionTab === 2) fetchPurchaseDemands()
if (pageData.currentTransactionTab === 2)
getSupplyList()
} }
// 采购/供应标签点击事件 // 采购/供应标签点击事件
function onTransactionTabClick(tab: any) { function onTransactionTabClick(tab: any) {
// 在这里添加具体的采购/供应标签点击逻辑
pageData.currentTransactionTab = tab.id pageData.currentTransactionTab = tab.id
pageData.search.pageNo = 1 pageData.search.pageNo = 1
pageData.purchaseDemands = [] pageData.purchaseDemands = []
pageData.supplyInfos = [] pageData.supplyInfos = []
if (pageData.currentTransactionTab === 1) if (pageData.currentTransactionTab === 1) fetchSupplyInfos()
getPurchaseList() if (pageData.currentTransactionTab === 2) fetchPurchaseDemands()
if (pageData.currentTransactionTab === 2)
getSupplyList()
} }
// 新需求提醒点击事件 // 新需求提醒点击事件
...@@ -249,16 +249,14 @@ getSupplyList() ...@@ -249,16 +249,14 @@ getSupplyList()
onReachBottom(() => { onReachBottom(() => {
console.log('触底了') console.log('触底了')
if (pageData.currentTransactionTab === 1) { if (pageData.currentTransactionTab === 1) {
if (pageData.total <= pageData.purchaseDemands.length) if (pageData.total <= pageData.supplyInfos.length) return
return
pageData.search.pageNo++ pageData.search.pageNo++
getPurchaseList() fetchSupplyInfos()
} }
if (pageData.currentTransactionTab === 2) { if (pageData.currentTransactionTab === 2) {
if (pageData.total <= pageData.supplyInfos.length) if (pageData.total <= pageData.purchaseDemands.length) return
return
pageData.search.pageNo++ pageData.search.pageNo++
getSupplyList() fetchPurchaseDemands()
} }
}) })
</script> </script>
...@@ -352,143 +350,77 @@ return ...@@ -352,143 +350,77 @@ return
<fui-empty src="/static/images/no-data.png" title="暂无数据" /> <fui-empty src="/static/images/no-data.png" title="暂无数据" />
</view> </view>
<!-- 供应信息列表 --> <!-- 供应信息列表 -->
<template v-for="(info, index) in pageData.supplyInfos" :key="info.id"> <view v-if="pageData.currentTransactionTab === 1">
<view <view
v-show="pageData.currentTransactionTab === 1" v-for="(info, index) in pageData.supplyInfos"
class="codefun-flex-row" :key="info.id"
:class="[index === 0 ? 'group_10' : 'group_12']" class="product-card"
@click="onSupplyInfoClick(info)" @click="onSupplyInfoClick(info)"
> >
<view <view class="product-image-box">
class="codefun-flex-col codefun-justify-start codefun-items-center codefun-self-center codefun-relative" <image class="product-image" :src="info.image" mode="aspectFill" />
:class="index === 0 ? 'section_7' : 'image-wrapper view'"
>
<image class="image_7" :src="info.image" />
<!-- <view
v-if="info.tag.show"
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_6"
:class="index === 0 ? 'pos_2' : ''"
>
<text class="font_3" :class="`text_${index === 0 ? 25 : 31}`">{{ info.tag.text }}</text>
</view> -->
</view> </view>
<view <view class="product-info">
class="codefun-ml-12 codefun-flex-col codefun-flex-1" <view class="product-title-row">
:class="index === 0 ? 'codefun-self-start group_11' : ''" <text class="product-title">{{ info.title }}</text>
> </view>
<text class="codefun-self-start font_4" :class="index === 0 ? 'text_24' : ''"> <view class="product-spec" v-if="info.productSpecs">
{{ info.title }}
</text>
<text
v-if="info.productSpecs"
class="codefun-self-start font_2"
:class="index === 0 ? 'text_26 mt-11' : 'mt-11'"
>
{{ info.productSpecs }} {{ info.productSpecs }}
</text>
<view
class="codefun-flex-row codefun-justify-between codefun-items-center codefun-self-stretch"
:class="index === 0 ? 'mt-11' : 'mt-11'"
>
<view class="codefun-flex-row">
<image class="codefun-shrink-0 image_9" :src="pageData.icons.location" />
<text class="codefun-ml-4 font_2" :class="`text_${index === 0 ? 27 : 30}`">{{
info.location
}}</text>
</view> </view>
<view <view class="product-price-row">
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_5" <text class="price-symbol">¥</text>
@click.stop="onQuoteClick(info)" <text class="price-value">{{ info.minPrice }}-{{ info.maxPrice }}</text>
> <text class="price-unit">/{{ info.unit }}</text>
<text class="font_9">立即报价</text> </view>
<view class="product-footer">
<view class="footer-left">
<fui-icon name="location" :size="24" color="#999"></fui-icon>
<text class="footer-text">{{ info.location }}</text>
</view>
<view class="quote-btn supply" @click.stop="onQuoteClick(info)">
立即报价
</view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</template>
<!-- 采购需求列表 --> <!-- 采购需求列表 -->
<view v-if="pageData.currentTransactionTab === 2">
<view <view
v-if="pageData.currentTransactionTab === 2 && pageData.purchaseDemands.length === 0" v-for="(demand, index) in pageData.purchaseDemands"
style="height: 528rpx" :key="demand.id"
> class="product-card purchase"
<fui-empty src="/static/images/no-data.png" title="暂无数据" />
</view>
<view v-else>
<template v-for="(demand, index) in pageData.purchaseDemands" :key="demand.id">
<view
v-show="pageData.currentTransactionTab === 2"
class="codefun-flex-row codefun-items-center my-3"
:class="[index === 0 ? 'group_2' : '', index === 1 ? 'group_6' : '']"
@click="onPurchaseDemandClick(demand)" @click="onPurchaseDemandClick(demand)"
> >
<view <view class="product-image-box">
class="codefun-flex-col codefun-justify-start codefun-items-center codefun-relative" <image class="product-image" :src="demand.image" mode="aspectFill" />
:class="index === 0 ? 'section_6' : 'image-wrapper'" <view class="status-badge">采购中</view>
>
<image class="image_7" :src="demand.image" />
<!-- <view
v-if="demand.tag.show"
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_4"
:class="index === 0 ? 'pos' : ''"
>
<text class="font_3 text_11">{{ demand.tag.text }}</text>
</view> -->
</view> </view>
<view class="codefun-ml-12 codefun-flex-col codefun-flex-1"> <view class="product-info">
<text class="codefun-self-start font_4">{{ demand.title }}</text> <view class="product-title-row">
<view class="codefun-flex-row codefun-self-stretch mt-2" v-if="demand.deadline"> <text class="product-title">{{ demand.title }}</text>
<image class="image_8" :src="pageData.icons.deadline" />
<text class="codefun-ml-4 font_2" :class="`text_${index === 0 ? 13 : 19}`">{{
demand.deadline
}}</text>
</view> </view>
<view class="codefun-flex-row codefun-self-stretch mt-2"> <view class="product-meta">
<image class="image_9" :src="pageData.icons.location" /> <text class="meta-item">需:{{ demand.count }}{{ demand.unit }}</text>
<text class="codefun-ml-4 font_2" :class="`text_${index === 0 ? 14 : 20}`">{{ <text class="meta-item deadline" v-if="demand.deadline">截止:{{ demand.deadline }}</text>
demand.location
}}</text>
</view> </view>
<view class="codefun-self-start" :class="index === 0 ? 'group_4' : 'group_8'"> <view class="product-price-row purchase">
<text <text class="price-label">预算:</text>
v-if="demand.priceStart || demand.priceEnd" <text class="price-symbol">¥</text>
class="font_6" <text class="price-value">{{ demand.priceStart }}-{{ demand.priceEnd }}</text>
:class="`text_${index === 0 ? 15 : 21}`"
</text
>
<text v-if="demand.priceStart || demand.priceEnd" class="font_5">{{
`${demand.priceStart}-${demand.priceEnd}`
}}</text>
<text v-if="demand.unit" class="font_7" :class="`text_${index === 0 ? 16 : 22}`"
>/{{ demand.unit }}</text
>
</view> </view>
<view class="product-footer">
<view class="footer-left">
<fui-icon name="location" :size="24" color="#999"></fui-icon>
<text class="footer-text">{{ demand.location }}</text>
</view> </view>
<view class="quote-btn purchase" @click.stop="onQuoteClick(demand)">
我有货
</view> </view>
<view
v-show="pageData.currentTransactionTab === 2"
class="codefun-flex-row codefun-justify-between codefun-items-center"
:class="[index === 0 ? 'group_5' : '', index === 1 ? 'group_9' : '']"
>
<view class="codefun-flex-row codefun-items-center">
<image class="codefun-shrink-0 image_10" :src="pageData.icons.quote" />
<text
class="font_8"
:class="[index === 0 ? 'text_17' : 'text_23', index === 0 ? 'ml-5' : 'codefun-ml-4']"
>
{{ demand.supplyCounts }}人报价
</text>
</view> </view>
<view
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_5"
@click.stop="onQuoteClick(demand)"
>
<text class="font_9">立即报价</text>
</view> </view>
</view> </view>
<!-- 分隔线 -->
<view class="divider" v-if="index < pageData.purchaseDemands.length - 1" />
</template>
</view> </view>
</view> </view>
</view> </view>
...@@ -646,256 +578,140 @@ return ...@@ -646,256 +578,140 @@ return
background-color: #ffffff; background-color: #ffffff;
border-radius: 21.8rpx; border-radius: 21.8rpx;
mix-blend-mode: NOTTHROUGH; mix-blend-mode: NOTTHROUGH;
.image_7 { min-height: 400rpx;
/* 商品卡片式设计 */
.product-card {
display: flex;
padding: 24rpx 0;
border-bottom: 1rpx solid #f2f2f2;
&:last-child {
border-bottom: none;
}
.product-image-box {
width: 180rpx;
height: 180rpx;
border-radius: 12rpx;
overflow: hidden;
background-color: #f8f8f8;
position: relative;
flex-shrink: 0;
.product-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.section_5 {
padding: 16rpx 20rpx 8rpx; .status-badge {
background-color: #fff7ec;
border-radius: 16rpx;
mix-blend-mode: NOTTHROUGH;
.image_6 {
width: 26rpx;
height: 32rpx;
}
.text_10 {
color: #ff8d1a;
line-height: 40rpx;
}
}
.group_2 {
margin-top: 24rpx;
.section_6 {
background-color: #f9fafb;
border-radius: 8rpx;
width: 160rpx;
height: 160rpx;
.text-wrapper_4 {
padding: 8rpx 0;
background-image: linear-gradient(90deg, #fb6c26 0%, #f2130d 100%);
border-radius: 0rpx 8rpx 8rpx 0rpx;
mix-blend-mode: NOTTHROUGH;
width: 64rpx;
.text_11 {
line-height: 18.46rpx;
}
}
.pos {
position: absolute; position: absolute;
left: 0; left: 0;
top: 8rpx; top: 0;
} background: rgba(250, 140, 22, 0.9);
} color: #fff;
.group_3 { font-size: 20rpx;
margin-top: 16rpx; padding: 4rpx 12rpx;
.text_13 { border-bottom-right-radius: 12rpx;
line-height: 22.1rpx;
}
.text_14 {
line-height: 22.24rpx;
}
}
.group_4 {
margin-left: 8rpx;
margin-top: 36rpx;
line-height: 24.44rpx;
.text_15 {
line-height: 17.3rpx;
}
.text_16 {
line-height: 24.24rpx;
} }
} }
.product-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.product-title {
font-size: 30rpx;
color: #333;
font-weight: bold;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
line-height: 1.4;
} }
.font_2 {
.product-spec, .product-meta {
font-size: 24rpx; font-size: 24rpx;
font-family: DingTalk Sans; color: #999;
line-height: 22.42rpx;
color: #999999;
}
.group_5 {
margin-top: 8rpx; margin-top: 8rpx;
.meta-item {
margin-right: 16rpx;
&.deadline {
color: #ff4d4f;
} }
.divider {
margin-top: 20rpx;
background-color: #eeeeee;
height: 2rpx;
}
.group_6 {
margin-top: 18rpx;
.group_7 {
margin-top: 16rpx;
.text_19 {
line-height: 22.1rpx;
}
.text_20 {
line-height: 22.28rpx;
}
}
.group_8 {
margin-left: 8rpx;
margin-top: 36rpx;
line-height: 24.44rpx;
.text_21 {
line-height: 17.3rpx;
}
.text_22 {
line-height: 24.24rpx;
}
}
}
.font_4 {
font-size: 32rpx;
font-family: DingTalk Sans;
line-height: 40rpx;
color: #333333;
} }
.image_8 {
width: 24rpx;
height: 24rpx;
} }
.image_9 {
width: 22rpx; .product-price-row {
height: 26rpx; margin-top: 10rpx;
} display: flex;
.font_6 { align-items: baseline;
.price-symbol {
font-size: 24rpx; font-size: 24rpx;
font-family: DingTalk Sans; color: #5db66f;
line-height: 18.44rpx; font-weight: bold;
font-weight: 700;
color: #f2130d;
} }
.font_5 { .price-value {
font-size: 32rpx; font-size: 34rpx;
font-family: DingTalk Sans; color: #5db66f;
line-height: 24.44rpx; font-weight: bold;
color: #f2130d; margin: 0 4rpx;
} }
.font_7 { .price-unit {
font-size: 24rpx; font-size: 24rpx;
font-family: DingTalk Sans; color: #999;
line-height: 24.44rpx;
color: #999999;
} }
.group_9 {
margin-top: 8rpx; &.purchase {
} .price-label {
.image_10 {
width: 80rpx;
height: 30rpx;
}
.font_8 {
font-size: 20rpx;
font-family: DingTalk Sans;
color: #999999;
}
.text-wrapper_5 {
padding: 12rpx 0;
background-color: #5db66f;
border-radius: 400rpx;
mix-blend-mode: NOTTHROUGH;
width: 136rpx;
height: 48rpx;
.font_9 {
font-size: 24rpx; font-size: 24rpx;
font-family: DingTalk Sans; color: #666;
line-height: 22.42rpx;
color: #ffffff;
}
}
.group_10 {
margin-top: 0;
padding: 30rpx 0;
border-bottom: solid 2rpx #eeeeee;
.section_7 {
padding: 30rpx 0 16rpx;
background-color: #f9fafb;
border-radius: 8rpx;
width: 160rpx;
height: 160rpx;
.image_12 {
width: 152rpx;
height: 114rpx;
}
.text-wrapper_6 {
padding: 8rpx 0;
background-color: #ffb700;
border-radius: 0rpx 8rpx 8rpx 0rpx;
mix-blend-mode: NOTTHROUGH;
width: 64rpx;
.text_25 {
line-height: 18.62rpx;
}
}
.pos_2 {
position: absolute;
left: 0;
top: 16rpx;
}
}
.group_11 {
margin-top: 16rpx;
.text_24 {
line-height: 30.28rpx;
}
.text_26 {
line-height: 22.92rpx;
} }
.text_27 { .price-symbol, .price-value {
line-height: 22.46rpx; color: #fa8c16;
} }
} }
} }
.font_3 {
font-size: 20rpx; .product-footer {
font-family: DingTalk Sans; margin-top: 12rpx;
line-height: 18.44rpx; display: flex;
color: #ffffff; justify-content: space-between;
} align-items: center;
.group_12 {
margin-top: 18rpx; .footer-left {
.view { display: flex;
margin-bottom: 8rpx; align-items: center;
} .footer-text {
.group_13 { font-size: 22rpx;
margin-top: 16rpx; color: #999;
.text_29 { margin-left: 4rpx;
line-height: 22.1rpx; max-width: 220rpx;
} overflow: hidden;
.text_30 { text-overflow: ellipsis;
line-height: 22.46rpx; white-space: nowrap;
}
} }
.group_14 {
margin-left: 8rpx;
margin-top: 28rpx;
.text_31 {
margin-left: 20rpx;
margin-right: -104rpx;
color: #f2130d;
line-height: 40rpx;
} }
.text_32 {
line-height: 40rpx; .quote-btn {
position: relative; font-size: 22rpx;
left: -24rpx; padding: 6rpx 20rpx;
border-radius: 30rpx;
&.supply {
background-color: #5db66f;
color: #fff;
} }
&.purchase {
background-color: #fa8c16;
color: #fff;
} }
} }
.image-wrapper {
padding: 30rpx 0 20rpx;
background-color: #f9fafb;
border-radius: 8rpx;
width: 160rpx;
height: 160rpx;
.image_11 {
width: 114rpx;
height: 110rpx;
} }
} }
.text_33 {
line-height: 18.6rpx;
} }
} }
.equal-division { .equal-division {
......
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRefs } from 'vue' import { reactive, toRefs, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as ChanxiaoAPI from '@/api/model/chanxiao' import * as ChanxiaoAPI from '@/api/model/chanxiao'
import * as UserInfoAPI from '@/api/model/userInfo' import * as UserInfoAPI from '@/api/model/userInfo'
import { getDictData, getText } from '@/utils/dict/area' import { getCodeByText } from '@/utils/areaData'
import { useDictStore } from '@/store/modules/dict' import { useDictStore } from '@/store/modules/dict'
import { getText } from '@/utils/dict/area'
import AreaPicker from '@/components/AreaPicker/index.vue'
const dictStore = useDictStore() const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
const isSave = ref(false) const isSave = ref(false)
const pageText = ref('采购') const pageText = ref('采购')
onLoad((option) => { onLoad((option) => {
...@@ -18,35 +21,28 @@ ...@@ -18,35 +21,28 @@
if (option.id) { if (option.id) {
isSave.value = false isSave.value = false
getDetails(option.id) getDetails(option.id)
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({ title: '采购需求详情' })
title: '供应需求', pageText.value = '采购'
})
pageText.value = '供应'
} else { } else {
isSave.value = true isSave.value = true
// 获取当前位置
getCurrentAddressInfo() getCurrentAddressInfo()
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({ title: '发布采购需求' })
title: '发布采购需求',
})
pageText.value = '采购' pageText.value = '采购'
} }
}) })
onShow(() => { onShow(() => {
// 数据字典赋值
initDict() initDict()
}) })
const pageData = reactive({ const pageData = reactive({
title: '发布供应需求y', loading: false,
show: { show: {
time: false, time: false,
classify: false, classify: false,
address: false, address: false,
}, },
options: { options: {
address: [],
classify: [], classify: [],
}, },
form: { form: {
...@@ -67,57 +63,17 @@ ...@@ -67,57 +63,17 @@
image: null, image: null,
imageObj: null, imageObj: null,
}, },
position: [],
rules: [ rules: [
{ { name: 'classify', rule: ['required'], msg: ['请选择采购类别'] },
name: 'classify', { name: 'title', rule: ['required'], msg: ['请输入采购标题'] },
rule: ['required'], { name: 'count', rule: ['required'], msg: ['请输入数量'] },
msg: ['请选择采购类别'], { name: 'unit', rule: ['required'], msg: ['请输入单位'] },
}, { name: 'deadLine', rule: ['required'], msg: ['请选择截至时间'] },
{ { name: 'priceStart', rule: ['required'], msg: ['请输入最低价'] },
name: 'title', { name: 'priceEnd', rule: ['required'], msg: ['请输入最高价'] },
rule: ['required'], { name: 'address', rule: ['required'], msg: ['请选择收货地区'] },
msg: ['请输入采购标题'], { name: 'image', rule: ['required'], msg: ['请上传参考图片'] },
},
{
name: 'count',
rule: ['required'],
msg: ['请输入数量'],
},
{
name: 'count',
rule: ['required'],
msg: ['请输入数量'],
},
{
name: 'unit',
rule: ['required'],
msg: ['请输入单位'],
},
{
name: 'deadLine',
rule: ['required'],
msg: ['请选择截至时间'],
},
{
name: 'priceStart',
rule: ['required'],
msg: ['请输入最低价'],
},
{
name: 'priceEnd',
rule: ['required'],
msg: ['请输入最高价'],
},
{
name: 'address',
rule: ['required'],
msg: ['请选择省/市/区县'],
},
{
name: 'image',
rule: ['required'],
msg: ['请上传示例图片'],
},
], ],
}) })
...@@ -125,568 +81,194 @@ ...@@ -125,568 +81,194 @@
async function initDict() { async function initDict() {
pageData.options.classify = dictStore.getDictList.classify.map((item) => { pageData.options.classify = dictStore.getDictList.classify.map((item) => {
return { return { value: item.value, text: item.text }
value: item.value,
text: item.text,
}
}) })
pageData.options.address = await getDictData()
} }
function getCurrentAddressInfo() { function handleAreaConfirm(e) {
if (!uni.getStorageSync('location')) { pageData.form.address = e.fullCode
return pageData.form.province = e.text[0]
pageData.form.city = e.text[1]
pageData.form.country = e.text[2]
pageData.form.areaText = e.fullText
} }
const { lon, lat } = uni.getStorageSync('location') function getCurrentAddressInfo() {
UserInfoAPI.location({ const loc = uni.getStorageSync('location')
lon, if (!loc) return
lat, pageData.position = [loc.lon, loc.lat]
}).then((res) => { UserInfoAPI.location({ lon: loc.lon, lat: loc.lat }).then((res) => {
pageData.form.province = res.province pageData.form.province = res.province
pageData.form.city = res.city pageData.form.city = res.city
pageData.form.country = res.country pageData.form.country = res.country
pageData.form.address = `${res.province}/${res.city}/${res.country}` pageData.form.address = `${res.province},${res.city},${res.country}`
pageData.form.areaText = `${res.province}/${res.city}/${res.country}`
}) })
} }
function getDetails(id) { function getDetails(id) {
pageData.loading = true
ChanxiaoAPI.purchaseSellDetails({ id }).then((res) => { ChanxiaoAPI.purchaseSellDetails({ id }).then((res) => {
pageData.form = res pageData.form = res
pageData.form.address = `${res.province},${res.city},${res.country}` // 格式化地区显示
pageData.form.classifyText = pageData.options.classify.find((item) => item.value == res.classify)?.text pageData.form.areaText = `${res.province || ''}/${res.city || ''}/${res.country || ''}`.replace(/\/+$/, '')
pageData.form.imageObj = pageData.form.image && parseUrlInfo(pageData.form.image)
})
}
function parseUrlInfo(url) {
// 从URL中提取文件名
const pathParts = url.split('/')
const fileName = pathParts[pathParts.length - 1]
// 提取扩展名 if (res.classify && pageData.options.classify.length) {
const fileParts = fileName.split('.') const item = pageData.options.classify.find(i => i.value == res.classify)
const extname = fileParts[fileParts.length - 1] pageData.form.classifyText = item ? item.text : ''
// 返回格式化的对象
return {
name: fileName,
extname,
url,
} }
pageData.form.imageObj = res.image && parseUrlInfo(res.image)
}).finally(() => pageData.loading = false)
} }
function handleChangeTime(e) { function parseUrlInfo(url) {
pageData.form.deadLine = e.result const fileName = url.split('/').pop()
pageData.show.time = false return { name: fileName, extname: fileName.split('.').pop(), url }
}
function handleChangeClassify(e) {
pageData.form.classify = e.value
pageData.form.classifyText = e.text
pageData.show.classify = false
}
function handleChangeAddress(e) {
pageData.form.address = e.value.join(',')
pageData.show.address = false
} }
function handleChangeTime(e) { pageData.form.deadLine = e.result; pageData.show.time = false; }
function handleChangeClassify(e) { pageData.form.classify = e.value; pageData.form.classifyText = e.text; pageData.show.classify = false; }
const toastRef = ref() const toastRef = ref()
const uploadRef = ref() const uploadRef = ref()
// 文件上传
function handleUpload(file) { function handleUpload(file) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: file.tempFiles[0].path, filePath: file.tempFiles[0].path,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success', pageData.form.image = data.message
text: '上传成功',
})
pageData.form.image = data.message // 保存返回的图片信息
} }
} }
},
fail: () => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.form.image = null
},
}) })
} }
// 文件删除
function handleDelete() {
uploadRef.value.clearFiles()
pageData.form.image = null
}
const formRef = ref() const formRef = ref()
function submit() { function submit() {
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
changeAddressValue(pageData.form)
ChanxiaoAPI.purchaseSellAdd(pageData.form).then(() => { ChanxiaoAPI.purchaseSellAdd(pageData.form).then(() => {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '发布成功' })
type: 'success', setTimeout(() => uni.navigateBack(), 1500)
text: '需求发布成功',
})
uni.switchTab({
url: '/pages/chanxiao/chanxiao',
})
}) })
} }
}) })
} }
/** function handleContact() {
* 处理地区值 uni.makePhoneCall({ phoneNumber: '10086' })
* @param formData 表单数据
*/
function changeAddressValue(formData) {
const addressValue = formData.address.split('/')
if (addressValue.length === 3) {
formData.province = addressValue[0]
formData.city = addressValue[1]
formData.country = addressValue[2]
}
}
function _getCurrentDate() {
const date = new Date()
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
} }
</script> </script>
<template> <template>
<view class="page"> <view class="page">
<view class="formBox"> <!-- 发布/编辑模式 -->
<view v-if="isSave" class="formBox">
<fui-form ref="formRef" label-weight="auto" top="60"> <fui-form ref="formRef" label-weight="auto" top="60">
<view class="mt20"> <view class="mt20">
<view class="form-section" style="padding: 0 10rpx"> <fui-input disabled required label="采购类别" v-model="form.classifyText" @click="show.classify = true" labelSize="28" label-width="180" />
<view class="form-item required flex align-center"> <fui-input required label="招聘标题" placeholder="请输入标题" v-model="form.title" labelSize="28" label-width="180" />
<text class="label">{{ pageText }}类别</text>
<view
class="time-input"
:style="isSave ? '' : 'pointer-events: none'"
@click="show.classify = true"
>
<text class="select-text" :class="{ placeholder: !form.classifyText }">
{{ form.classifyText || `请选择${pageText}类别` }}
</text>
</view> </view>
<view class="mt20">
<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> </view>
</view> </view>
<fui-input <fui-input required type="number" label="采购数量" v-model="form.count" labelSize="28" label-width="180" />
:disabled="!isSave" <fui-input required label="单位" v-model="form.unit" labelSize="28" label-width="180" />
required
:label="`${pageText}标题`"
:placeholder="`请输入${pageText}标题`"
v-model="form.title"
labelSize="28"
label-width="180"
maxlength="16"
size="28"
/>
</view> </view>
<view class="mt20"> <view class="mt20">
<!-- 价格区间 --> <fui-input disabled required label="收货地区" v-model="form.areaText" @click="show.address = true" labelSize="28" label-width="180" />
<view class="form-section" style="padding: 0 10rpx"> <fui-input required label="截止日期" v-model="form.deadLine" @click="show.time = true" disabled labelSize="28" label-width="180" />
<view class="form-item required flex align-center">
<text class="label">价格区间</text>
<view class="price-range">
<input
:disabled="!isSave"
type="number"
class="price-input"
v-model="form.priceStart"
placeholder="最低价"
:min="0"
maxlength="6"
/>
<text class="price-separator"></text>
<input
:disabled="!isSave"
type="number"
class="price-input"
v-model="form.priceEnd"
placeholder="最高价"
:min="0"
maxlength="6"
/>
</view> </view>
<view class="bg-white mt20" style="padding: 30rpx">
<view class="mb-1" style="font-size: 28rpx"><span style="color: red">*&nbsp;</span>上传参考图片</view>
<uni-file-picker :value="form.imageObj" limit="1" @select="handleUpload" @delete="form.image = null" />
</view> </view>
<view class="fui-btn__box">
<fui-button text="立即发布" bold radius="100rpx" @click="submit" />
</view> </view>
<fui-input </fui-form>
:disabled="!isSave"
type="number"
required
:label="`${pageText}数量`"
:placeholder="`请输入${pageText}数量`"
v-model="form.count"
labelSize="28"
label-width="180"
maxlength="8"
size="28"
/>
<fui-input
:disabled="!isSave"
required
label="单位"
placeholder="请输入单位(如:个、kg、袋等)"
v-model="form.unit"
labelSize="28"
label-width="180"
maxlength="4"
size="28"
/>
<view class="form-item required flex align-center" style="padding: 20rpx 10rpx">
<text class="label">区域</text>
<view
class="time-input"
:style="isSave ? '' : 'pointer-events: none'"
@click="show.address = true"
>
<text class="select-text" :class="{ placeholder: !form.address }">
{{ getText(form.address, ' / ') || '请选择区域' }}
</text>
</view> </view>
<!-- 需求详情模式 -->
<view v-else class="product-detail">
<view class="detail-banner">
<image v-if="form.image" :src="form.image" mode="aspectFill" class="banner-img" />
<view v-else class="banner-placeholder"><fui-icon name="picture" :size="100" color="#ddd"></fui-icon></view>
<view class="status-tag">采购中</view>
</view>
<view class="info-card">
<view class="price-section">
<text class="price-label">预算:</text>
<text class="currency">¥</text>
<text class="price">{{ form.priceStart }} - {{ form.priceEnd }}</text>
<text class="unit"></text>
</view> </view>
<text class="product-title">{{ form.title }}</text>
<view class="tags-row" v-if="form.classifyText"><view class="tag">{{ form.classifyText }}</view></view>
</view> </view>
<view class="mt20">
<!-- 截至时间 --> <view class="detail-section">
<view class="form-section" style="padding: 0 10rpx"> <view class="section-title">采购需求</view>
<view class="form-item required flex align-center"> <view class="info-grid">
<text class="label">截止时间</text> <view class="grid-item">
<view <text class="grid-label">采购数量</text>
class="time-input" <text class="grid-value">{{ form.count }}{{ form.unit }}</text>
:style="isSave ? '' : 'pointer-events: none'"
@click="show.time = true"
>
<text class="time-text" :class="{ placeholder: !form.deadLine }">
{{ form.deadLine || `请选择${pageText}截止时间` }}
</text>
</view> </view>
<view class="grid-item">
<text class="grid-label">截止日期</text>
<text class="grid-value danger">{{ form.deadLine }}</text>
</view> </view>
</view> </view>
<view class="mb-1 flex justify-start" style="font-size: 28rpx"> <view class="info-list">
<span style="color: red; margin-left: 10rpx">*&nbsp;</span> <view class="info-item">
<span>图片</span> <text class="info-label">收货地区</text>
<text class="info-value">{{ form.areaText || getText(form.address, ' / ') }}</text>
</view>
<view class="info-item" v-if="form.inputTextArea">
<text class="info-label">具体要求</text>
<text class="info-value">{{ form.inputTextArea }}</text>
</view> </view>
<uni-file-picker
:readonly="!isSave"
:value="form.imageObj"
ref="uploadRef"
limit="1"
:auto-upload="false"
@select="handleUpload"
@delete="handleDelete"
style="margin-left: 35rpx"
/>
</view> </view>
<view class="fui-btn__box" v-if="!form.id" style="margin-top: 30rpx">
<fui-button text="发布" bold radius="96rpx" @click="submit" />
</view> </view>
</fui-form>
<fui-date-picker :show="show.time" type="3" @change="handleChangeTime" @cancel="show.time = false" /> <view class="bottom-bar">
<fui-picker <view class="action-btns">
:show="show.classify" <view class="contact-btn" @click="handleContact">我要供货</view>
:layer="1" </view>
:linkage="true"
:options="options.classify"
@change="handleChangeClassify"
@cancel="show.classify = false"
/>
<fui-picker
:show="show.address"
:options="options.address"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="show.address = false"
/>
<fui-toast ref="toastRef" />
</view> </view>
<view style="height: 140rpx"></view>
</view> </view>
<fui-date-picker
:show="show.time" <AreaPicker v-model:show="show.address" :layer="3" @confirm="handleAreaConfirm" />
type="3" <fui-picker :show="show.classify" :options="options.classify" @change="handleChangeClassify" @cancel="show.classify = false" />
@change="handleChangeTime" <fui-date-picker :show="show.time" type="3" @change="handleChangeTime" @cancel="show.time = false" />
@cancel="show.time = false"
minDate="2025-01-01"
/>
<fui-picker
:show="show.classify"
:layer="1"
:linkage="true"
:options="options.classify"
@change="handleChangeClassify"
@cancel="show.classify = false"
/>
<fui-picker
:show="show.address"
:options="options.address"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="show.address = false"
/>
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
</view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body { .page { background-color: #f7f8fa; min-height: 100vh; font-family: 'DingTalk Sans', sans-serif; }
background-color: #e6f5e8; .formBox { padding: 24rpx; .mt20 { background: #fff; border-radius: 20rpx; padding: 10rpx 24rpx; margin-bottom: 24rpx; } }
} .price-range { display: flex; align-items: center; flex: 1; margin-left: 20rpx; .price-input { flex: 1; text-align: center; font-size: 28rpx; } .sep { margin: 0 10rpx; color: #ccc; } }
.form-item { padding: 30rpx 0; display: flex; align-items: center; border-bottom: 1rpx solid #f8f8f8; .label { font-size: 28rpx; color: #333; width: 180rpx; .red { color: #ff4d4f; } } }
.page {
background-color: #e6f5e8;
width: 750rpx;
overflow-x: hidden;
.mt20 {
margin-top: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.formBox {
width: 690rpx;
margin: 30rpx auto;
}
.purchase-form {
background: transparent;
}
.form-section {
// background: #ffffff;
// border-radius: 12rpx;
// margin-bottom: 20rpx;
// box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04); /* 采购详情样式 */
border-bottom: 1rpx solid #f5f5f5; .product-detail {
.detail-banner { width: 750rpx; height: 500rpx; 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: 30rpx; top: 30rpx; background-color: rgba(93, 182, 111, 0.9); color: #fff; padding: 8rpx 20rpx; border-radius: 30rpx; font-size: 24rpx; } }
.info-card { background-color: #fff; margin: -40rpx 24rpx 20rpx; padding: 32rpx; border-radius: 24rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); position: relative; .price-section { display: flex; align-items: baseline; margin-bottom: 16rpx; .price-label { font-size: 28rpx; color: #666; } .currency { font-size: 32rpx; color: #ff4d4f; font-weight: bold; } .price { font-size: 48rpx; color: #ff4d4f; font-weight: bold; } .unit { font-size: 26rpx; color: #ff4d4f; margin-left: 10rpx; } } .product-title { font-size: 36rpx; color: #333; font-weight: bold; line-height: 1.4; display: block; } .tags-row { margin-top: 20rpx; .tag { padding: 4rpx 16rpx; background-color: #fff2e8; color: #fa8c16; font-size: 22rpx; border-radius: 6rpx; } } }
.detail-section { background-color: #fff; margin: 0 24rpx 20rpx; padding: 32rpx; border-radius: 24rpx; .section-title { font-size: 30rpx; color: #333; font-weight: bold; margin-bottom: 24rpx; padding-left: 16rpx; border-left: 8rpx solid #fa8c16; } .info-grid { display: flex; background-color: #fff7f2; padding: 20rpx; border-radius: 12rpx; margin-bottom: 24rpx; .grid-item { flex: 1; display: flex; flex-direction: column; align-items: center; .grid-label { font-size: 22rpx; color: #999; margin-bottom: 8rpx; } .grid-value { font-size: 28rpx; color: #333; font-weight: bold; &.danger { color: #ff4d4f; } } } } .info-list { .info-item { display: flex; padding: 20rpx 0; border-bottom: 1rpx solid #f5f5f5; &:last-child { border-bottom: none; } .info-label { width: 160rpx; font-size: 26rpx; color: #999; } .info-value { flex: 1; font-size: 26rpx; color: #333; } } } }
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; height: 110rpx; background-color: #fff; padding: 0 30rpx; display: flex; align-items: center; box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); z-index: 100; .action-btns { flex: 1; .contact-btn { height: 80rpx; background-color: #fa8c16; color: #fff; display: flex; align-items: center; justify-content: center; border-radius: 40rpx; font-size: 28rpx; font-weight: bold; } } }
} }
.form-item { :deep(.fui-input__label) { font-family: 'DingTalk Sans' !important; }
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
&.required .label::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
// 添加点击区域样式
.select-input {
position: relative;
}
}
.form-row {
display: flex;
justify-content: space-between;
}
.half-width {
width: 48%;
}
.align-center {
align-items: center;
}
.label {
display: block;
font-size: 28rpx;
color: #333333;
font-weight: 500;
width: 180rpx;
// margin-right: 20rpx;
}
.input {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #999999;
}
}
.price-range {
display: flex;
align-items: center;
// justify-content: space-between;
}
.price-input {
width: 15%;
// height: 80rpx;
// background: #f8f9fa;
// border-radius: 8rpx;
padding: 0 10rpx;
font-size: 28rpx;
text-align: center;
}
.price-separator {
color: #666666;
font-size: 28rpx;
margin: 0 10rpx;
}
.select-input {
flex: 1;
border-radius: 8rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.select-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
.select-arrow {
color: #999999;
font-size: 24rpx;
line-height: 1;
}
.time-range {
display: flex;
align-items: center;
justify-content: space-between;
}
.time-input {
width: 45%;
// height: 80rpx;
// background: #f8f9fa;
border-radius: 8rpx;
display: flex;
align-items: center;
}
.time-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
.time-separator {
color: #666666;
font-size: 28rpx;
margin: 0 10rpx;
}
.upload-area {
margin-top: 10rpx;
}
.custom-uploader {
:deep(.uni-file-picker__container) {
border: 2rpx dashed #d9d9d9;
border-radius: 8rpx;
background: #f8f9fa;
}
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200rpx;
color: #999999;
}
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
}
.upload-text {
font-size: 24rpx;
}
.submit-section {
background: transparent;
padding: 40rpx 0;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #5db66f;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #4ca85c;
opacity: 0.9;
}
}
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.fui-button) {
width: 690rpx;
border-color: #5db66f !important;
background: #5db66f !important;
}
// 移除fui-form的默认样式
:deep(.fui-form) {
background: transparent;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRefs } from 'vue' import { reactive, toRefs, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as ChanxiaoAPI from '@/api/model/chanxiao' import * as ChanxiaoAPI from '@/api/model/chanxiao'
import * as UserInfoAPI from '@/api/model/userInfo' import * as UserInfoAPI from '@/api/model/userInfo'
import { getDictData, getText } from '@/utils/dict/area' import { getDictData, getText } from '@/utils/dict/area'
import AreaPicker from '@/components/AreaPicker/index.vue'
import { useDictStore } from '@/store/modules/dict' import { useDictStore } from '@/store/modules/dict'
const dictStore = useDictStore() const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
const isSave = ref(false) const isSave = ref(false)
const pageText = ref('采购') const pageText = ref('供应')
onLoad((option) => { onLoad((option) => {
// 获取数据详情
if (option.id) { if (option.id) {
isSave.value = false isSave.value = false
getDetails(option.id) getDetails(option.id)
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({ title: '供应详情' })
title: '采购需求', pageText.value = '供应'
})
pageText.value = '采购'
} else { } else {
isSave.value = true isSave.value = true
// 获取当前位置
getCurrentAddressInfo() getCurrentAddressInfo()
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({ title: '发布供应需求' })
title: '发布供应需求',
})
pageText.value = '供应' pageText.value = '供应'
} }
}) })
onShow(() => { onShow(() => {
// 数据字典赋值
initDict() initDict()
}) })
const pageData = reactive({ const pageData = reactive({
title: '发布供应需求', loading: false,
show: { show: {
time1: false, time1: false,
time2: false, time2: false,
address: false, address: false,
status: false,
classify: false, classify: false,
}, },
options: { options: {
address: [],
status: [],
classify: [], classify: [],
}, },
form: { form: {
...@@ -59,17 +51,14 @@ ...@@ -59,17 +51,14 @@
minPrice: '', minPrice: '',
maxPrice: '', maxPrice: '',
unit: '', unit: '',
currency: '',
supplyQuantity: '', supplyQuantity: '',
minOrderQuantity: '', minOrderQuantity: '',
address: '', address: '',
detailedAddress: '', detailedAddress: '',
supplyStartDate: '', supplyStartDate: '',
supplyEndDate: '', supplyEndDate: '',
status: '',
classifyText: '', classifyText: '',
classify: '', classify: '',
statusText: '',
province: '', province: '',
city: '', city: '',
country: '', country: '',
...@@ -77,698 +66,225 @@ ...@@ -77,698 +66,225 @@
imageObj: null, imageObj: null,
}, },
rules: [ rules: [
{ { name: 'classify', rule: ['required'], msg: ['请选择分类'] },
name: 'title', { name: 'title', rule: ['required'], msg: [`请输入标题`] },
rule: ['required'], { name: 'minPrice', rule: ['required'], msg: ['请输入最低价'] },
msg: ['请输入标题'], { name: 'maxPrice', rule: ['required'], msg: ['请输入最高价'] },
}, { name: 'unit', rule: ['required'], msg: ['请输入单位'] },
{ { name: 'supplyQuantity', rule: ['required'], msg: [`请输入数量`] },
name: 'minPrice', { name: 'minOrderQuantity', rule: ['required'], msg: ['请输入最小起订量'] },
rule: ['required'], { name: 'address', rule: ['required'], msg: ['请选择区域'] },
msg: ['请输入最低价格'], { name: 'detailedAddress', rule: ['required'], msg: ['请输入详细地址'] },
}, { name: 'image', rule: ['required'], msg: ['请上传图片'] },
{
name: 'maxPrice',
rule: ['required'],
msg: ['请输入最高价格'],
},
{
name: 'unit',
rule: ['required'],
msg: ['请输入单位'],
},
{
name: 'currency',
rule: ['required'],
msg: ['请选择币种'],
},
{
name: 'supplyQuantity',
rule: ['required'],
msg: ['请输入供应数量'],
},
{
name: 'minOrderQuantity',
rule: ['required'],
msg: ['请输入最小订单数量'],
},
{
name: 'address',
rule: ['required'],
msg: ['请选择省/市/区县'],
},
/* {
name: 'status',
rule: ['required'],
msg: ['请选择状态'],
}, */
{
name: 'classify',
rule: ['required'],
msg: ['请选择分类'],
},
], ],
}) })
const { show, options, form } = toRefs(pageData) const { show, options, form } = toRefs(pageData)
async function initDict() { async function initDict() {
pageData.options.address = await getDictData()
pageData.options.status = dictStore.getDictList.purchase_status.map((item) => {
return {
value: item.value,
text: item.text,
}
})
pageData.options.classify = dictStore.getDictList.classify.map((item) => { pageData.options.classify = dictStore.getDictList.classify.map((item) => {
return { return { value: item.value, text: item.text }
value: item.value,
text: item.text,
}
}) })
} }
function getCurrentAddressInfo() { function handleAreaConfirm(e) {
if (!uni.getStorageSync('location')) pageData.form.address = e.fullCode
return pageData.form.province = e.text[0]
pageData.form.city = e.text[1]
pageData.form.country = e.text[2]
}
const { lon, lat } = uni.getStorageSync('location') function getCurrentAddressInfo() {
UserInfoAPI.location({ const loc = uni.getStorageSync('location')
lon, if (!loc) return
lat, UserInfoAPI.location({ lon: loc.lon, lat: loc.lat }).then((res) => {
}).then((res) => {
pageData.form.province = res.province pageData.form.province = res.province
pageData.form.city = res.city pageData.form.city = res.city
pageData.form.country = res.country pageData.form.country = res.country
pageData.form.address = `${res.province}/${res.city}/${res.country}` pageData.form.address = `${res.province},${res.city},${res.country}`
}) })
} }
function getDetails(id) { function getDetails(id) {
ChanxiaoAPI.purchaseSellDetails({ id }).then((res) => { pageData.loading = true
ChanxiaoAPI.supplyDetails({ id }).then((res) => {
pageData.form = res pageData.form = res
pageData.form.address = `${res.province}/${res.city}/${res.country}` // 格式化地区显示
pageData.form.statusText = pageData.options.status.find((item) => item.value == res.status).text pageData.form.areaText = `${res.province || ''}/${res.city || ''}/${res.country || ''}`.replace(/\/+$/, '')
pageData.form.classifyText = pageData.options.classify.find((item) => item.value == res.classify).text
pageData.form.imageObj = pageData.form.image && parseUrlInfo(pageData.form.image)
})
}
function parseUrlInfo(url) {
// 从URL中提取文件名
const pathParts = url.split('/')
const fileName = pathParts[pathParts.length - 1]
// 提取扩展名
const fileParts = fileName.split('.')
const extname = fileParts[fileParts.length - 1]
// 返回格式化的对象 if (res.classify) {
return { const item = pageData.options.classify.find(i => i.value == res.classify)
name: fileName, pageData.form.classifyText = item ? item.text : ''
extname,
url,
} }
if (pageData.form.detailedAddress) {
const parts = pageData.form.detailedAddress.split('||')
if (parts.length > 1) pageData.form.detailedAddress = parts[1]
} }
pageData.form.imageObj = res.image && parseUrlInfo(res.image)
function handleChangeTime1(e) { }).finally(() => pageData.loading = false)
pageData.form.supplyStartDate = e.result
pageData.show.time1 = false
}
function handleChangeTime2(e) {
pageData.form.supplyEndDate = e.result
pageData.show.time2 = false
} }
function handleChangeClassify(e) {
pageData.form.classify = e.value function parseUrlInfo(url) {
pageData.form.classifyText = e.text const fileName = url.split('/').pop()
pageData.show.classify = false return { name: fileName, extname: fileName.split('.').pop(), url }
}
function handleChangeAddress(e) {
pageData.form.address = e.value.join(',')
pageData.show.address = false
}
function handleChangeStatus(e) {
pageData.form.status = e.value
pageData.form.statusText = e.text
pageData.show.status = false
} }
function handleChangeTime1(e) { pageData.form.supplyStartDate = e.result; pageData.show.time1 = false; }
function handleChangeTime2(e) { pageData.form.supplyEndDate = e.result; pageData.show.time2 = false; }
function handleChangeClassify(e) { pageData.form.classify = e.value; pageData.form.classifyText = e.text; pageData.show.classify = false; }
const toastRef = ref() const toastRef = ref()
const uploadRef = ref() const uploadRef = ref()
// 文件上传
function handleUpload(file) { function handleUpload(file) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: file.tempFiles[0].path, filePath: file.tempFiles[0].path,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.show({ type: 'success', text: '上传成功' })
type: 'success', pageData.form.image = data.message
text: '上传成功',
})
pageData.form.image = data.message // 保存返回的图片信息
} }
} }
},
fail: (err) => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.form.image = null
},
}) })
} }
// 文件删除
function handleDelete(file) {
uploadRef.value.clearFiles()
pageData.form.image = null
}
const formRef = ref() const formRef = ref()
function submit() { function submit() {
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
changeAddressValue(pageData.form) ChanxiaoAPI.supplyAdd(pageData.form).then(() => {
ChanxiaoAPI.supplyAdd(pageData.form).then((res) => { toastRef.show({ type: 'success', text: '发布成功' })
toastRef.value.show({ setTimeout(() => uni.navigateBack(), 1500)
type: 'success',
text: '需求发布成功',
})
uni.switchTab({
url: '/pages/chanxiao/chanxiao',
})
}) })
} }
}) })
} }
/** function handleContact() {
* 处理地区值 uni.makePhoneCall({ phoneNumber: '10086' })
* @param formData 表单数据
*/
function changeAddressValue(formData) {
const addressValue = formData.address.split(',')
if (addressValue.length === 3) {
formData.province = addressValue[0]
formData.city = addressValue[1]
formData.country = addressValue[2]
}
}
function getCurrentDate() {
const date = new Date()
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
} }
</script> </script>
<template> <template>
<view class="page"> <view class="page">
<view class="formBox"> <!-- 发布/编辑模式 -->
<view v-if="isSave" class="formBox">
<fui-form ref="formRef" label-weight="auto" top="60"> <fui-form ref="formRef" label-weight="auto" top="60">
<view class="mt20"> <view class="mt20">
<fui-input <fui-input required label="标题" placeholder="请输入标题" v-model="form.title" labelSize="28" label-width="180" />
:disabled="!isSave" <fui-input label="规格说明" placeholder="请输入规格说明" v-model="form.productSpecs" labelSize="28" label-width="180" />
:label="`${pageText}标题`"
:placeholder="`请输入${pageText}标题`"
placeholderStyle="font-size: 26rpx"
v-model="form.title"
labelSize="28"
label-width="180"
required
/>
<fui-input
:disabled="!isSave"
label="规格说明"
placeholder="请输入规格说明"
placeholderStyle="font-size: 26rpx"
v-model="form.productSpecs"
labelSize="28"
label-width="180"
/>
</view> </view>
<view class="mt20"> <view class="mt20">
<!-- 价格区间 --> <view class="form-item required">
<view class="form-section" style="padding: 0 10rpx"> <text class="label">价格区间</text>
<view class="form-item required flex align-center">
<text class="label" style="font-size: 28rpx">价格区间</text>
<view class="price-range"> <view class="price-range">
<input <input type="number" class="price-input" v-model="form.minPrice" placeholder="最低价" />
:disabled="!isSave" <text class="sep">-</text>
type="number" <input type="number" class="price-input" v-model="form.maxPrice" placeholder="最高价" />
class="price-input"
v-model="form.minPrice"
placeholder="最低价"
placeholderStyle="font-size: 26rpx"
:min="0"
/>
<text class="price-separator">-</text>
<input
:disabled="!isSave"
type="number"
class="price-input"
v-model="form.maxPrice"
placeholder="最高价"
placeholderStyle="font-size: 26rpx"
:min="0"
/>
</view> </view>
</view> </view>
</view> <fui-input required label="单位" placeholder="请输入" v-model="form.unit" labelSize="28" label-width="180" />
<fui-input <fui-input required type="number" label="供应数量" v-model="form.supplyQuantity" labelSize="28" label-width="180" />
:disabled="!isSave" <fui-input required type="number" label="起订量" v-model="form.minOrderQuantity" labelSize="28" label-width="180" />
required
label="计量单位"
placeholder="请输入计量单位"
placeholderStyle="font-size: 26rpx"
v-model="form.unit"
labelSize="28"
label-width="180"
/>
<fui-input
:disabled="!isSave"
required
label="币种"
placeholder="请输入币种"
placeholderStyle="font-size: 26rpx"
v-model="form.currency"
labelSize="28"
label-width="180"
/>
<fui-input
:disabled="!isSave"
required
:label="`${pageText}数量`"
:placeholder="`请输入${pageText}数量`"
placeholderStyle="font-size: 26rpx"
v-model="form.supplyQuantity"
labelSize="28"
label-width="180"
type="number"
/>
<fui-input
:disabled="!isSave"
required
label="最小起订量"
placeholder="请输入最小起订量"
placeholderStyle="font-size: 26rpx"
v-model="form.minOrderQuantity"
labelSize="28"
label-width="180"
type="number"
/>
</view> </view>
<view class="mt20"> <view class="mt20">
<view class="form-item required flex align-center" style="padding: 20rpx 10rpx"> <fui-input disabled required label="所在地区" v-model="form.address" @click="show.address = true" labelSize="28" label-width="180" />
<text class="label" style="font-size: 28rpx">请选择区域</text> <fui-input required label="详细地址" v-model="form.detailedAddress" labelSize="28" label-width="180" />
<view <fui-input disabled required label="分类" v-model="form.classifyText" @click="show.classify = true" labelSize="28" label-width="180" />
class="select-input" </view>
:style="isSave ? '' : 'pointer-events: none'" <view class="bg-white mt20" style="padding: 30rpx">
@click="show.address = true" <view class="mb-1" style="font-size: 28rpx"><span style="color: red">*&nbsp;</span>上传图片</view>
> <uni-file-picker :value="form.imageObj" limit="1" @select="handleUpload" @delete="form.image = null" />
<text class="time-text" :class="{ placeholder: !form.address }"> </view>
{{ getText(form.address, ' / ') || '请选择省/市/区县' }} <view class="fui-btn__box">
</text> <fui-button text="立即发布" bold radius="100rpx" @click="submit" />
</view>
</fui-form>
</view> </view>
<!-- 商品详情模式 -->
<view v-else class="product-detail">
<view class="detail-banner">
<image v-if="form.image" :src="form.image" mode="aspectFill" class="banner-img" />
<view v-else class="banner-placeholder"><fui-icon name="picture" :size="100" color="#ddd"></fui-icon></view>
</view> </view>
<fui-input
:disabled="!isSave"
required
label="详细地址"
placeholder="请输入详细地址"
placeholderStyle="font-size: 26rpx"
v-model="form.detailedAddress"
labelSize="28"
label-width="180"
/>
<!-- 供应时间 --> <view class="info-card">
<view class="form-section" style="padding: 0 30rpx"> <view class="price-section">
<view class="form-item flex align-center"> <text class="currency">¥</text>
<text class="label" style="font-size: 28rpx">{{ pageText }}时间</text> <text class="price">{{ form.minPrice }} - {{ form.maxPrice }}</text>
<view class="time-range"> <text class="unit">/ {{ form.unit }}</text>
<view
class="time-input"
:style="isSave ? '' : 'pointer-events: none'"
@click="show.time1 = true"
>
<text
class="time-text"
:class="{ placeholder: !form.supplyStartDate }"
style="font-size: 26rpx"
>
{{ form.supplyStartDate || '开始时间' }}
</text>
</view> </view>
<text class="time-separator">-</text> <text class="product-title">{{ form.title }}</text>
<view <view class="tags-row" v-if="form.classifyText"><view class="tag">{{ form.classifyText }}</view></view>
class="time-input"
:style="isSave ? '' : 'pointer-events: none'"
@click="show.time2 = true"
>
<text
class="time-text"
:class="{ placeholder: !form.supplyEndDate }"
style="font-size: 26rpx"
>
{{ form.supplyEndDate || '结束时间' }}
</text>
</view> </view>
<view class="detail-section">
<view class="section-title">供应信息</view>
<view class="info-grid">
<view class="grid-item">
<text class="grid-label">供应总量</text>
<text class="grid-value">{{ form.supplyQuantity }}{{ form.unit }}</text>
</view> </view>
<view class="grid-item">
<text class="grid-label">起订量</text>
<text class="grid-value">{{ form.minOrderQuantity }}{{ form.unit }}</text>
</view> </view>
</view> </view>
<view class="form-section" style="padding: 0 10rpx"> <view class="info-list">
<view class="form-item required flex align-center"> <view class="info-item" v-if="form.supplyStartDate">
<text class="label" style="font-size: 28rpx">分类</text> <text class="info-label">供应周期</text>
<view <text class="info-value">{{ form.supplyStartDate }}{{ form.supplyEndDate }}</text>
class="time-input"
:style="isSave ? '' : 'pointer-events: none'"
@click="show.classify = true"
>
<text
class="select-text"
:class="{ placeholder: !form.classifyText }"
style="font-size: 26rpx"
>
{{ form.classifyText || '请选择分类' }}
</text>
</view> </view>
<view class="info-item" v-if="form.productSpecs">
<text class="info-label">规格说明</text>
<text class="info-value">{{ form.productSpecs }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="bg-white mt20" style="padding: 0.875rem 1rem"> <view class="detail-section">
<view class="mb-1 flex justify-start"> 上传图片 </view> <view class="section-title">产地及详细地址</view>
<uni-file-picker <view class="location-box">
:readonly="!isSave" <view class="location-top">
:value="form.imageObj" <fui-icon name="location" :size="28" color="#5db66f"></fui-icon>
ref="uploadRef" <text class="address-main">{{ form.areaText || form.address }}</text>
limit="1" </view>
:auto-upload="false" <view class="address-detail" v-if="form.detailedAddress">{{ form.detailedAddress }}</view>
@select="handleUpload"
@delete="handleDelete"
/>
</view> </view>
<view class="fui-btn__box" v-if="!form.id" style="margin-top: 30rpx">
<fui-button text="发布需求" bold radius="96rpx" @click="submit" />
</view> </view>
</fui-form>
<fui-date-picker <view class="bottom-bar">
:show="show.time1" <view class="action-btns">
type="3" <view class="contact-btn" @click="handleContact">立即沟通</view>
@change="handleChangeTime1" </view>
@cancel="show.time1 = false" </view>
:minDate="getCurrentDate()" <view style="height: 140rpx"></view>
/>
<fui-date-picker
:show="show.time2"
type="3"
@change="handleChangeTime2"
@cancel="show.time2 = false"
:minDate="getCurrentDate()"
/>
<fui-picker
:show="show.status"
:layer="1"
:linkage="true"
:options="options.status"
@change="handleChangeStatus"
@cancel="show.status = false"
/>
<fui-picker
:show="show.classify"
:layer="1"
:linkage="true"
:options="options.classify"
@change="handleChangeClassify"
@cancel="show.classify = false"
/>
<fui-picker
:show="show.address"
:options="options.address"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="show.address = false"
/>
<fui-toast ref="toastRef" />
</view> </view>
<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> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body { .page { background-color: #f7f8fa; min-height: 100vh; font-family: 'DingTalk Sans', sans-serif; }
background-color: #e6f5e8; .formBox { padding: 24rpx; .mt20 { background: #fff; border-radius: 20rpx; padding: 10rpx 24rpx; margin-bottom: 24rpx; } }
} .price-range { display: flex; align-items: center; flex: 1; margin-left: 20rpx; .price-input { flex: 1; text-align: center; font-size: 28rpx; } .sep { margin: 0 10rpx; color: #ccc; } }
.form-item { padding: 30rpx 0; display: flex; align-items: center; border-bottom: 1rpx solid #f8f8f8; .label { font-size: 28rpx; color: #333; width: 180rpx; .red { color: #ff4d4f; } } }
.page {
background-color: #e6f5e8;
width: 750rpx;
overflow-x: hidden;
.mt20 {
margin-top: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.formBox {
width: 690rpx;
margin: 30rpx auto;
}
.purchase-form {
background: transparent;
}
.form-section {
// background: #ffffff;
// border-radius: 12rpx;
// margin-bottom: 20rpx;
// box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
border-bottom: 1rpx solid #f5f5f5;
}
.form-item {
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
&.required .label::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
// 添加点击区域样式
.select-input {
position: relative;
}
}
.form-row {
display: flex;
justify-content: space-between;
}
.half-width {
width: 48%;
}
.align-center {
align-items: center;
}
.label {
display: block;
font-size: 28rpx;
color: #333333;
font-weight: 500;
width: 180rpx;
// margin-right: 20rpx;
}
.input {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #999999;
}
}
.price-range {
display: flex;
align-items: center;
// justify-content: space-between;
}
.price-input {
width: 15%;
// height: 80rpx;
// background: #f8f9fa;
// border-radius: 8rpx;
padding: 0 10rpx;
font-size: 28rpx;
text-align: center;
}
.price-separator {
color: #666666;
font-size: 28rpx;
margin: 0 10rpx;
}
.select-input {
flex: 1;
border-radius: 8rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.select-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
.select-arrow {
color: #999999;
font-size: 24rpx;
line-height: 1;
}
.time-range { /* 商品详情模式样式 */
display: flex; .product-detail {
align-items: center; .detail-banner { width: 750rpx; height: 600rpx; 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; } }
justify-content: space-between; .info-card { background-color: #fff; margin: -40rpx 24rpx 20rpx; padding: 32rpx; border-radius: 24rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); position: relative; .price-section { display: flex; align-items: baseline; margin-bottom: 16rpx; .currency { font-size: 32rpx; color: #ff4d4f; font-weight: bold; } .price { font-size: 48rpx; color: #ff4d4f; font-weight: bold; } .unit { font-size: 24rpx; color: #999; margin-left: 10rpx; } } .product-title { font-size: 36rpx; color: #333; font-weight: bold; line-height: 1.4; display: block; } .tags-row { margin-top: 20rpx; .tag { padding: 4rpx 16rpx; background-color: #e6f5e8; color: #5db66f; font-size: 22rpx; border-radius: 6rpx; } } }
.detail-section { background-color: #fff; margin: 0 24rpx 20rpx; padding: 32rpx; border-radius: 24rpx; .section-title { font-size: 30rpx; color: #333; font-weight: bold; margin-bottom: 24rpx; padding-left: 16rpx; border-left: 8rpx solid #5db66f; } .info-grid { display: flex; background-color: #f9f9f9; padding: 20rpx; border-radius: 12rpx; margin-bottom: 24rpx; .grid-item { flex: 1; display: flex; flex-direction: column; align-items: center; .grid-label { font-size: 22rpx; color: #999; margin-bottom: 8rpx; } .grid-value { font-size: 28rpx; color: #333; font-weight: bold; } } } .info-list { .info-item { display: flex; padding: 20rpx 0; border-bottom: 1rpx solid #f5f5f5; &:last-child { border-bottom: none; } .info-label { width: 160rpx; font-size: 26rpx; color: #999; } .info-value { flex: 1; font-size: 26rpx; color: #333; } } } .location-box { .location-top { display: flex; align-items: center; .address-main { font-size: 28rpx; color: #333; font-weight: bold; margin-left: 12rpx; } } .address-detail { font-size: 26rpx; color: #666; margin-top: 16rpx; padding-left: 40rpx; } } }
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; height: 110rpx; background-color: #fff; padding: 0 30rpx; display: flex; align-items: center; box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); z-index: 100; .action-btns { flex: 1; .contact-btn { height: 80rpx; background-color: #5db66f; color: #fff; display: flex; align-items: center; justify-content: center; border-radius: 40rpx; font-size: 28rpx; font-weight: bold; } } }
} }
.time-input { :deep(.fui-input__label) { font-family: 'DingTalk Sans' !important; }
width: 45%;
// height: 80rpx;
// background: #f8f9fa;
border-radius: 8rpx;
display: flex;
align-items: center;
}
.time-text {
font-size: 28rpx;
color: #333333;
&.placeholder {
color: #999999;
}
}
.time-separator {
color: #666666;
font-size: 28rpx;
margin: 0 10rpx;
}
.upload-area {
margin-top: 10rpx;
}
.custom-uploader {
:deep(.uni-file-picker__container) {
border: 2rpx dashed #d9d9d9;
border-radius: 8rpx;
background: #f8f9fa;
}
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200rpx;
color: #999999;
}
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
}
.upload-text {
font-size: 24rpx;
}
.submit-section {
background: transparent;
padding: 40rpx 0;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #5db66f;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #4ca85c;
opacity: 0.9;
}
}
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.fui-button) {
width: 690rpx;
border-color: #5db66f !important;
background: #5db66f !important;
}
// 移除fui-form的默认样式
:deep(.fui-form) {
background: transparent;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive, toRefs, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as NongchangAPI from '@/api/model/nongchang' import * as NongchangAPI from '@/api/model/nongchang'
import { areaTree } from '@/utils/areaData' import * as UserInfoAPI from '@/api/model/userInfo'
import { useDictStore } from '@/store/modules/dict' import { useDictStore } from '@/store/modules/dict'
import { useFarmStore } from '@/store/modules/farm' import AreaPicker from '@/components/AreaPicker/index.vue'
const dictStore = useDictStore() const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
const farmStore = useFarmStore()
onLoad((pageOptions) => { const isSave = ref(false)
// 页面加载时的初始化操作
const farmInfo = farmStore.getFarm onLoad((option) => {
if (farmInfo) { if (option.id) {
pageData.form.farmId = farmInfo.id isSave.value = false
getDetails(option.id)
uni.setNavigationBarTitle({ title: '基地详情' })
} else {
isSave.value = true
getCurrentAddressInfo()
uni.setNavigationBarTitle({ title: '新增基地' })
} }
pageData.form.farmId = pageOptions.farmId
}) })
onShow(() => { onShow(() => {
// 数据字典赋值
initDict() initDict()
pageData.form.userId = userStore.getUserInfo.id
}) })
const pageData = reactive({ const pageData = reactive({
loading: false, loading: false,
show: { show: {
address: false,
growCrops: false, growCrops: false,
}, },
options: { options: {
address: [],
mainProducts: [],
growCrops: [], growCrops: [],
}, },
form: { form: {
id: '', id: '',
userId: '',
farmId: '',
baseName: '', baseName: '',
province: '',
city: '',
country: '',
address: '',
addressDetail: '',
scale: '', scale: '',
managerName: '',
contactPhone: '',
growCrops: '', growCrops: '',
growCropsText: '', growCropsText: '',
contactPerson: '',
contactMobile: '',
profile: '',
longitude: '',
latitude: '',
}, },
rules: [ rules: [
{ { name: 'baseName', rule: ['required'], msg: ['请输入基地名称'] },
name: 'baseName', { name: 'address', rule: ['required'], msg: ['请选择所在地区'] },
rule: ['required'], { name: 'addressDetail', rule: ['required'], msg: ['请输入详细地址'] },
msg: ['请输入基地名称'], { name: 'scale', rule: ['required'], msg: ['请输入基地规模'] },
}, { name: 'growCrops', rule: ['required'], msg: ['请选择种植作物'] },
{
name: 'scale',
rule: ['required'],
msg: ['请输入面积'],
},
{
name: 'managerName',
rule: ['required'],
msg: ['请输入负责人'],
},
{
name: 'contactPhone',
rule: ['required'],
msg: ['请输入联系电话'],
},
{
name: 'growCrops',
rule: ['required'],
msg: ['请选择种植作物'],
},
], ],
}) })
function initDict() { const { show, options, form } = toRefs(pageData)
pageData.options.address = areaTree
async function initDict() {
pageData.options.growCrops = dictStore.getDictList.crops_type.map((item) => { pageData.options.growCrops = dictStore.getDictList.crops_type.map((item) => {
return { return { value: item.value, text: item.text }
value: item.value, })
text: item.text,
} }
function handleAreaConfirm(e) {
pageData.form.province = e.text[0]
pageData.form.city = e.text[1]
pageData.form.country = e.text[2]
pageData.form.address = e.fullText
}
function getCurrentAddressInfo() {
const loc = uni.getStorageSync('location')
if (!loc) return
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}`
}) })
} }
function handleChangeGrowCrops(e) {
function getDetails(id) {
pageData.loading = true
NongchangAPI.getFarmbaseDetail({ id }).then((res) => {
pageData.form = res
if (res.growCrops) {
const item = pageData.options.growCrops.find(i => i.value == res.growCrops)
pageData.form.growCropsText = item ? item.text : ''
}
}).finally(() => pageData.loading = false)
}
function handleGrowCropsChange(e) {
pageData.form.growCrops = e.value pageData.form.growCrops = e.value
pageData.form.growCropsText = e.text pageData.form.growCropsText = e.text
pageData.show.growCrops = false pageData.show.growCrops = false
} }
const toastRef = ref()
const toastRef = ref()
const formRef = ref() const formRef = ref()
function submit() { function submit() {
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
pageData.loading = true NongchangAPI.saveFarmbase(pageData.form).then(() => {
// 根据是否有ID决定调用编辑还是新增接口 toastRef.value.show({ type: 'success', text: '保存成功' })
const apiCall = pageData.form.id ? NongchangAPI.editFarmbase : NongchangAPI.addFarmbase setTimeout(() => uni.navigateBack(), 1500)
apiCall(pageData.form)
.then(() => {
toastRef.value.show({
type: 'success',
text: '添加基地成功',
})
setTimeout(() => {
uni.navigateBack()
}, 800)
// uni.switchTab({
// url: '/pages/mine/index',
// })
})
.finally(() => {
pageData.loading = false
}) })
} }
}) })
...@@ -123,238 +124,37 @@ ...@@ -123,238 +124,37 @@
<template> <template>
<view class="page"> <view class="page">
<view class="page-container"> <view class="container">
<fui-form ref="formRef"> <fui-form ref="formRef">
<!-- 第一组表单 --> <view class="section-card">
<view class="form-card"> <fui-input label="基地名称" placeholder="请输入" v-model="form.baseName" required />
<fui-input <fui-input disabled required label="所在地区" v-model="form.address" @click="show.address = true" />
required <fui-input label="详细地址" placeholder="请输入" v-model="form.addressDetail" required />
label="基地名称"
labelSize="28"
size="28"
:labelWeight="400"
labelWidth="auto"
placeholder="请输入基地名称"
v-model="pageData.form.baseName"
/>
<fui-input
required
number
label="面积"
labelSize="28"
size="28"
:labelWeight="400"
labelWidth="auto"
placeholder="请输入基地面积"
v-model="pageData.form.scale"
>
<view class="fontsize_28"></view>
</fui-input>
<fui-input
required
labelSize="28"
size="28"
:labelWeight="400"
labelWidth="auto"
label="负责人"
placeholder="请输入联系人名称"
v-model="pageData.form.managerName"
/>
<fui-input
required
type="number"
labelSize="28"
size="28"
:labelWeight="400"
labelWidth="auto"
label="负责人电话"
placeholder="请输入负责人电话"
v-model="pageData.form.contactPhone"
/>
<fui-form-item
required
asterisk
label="种植作物"
labelSize="28"
:labelWeight="400"
labelWidth="auto"
>
<view class="time-input" @click="pageData.show.growCrops = true">
<text class="select-text" :class="{ placeholder: !pageData.form.growCropsText }">
{{ pageData.form.growCropsText || '请选择种植作物' }}
</text>
</view> </view>
</fui-form-item> <view class="section-card">
<fui-input type="number" label="基地规模" v-model="form.scale" required>
<template #suffix><text class="unit"></text></template>
</fui-input>
<fui-input disabled required label="种植作物" v-model="form.growCropsText" @click="show.growCrops = true" />
</view> </view>
<!-- 更多字段... -->
<view class="submit-btn-box"> <view class="submit-area" v-if="isSave">
<fui-button text="添加基地" bold radius="96rpx" @click="submit" /> <fui-button text="确认保存" radius="100rpx" @click="submit" />
</view> </view>
</fui-form> </fui-form>
</view> </view>
<fui-picker <AreaPicker v-model:show="show.address" :layer="3" title="选择所属地区" @confirm="handleAreaConfirm" />
:show="pageData.show.growCrops" <fui-picker :show="show.growCrops" :options="options.growCrops" @change="handleGrowCropsChange" @cancel="show.growCrops = false" />
:layer="1"
:linkage="true"
:options="pageData.options.growCrops"
@change="handleChangeGrowCrops"
@cancel="pageData.show.growCrops = false"
/>
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" />
</view> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.page { .page { background-color: #f7f8fa; min-height: 100vh; font-family: 'DingTalk Sans', sans-serif; }
background: #e8f5e9; .container { padding: 24rpx; }
min-height: 100vh; .section-card { background: #fff; border-radius: 24rpx; padding: 10rpx 24rpx; margin-bottom: 24rpx; }
} .submit-area { padding: 40rpx 20rpx; }
.unit { font-size: 26rpx; color: #999; margin-left: 12rpx; }
.page-container { :deep(.fui-input__label) { font-family: 'DingTalk Sans' !important; }
padding: 28rpx;
}
.form-card {
background-color: #ffffff;
border-radius: 20rpx;
margin-bottom: 20rpx;
padding: 0;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.upload-title {
padding: 28rpx;
font-size: 28rpx;
color: #333333;
font-weight: 400;
}
.upload-content {
padding: 0 32rpx 32rpx;
}
.submit-btn-box {
padding: 32rpx 0;
}
:deep(.fui-button) {
border-color: #ff9800 !important;
background: #ff9800 !important;
}
:deep(.fui-input__wrap),
:deep(.fui-textarea__wrap) {
padding: 28rpx 32rpx;
// border-bottom: 1px solid #e5e5e5;
}
:deep(.fui-input__label),
:deep(.fui-textarea__label) {
font-size: 28rpx;
color: #333333;
min-width: 160rpx;
font-weight: 400;
}
:deep(.fui-input__self),
:deep(.fui-textarea__self) {
font-size: 28rpx;
color: #333333;
}
:deep(.fui-input__placeholder),
:deep(.fui-textarea__placeholder) {
color: #d0d0d0;
font-size: 28rpx;
}
.form-card :deep(.fui-input__wrap:last-child),
.form-card :deep(.fui-textarea__wrap:last-child) {
border-bottom: none;
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.uni-textarea-placeholder) {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.fui-button) {
width: 690rpx;
border-color: #5db66f !important;
background: #5db66f !important;
}
// 移除fui-form的默认样式
:deep(.fui-form) {
background: transparent;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
:deep(.fui-input__border-bottom) {
right: 32rpx !important;
}
.form-item-block {
padding: 24rpx 12rpx;
// border-bottom: 1px solid #e5e5e5;
&:last-child {
border-bottom: none;
}
}
.form-item-label {
font-size: 28rpx;
color: #333333;
font-weight: 400;
margin-bottom: 20rpx;
&.required::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
}
.block-textarea {
width: 100%;
padding: 0rpx 12rpx !important;
}
.block-upload {
width: 100%;
padding: 0 20rpx;
:deep(.uni-file-picker__container) {
background-color: #fff !important;
border: unset !important;
border-radius: 16rpx;
min-height: 200rpx;
display: flex;
justify-content: left !important;
}
}
.select-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
.fui-input-wrapper {
cursor: pointer;
}
</style> </style>
...@@ -12,40 +12,41 @@ ...@@ -12,40 +12,41 @@
baseName: '', baseName: '',
}, },
list: [], list: [],
total: 0, // 基地总数 total: 0,
type_success_total: 0, // 已认证 type_success_total: 0,
type_unverified_total: 0, // 未认证 type_unverified_total: 0,
}) })
onNavigationBarButtonTap((e) => { onNavigationBarButtonTap((e) => {
console.log('onNavigationBarButtonTap', e) uni.navigateTo({
url: './add',
})
}) })
onLoad(() => {})
onShow(() => { onShow(() => {
nextTick(() => { nextTick(() => {
if (!isOnePage.value && paging.value) { if (!isOnePage.value && paging.value) {
paging.value.reload() paging.value.reload()
} }
isOnePage.value = false isOnePage.value = false
}) })
}) })
function getList() { function getList() {
if (!paging.value) if (!paging.value) return
return
NongchangAPI.getFarmbaseList(pageData.param) NongchangAPI.getFarmbaseList(pageData.param)
.then((res) => { .then((res) => {
pageData.total = res.total pageData.total = res.total
// 模拟已认证/待认证统计,实际应由后端返回
pageData.type_success_total = res.records.filter(i => i.auditStatus == 1).length
pageData.type_unverified_total = pageData.total - pageData.type_success_total
paging.value.complete(res.records) paging.value.complete(res.records)
}) })
.catch(() => { .catch(() => {
paging.value.complete(false) paging.value.complete(false)
}) })
} }
onNavigationBarButtonTap((_) => {
uni.navigateTo({
url: './add',
})
})
function queryList(pageNo, pageSize) { function queryList(pageNo, pageSize) {
pageData.param.pageNo = pageNo pageData.param.pageNo = pageNo
pageData.param.pageSize = pageSize pageData.param.pageSize = pageSize
...@@ -53,31 +54,24 @@ return ...@@ -53,31 +54,24 @@ return
} }
function handleSearch() { function handleSearch() {
// 重置页码为1,重新搜索 if (paging.value) paging.value.reload()
pageData.param.pageNo = 1
if (paging.value) {
paging.value.reload()
}
} }
function goDetail(id) { function goDetail(id) {
uni.navigateTo({ uni.navigateTo({
url: `./add?id=${id}`, url: `./add?id=${id}`,
}) })
} }
function del(id) { function del(id) {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '确定删除吗?', content: '确定删除该基地吗?',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
NongchangAPI.delFarmbase(id).then(() => { NongchangAPI.delFarmbase(id).then(() => {
uni.showToast({ uni.showToast({ title: '删除成功', icon: 'success' })
title: '删除成功', if (paging.value) paging.value.reload()
icon: 'none',
})
if (paging.value) {
paging.value.reload()
}
}) })
} }
}, },
...@@ -86,125 +80,82 @@ return ...@@ -86,125 +80,82 @@ return
</script> </script>
<template> <template>
<view class="codefun-flex-col page"> <view class="page">
<z-paging ref="paging" v-model="pageData.list" @query="queryList"> <z-paging ref="paging" v-model="pageData.list" @query="queryList" :fixed="true">
<view class="codefun-flex-col group_3"> <template #top>
<view class="codefun-flex-row codefun-items-center section_2"> <view class="header-section">
<image class="image_6" src="/static/images/codefun/6c5c5a3c082b8c60a307d3a7caee623c.png" /> <view class="search-box">
<u-input <fui-icon name="search" :size="32" color="#999"></fui-icon>
<input
class="search-input"
v-model="pageData.param.baseName" v-model="pageData.param.baseName"
placeholder="请输入搜索内容" placeholder="搜索基地名称"
border="none"
class="codefun-ml-8"
@confirm="handleSearch" @confirm="handleSearch"
/> />
</view> </view>
<view class="codefun-mt-12 codefun-flex-col section_3">
<view class="codefun-flex-row codefun-justify-between group_4"> <view class="stats-card">
<view class="codefun-flex-row codefun-self-start"> <view class="stats-main">
<image <view class="stats-item total">
class="codefun-shrink-0 image_7" <text class="stats-num">{{ pageData.total }}</text>
src="/static/images/codefun/db26a8ae3f4d5f1e0a3f11d8fb5bc491.png" <text class="stats-label">基地总数</text>
/>
<view
class="codefun-flex-col codefun-items-start codefun-shrink-0 codefun-self-center group_7"
>
<text class="text_5">{{ pageData.total }}</text>
<text class="font_4 text_7 mt-5">总基地数</text>
</view>
<text class="codefun-self-start font_4 text_4"></text>
</view>
<view class="codefun-flex-col codefun-self-center group_5">
<view class="codefun-flex-row codefun-items-center group_6">
<image
class="image_6"
src="/static/images/codefun/118c884c539aaba710313f0682db00e1.png"
/>
<view
class="codefun-ml-4 codefun-flex-col codefun-justify-start codefun-items-center text-wrapper"
>
<text class="font_3 text_6">已认证:</text>
</view>
<view
class="codefun-ml-4 codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_2"
>
<text class="font_5">{{ pageData.type_success_total }}</text>
</view>
</view>
<view class="codefun-flex-row codefun-items-center group_8">
<image
class="image_6"
src="/static/images/codefun/27ef797870c2085d1a14446c50cf53e0.png"
/>
<view
class="codefun-ml-4 codefun-flex-col codefun-justify-start codefun-items-center text-wrapper"
>
<text class="font_3 text_6">待认证:</text>
</view> </view>
<view <view class="stats-divider"></view>
class="codefun-ml-4 codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_3" <view class="stats-sub">
> <view class="sub-item">
<text class="font_3 text_8">{{ pageData.type_unverified_total }}</text> <text class="sub-label">已认证</text>
<text class="sub-num green">{{ pageData.type_success_total }}</text>
</view> </view>
<view class="sub-item">
<text class="sub-label">待认证</text>
<text class="sub-num orange">{{ pageData.type_unverified_total }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="codefun-flex-row codefun-justify-between codefun-items-center group_9"> <view class="guide-bar">
<view class="codefun-flex-row"> <text class="guide-text">基地认证后可获得更多政策补贴支持</text>
<image class="image_8" src="/static/images/codefun/c24e87154a833caadfcb70fb24ae52dc.png" /> <text class="guide-link">认证指南 ></text>
<view
class="codefun-ml-4 codefun-flex-col codefun-justify-start codefun-items-start text-wrapper_4"
>
<text class="font_5">基地认证指南</text>
</view> </view>
</view> </view>
<image class="image_9" src="/static/images/codefun/7e185c6d21ed1b0d04422b2d3835fce8.png" />
</view> </view>
</template>
<view class="list-container">
<view v-for="(item, index) in pageData.list" :key="item.id" class="base-card">
<view class="card-header">
<view class="status-badge" :class="{ 'is-auth': item.auditStatus == 1 }">
{{ item.auditStatus == 1 ? '已认证' : '待认证' }}
</view> </view>
<view class="codefun-mt-12 codefun-flex-col section_4" v-if="pageData.list.length"> <text class="base-name">{{ item.baseName }}</text>
<view class="codefun-flex-col codefun-self-stretch list">
<view
class="codefun-flex-row codefun-items-center list-item"
v-for="(item, index) in pageData.list"
:key="index"
>
<view class="codefun-flex-col codefun-justify-start codefun-items-start section_5">
<view
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_5"
>
<text class="font_6">{{ item.auditStatus_dictText }}</text>
</view> </view>
<view class="card-content">
<view class="info-row">
<view class="info-item">
<text class="info-label">主要作物:</text>
<text class="info-val">{{ item.growCropsText || item.growCrops || '未设置' }}</text>
</view> </view>
<view class="codefun-flex-col codefun-flex-1 group_10 ml-13"> <view class="info-item">
<view class="codefun-flex-col"> <text class="info-label">基地规模:</text>
<text class="codefun-self-start font">{{ item.baseName }}</text> <text class="info-val">{{ item.scale }}亩</text>
<view class="codefun-flex-row codefun-self-stretch mt-11">
<text class="font_4 text_9">类型:{{ item.baseType_dictText }}</text>
<text class="font_4 text_10 ml-13">规模:{{ item.scale }}</text>
</view> </view>
</view> </view>
<view <view class="info-row" v-if="item.province">
class="codefun-mt-18 codefun-flex-row codefun-justify-between codefun-items-center group_11" <view class="info-item full">
> <fui-icon name="location" :size="24" color="#999"></fui-icon>
<view class="codefun-flex-row" @click="goDetail(item.id)"> <text class="info-val addr">{{ item.province }}{{ item.city }}{{ item.country }}</text>
<image
class="image_10"
src="/static/images/codefun/3566724e23f8a91e60ae87c2744e4090.png"
/>
<view
class="codefun-ml-4 codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_6"
>
<text class="font_7 text_11">详情</text>
</view> </view>
</view> </view>
<view class="codefun-flex-row codefun-items-center" @click="del(item.id)">
<image
class="codefun-shrink-0 image_10"
src="/static/images/codefun/8630d19ebb6334e2028daa7d7b8b5983.png"
/>
<text class="font_8 ml-5">删除</text>
</view> </view>
<view class="card-actions">
<view class="btn-group">
<view class="action-btn plain" @click="del(item.id)">
<fui-icon name="delete" :size="28" color="#999"></fui-icon>
<text>删除</text>
</view> </view>
<view class="action-btn primary" @click="goDetail(item.id)">
查看详情
</view> </view>
</view> </view>
</view> </view>
...@@ -215,492 +166,189 @@ return ...@@ -215,492 +166,189 @@ return
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
body {
background-color: #e6f5e8;
}
.mt-5 {
margin-top: 10rpx;
}
.mt-11 {
margin-top: 22rpx;
}
.ml-5 {
margin-left: 10rpx;
}
.ml-13 {
margin-left: 26rpx;
}
.ml-9 {
margin-left: 18rpx;
}
.page { .page {
background-color: #e6f5e8; background-color: #f7f8fa;
mix-blend-mode: NOTTHROUGH; min-height: 100vh;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
.section {
padding: 32rpx 24rpx 32rpx 36rpx;
background-color: #5db66f;
mix-blend-mode: NOTTHROUGH;
.image {
border-radius: 64rpx;
width: 108rpx;
height: 42rpx;
}
.group {
margin-right: 4rpx;
.image_2 {
mix-blend-mode: NOTTHROUGH;
width: 34rpx;
height: 22rpx;
} }
.image_3 { .header-section {
mix-blend-mode: NOTTHROUGH; background-color: #5db66f;
width: 30rpx; padding: 20rpx 30rpx 80rpx;
height: 22rpx; }
}
.search-box {
.image_4 { background-color: #fff;
width: 48rpx; height: 80rpx;
height: 22rpx; border-radius: 40rpx;
} display: flex;
} align-items: center;
padding: 0 30rpx;
.group_2 { margin-bottom: 30rpx;
padding-left: 6rpx; .search-input {
flex: 1;
.image_5 { margin-left: 20rpx;
mix-blend-mode: NOTTHROUGH; font-size: 28rpx;
width: 14rpx;
height: 26rpx;
}
.pos {
position: absolute;
left: 6rpx;
top: 50%;
transform: translateY(-50%);
}
.text {
color: #ffffffe6;
line-height: 29.6rpx;
}
.pos_2 {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
.text_2 {
line-height: 25.78rpx;
}
}
}
}
.group_3 {
padding: 28rpx 24rpx 58rpx;
.section_2 {
padding: 16rpx 20rpx;
background-color: #ffffff;
border-radius: 1998rpx;
mix-blend-mode: NOTTHROUGH;
.text_3 {
color: #cccccc;
line-height: 26.02rpx;
}
} }
.section_3 {
padding: 0 24rpx;
background-color: #ffffff;
border-radius: 18.46rpx;
mix-blend-mode: NOTTHROUGH;
.group_4 {
padding: 42rpx 4rpx 30rpx 22rpx;
border-bottom: solid 2rpx #eeeeee;
.image_7 {
width: 100rpx;
height: 86rpx;
} }
.group_7 { .stats-card {
margin-left: 32rpx; background-color: #fff;
border-radius: 20rpx;
.text_5 { padding: 30rpx;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.05);
.stats-main {
display: flex;
align-items: center;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.stats-item.total {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.stats-num {
font-size: 48rpx;
font-weight: bold;
color: #5db66f; color: #5db66f;
font-size: 40rpx;
font-family: SourceHanSansCN;
line-height: 40rpx;
}
.text_7 {
line-height: 22.18rpx;
}
}
.text_4 {
color: #333333;
line-height: 40rpx;
}
.group_5 {
width: 219.94rpx;
.group_6 {
overflow: hidden;
.text-wrapper_2 {
padding: 8rpx 0 4rpx;
overflow: hidden;
width: 60rpx;
height: 40rpx;
}
}
.group_8 {
padding-top: 16rpx;
.text-wrapper_3 {
padding: 8rpx 0 4rpx;
overflow: hidden;
width: 60rpx;
height: 40rpx;
.text_8 {
color: #ff9800;
}
}
}
.text-wrapper {
padding: 8rpx 0 4rpx;
overflow: hidden;
width: 112rpx;
height: 40rpx;
.text_6 {
line-height: 25.74rpx;
}
}
}
} }
.stats-label {
.group_9 { font-size: 24rpx;
padding: 12rpx 4rpx; color: #999;
margin-top: 4rpx;
.image_8 { }
margin: 4rpx 0; }
width: 40rpx; .stats-divider {
height: 40rpx; width: 1rpx;
height: 60rpx;
background-color: #eee;
margin: 0 40rpx;
}
.stats-sub {
flex: 1.5;
display: flex;
justify-content: space-around;
.sub-item {
display: flex;
flex-direction: column;
.sub-label {
font-size: 22rpx;
color: #999;
}
.sub-num {
font-size: 32rpx;
font-weight: bold;
margin-top: 4rpx;
&.green { color: #5db66f; }
&.orange { color: #fa8c16; }
} }
.text-wrapper_4 {
padding: 12rpx 0;
overflow: hidden;
width: 168rpx;
height: 48rpx;
} }
.image_9 {
margin-right: 12rpx;
width: 16rpx;
height: 26rpx;
} }
.guide-bar {
margin-top: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
.guide-text {
font-size: 22rpx;
color: #666;
} }
.guide-link {
.font_5 { font-size: 22rpx;
font-size: 28rpx;
font-family: SourceHanSansCN;
line-height: 25.76rpx;
color: #5db66f; color: #5db66f;
font-weight: bold;
} }
} }
.font_3 {
font-size: 28rpx;
font-family: SourceHanSansCN;
line-height: 25.76rpx;
color: #1f2937;
} }
.section_4 { .list-container {
padding-left: 6rpx; padding: 20rpx 30rpx;
padding-bottom: 26rpx; margin-top: -40rpx;
background-color: #ffffff;
border-radius: 26.28rpx;
mix-blend-mode: NOTTHROUGH;
.list {
margin-left: 18rpx;
margin-right: 24rpx;
.list-item {
padding: 24rpx 0;
border-bottom: solid 2rpx #eeeeee;
.section_5 {
padding: 12rpx 0 116rpx;
background-image: url('/static/images/codefun/4be80e2618f3c4b4aa1ce64fd9063abf.png');
background-size: 100% 100%;
background-repeat: no-repeat;
width: 160rpx;
height: 160rpx;
.text-wrapper_5 {
padding: 8rpx 0;
background-image: linear-gradient(90deg, #43cf7c 0%, #5db66f 100%);
border-radius: 0rpx 8rpx 8rpx 0rpx;
mix-blend-mode: NOTTHROUGH;
width: 84rpx;
.font_6 {
font-size: 20rpx;
font-family: SourceHanSansCN;
line-height: 18.38rpx;
color: #ffffff;
}
} }
}
.group_10 {
margin-right: 40rpx;
.text_9 { .base-card {
line-height: 22.34rpx; background-color: #fff;
} border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
.text_10 { .card-header {
line-height: 22.22rpx; display: flex;
} align-items: center;
margin-bottom: 24rpx;
.group_11 { .status-badge {
padding-left: 64rpx; font-size: 20rpx;
padding: 4rpx 16rpx;
.image_10 { background-color: #f5f5f5;
width: 28rpx; color: #999;
height: 28rpx; border-radius: 6rpx;
} margin-right: 20rpx;
&.is-auth {
.text-wrapper_6 { background-color: #e6f5e8;
padding-top: 8rpx; color: #5db66f;
overflow: hidden;
width: 48rpx;
height: 32rpx;
.text_11 {
line-height: 22.12rpx;
} }
} }
.base-name {
.font_8 { font-size: 32rpx;
font-weight: bold;
color: #333;
flex: 1;
}
}
.card-content {
background-color: #f9f9f9;
border-radius: 12rpx;
padding: 20rpx;
.info-row {
display: flex;
margin-bottom: 12rpx;
&:last-child { margin-bottom: 0; }
.info-item {
flex: 1;
display: flex;
align-items: center;
font-size: 24rpx; font-size: 24rpx;
font-family: SourceHanSansCN; &.full { flex: none; width: 100%; }
line-height: 22.28rpx; .info-label { color: #999; }
color: #f44336; .info-val {
} color: #333;
} &.addr {
} margin-left: 10rpx;
} white-space: nowrap;
}
.group_14 {
margin: 18rpx 24rpx 0;
.text_14 {
line-height: 26.12rpx;
}
.text-wrapper_8 {
padding: 4rpx 0;
overflow: hidden; overflow: hidden;
width: 48rpx; text-overflow: ellipsis;
height: 32rpx;
.text_15 {
line-height: 22.2rpx;
}
}
}
.font_7 {
font-size: 24rpx;
font-family: SourceHanSansCN;
line-height: 22.28rpx;
color: #5db66f;
}
.group_15 {
margin-left: 24rpx;
margin-top: 28rpx;
width: 528.18rpx;
.text_16 {
line-height: 25.12rpx;
} }
.text_17 {
line-height: 26.26rpx;
}
}
.group_16 {
margin-left: 24rpx;
margin-top: 28rpx;
width: 216.94rpx;
.text_18 {
line-height: 25.82rpx;
}
.text_19 {
line-height: 25.34rpx;
} }
} }
.font_9 {
font-size: 28rpx;
font-family: SourceHanSansCN;
line-height: 25.76rpx;
color: #6b7280;
}
.group_17 {
margin-left: 24rpx;
margin-top: 28rpx;
.text_20 {
line-height: 26.16rpx;
}
.text_21 {
line-height: 22.66rpx;
} }
} }
.divider { .card-actions {
margin: 28rpx 18rpx 0 24rpx; margin-top: 30rpx;
background-color: #f3f4f6; .btn-group {
height: 2rpx; display: flex;
} justify-content: flex-end;
align-items: center;
.text_22 { .action-btn {
margin-left: 24rpx; height: 60rpx;
margin-top: 32rpx; padding: 0 30rpx;
line-height: 26.12rpx; border-radius: 30rpx;
} display: flex;
align-items: center;
.group_18 {
margin-left: 24rpx;
margin-top: 32rpx;
.text_23 {
line-height: 26.06rpx;
}
.text_24 {
line-height: 22.66rpx;
}
}
.font_10 {
font-size: 28rpx;
font-family: SourceHanSansCN;
line-height: 22.28rpx;
color: #1f2937;
}
.group_19 {
margin-left: 24rpx;
margin-top: 28rpx;
.text_25 {
line-height: 25.9rpx;
}
.text_26 {
line-height: 25.78rpx;
}
}
.group_20 {
margin-left: 24rpx;
margin-top: 32rpx;
.text_27 {
line-height: 25.96rpx;
}
.text_28 {
line-height: 25.88rpx;
}
}
.divider_2 {
margin-top: 28rpx;
background-color: #f3f4f6;
width: 716rpx;
height: 2rpx;
}
.text-wrapper_9 {
margin-left: 24rpx;
margin-top: 32rpx;
padding: 24rpx 0;
background-color: #5db66f;
border-radius: 400rpx;
mix-blend-mode: NOTTHROUGH;
.text_29 {
line-height: 25.9rpx;
}
}
}
.font_4 {
font-size: 24rpx; font-size: 24rpx;
font-family: SourceHanSansCN; margin-left: 20rpx;
line-height: 22.28rpx; &.plain {
color: #555555; color: #999;
border: 1rpx solid #eee;
} }
&.primary {
background-color: #5db66f;
color: #fff;
} }
.image_6 {
width: 32rpx;
height: 32rpx;
} }
.font {
font-size: 32rpx;
font-family: SourceHanSansCN;
line-height: 29.88rpx;
color: #333333;
} }
.font_2 {
font-size: 28rpx;
font-family: SourceHanSansCN;
line-height: 25.76rpx;
color: #ffffff;
} }
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue' import { reactive, ref, toRefs } from 'vue'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as NongzhiAPI from '@/api/model/nongzhi' import * as NongzhiAPI from '@/api/model/nongzhi'
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
let enterpriseId = null let enterpriseId = null
onLoad((options) => { onLoad((options) => {
const param = JSON.parse(decodeURIComponent(options.param)) const param = JSON.parse(decodeURIComponent(options.param))
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
...@@ -21,168 +22,115 @@ ...@@ -21,168 +22,115 @@
queryByCategory() queryByCategory()
getGoodsQueryById() getGoodsQueryById()
}) })
const toastRef = ref() const toastRef = ref()
const unitPopupRef = ref(null) const unitPopupRef = ref(null)
const uploadRef = ref() const uploadRef = ref()
const bgColorData = ['#EEFAEB', '#EFF1FF', '#FFF3F1', '#E8F7F7', '#FFF3E7', '#FAF8EA']
const pageData = reactive({ const pageData = reactive({
data: null, data: {
enterpriseName: '',
enterpriseLogoUrl: '',
businessScope: '',
profile: '',
contactPerson: '',
contactMobile: '',
provinceName: '',
cityName: '',
districtName: ''
},
enterpriseCers: [], enterpriseCers: [],
isPopupShow: false, isPopupShow: false,
unitPopup: false, unitPopup: false,
unitOptions: [], unitOptions: [],
unitVal: [], unitVal: [],
productImageArr: [], productImageArr: [],
categoryPopup: false, categoryPopup: false,
categoryOptions: [], categoryOptions: [],
categoryText: [], categoryText: [],
categoryVal: [], categoryVal: [],
enterpriseProduct: [], enterpriseProduct: [],
contactName: '', contactName: '',
showConfirmDialog: false, showConfirmDialog: false,
loading: false,
rules: [ rules: [
{ { name: 'name', rule: ['required'], msg: ['请输入商品名称'] },
name: 'name', { name: 'category', rule: ['required'], msg: ['请选择商品分类'] },
rule: ['required'], { name: 'minSellPrice', rule: ['required'], msg: ['请输入最小销售价'] },
msg: ['请输入商品名称'], { name: 'maxSellPrice', rule: ['required'], msg: ['请输入最大销售价'] },
}, { name: 'unit', rule: ['required'], msg: ['请选择单位'] },
/* { { name: 'image', rule: ['required'], msg: ['请选择商品图片'] },
name: "mobile",
rule: ["required", "isMobile"],
msg: ["请输入联系电话", "请输入正确的联系电话"]
}, */
{
name: 'category',
rule: ['required'],
msg: ['请选择商品分类'],
},
{
name: 'minSellPrice',
rule: ['required'],
msg: ['请输入最小销售价'],
},
{
name: 'maxSellPrice',
rule: ['required'],
msg: ['请输入最大销售价'],
},
{
name: 'unit',
rule: ['required'],
msg: ['请选择单位'],
},
{
name: 'image',
rule: ['required'],
msg: ['请选择商品图片'],
},
], ],
}) })
const productInfo = reactive({ const productInfo = reactive({
id: '', id: '',
name: '', // 商品名称 name: '',
// mobile:"", // 联系方式 category: '',
category: '', // 分类 minSellPrice: '',
minSellPrice: '', // 最小销售价 maxSellPrice: '',
maxSellPrice: '', // 最大销售价 unit: '',
unit: '', // 单位 unitCode: '',
unitCode: '', // 单位编码 enterpriseId: '',
enterpriseId: '', // 企业ID
image: '', image: '',
}) })
function getGoodsQueryById() { function getGoodsQueryById() {
NongzhiAPI.getGoodsQueryById({ enterpriseId }).then((res) => { NongzhiAPI.getGoodsQueryById({ enterpriseId }).then((res) => {
pageData.enterpriseProduct = res pageData.enterpriseProduct = res
}) })
} }
// 文件上传
function handleUpload(file) { function handleUpload(file) {
const tempFilePaths = file.tempFilePaths const tempFilePath = file.tempFiles[0].path
// 处理每张选中的图片
for (let i = 0; i < tempFilePaths.length; i++) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: tempFilePaths[i], filePath: tempFilePath,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success', pageData.productImageArr = [{ url: data.message }]
text: '上传成功', productInfo.image = data.message
})
pageData.productImageArr[0] = data.message // 保存返回的图片信息
} }
} }
},
fail: (err) => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.productImageArr[0] = null
},
}) })
} }
function handleDelete() {
pageData.productImageArr = []
productInfo.image = ''
} }
// 文件删除
function handleDelete(file, type) {
uploadRef.value.clearFiles()
pageData.productImageArr[0] = null
}
function changeCategory(e) { function changeCategory(e) {
productInfo.category = e.value productInfo.category = e.value
pageData.categoryPopup = false pageData.categoryPopup = false
} }
function selectCompleteUnit(e) { function selectCompleteUnit(e) {
productInfo.unit = e.text[e.text.length - 1] productInfo.unit = e.text[e.text.length - 1]
pageData.unitPopup = false pageData.unitPopup = false
} }
function changeUnit(e) { function changeUnit(e) {
const val = e.value queryByCategoryAndCode(e.value, e)
queryByCategoryAndCode(val, e)
} }
function handleSelectCategory() {
pageData.categoryPopup = true
}
function handleSelectUnit() {
pageData.unitPopup = true
}
// 查询商品分类
function queryByCategory() { function queryByCategory() {
LinghuoyonggongAPI.gitListByCodeDict({ code: 'category' }).then((res) => { LinghuoyonggongAPI.gitListByCodeDict({ code: 'category' }).then((res) => {
pageData.categoryOptions = res pageData.categoryOptions = res
const textArr = [] pageData.categoryText = res.map(i => i.itemText)
for (let i = 0; i < res.length; i++) {
textArr.push(res[i].itemText)
}
pageData.categoryText = textArr
}) })
} }
// 查询单位字典
function queryByCategoryAndCode(code, e) { function queryByCategoryAndCode(code, e) {
LinghuoyonggongAPI.queryByCategoryAndCode({ category: 2, code }).then((res) => { LinghuoyonggongAPI.queryByCategoryAndCode({ category: 2, code }).then((res) => {
if (res.length) { if (res.length) {
const dataArr = [] const dataArr = res.map(i => ({ text: i.name, value: i.code }))
for (let i = 0; i < res.length; i++) {
const obj = { text: '', value: '' }
obj.text = res[i].name
obj.value = res[i].code
dataArr.push(obj)
}
if (!pageData.unitOptions.length) { if (!pageData.unitOptions.length) {
pageData.unitOptions = dataArr pageData.unitOptions = dataArr
} else { } else {
...@@ -194,54 +142,40 @@ ...@@ -194,54 +142,40 @@
}) })
} }
// 发布
const formRef = ref() const formRef = ref()
function addData() { function addData() {
productInfo.image = pageData.productImageArr[0]
formRef.value.validator(productInfo, pageData.rules, true).then((res) => { formRef.value.validator(productInfo, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
NongzhiAPI.postGoodsAdd(productInfo).then((res) => { NongzhiAPI.postGoodsAdd(productInfo).then(() => {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '发布成功' })
type: 'success',
text: '发布成功',
})
pageData.isPopupShow = false pageData.isPopupShow = false
setTimeout(() => {
getGoodsQueryById() getGoodsQueryById()
}, 500)
}) })
} }
}) })
} }
function getDetailData(id) { function getDetailData(id) {
pageData.loading = true
NongzhiAPI.getEnterpriseDetail({ id }).then((res) => { NongzhiAPI.getEnterpriseDetail({ id }).then((res) => {
pageData.enterpriseCers = res.enterpriseCers.split(',') pageData.enterpriseCers = res.enterpriseCers ? res.enterpriseCers.split(',') : []
pageData.data = res pageData.data = res
pageData.contactName = pageData.contactName = res.contactPerson ? (res.contactPerson.substring(0, 1) + '**') : '商家'
res.contactPerson.substring(0, 1) + Array.from({ length: res.contactPerson.length }).join('*')
productInfo.unitCode = res.enterpriseCode productInfo.unitCode = res.enterpriseCode
productInfo.enterpriseId = res.id productInfo.enterpriseId = res.id
}).finally(() => {
pageData.loading = false
}) })
} }
function getBgColor(index: any) {
if (index < 6) {
return bgColorData[index]
} else {
return bgColorData[Math.floor(Math.random() * 5)]
}
}
function handlePublish() { function handlePublish() {
productInfo.id = '' Object.assign(productInfo, {
productInfo.name = '' id: '', name: '', category: '', minSellPrice: '', maxSellPrice: '', unit: '', image: ''
// productInfo.mobile = ""; })
productInfo.category = ''
productInfo.minSellPrice = ''
productInfo.maxSellPrice = ''
productInfo.unit = ''
productInfo.image = ''
pageData.productImageArr = [] pageData.productImageArr = []
pageData.isPopupShow = true pageData.isPopupShow = true
} }
function makePhoneCall() { function makePhoneCall() {
uni.makePhoneCall({ uni.makePhoneCall({
phoneNumber: pageData.data.contactMobile, phoneNumber: pageData.data.contactMobile,
...@@ -250,361 +184,355 @@ ...@@ -250,361 +184,355 @@
</script> </script>
<template> <template>
<view class="w-full bg-#E6F5E8 detail_page"> <view class="page">
<view class="module_width top_content"> <!-- 商家头部卡片 -->
<view class="enterprise_logo_view" <view class="shop-header">
><image class="enterprise_logo" mode="heightFix" :src="pageData.data.enterpriseLogoUrl" <view class="shop-main">
/></view> <image class="shop-logo" :src="pageData.data.enterpriseLogoUrl || '/static/images/nongjifuwu/default-corp.png'" mode="aspectFill" />
<view class="enterprise_business">主营:{{ pageData.data.businessScope }}</view> <view class="shop-info">
<view class="enterprise_description">{{ pageData.data.profile }}</view> <text class="shop-name">{{ pageData.data.enterpriseName }}</text>
<view class="shop-tags">
<text class="tag">认证入驻</text>
<text class="tag green">诚信商家</text>
</view>
</view>
</view>
<view class="shop-intro">
<text class="label">主营:</text>
<text class="content">{{ pageData.data.businessScope }}</text>
</view>
<view class="shop-addr" v-if="pageData.data.provinceName">
<fui-icon name="location" :size="24" color="#999"></fui-icon>
<text>{{ pageData.data.provinceName }}{{ pageData.data.cityName }}{{ pageData.data.districtName }}</text>
</view> </view>
<view class="module_width middle_content"> </view>
<view class="module_title">资质证书</view>
<view class="module_separate" /> <!-- 资质证书 -->
<view class="module_qualification"> <view class="section-card" v-if="pageData.enterpriseCers.length">
<view <view class="section-title">资质证书</view>
class="qualification_item" <scroll-view scroll-x class="cert-scroll">
:style="`height:126rpx;background:${getBgColor(index)}`" <view class="cert-list">
<image
v-for="(item, index) in pageData.enterpriseCers" v-for="(item, index) in pageData.enterpriseCers"
:key="index" :key="index"
> :src="item"
<image class="enterprise_logo" mode="heightFix" :src="item" /> mode="aspectFit"
<!-- <view class="qualification_name ellipsis">{{item}}</view> --> class="cert-img"
@click="uni.previewImage({ urls: pageData.enterpriseCers, current: index })"
/>
</view> </view>
</scroll-view>
</view>
<!-- 商品展示 (商城化) -->
<view class="section-card">
<view class="section-title">主营产品</view>
<view v-if="!pageData.enterpriseProduct.length" class="empty-products">
商家暂未发布产品
</view> </view>
<view class="product-grid">
<view class="product-item" v-for="item in pageData.enterpriseProduct" :key="item.id">
<image class="p-img" :src="item.image" mode="aspectFill" lazy-load />
<view class="p-info">
<text class="p-name">{{ item.name }}</text>
<view class="p-price-row">
<text class="p-symbol">¥</text>
<text class="p-price">{{ item.minSellPrice }}</text>
<text class="p-unit">/{{ item.unit }}</text>
</view> </view>
<view class="module_width footer_content"> <view class="p-buy-btn" @click="pageData.showConfirmDialog = true">咨询</view>
<view class="module_title">主营产品</view>
<view class="module_separate" />
<view class="module_business">
<view class="business_item" v-for="item in pageData.enterpriseProduct" :key="item.id">
<image class="business_img" mode="heightFix" :src="item.image" />
<view class="qualification_name ellipsis">{{ item.name }}</view>
<view class="qualification_price ellipsis"
>¥{{ item.minSellPrice }}<text> ~ </text> {{ item.maxSellPrice
}}<text class="qualification_text">/{{ item.unit }}</text></view
>
</view> </view>
</view> </view>
</view> </view>
</view>
<!-- 商家简介 -->
<view class="section-card" v-if="pageData.data.profile">
<view class="section-title">商家简介</view>
<text class="profile-text">{{ pageData.data.profile }}</text>
</view>
<view style="height: 140rpx"></view>
<view class="make-phone-view"> <!-- 底部联系栏 -->
<fui-button text="我要购买" bold radius="96rpx" @click="pageData.showConfirmDialog = true" height="80rpx" /> <view class="footer-bar">
<view class="contact-box" @click="pageData.showConfirmDialog = true">
<fui-icon name="mobile" :size="40" color="#5db66f"></fui-icon>
<text>联系商家</text>
</view> </view>
<view class="main-btn" @click="pageData.showConfirmDialog = true">
我要购买
</view> </view>
<!-- 确认对话框 --> </view>
<!-- 弹窗组件 -->
<ConfirmDialog <ConfirmDialog
v-model:show="pageData.showConfirmDialog" v-model:show="pageData.showConfirmDialog"
title="温馨提示" title="温馨提示"
:content="`你将与${pageData.contactName}进行电话沟通,若继续,请点击确认按钮!`" :content="`您将与商家【${pageData.data.enterpriseName}】进行电话沟通,确认拨打吗?`"
cancelText="取消"
confirmText="确认"
@confirm="makePhoneCall" @confirm="makePhoneCall"
/> />
<fui-fab position="right" distance="10" bottom="240" width="120" @click="handlePublish"> <fui-fab position="right" distance="20" bottom="240" width="100" @click="handlePublish">
<view class="text-white text-center"> <view class="fab-content">
<view class="fab-icon" /> <fui-icon name="plus" :size="40" color="#fff"></fui-icon>
<view style="font-size: 24rpx">发布产品</view> <text style="font-size: 20rpx; color:#fff">发布产品</text>
</view> </view>
</fui-fab> </fui-fab>
<!-- 发布产品弹窗 -->
<fui-bottom-popup :show="pageData.isPopupShow" @close="pageData.isPopupShow = false"> <fui-bottom-popup :show="pageData.isPopupShow" @close="pageData.isPopupShow = false">
<view class="fui-custom__wrap yr_person_popup"> <view class="popup-wrap">
<view class="popup_top"> <view class="popup-header">
<uni-icons type="left" size="24" color="#333333" @click="pageData.isPopupShow = false" /> <text>发布新产品</text>
<view class="add_person_text" style="font-size: 36rpx">产品信息</view> <fui-icon name="close" @click="pageData.isPopupShow = false"></fui-icon>
<view class="del_person_btn" />
</view> </view>
<view class="popup_content"> <fui-form ref="formRef" top="20">
<fui-form label-weight="auto" ref="formRef" top="10"> <fui-input label="产品名称" placeholder="请输入产品名称" v-model="productInfo.name" required />
<fui-input <fui-input label="产品分类" placeholder="请选择" v-model="productInfo.category" disabled @click="pageData.categoryPopup = true" required />
required <view class="price-edit-row">
label="商品名称" <text class="label">售价范围</text>
placeholder="请输入商品名称" <input type="number" v-model="productInfo.minSellPrice" placeholder="最低价" class="p-input" />
v-model="productInfo.name" <text class="sep">-</text>
label-width="212" <input type="number" v-model="productInfo.maxSellPrice" placeholder="最高价" class="p-input" />
size="28" <view class="unit-sel" @click="pageData.unitPopup = true">
/> {{ productInfo.unit || '单位' }}
</view>
<fui-input </view>
@click="handleSelectCategory" <view class="upload-section">
required <text class="label">产品图片</text>
label="商品分类" <uni-file-picker v-model="pageData.productImageArr" limit="1" @select="handleUpload" @delete="handleDelete" />
placeholder="请选择商品分类"
disabled
:value="productInfo.category"
label-width="212"
size="28"
/>
<!-- <fui-input required maxlength="11" label="联系电话" placeholder="请输入联系电话" v-model="productInfo.mobile" label-width="212" size="28"></fui-input> -->
<fui-form-item asterisk label="价格(元)" label-width="212" size="28">
<fui-input
type="number"
v-model="productInfo.minSellPrice"
style="width: 120rpx !important"
:borderBottom="false"
:padding="[0]"
placeholder="最低价"
size="28"
/>
<template #right>
<view style="color: #cccccc; margin-right: 40rpx"></view>
<fui-input
v-model="productInfo.maxSellPrice"
type="number"
style="width: 120rpx !important"
:borderBottom="false"
:padding="[0]"
placeholder="最高价"
size="28"
/>
<fui-text
v-if="productInfo.unit == ''"
size="28"
text="单位"
color="#cccccc"
@click="handleSelectUnit()"
/>
<fui-text v-else :text="productInfo.unit" size="28" @click="handleSelectUnit()" />
</template>
</fui-form-item>
<fui-form-item asterisk label="商品图片" :bottomBorder="false">
<template #vertical>
<uni-file-picker
:value="pageData.productImageArr"
ref="uploadRef"
limit="1"
:auto-upload="false"
@select="handleUpload"
@delete="handleDelete"
/>
</template>
</fui-form-item>
</fui-form>
</view> </view>
<fui-button style="margin-top: 36rpx" text="保存" bold radius="96rpx" @click="addData" height="80rpx" /> <view class="submit-btn-box">
<fui-button text="立即发布" radius="100rpx" @click="addData"></fui-button>
</view>
</fui-form>
</view> </view>
</fui-bottom-popup> </fui-bottom-popup>
<fui-picker :options="pageData.categoryText" :show="pageData.categoryPopup" @change="changeCategory" @cancel="pageData.categoryPopup = false" />
<fui-bottom-popup :show="pageData.unitPopup"> <fui-bottom-popup :show="pageData.unitPopup">
<view class="fui-scroll__wrap"> <view class="area-sel-wrap">
<view class="fui-title">请选择</view> <fui-cascader :options="pageData.unitOptions" @change="changeUnit" @complete="selectCompleteUnit" stepLoading ref="unitPopupRef" />
<fui-cascader
ref="unitPopupRef"
:value="pageData.unitVal"
stepLoading
@change="changeUnit"
@complete="selectCompleteUnit"
:options="pageData.unitOptions"
/>
<view class="fui-icon__close" @tap.stop="pageData.unitPopup = false">
<fui-icon name="close" :size="48" />
</view>
</view> </view>
</fui-bottom-popup> </fui-bottom-popup>
<fui-picker
:options="pageData.categoryText"
layer="1"
:show="pageData.categoryPopup"
@change="changeCategory"
@cancel="pageData.categoryPopup = false"
zIndex="9999"
/>
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
</view>
</template> </template>
<style lang="less" scoped> <style lang="scss" scoped>
.detail_page { .page {
display: flex; background-color: #f7f8fa;
flex-wrap: wrap; min-height: 100vh;
justify-content: center;
align-items: flex-start;
align-content: flex-start;
padding-bottom: 40rpx;
min-height: calc(100vh - 88rpx);
.module_width {
width: 690rpx;
margin-top: 24rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
box-sizing: border-box;
} }
.ellipsis {
white-space: nowrap; .shop-header {
overflow: hidden; background-color: #5db66f;
text-overflow: ellipsis; padding: 40rpx 30rpx 60rpx;
color: #fff;
.shop-main {
display: flex;
align-items: center;
.shop-logo {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
border: 4rpx solid rgba(255,255,255,0.3);
background-color: #fff;
}
.shop-info {
margin-left: 24rpx;
.shop-name {
font-size: 36rpx;
font-weight: bold;
display: block;
} }
.top_content { .shop-tags {
.enterprise_logo_view { margin-top: 12rpx;
width: 630rpx;
display: flex; display: flex;
align-items: flex-start; .tag {
.enterprise_logo { font-size: 20rpx;
height: 80rpx; padding: 2rpx 12rpx;
background-color: rgba(255,255,255,0.2);
border-radius: 4rpx;
margin-right: 12rpx;
&.green { color: #fff; border: 1rpx solid #fff; }
} }
} }
.enterprise_business {
width: 636rpx;
padding: 8rpx 20rpx;
border-radius: 8rpx;
background: linear-gradient(233.81deg, rgba(92, 181, 110, 1) 0%, rgba(100, 214, 62, 1) 100%);
font-size: 24rpx;
font-weight: 500;
line-height: 40rpx;
color: #ffffff;
margin-top: 32rpx;
} }
.enterprise_description { }
margin-top: 20rpx; .shop-intro {
margin-top: 30rpx;
font-size: 24rpx; font-size: 24rpx;
font-weight: 400; background-color: rgba(0,0,0,0.05);
letter-spacing: 0px; padding: 16rpx 20rpx;
line-height: 40rpx; border-radius: 12rpx;
color: #333333; .label { opacity: 0.8; }
text-align: justify;
vertical-align: middle;
text-indent: 2em;
} }
.shop-addr {
margin-top: 20rpx;
display: flex;
align-items: center;
font-size: 22rpx;
opacity: 0.9;
} }
.middle_content {
} }
.module_title {
font-size: 32rpx; .section-card {
font-weight: 500; background-color: #fff;
line-height: 40rpx; margin: 20rpx 24rpx;
color: #333333; border-radius: 20rpx;
text-align: justify; padding: 30rpx;
vertical-align: middle; .section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 24rpx;
padding-left: 16rpx;
border-left: 8rpx solid #5db66f;
} }
.module_separate {
margin-top: 20rpx;
width: 128rpx;
height: 6rpx;
opacity: 1;
background: linear-gradient(233.81deg, #5cb56e 0%, #64d63e 100%);
} }
.module_qualification {
margin-top: 6rpx; .cert-scroll {
width: 100%;
.cert-list {
display: flex; display: flex;
flex-wrap: wrap; padding-bottom: 10rpx;
align-items: flex-start; .cert-img {
align-content: flex-start; width: 200rpx;
.qualification_item { height: 140rpx;
width: 198rpx;
height: 166rpx;
margin-left: 20rpx;
margin-top: 26rpx;
border-radius: 8rpx; border-radius: 8rpx;
padding-top: 8rpx; margin-right: 20rpx;
box-sizing: border-box; background-color: #f8f8f8;
display: flex; flex-shrink: 0;
flex-wrap: wrap;
justify-content: center;
.enterprise_logo {
height: 114rpx;
max-width: 160rpx;
} }
.qualification_name {
height: 40rpx;
line-height: 40rpx;
font-size: 20rpx;
color: #333333;
max-width: 160rpx;
}
}
.qualification_item:nth-child(3n + 1) {
margin-left: 0rpx;
} }
} }
.footer_content { .product-grid {
margin-bottom: 100rpx;
}
.module_business {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-start; margin: 0 -10rpx;
align-content: flex-start; .product-item {
justify-content: space-between; width: 50%;
.business_item { padding: 10rpx;
width: 310rpx;
height: 292rpx;
margin-top: 24rpx;
border-radius: 16rpx;
box-sizing: border-box; box-sizing: border-box;
display: flex; .p-img {
justify-content: center; width: 100%;
align-items: center; height: 280rpx;
flex-direction: column; border-radius: 12rpx 12rpx 0 0;
background: #f9fafb; background-color: #f8f8f8;
.business_img { }
width: 310rpx; .p-info {
height: 180rpx; background-color: #f9f9f9;
opacity: 1; padding: 16rpx;
border-radius: 16rpx 16rpx 0rpx 0rpx; border-radius: 0 0 12rpx 12rpx;
} .p-name {
.qualification_name { font-size: 26rpx;
height: 48rpx; color: #333;
line-height: 48rpx; height: 72rpx;
font-size: 32rpx; display: -webkit-box;
color: #000000; -webkit-box-orient: vertical;
max-width: 280rpx; -webkit-line-clamp: 2;
margin-top: 6rpx; overflow: hidden;
} }
.qualification_price { .p-price-row {
height: 48rpx; margin-top: 12rpx;
width: 280rpx; color: #ff4d4f;
opacity: 1; .p-symbol { font-size: 20rpx; }
font-size: 32rpx; .p-price { font-size: 32rpx; font-weight: bold; }
font-weight: 400; .p-unit { font-size: 22rpx; color: #999; }
letter-spacing: 0px; }
line-height: 48rpx; .p-buy-btn {
margin-top: 16rpx;
background-color: #5db66f;
color: #fff;
font-size: 22rpx;
text-align: center; text-align: center;
color: #5db66f; padding: 8rpx 0;
.qualification_text { border-radius: 24rpx;
font-size: 12px;
} }
} }
} }
.business_item:nth-child(odd) {
margin-left: 0rpx;
} }
.profile-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
} }
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 110rpx;
background-color: #fff;
display: flex;
align-items: center;
padding: 0 30rpx;
box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.05);
z-index: 100;
.contact-box {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 40rpx;
text { font-size: 20rpx; color: #666; margin-top: 4rpx; }
} }
::v-deep .fui-fab__btn-main { .main-btn {
background: linear-gradient(124.25deg, #a5d63f 0%, #5db66f 100%) !important; flex: 1;
box-shadow: 0px 1px 8px #5db66f; height: 80rpx;
background-color: #5db66f;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 40rpx;
font-size: 30rpx;
font-weight: bold;
} }
.fui-scroll__wrap {
padding-top: 30rpx;
position: relative;
} }
.fui-title { .popup-wrap {
font-size: 30rpx; background-color: #fff;
padding: 30rpx;
border-radius: 30rpx 30rpx 0 0;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
font-weight: bold; font-weight: bold;
text-align: center; margin-bottom: 20rpx;
padding-bottom: 24rpx; }
} }
.fui-icon__close { .price-edit-row {
position: absolute; display: flex;
top: 24rpx; align-items: center;
right: 24rpx; padding: 30rpx 0;
border-bottom: 1rpx solid #eee;
.label { width: 160rpx; font-size: 28rpx; color: #333; }
.p-input { flex: 1; font-size: 28rpx; }
.sep { margin: 0 20rpx; color: #ccc; }
.unit-sel {
padding: 4rpx 20rpx;
background-color: #f5f5f5;
font-size: 24rpx;
color: #666;
border-radius: 20rpx;
}
} }
.make-phone-view { .upload-section {
width: 690rpx; padding: 30rpx 0;
height: 80rpx; .label { display: block; font-size: 28rpx; color: #333; margin-bottom: 20rpx; }
position: fixed;
left: 30rpx;
bottom: 20rpx;
} }
.submit-btn-box { padding: 40rpx 0; }
.fab-content { display: flex; flex-direction: column; align-items: center; }
.empty-products { text-align: center; padding: 60rpx 0; color: #999; font-size: 26rpx; }
.area-sel-wrap { background-color: #fff; padding: 30rpx; }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { onPullDownRefresh, onShow } from '@dcloudio/uni-app' import { onPullDownRefresh, onShow, onReachBottom, onNavigationBarButtonTap } from '@dcloudio/uni-app'
import { reactive } from 'vue' import { reactive, toRefs } from 'vue'
import Navigate from '@/utils/page/navigate' import Navigate from '@/utils/page/navigate'
import * as NongzhiAPI from '@/api/model/nongzhi' import * as NongzhiAPI from '@/api/model/nongzhi'
onPullDownRefresh(() => { onPullDownRefresh(() => {
resetData() resetData()
getList(pageData.params) fetchList()
}) })
onShow(() => { onShow(() => {
resetData() resetData()
getList(pageData.params) fetchList()
}) })
onNavigationBarButtonTap(() => {
Navigate.to('/pages/kexinnongzi/shenqingruzhu')
})
const pageData = reactive({ const pageData = reactive({
params: { params: {
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
bizCategory: 2, bizCategory: 2,
reviewStatus: 1, reviewStatus: 1,
}, // 分页参数 },
hasMore: true, // 是否还有更多数据 hasMore: 'more',
loading: false, // 是否正在加载 loading: false,
dataList: [], dataList: [],
total: 0
}) })
async function getList(params) { const { dataList, loading, hasMore } = toRefs(pageData)
if (pageData.loading)
return async function fetchList() {
if (pageData.loading || pageData.hasMore === 'noMore') return
pageData.loading = true pageData.loading = true
await NongzhiAPI.getEnterpriseList(params).then((res) => {
if (res.records.length > 0) { try {
const { records } = res const res = await NongzhiAPI.getEnterpriseList(pageData.params)
const { records, total } = res
if (pageData.params.pageNo === 1) { if (pageData.params.pageNo === 1) {
pageData.dataList = records pageData.dataList = records
} else { } else {
pageData.dataList = [...pageData.dataList, ...records] pageData.dataList = [...pageData.dataList, ...records]
} }
pageData.hasMore = records.length === pageData.params.pageSize
pageData.params.pageNo++ pageData.total = total
if (pageData.dataList.length >= total) {
pageData.hasMore = 'noMore'
} else { } else {
pageData.hasMore = false pageData.hasMore = 'more'
pageData.params.pageNo++
} }
}) } finally {
pageData.loading = false pageData.loading = false
uni.stopPullDownRefresh()
}
} }
function toDetail(item) { function toDetail(item) {
...@@ -57,134 +63,316 @@ return ...@@ -57,134 +63,316 @@ return
function resetData() { function resetData() {
pageData.params.pageNo = 1 pageData.params.pageNo = 1
pageData.hasMore = true pageData.hasMore = 'more'
pageData.loading = false pageData.loading = false
pageData.dataList = []
} }
onReachBottom(() => {
fetchList()
})
</script> </script>
<template> <template>
<view class="w-full h-95vh bg-#E6F5E8 yr_page_view"> <view class="page">
<!-- 列表 --> <!-- 顶部装饰图 -->
<scroll-view <view class="banner-section">
class="w-full h-full" <image class="banner-bg" src="/static/images/comm/agr_sup_img.png" mode="aspectFill" />
style="font-family: '思源黑体'; font-weight: 400" <view class="banner-mask"></view>
scroll-y <view class="banner-content">
@scrolltolower="getList(pageData.params)" <text class="banner-title">可信农资服务</text>
:show-scrollbar="false" <text class="banner-desc">源头直供 · 质量保证 · 诚信经营</text>
> </view>
<view class="top_img"> </view>
<image class="agr_sup_img" src="/static/images/comm/agr_sup_img.png" />
<view class="list-container">
<view v-if="!dataList.length && !loading" class="empty-box">
<fui-empty src="/static/images/no-data.png" title="暂无入驻农资商" />
</view> </view>
<view class="page_content"> <view
<view class="item_list" v-for="item in pageData.dataList" :key="item.id"> class="enterprise-card"
<view class="item_left"> v-for="item in dataList"
<image class="enterprise_logo" mode="aspectFit" :src="item.enterpriseLogoUrl" /> :key="item.id"
@click="toDetail(item)"
>
<view class="card-top">
<image class="logo" mode="aspectFill" :src="item.enterpriseLogoUrl || '/static/images/nongjifuwu/default-corp.png'" lazy-load />
<view class="info">
<view class="name-row">
<text class="name">{{ item.enterpriseName }}</text>
<view class="auth-tag">
<fui-icon name="checkbox-fill" :size="24" color="#5db66f"></fui-icon>
<text>已认证</text>
</view>
</view>
<text class="scope">主营:{{ item.businessScope || '优质农资供应' }}</text>
<view class="tags-row">
<text class="tag">厂家直销</text>
<text class="tag blue">质检合格</text>
</view> </view>
<view class="item_right">
<view class="item_name ellipsis">{{ item.enterpriseName }}</view>
<view class="item_description ellipsis">{{ item.businessScope }}</view>
<view class="item_details">
<view class="detail_btn" @click="toDetail(item)">查看详情</view>
</view> </view>
</view> </view>
<view class="card-bottom">
<view class="location">
<fui-icon name="location" :size="24" color="#999"></fui-icon>
<text class="addr-text">{{ item.provinceName }}{{ item.cityName }}{{ item.districtName }}</text>
</view> </view>
<view class="enter-btn">进入店铺</view>
</view>
</view>
<fui-loadmore v-if="loading" />
<view v-if="hasMore === 'noMore' && dataList.length > 0" class="no-more">
- 只有这么多了 -
</view>
</view>
<!-- 入驻悬浮按钮 (增加水波纹动效) -->
<view class="fab-container" @click="Navigate.to('/pages/kexinnongzi/shenqingruzhu')">
<view class="ripple"></view>
<view class="ripple ripple-2"></view>
<view class="fab-entry">
<fui-icon name="plus" :size="48" color="#fff"></fui-icon>
<text>商家入驻</text>
</view> </view>
<!-- 加载状态 -->
<view class="loading-status">
<text v-if="pageData.loading">加载中...</text>
<text v-else-if="!pageData.hasMore">没有更多数据了</text>
<text v-else>上拉加载更多</text>
</view> </view>
</scroll-view>
</view> </view>
</template> </template>
<style lang="less" scoped> <style lang="scss" scoped>
.yr_page_view { .page {
padding: 28rpx; min-height: 100vh;
.top_img, background-color: #f7f8fa;
.agr_sup_img {
height: 280rpx;
width: 690rpx;
} }
.top_img {
margin-bottom: 24rpx; .banner-section {
width: 750rpx;
height: 320rpx;
position: relative;
.banner-bg {
width: 100%;
height: 100%;
}
.banner-mask {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, rgba(0,0,0,0.1), rgba(0,0,0,0.4));
}
.banner-content {
position: absolute;
left: 40rpx;
bottom: 60rpx;
.banner-title {
font-size: 44rpx;
color: #fff;
font-weight: bold;
display: block;
}
.banner-desc {
font-size: 24rpx;
color: rgba(255,255,255,0.8);
margin-top: 10rpx;
display: block;
} }
.page_content {
border-radius: 16rpx;
background: #ffffff;
} }
.ellipsis {
width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.item_list { .list-container {
padding: 20rpx 30rpx;
margin-top: -30rpx;
position: relative;
z-index: 10;
}
.enterprise-card {
background-color: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
.card-top {
display: flex;
.logo {
width: 140rpx;
height: 140rpx;
border-radius: 12rpx;
background-color: #f8f8f8;
flex-shrink: 0;
}
.info {
flex: 1;
margin-left: 24rpx;
display: flex; display: flex;
align-items: flex-start; flex-direction: column;
.name-row {
display: flex;
align-items: center;
justify-content: space-between; justify-content: space-between;
padding-left: 24rpx; .name {
padding-right: 30rpx;
padding-bottom: 24rpx;
padding-top: 24rpx;
border-bottom: 2rpx solid #eeeeee;
.item_left,
.enterprise_logo {
width: 192rpx;
max-height: 160rpx;
}
.item_right {
width: 436rpx;
.item_name {
font-size: 32rpx; font-size: 32rpx;
font-weight: 500; font-weight: bold;
color: #333333; color: #333;
max-width: 300rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.auth-tag {
display: flex;
align-items: center;
background-color: #e6f5e8;
padding: 4rpx 12rpx;
border-radius: 20rpx;
text {
font-size: 20rpx;
color: #5db66f;
margin-left: 4rpx;
font-weight: bold;
}
}
} }
.item_description { .scope {
font-size: 24rpx; font-size: 24rpx;
font-weight: 400; color: #999;
color: #999999; margin-top: 12rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
.tags-row {
display: flex;
margin-top: 16rpx; margin-top: 16rpx;
margin-bottom: 12rpx; .tag {
font-size: 20rpx;
padding: 2rpx 12rpx;
background-color: #f5f5f5;
color: #666;
border-radius: 4rpx;
margin-right: 12rpx;
&.blue {
background-color: #e6f7ff;
color: #1890ff;
}
}
} }
.item_details { }
}
.card-bottom {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f5f5f5;
display: flex;
justify-content: space-between;
align-items: center;
.location {
display: flex; display: flex;
justify-content: flex-end; align-items: center;
.addr-text {
font-size: 22rpx;
color: #999;
margin-left: 6rpx;
max-width: 350rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.enter-btn {
padding: 10rpx 30rpx;
background-color: #5db66f;
color: #fff;
font-size: 24rpx;
border-radius: 30rpx;
font-weight: bold;
} }
.detail_btn { }
border-radius: 100rpx; }
background: #5db66f;
.fab-container {
position: fixed;
right: 30rpx;
bottom: 150rpx;
width: 110rpx;
height: 110rpx;
z-index: 100;
display: flex; display: flex;
align-items: center;
justify-content: 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: 110rpx;
height: 110rpx;
background: linear-gradient(135deg, #a5d63f 0%, #2e8b57 100%);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding: 20rpx; justify-content: center;
width: 136rpx; box-shadow: 0 8rpx 30rpx rgba(46, 139, 87, 0.5);
height: 48rpx; animation: breathe 2.5s infinite ease-in-out;
font-size: 24rpx; z-index: 2;
font-weight: 400;
color: #ffffff; text {
font-size: 18rpx;
color: #fff;
margin-top: -4rpx;
font-weight: bold;
}
} }
} }
@keyframes ripple {
0% {
transform: scale(1);
opacity: 0.4;
} }
.item_list:last-child { 100% {
border-bottom: none; transform: scale(1.8);
opacity: 0;
} }
} }
.loading-status { @keyframes breathe {
0%, 100% {
transform: scale(1);
box-shadow: 0 8rpx 30rpx rgba(46, 139, 87, 0.5);
}
50% {
transform: scale(1.08);
box-shadow: 0 12rpx 45rpx rgba(46, 139, 87, 0.7);
}
}
.no-more {
text-align: center; text-align: center;
padding: 20rpx; font-size: 24rpx;
color: #999; color: #ccc;
font-size: 28rpx; padding: 40rpx 0;
} }
.ellipsis-multiline {
display: -webkit-box; /* 必须 */ .empty-box {
-webkit-box-orient: vertical; /* 垂直排列 */ padding-top: 100rpx;
-webkit-line-clamp: 2; /* 限制显示3行 */
overflow: hidden; /* 隐藏超出内容 */
text-overflow: ellipsis; /* 可选,部分浏览器不生效 */
} }
</style> </style>
...@@ -3,666 +3,471 @@ ...@@ -3,666 +3,471 @@
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import * as NongzhiAPI from '@/api/model/nongzhi' import * as NongzhiAPI from '@/api/model/nongzhi'
import AreaPicker from '@/components/AreaPicker/index.vue'
import { useDictStore } from '@/store/modules/dict'
const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
onLoad((option) => {
// 获取数据详情
getProvince(0, null)
})
// 勾选协议校验
function checkAgree(agree) {
return agree
}
const areaPopupRef = ref(null)
const pageData = reactive({ const pageData = reactive({
loading: false, loading: false,
show: { show: {
time1: false,
time2: false,
area: false, area: false,
urgentdegree: false,
type: false,
}, },
options: {
area: [],
areaVal: [],
urgentdegree: [],
type: [],
},
cersImageIndex: 0,
cersImageArr: [], cersImageArr: [],
enterpriseLogoArr: [], enterpriseLogoArr: [],
selectAreaVal: [],
form: { form: {
id: '', id: '',
enterpriseName: '', // 企业名称 enterpriseName: '',
enterpriseCode: '', // 企业编码 enterpriseCode: '',
businessScope: '', // 企业经营范围 businessScope: '',
contactPerson: '', // 联系人 contactPerson: '',
contactMobile: '', // 联系人手机号 contactMobile: '',
profile: '', // 企业简介 profile: '',
provinceName: '',
provinceName: '', // 省 cityName: '',
cityName: '', // 市 districtName: '',
districtName: '', // 区县 townName: '',
townName: '', // 乡镇 provinceCode: '',
cityCode: '',
provinceCode: '', // 省 districtCode: '',
cityCode: '', // 市 townCode: '',
districtCode: '', // 区县 addr: '',
townCode: '', // 乡镇 lon: '',
lat: '',
addr: '', // 详细地址 bizCategory: 2,
lon: '', // 经度 enterpriseLogoUrl: null,
lat: '', // 纬度 enterpriseCers: null,
bizCategory: 2, // 业务分类【1:代理记账、2:农资、3:农机、4:金融】
enterpriseLogoUrl: null, // 企业logo url
enterpriseCers: null, // 企业资质url
areaText: '', areaText: '',
agree: false, agree: false,
}, },
position: [],
rules: [ rules: [
{ { name: 'enterpriseName', rule: ['required'], msg: ['请输入公司名称'] },
name: 'enterpriseName', { name: 'enterpriseCode', rule: ['required'], msg: ['请输入企业编码'] },
rule: ['required'], { name: 'businessScope', rule: ['required'], msg: ['请输入经营业务'] },
msg: ['请输入公司名称'], { name: 'profile', rule: ['required'], msg: ['请输入企业介绍'] },
}, { name: 'areaText', rule: ['required'], msg: ['请选择地区'] },
{ { name: 'addr', rule: ['required'], msg: ['请输入详细地址'] },
name: 'enterpriseCode', { name: 'contactPerson', rule: ['required'], msg: ['请输入联系人'] },
rule: ['required'], { name: 'contactMobile', rule: ['required', 'isMobile'], msg: ['请输入联系电话', '请输入正确的联系电话'] },
msg: ['请输入企业编码'], { name: 'enterpriseLogoUrl', rule: ['required'], msg: ['请上传公司logo'] },
}, { name: 'enterpriseCers', rule: ['required'], msg: ['请上传公司资质证件'] },
{ { name: 'agree', validator: [{ msg: '请勾选并同意协议', method: (a) => a }] },
name: 'businessScope',
rule: ['required'],
msg: ['请输入经营业务'],
},
{
name: 'profile',
rule: ['required'],
msg: ['请输入平台介绍'],
},
{
name: 'areaText',
rule: ['required'],
msg: ['请选择地区'],
},
{
name: 'addr',
rule: ['required'],
msg: ['请输入详细地址'],
},
{
name: 'contactPerson',
rule: ['required'],
msg: ['请输入联系人'],
},
{
name: 'contactMobile',
rule: ['required', 'isMobile'],
msg: ['请输入联系电话', '请输入正确的联系电话'],
},
{
name: 'enterpriseLogoUrl',
rule: ['required'],
msg: ['请上传公司logo'],
},
{
name: 'enterpriseCers',
rule: ['required'],
msg: ['请上传公司资质证件'],
},
{
name: 'agree',
validator: [
{
msg: '请勾选并同意《入驻协议》与《服务条款》',
method: checkAgree,
},
],
},
], ],
}) })
function agreeChange(e) { const { show, form } = toRefs(pageData)
pageData.form.agree = e.checked const toastRef = ref()
}
// 选择地区完成 function handleAreaConfirm(e) {
function selectCompleteArea(e) {
const areaAttr = ['province', 'city', 'district', 'town'] const areaAttr = ['province', 'city', 'district', 'town']
const text = e.text const { text, value } = e
const value = e.value
const formData = pageData.form
for (let i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
formData[`${areaAttr[i]}Name`] = text[i] pageData.form[`${areaAttr[i]}Name`] = text[i]
formData[`${areaAttr[i]}Code`] = value[i] pageData.form[`${areaAttr[i]}Code`] = value[i]
}
pageData.form.areaText = text.join('')
pageData.show.area = false
}
// 在选择地区
function changeArea(e) {
const val = e.value
getProvince(val, e)
}
// 获取下一级地区
function getProvince(code, e) {
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then((res) => {
if (res.length) {
const dataArr = []
for (let i = 0; i < res.length; i++) {
const obj = { text: '', value: '' }
obj.text = res[i].name
obj.value = res[i].code
dataArr.push(obj)
}
if (!pageData.options.area.length) {
pageData.options.area = dataArr
} else {
areaPopupRef.value.setRequestData(dataArr, e.layer)
} }
} else { pageData.form.areaText = e.fullText
areaPopupRef.value.end()
}
})
} }
const { show, options, form } = toRefs(pageData)
const toastRef = ref()
const uploadLogoRef = ref()
const uploadCersRef = ref()
// 文件上传
function handleUpload(file, type) { function handleUpload(file, type) {
const tempFilePaths = file.tempFilePaths const tempFilePath = file.tempFiles[0].path
// 处理每张选中的图片
for (let i = 0; i < tempFilePaths.length; i++) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: tempFilePaths[i], filePath: tempFilePath,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success',
text: '上传成功',
})
if (type == 'logo') { if (type == 'logo') {
pageData.enterpriseLogoArr[0] = data.message // 保存返回的图片信息 pageData.enterpriseLogoArr = [{ url: data.message }]
pageData.form.enterpriseLogoUrl = data.message
} else { } else {
pageData.cersImageIndex++ pageData.cersImageArr.push({ url: data.message })
pageData.cersImageArr.push(data.message)
} }
} }
} }
},
fail: (err) => {
toastRef.value.show({
type: 'error',
text: '上传失败',
}) })
}
function handleDelete(e, type) {
if (type == 'logo') { if (type == 'logo') {
uploadLogoRef.value.clearFiles() pageData.enterpriseLogoArr = []
pageData.enterpriseLogoArr[0] = null pageData.form.enterpriseLogoUrl = null
} else { } else {
uploadCersRef.value.clearFiles(pageData.cersImageIndex) const index = pageData.cersImageArr.findIndex(i => i.url === e.tempFilePath)
pageData.form.enterpriseCers = null if (index > -1) pageData.cersImageArr.splice(index, 1)
}
},
})
}
} }
// 文件删除
function handleDelete(file, type) {
if ((type = 'logo')) {
uploadLogoRef.value.clearFiles()
pageData.enterpriseLogoArr[0] = null
} else {
const num = pageData.cersImageArr.findIndex((v) => v.url === file.tempFilePath)
pageData.cersImageArr.splice(num, 1)
} }
function handleViewProtocol() {
uni.showModal({
title: '农贸入驻服务协议',
content: '协议内容预览...',
showCancel: false
})
} }
const formRef = ref() const formRef = ref()
function submit() { function submit() {
const { lon, lat } = uni.getStorageSync('location') const { lon, lat } = uni.getStorageSync('location') || { lon: '', lat: '' }
pageData.position = [lon, lat] pageData.form.lon = lon
pageData.form.enterpriseLogoUrl = pageData.enterpriseLogoArr.join('') pageData.form.lat = lat
pageData.form.enterpriseCers = pageData.cersImageArr.join(',') pageData.form.enterpriseCers = pageData.cersImageArr.map(i => i.url).join(',')
if (pageData.position.length == 0) {
toastRef.value.show({
type: 'error',
text: '无法获取位置',
})
return
}
pageData.form.lon = pageData.position[0]
pageData.form.lat = pageData.position[1]
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
NongzhiAPI.postEnterpriseAdd(pageData.form).then((res) => { pageData.loading = true
toastRef.value.show({ NongzhiAPI.postEnterpriseAdd(pageData.form).then(() => {
type: 'success', toastRef.value.show({ type: 'success', text: '申请提交成功' })
text: '发布成功', setTimeout(() => uni.navigateBack(), 1500)
}) }).finally(() => {
setTimeout(() => { pageData.loading = false
uni.navigateBack({
delta: 1, // 返回的页面数
})
}, 1000)
}) })
} }
}) })
} }
function getCurrentDate() {
const date = new Date()
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${month}-${day}`
}
</script> </script>
<template> <template>
<view class="page"> <view class="page">
<view class="formBox"> <!-- 头部氛围区 -->
<fui-form ref="formRef" label-weight="auto" top="60" :disabled="form.id ? true : false"> <view class="header-banner">
<view class="mt20"> <view class="banner-title">农贸入驻申请</view>
<fui-input <view class="banner-subtitle">加入可信农资平台,拓展优质销路</view>
required
label="公司名称"
placeholder="请输入公司名称"
v-model="form.enterpriseName"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
required
label="企业编码"
placeholder="请输入企业编码"
v-model="form.enterpriseCode"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
required
label="经营业务"
placeholder="请输入经营业务"
v-model="form.businessScope"
labelSize="28"
label-width="180"
size="28"
/>
<fui-form-item asterisk label="平台介绍" :bottomBorder="false" prop="descr" error-align="left">
<template #vertical>
<fui-textarea
isCounter
maxlength="-1"
:padding="['0', '32rpx', '32rpx']"
:border-bottom="false"
:border-top="false"
size="28"
placeholder="请输入平台介绍..."
v-model="form.profile"
/>
</template>
</fui-form-item>
</view> </view>
<view class="mt20">
<fui-input <!-- 步骤引导 -->
required <view class="step-guide">
label="地区" <view class="step-item active">
placeholder="请选择地区" <view class="step-num">1</view>
v-model="form.areaText" <text class="step-text">资料填写</text>
labelSize="28"
label-width="180"
@click="show.area = true"
size="28"
disabled
/>
<fui-input
required
label="详细地址"
placeholder="请输入详细地址"
v-model="form.addr"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
required
label="联系人"
placeholder="请输入联系人"
v-model="form.contactPerson"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
type="tel"
required
label="联系电话"
placeholder="请输入联系电话"
v-model="form.contactMobile"
labelSize="28"
label-width="180"
size="28"
/>
</view> </view>
<view class="bg-white mt20" style="padding: 0.875rem 1rem"> <view class="step-line active"></view>
<view class="mb-1 flex justify-start" style="font-size: 28rpx" <view class="step-item">
><span style="color: red">*&nbsp;</span> 公司logo <view class="step-num">2</view>
<text class="step-text">资料审核</text>
</view> </view>
<uni-file-picker <view class="step-line"></view>
:value="pageData.enterpriseLogoArr" <view class="step-item">
ref="uploadLogoRef" <view class="step-num">3</view>
limit="1" <text class="step-text">入驻成功</text>
:auto-upload="false"
@select="handleUpload($event, 'logo')"
@delete="handleDelete($event, 'logo')"
/>
</view> </view>
<view class="bg-white mt20" style="padding: 0.875rem 1rem">
<view class="mb-1 flex justify-start" style="font-size: 28rpx"
><span style="color: red">*&nbsp;</span> 公司资质证件
</view> </view>
<view class="mb-1 flex justify-start" style="font-size: 24rpx; color: #cccccc"
>前6张资质证件将展示在详情页,拖拽图片可自定义排序。</view <view class="form-container">
> <fui-form ref="formRef" top="0">
<uni-file-picker <!-- 企业基本信息 -->
limit="9" <view class="section-card">
:value="pageData.cersImageArr" <view class="section-header">
ref="uploadCersRef" <view class="header-line"></view>
:auto-upload="false" <text class="section-title">企业身份信息</text>
@select="handleUpload($event, 'cers')"
@delete="handleDelete($event, 'cers')"
/>
</view> </view>
<view class="fui-clause--cell fui-clause--wrap"> <view class="form-group">
<fui-label> <fui-input label="公司名称" placeholder="请与营业执照一致" v-model="form.enterpriseName" required label-size="28" />
<view class="fui-clause--cell"> <fui-input label="企业编码" placeholder="统一社会信用代码" v-model="form.enterpriseCode" required label-size="28" />
<fui-checkbox :scaleRatio="0.8" @change="agreeChange" /> <fui-input label="经营业务" placeholder="如:种子、化肥、农机具销售等" v-model="form.businessScope" required label-size="28" />
<text class="fui-clause--text">我已阅读并同意</text>
<view class="textarea-box">
<view class="textarea-label"><text class="red">*</text>企业简介</view>
<fui-textarea placeholder="请简要介绍您的经营规模、品牌优势等..." v-model="form.profile" height="180rpx" size="28" background="#f9f9f9" :padding="['20rpx', '24rpx']" radius="12" />
</view> </view>
</fui-label>
<fui-text class="fui-color__link">《入驻协议》</fui-text> <view class="upload-field">
<text></text> <view class="upload-label"><text class="red">*</text>企业Logo</view>
<fui-text class="fui-color__link">《服务条款》</fui-text> <view class="upload-box">
<uni-file-picker :value="pageData.enterpriseLogoArr" limit="1" @select="handleUpload($event, 'logo')" @delete="handleDelete($event, 'logo')" />
<text class="upload-tips">建议尺寸 200x200,支持 jpg/png</text>
</view> </view>
<view class="fui-btn__box" v-if="!form.id" style="margin-top: 30rpx">
<fui-button text="提交申请" bold radius="96rpx" @click="submit" />
</view> </view>
</fui-form>
</view> </view>
</view> </view>
<!-- <fui-date-picker :show="show.time1" type="3" @change="handleChangeTime1" :min-date="getCurrentDate()" @cancel="show.time1 = false" /> <!-- 经营地点与联系人 -->
<fui-date-picker :show="show.time2" type="3" @change="handleChangeTime2" :min-date="getCurrentDate()" @cancel="show.time2 = false" /> <view class="section-card">
<fui-picker :show="show.type" :layer="1" :linkage="true" :options="options.type" @change="handleChangetype" @cancel="show.type = false" /> <view class="section-header">
<fui-picker :show="show.urgentdegree" :layer="1" :linkage="true" :options="options.urgentdegree" @change="handleChangeUrgentdegree" @cancel="show.urgentdegree = false"/> <view class="header-line"></view>
--><!-- <fui-picker :show="show.area" :options="options.area" :linkage="true" :layer="3" @change="handleChangeAddress" @cancel="show.area = false" /> --> <text class="section-title">经营联系信息</text>
<!-- 地区的选择 --> </view>
<fui-bottom-popup :show="show.area"> <view class="form-group">
<view class="fui-scroll__wrap"> <fui-input label="所属地区" placeholder="点击选择省/市/区" v-model="form.areaText" @click="show.area = true" disabled required label-size="28" />
<view class="fui-title">请选择</view> <fui-input label="详细地址" placeholder="街道、门牌号等" v-model="form.addr" required label-size="28" />
<fui-cascader <fui-input label="联系人" placeholder="请输入负责人姓名" v-model="form.contactPerson" required label-size="28" />
ref="areaPopupRef" <fui-input label="联系电话" type="tel" placeholder="请输入手机号码" v-model="form.contactMobile" required label-size="28" />
:value="pageData.options.areaVal" </view>
stepLoading </view>
@change="changeArea"
@complete="selectCompleteArea" <!-- 资质证照 -->
:options="pageData.options.area" <view class="section-card">
/> <view class="section-header">
<view class="fui-icon__close" @tap.stop="pageData.show.area = false"> <view class="header-line"></view>
<fui-icon name="close" :size="48" /> <text class="section-title">资质证照上传</text>
<text class="section-sub">(必填)</text>
</view>
<view class="upload-field-v">
<view class="cert-tips">请上传清晰的营业执照、经营许可证或品牌授权书</view>
<view class="mt-20">
<uni-file-picker limit="9" :value="pageData.cersImageArr" @select="handleUpload($event, 'cers')" @delete="handleDelete($event, 'cers')" />
</view>
</view>
</view>
<!-- 协议勾选 -->
<view class="agreement-section">
<fui-checkbox-group @change="e => form.agree = e.detail.value.length > 0">
<fui-label class="agree-label">
<view class="agree-inner">
<fui-checkbox :value="true" :scaleRatio="0.7" color="#5db66f" />
<view class="agree-text-content">
<text class="agree-text">我已认真阅读并同意遵守</text>
<text class="link-text" @click.stop="handleViewProtocol">《农贸入驻服务协议》</text>
</view>
</view> </view>
</fui-label>
</fui-checkbox-group>
</view> </view>
</fui-bottom-popup>
<!-- 提交区域 -->
<view class="submit-area">
<fui-button text="确认并提交申请" radius="100rpx" @click="submit" :loading="pageData.loading" background="#5db66f" border-color="#5db66f" />
<view class="secure-tips">
<fui-icon name="safe" :size="24" color="#ccc"></fui-icon>
<text>您的信息将被严格保密,仅用于入驻审核</text>
</view>
</view>
</fui-form>
</view>
<!-- 地区选择 -->
<AreaPicker v-model:show="show.area" :layer="4" @confirm="handleAreaConfirm" />
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" /> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body {
background-color: #e6f5e8;
}
.page { .page {
background-color: #e6f5e8; min-height: 100vh;
width: 750rpx; background-color: #f4f7f5;
overflow-x: hidden; padding-bottom: 80rpx;
.mt20 { font-family: 'DingTalk Sans', system-ui, -apple-system, sans-serif;
margin-top: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.formBox {
width: 690rpx;
margin: 30rpx auto;
}
.form-section {
// border-bottom: 1rpx solid #f5f5f5;
} }
.form-item { .header-banner {
padding: 30rpx 0; background-color: #5db66f;
border-bottom: 1rpx solid #f5f5f5; padding: 60rpx 40rpx 100rpx;
color: #fff;
&:last-child { .banner-title {
border-bottom: none; font-size: 48rpx;
font-weight: bold;
letter-spacing: 2rpx;
} }
.banner-subtitle {
&.required .label::before { font-size: 26rpx;
content: '*'; opacity: 0.9;
color: #ff4d4f; margin-top: 12rpx;
margin-right: 8rpx;
} }
} }
.form-row { .step-guide {
display: flex; display: flex;
justify-content: space-between; align-items: center;
} justify-content: center;
padding: 40rpx 30rpx;
.half-width { background-color: #fff;
width: 48%; margin: -40rpx 24rpx 0;
} border-radius: 20rpx;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.05);
position: relative;
z-index: 10;
.align-center { .step-item {
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
.step-num {
width: 48rpx;
height: 48rpx;
background-color: #f0f0f0;
color: #999;
border-radius: 50%;
display: flex;
align-items: center; align-items: center;
justify-content: center;
font-size: 26rpx;
font-weight: bold;
margin-bottom: 12rpx;
} }
.step-text { font-size: 22rpx; color: #999; }
.label { &.active {
display: block; .step-num { background-color: #5db66f; color: #fff; }
font-size: 28rpx; .step-text { color: #333; font-weight: bold; }
color: #333333;
font-weight: 500;
width: 180rpx;
} }
}
.input { .step-line {
width: 100%; width: 60rpx;
height: 80rpx; height: 2rpx;
background: #f8f9fa; background-color: #eee;
border-radius: 8rpx; margin: 0 16rpx;
padding: 0 20rpx; margin-top: -36rpx;
font-size: 28rpx; &.active { background-color: #5db66f; }
color: #333333;
&::placeholder {
color: #999999;
} }
} }
.time-range { .form-container {
display: flex; padding: 24rpx;
align-items: center; margin-top: 10rpx;
justify-content: space-between;
} }
.time-input { .section-card {
width: 45%; background-color: #fff;
border-radius: 8rpx; border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.02);
.section-header {
margin-bottom: 32rpx;
display: flex; display: flex;
align-items: center; align-items: center;
.header-line {
width: 8rpx;
height: 32rpx;
background-color: #5db66f;
border-radius: 4rpx;
margin-right: 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.section-sub {
font-size: 24rpx;
color: #ff4d4f;
margin-left: 8rpx;
} }
.time-text {
font-size: 28rpx;
color: #333333;
&.placeholder {
color: #999999;
} }
} }
.time-separator { .textarea-box {
color: #666666; padding: 24rpx 0;
.textarea-label {
font-size: 28rpx; font-size: 28rpx;
margin: 0 10rpx; color: #333;
margin-bottom: 20rpx;
.red { color: #ff4d4f; margin-right: 4rpx; }
} }
.upload-area {
margin-top: 10rpx;
} }
.custom-uploader { .upload-field {
:deep(.uni-file-picker__container) { padding: 20rpx 0;
border: 2rpx dashed #d9d9d9; .upload-label {
border-radius: 8rpx; font-size: 28rpx;
background: #f8f9fa; color: #333;
} margin-bottom: 24rpx;
.red { color: #ff4d4f; margin-right: 4rpx; }
} }
.upload-box {
.upload-placeholder {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; .upload-tips {
justify-content: center; font-size: 22rpx;
height: 200rpx; color: #ccc;
color: #999999; margin-top: 12rpx;
}
} }
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
} }
.upload-text { .upload-field-v {
.cert-tips {
font-size: 24rpx; font-size: 24rpx;
color: #999;
background-color: #f9f9f9;
padding: 16rpx 20rpx;
border-radius: 8rpx;
line-height: 1.5;
} }
.mt-20 { margin-top: 24rpx; }
.submit-section {
background: transparent;
padding: 40rpx 0;
} }
.submit-btn { .agreement-section {
width: 100%; display: flex;
height: 88rpx; justify-content: center;
background: #ff9800; padding: 40rpx 0 20rpx;
border-radius: 44rpx; .agree-label {
color: #ffffff; display: block;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #e68900;
opacity: 0.9;
}
} }
.agree-inner {
display: flex;
align-items: center;
} }
::v-deep .uni-input-placeholder { .agree-text-content {
font-size: 28rpx !important; display: flex;
color: #999999 !important; align-items: center;
flex-wrap: wrap;
margin-left: 12rpx;
} }
.agree-text {
// 移除fui-form的默认样式 font-size: 24rpx;
:deep(.fui-form) { color: #999;
background: transparent;
} }
.link-text {
:deep(.fui-form__item) { font-size: 24rpx;
background: transparent; color: #5db66f;
border: none; font-weight: 500;
margin-bottom: 0;
padding: 0;
} }
.unit-slot {
padding: 0 16rpx;
color: #333;
font-size: 28rpx;
} }
.fui-clause--cell { .submit-area {
margin-top: 20rpx;
padding: 0 20rpx;
.secure-tips {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 24rpx; margin-top: 24rpx;
color: #999999; text {
} font-size: 22rpx;
.fui-clause--wrap { color: #ccc;
width: 100%; margin-left: 8rpx;
margin-top: 64rpx;
} }
.fui-clause--text {
padding-left: 16rpx;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
} }
.fui-color__link {
color: #5fb771;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
} }
.fui-color__link:active { .popup-content {
opacity: 0.5; background-color: #fff;
padding: 30rpx;
border-radius: 32rpx 32rpx 0 0;
.popup-title {
font-size: 32rpx;
font-weight: bold;
text-align: center;
margin-bottom: 40rpx;
color: #333;
} }
.fui-scroll__wrap {
padding-top: 30rpx;
position: relative;
} }
.fui-title { /* 覆盖FirstUI的部分样式以统一字体 */
font-size: 30rpx; :deep(.fui-input__label),
font-weight: bold; :deep(.fui-input__input),
text-align: center; :deep(.fui-textarea__textarea),
padding-bottom: 24rpx; :deep(.fui-textarea__label) {
font-family: 'DingTalk Sans', system-ui !important;
color: #333 !important;
}
:deep(.uni-input-placeholder),
:deep(.uni-textarea-placeholder) {
font-family: 'DingTalk Sans' !important;
font-size: 28rpx !important;
} }
.fui-icon__close { :deep(.fui-button) {
position: absolute; font-family: 'DingTalk Sans' !important;
top: 24rpx; font-weight: bold !important;
right: 24rpx;
} }
</style> </style>
<!-- 农场详情 --> <!-- 灵活用工地图模式 -->
<script setup lang="ts"> <script setup lang="ts">
import * as turf from '@turf/turf' import * as turf from '@turf/turf'
import RegisterDialog from './register-dialog.vue' import RegisterDialog from './register-dialog.vue'
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
import { flyTo, useMapbox } from '@/components/Map/Mapbox/hook' import { flyTo, useMapbox } from '@/components/Map/Mapbox/hook'
import { getText } from '@/utils/dict/area' import { getText } from '@/utils/dict/area'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong' import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import { reactive, ref, onMounted, toRefs } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
// 页面参数 // 页面参数
const page = reactive<Page>({ const page = reactive<Page>({
...@@ -20,20 +22,12 @@ ...@@ -20,20 +22,12 @@
const model = reactive({ const model = reactive({
id: '', id: '',
name: '', name: '',
lonlat: `${uni.getStorageSync('location').lon},${uni.getStorageSync('location').lat}`, lonlat: `${uni.getStorageSync('location')?.lon || ''},${uni.getStorageSync('location')?.lat || ''}`,
address: '',
description: '',
// 地块、位置信息 GeoJSON
plots: [],
// 设备信息
devices: [],
employmentId: null, employmentId: null,
showConfirmDialog: false, showConfirmDialog: false,
contactName: '', contactName: '',
contactMobile: '', contactMobile: '',
// 分类标签
categoryTabs: [ categoryTabs: [
{ id: null, name: '全部' }, { id: null, name: '全部' },
{ id: 1, name: '种植' }, { id: 1, name: '种植' },
...@@ -43,7 +37,7 @@ ...@@ -43,7 +37,7 @@
], ],
search: { search: {
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 200, // 地图模式加载更多点位以展现全局视野
publishstatu: 1, publishstatu: 1,
type: null, type: null,
createBy: '', createBy: '',
...@@ -51,56 +45,56 @@ ...@@ -51,56 +45,56 @@
employmentList: [], employmentList: [],
loading: false, loading: false,
total: 0, total: 0,
requestDebounce: null,
// 用户当前位置
userLocation: { userLocation: {
longitude: uni.getStorageSync('location').lon, longitude: uni.getStorageSync('location')?.lon || 112.982273,
latitude: uni.getStorageSync('location').lat, latitude: uni.getStorageSync('location')?.lat || 28.19409,
}, },
// 选中的用工信息
selectedEmployment: null,
showEmploymentPopup: false,
searchText: '', searchText: '',
// 标记点击事件是否已绑定
clickEventBound: false,
// 用于区分是哪个模块进的
currentEmploymentId: 1, currentEmploymentId: 1,
markerIds: [], // 用于跟踪当前地图上的 Marker
}) })
onLoad((options) => { onLoad((options) => {
model.id = options.id model.id = options.id
model.currentEmploymentId = options.currentEmploymentId // 确保从 URL 参数中获取正确的业务 Tab ID
model.name = decodeURIComponent(options.name) if (options.currentEmploymentId) {
model.search.pageNo = 1 model.currentEmploymentId = Number(options.currentEmploymentId)
model.search.publishstatu = 1 }
model.search.createBy = '' model.name = decodeURIComponent(options.name || '')
model.employmentList = []
// 尝试获取精准位置
uni.getLocation({
type: 'wgs84',
success: (res) => {
model.userLocation.longitude = res.longitude
model.userLocation.latitude = res.latitude
// 定位成功后,如果地图已加载,渲染用户图标
if (map) renderUserLocation()
}
})
getEmploymentList() getEmploymentList()
}) })
// 地图组件 // 地图组件
const center: [number, number] = [uni.getStorageSync('location').lon, uni.getStorageSync('location').lat] const loc = uni.getStorageSync('location')
const center: [number, number] = [loc?.lon || 112.982273, loc?.lat || 28.19409]
const registerDialogRef = ref() const registerDialogRef = ref()
let mapReady = false
const [registerMap, map] = useMapbox({ const [registerMap, map] = useMapbox({
style: { center, zoom: 10 }, style: { center, zoom: 11 },
onLoaded: (data) => { onLoaded: () => {
// 渲染用户当前位置 mapReady = true
renderUserLocation() renderUserLocation()
}, // 地图就绪后,如果数据已先到达,立即渲染
onSourceRequestHandle: () => { if (model.employmentList.length) {
page.requests-- renderEmploymentMarkers()
if (page.requests === 0) {
Message.hideLoading()
} }
}, },
onSourceRequestErrorHandle: () => {
Message.hideLoading()
},
onMapEvent: (event) => { onMapEvent: (event) => {
// 处理来自 renderjs 层的自定义事件
if (event.type === 'custom' && event.name === 'navigateToDetail') { if (event.type === 'custom' && event.name === 'navigateToDetail') {
console.log('接收到导航事件,id:', event)
// Navigate.to(`/pages/linghuoyonggong/detail/index?id=${event.data}`)
if (model.currentEmploymentId == 2) { if (model.currentEmploymentId == 2) {
model.employmentId = event.data model.employmentId = event.data
getLaborAppDetail() getLaborAppDetail()
...@@ -110,617 +104,380 @@ ...@@ -110,617 +104,380 @@
} }
}, },
}) })
async function getLaborAppDetail() { async function getLaborAppDetail() {
await LinghuoyonggongAPI.getLaborAppDetail({ id: model.employmentId }).then((res) => { const res = await LinghuoyonggongAPI.getLaborAppDetail({ id: model.employmentId })
model.contactMobile = res.contactMobile model.contactMobile = res.contactMobile
model.contactName = model.contactName = res.contactName ? (res.contactName.substring(0, 1) + '**') : '负责人'
res.contactName.substring(0, 1) + Array.from({ length: res.contactName.length }).join('*')
model.showConfirmDialog = true model.showConfirmDialog = true
})
} }
function makePhoneCall() { function makePhoneCall() {
uni.makePhoneCall({ uni.makePhoneCall({ phoneNumber: model.contactMobile })
phoneNumber: model.contactMobile,
})
} }
// 创建弹框HTML内容 // 创建更美观的弹框HTML
function createPopupHTML(employment) { function createPopupHTML(item) {
if (!employment) if (!item) return ''
return '' const isLabor = model.currentEmploymentId == 2
const title = isLabor ? item.villageName : item.name
const subTitle = isLabor ? item.villageFullName : item.area
const distance = isLabor ? getDistanceText2(item) : getDistanceText(item)
const btnText = isLabor ? '拨号联系' : '我也去'
const stats = isLabor ? `待工人数 ${item.workers} ` : `💰 ${item.price}/ · 👥 ${item.workers}`
const image = isLabor ? './static/images/linghuoyonggong/avatar.png' : item.picture
if (model.currentEmploymentId == 2) {
return `
<div style="width: 220px;background:#FFFFFF;padding: 12px;display: flex;flex-direction: column;">
<div style="display: flex;justify-content: space-between;line-height: 20px;">
<div style="font-size: 16px;color: #333333;">${employment.villageName}</div>
<div style="color: #5DB66F;font-size: 12px;display: flex;align-items: center;">
<image style="width:11px;height:13px;margin-right:7px;" src="./static/images/linghuoyonggong/distance.png" />
<div>${getDistanceText2(employment)}</div>
</div>
</div>
<div style="color: #5DB66F;margin-top: 6px;line-height: 18px;font-size: 12px;display: flex;align-items: center;">
<image style="width:12px;height:12px;margin-right:7px;" src="./static/images/linghuoyonggong/avatar.png" />
<div>待工人数${employment.workers}名</div>
</div>
<div style="color: #999999;margin-top: 4px;line-height: 18px;font-size: 12px;display: flex;align-items: center;">
<image style="width:11px;height:13px;margin-right:7px;" src="./static/images/linghuoyonggong/skill.png" />
<div>技能:${employment.skills.length ? employment.skills.join('、') : '未填写'}</div>
</div>
<div style="color: #999999;margin-top: 4px;line-height: 18px;font-size: 12px;display: flex;align-items: center;">
<image style="width:11px;height:13px;margin-right:7px;" src="./static/images/linghuoyonggong/address.png" />
<div>地址:${employment.villageFullName}</div>
</div>
<a href="#" onclick="window.navigateToDetail('${employment.id}'); return false;" style="margin-top:8px;background: #5DB66F;text-decoration: none;border-radius: 200px;padding: 10px;text-align: center;color: #FFFFFF;font-size: 12px;display: flex;justify-content: center;align-items: center;">电话沟通</a>
</div>
`
} else {
const distance = getDistanceText(employment)
const stars = getStarsHTML(employment)
return ` return `
<div style=" <div class="map-popup-card">
max-width: 280px; <div class="popup-header">
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; <div class="popup-title-row">
"> <span class="p-title">${title}</span>
<div style="display: flex; gap: 6px; margin-bottom: 12px;"> <span class="p-dist">📍 ${distance}</span>
<!-- 左侧图标 -->
<div style="
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
">
<img src="${employment.picture}" style="width: 48px; height: 48px; border-radius: 50%;" />
</div>
<!-- 右侧内容 -->
<div style="flex: 1; min-width: 0;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
<div style="
font-size: 16px;
font-weight: 600;
color: #333333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
">${employment.name}</div>
<div style="
background: #E8F5E9;
color: #4CAF50;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
white-space: nowrap;
margin-left: 8px;
">📍 ${distance}</div>
</div>
<div style="display: flex; gap: 12px; font-size: 13px; color: #666666; margin-bottom: 8px;">
<span>⏱ 时长${
employment.daysDiff || getDaysDiff(employment.starttime, employment.estimatedendtime)
}天</span>
<span>👥 需${employment.workers || 0}人</span>
<span>💰 ${employment.price || 0}元/天</span>
</div>
</div> </div>
<div class="p-subtitle">${subTitle}</div>
</div> </div>
<div class="popup-body">
<div style="display: flex; align-items: center; justify-content: space-between;"> <div class="p-stats">${stats}</div>
<div style="display: flex; align-items: center; gap: 4px;"> ${!isLabor && item.productSpecs ? `<div class="p-specs">${item.productSpecs}</div>` : ''}
${stars.html}
<span style="font-size: 13px; color: #666666; margin-left: 4px;">${stars.rating}</span>
</div> </div>
<a href="#" onclick="window.navigateToDetail('${employment.id}'); return false;" style=" <div class="popup-footer" onclick="window.navigateToDetail('${item.id}')">
display: inline-block; ${btnText}
padding: 6px 20px;
background: linear-gradient(135deg, #5DB66F 0%, #4CAF50 100%);
color: white;
text-decoration: none;
border-radius: 16px;
font-size: 14px;
font-weight: 500;
outline: none;
border: none;
transition: opacity 0.2s;">
我想去
</a>
</div> </div>
</div> </div>
` `
} }
}
// 渲染用户位置
function renderUserLocation() { function renderUserLocation() {
const markerId = `my-local` const imageUrl = './static/images/local-map.png'
// 修复 App 端图片路径问题 map.addMarker('user-local', [model.userLocation.longitude, model.userLocation.latitude], '', false, imageUrl, [40, 40])
const imageUrl = '/static/images/local-map.png'
let finalImageUrl = imageUrl
// #ifdef APP-PLUS
// 在 App 端使用绝对路径
finalImageUrl = `./static/images/local-map.png`
// #endif
const iconSize: [number, number] = [40, 40] // 图标大小
// 使用 addMarker 添加到地图,带上 popup 和自定义图标
map.addMarker(
markerId,
[model.userLocation.longitude, model.userLocation.latitude],
'',
false, // 不默认打开
finalImageUrl, // 自定义图标图片URL
iconSize, // 图标大小
)
} }
// 渲染用工标记
function renderEmploymentMarkers() { function renderEmploymentMarkers() {
if (!map) { if (!map) return
console.warn('地图未初始化')
return
}
console.log('渲染用工标记数量:', model.employmentList.length) // 1. 先清空旧的 Marker
console.log('用工数据:', model.employmentList) clearMarkers()
let longitude = null // 2. 渲染新的 Marker
let latitude = null const points = []
// 使用 Marker 方式渲染带自定义图标的标记
model.employmentList.forEach((item) => { model.employmentList.forEach((item) => {
if (model.currentEmploymentId == 2) { const lon = model.currentEmploymentId == 2 ? item.lon : item.longitude
longitude = item.lon const lat = model.currentEmploymentId == 2 ? item.lat : item.latitude
latitude = item.lat if (lon && lat) {
} else { points.push([Number(lon), Number(lat)])
longitude = item.longitude let imageUrl = item.picture || './static/images/linghuoyonggong/default-icon.png'
latitude = item.latitude // #ifdef APP-PLUS
if (imageUrl.startsWith('/static/')) imageUrl = `.${imageUrl}`
// #endif
const markerId = `emp-${item.id}`
map.addMarker(markerId, [Number(lon), Number(lat)], createPopupHTML(item), false, imageUrl, [42, 42])
model.markerIds.push(markerId)
} }
if (longitude && latitude) { })
const markerId = `employment-marker-${item.id}`
let imageUrl = item.picture || '/static/images/linghuoyonggong/default-icon.png'
// let imageUrl = '/static/images/linghuoyonggong/default-icon.png'
const iconSize: [number, number] = [40, 40] // 图标大小
// #ifdef APP-PLUS // 3. 自动调整地图视角
// 处理 App 端图片路径 fitMapToMarkers(points)
if (imageUrl.startsWith('/static/')) {
imageUrl = `.${imageUrl}`
} }
// #endif
// 使用 addMarker 添加到地图,带上 popup 和自定义图标 function clearMarkers() {
map.addMarker( if (!map || !model.markerIds.length) return
markerId, model.markerIds.forEach(id => map.removeMarker(id))
[longitude, latitude], model.markerIds = []
createPopupHTML(item), }
false, // 不默认打开
imageUrl, // 自定义图标图片URL function fitMapToMarkers(points) {
iconSize, // 图标大小 if (!map) return
)
if (points.length === 0) {
// 如果没有点位,平滑回到用户当前位置
backToUserLocation()
return
} }
})
// 绑定点击事件标记 if (points.length === 1) {
if (!model.clickEventBound) { map.flyTo({ center: points[0], zoom: 14, speed: 0.8 })
console.log('标记点击事件已绑定') } else {
model.clickEventBound = true const features = points.map(p => turf.point(p))
const collection = turf.featureCollection(features)
const bbox = turf.bbox(collection)
// 自动缩放视角以包含所有点位
map.fitBounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]], {
padding: 120, // 增加边距,确保边缘点位不被遮挡
maxZoom: 16,
duration: 1200
})
} }
} }
// 分类标签点击事件
function onCategoryTabClick(tab: any) { function onCategoryTabClick(tab: any) {
model.search.type = tab.id model.search.type = tab.id
model.search.pageNo = 1 model.search.pageNo = 1
model.employmentList = [] model.employmentList = []
// 切换类型时立即清空地图上的点位,提供即时反馈
clearMarkers()
getEmploymentList() getEmploymentList()
// 在这里添加具体的分类标签点击逻辑
} }
async function getEmploymentList() { function onSearch() {
// 如果正在加载或没有更多数据,直接返回 if (model.currentEmploymentId == 1) {
if (model.loading || (model.total > 0 && model.employmentList.length >= model.total)) { model.search.createBy = model.searchText
return } else {
model.search.keyword = model.searchText
} }
model.loading = true model.search.pageNo = 1
try { model.employmentList = []
if (model.currentEmploymentId == 2) { getEmploymentList()
await LinghuoyonggongAPI.getLaborAppList(model.search).then(async (res) => {
const { records, total } = res
// 批量处理数据,避免多次DOM操作
const processedRecords = records.map((item) => {
// 缓存区域处理结果
item.area = getText(item.area, ' / ')
// 计算天数并缓存结果
if (item.starttime && item.estimatedendtime) {
item.daysDiff = getDaysDiff(item.starttime, item.estimatedendtime)
} }
return item function onListModeClick() {
}) uni.navigateBack()
// 一次性更新数据,避免多次响应式更新
if (model.search.pageNo === 1) {
model.employmentList = processedRecords
} else {
// 避免重复数据加载
const existingIds = new Set(model.employmentList.map((item) => item.id))
const newRecords = processedRecords.filter((item) => !existingIds.has(item.id))
model.employmentList = [...model.employmentList, ...newRecords]
} }
model.total = total async function getEmploymentList() {
// 数据加载完成后渲染地图标记 model.loading = true
setTimeout(() => { try {
renderEmploymentMarkers() const api = model.currentEmploymentId == 2 ? LinghuoyonggongAPI.getLaborAppList : LinghuoyonggongAPI.employmentList
}, 600) const res = await api(model.search)
/* nextTick(() => {
renderEmploymentMarkers()
}) */
})
} else {
await LinghuoyonggongAPI.employmentList(model.search).then(async (res) => {
const { records, total } = res const { records, total } = res
// 批量处理数据,避免多次DOM操作 model.employmentList = records.map(item => {
const processedRecords = records.map((item) => { item.area = getText(item.area || item.scope, ' / ')
// 缓存区域处理结果
item.area = getText(item.area, ' / ')
// 计算天数并缓存结果
if (item.starttime && item.estimatedendtime) {
item.daysDiff = getDaysDiff(item.starttime, item.estimatedendtime)
}
return item return item
}) })
// 一次性更新数据,避免多次响应式更新
if (model.search.pageNo === 1) {
model.employmentList = processedRecords
} else {
// 避免重复数据加载
const existingIds = new Set(model.employmentList.map((item) => item.id))
const newRecords = processedRecords.filter((item) => !existingIds.has(item.id))
model.employmentList = [...model.employmentList, ...newRecords]
}
model.total = total model.total = total
// 数据到达后:如果地图已就绪则立即渲染,否则等 onLoaded 回调时再渲染
// 数据加载完成后渲染地图标记 if (mapReady) {
setTimeout(() => {
renderEmploymentMarkers()
}, 600)
/* nextTick(() => {
renderEmploymentMarkers() renderEmploymentMarkers()
}) */
})
} }
} catch (error) {
console.error('获取用工列表失败:', error)
// 这里可以添加用户友好的错误提示
// uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' })
} finally { } finally {
model.loading = false model.loading = false
model.requestDebounce = null
}
}
// 获取时间差
function getDaysDiff(date1: Date | number | string, date2: Date | number | string): number {
// 将输入转换为Date对象
const d1 = new Date(date1)
const d2 = new Date(date2)
// 检查日期是否有效
if (Number.isNaN(d1.getTime()) || Number.isNaN(d2.getTime())) {
throw new TypeError('无效的日期格式')
} }
// 设置时间部分为00:00:00,只比较日期部分
d1.setHours(0, 0, 0, 0)
d2.setHours(0, 0, 0, 0)
// 计算两个日期之间的毫秒差
const diffTime = Math.abs(d2.getTime() - d1.getTime())
// 转换为天数
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays
} }
// 回到当前位置
function backToUserLocation() { function backToUserLocation() {
if (map && model.userLocation.longitude && model.userLocation.latitude) { console.log('backToUserLocation clicked', model.userLocation)
flyTo(map, [model.userLocation.longitude, model.userLocation.latitude], 15, 0.5) if (!model.userLocation.longitude) {
uni.showToast({ uni.showToast({ title: '尚未获取到定位信息', icon: 'none' })
title: '已回到当前位置', return
icon: 'none',
duration: 1500,
})
} else {
// 重新获取用户位置
uni.getLocation({
type: 'wgs84',
success: (res) => {
if (res.longitude !== 0 && res.latitude !== 0) {
model.userLocation.longitude = res.longitude
model.userLocation.latitude = res.latitude
// 更新缓存位置
uni.setStorageSync('location', {
lon: res.longitude,
lat: res.latitude,
})
// 飞到用户位置
flyTo(map, [res.longitude, res.latitude], 15, 0.5)
uni.showToast({
title: '已获取并回到当前位置',
icon: 'none',
duration: 1500,
})
} }
}, if (map) {
fail: (err) => { // 直接调用包装好的 flyTo 方法,确保参数正确
console.error('获取位置失败:', err) map.flyTo({
uni.showToast({ center: [model.userLocation.longitude, model.userLocation.latitude],
title: '获取位置失败,请检查定位权限', zoom: 14,
icon: 'none', essential: true,
duration: 2000, speed: 0.8
})
},
}) })
} }
} }
// 关闭弹框
function closeEmploymentPopup() {
model.showEmploymentPopup = false
model.selectedEmployment = null
}
// 计算距离显示
function getDistanceText2(item) { function getDistanceText2(item) {
if (!item || !item.lon || !model.userLocation.longitude) { if (!item.lon || !model.userLocation.longitude) return '未知'
return '未知' const d = turf.distance(turf.point([model.userLocation.longitude, model.userLocation.latitude]), turf.point([item.lon, item.lat]), { units: 'kilometers' })
} return d < 1 ? `${Math.round(d * 1000)}m` : `${d.toFixed(1)}km`
// 使用turf计算距离
const from = turf.point([model.userLocation.longitude, model.userLocation.latitude])
const to = turf.point([item.lon, item.lat])
const distance = turf.distance(from, to, { units: 'kilometers' })
if (distance < 1) {
return `${Math.round(distance * 1000)}m`
}
return `${distance.toFixed(1)}km`
}
// 计算距离显示
function getDistanceText(employment) {
if (!employment || !employment.longitude || !employment.latitude) {
return '未知'
}
// 使用turf计算距离
const from = turf.point([model.userLocation.longitude, model.userLocation.latitude])
const to = turf.point([employment.longitude, employment.latitude])
const distance = turf.distance(from, to, { units: 'kilometers' })
if (distance < 1) {
return `${Math.round(distance * 1000)}m`
}
return `${distance.toFixed(1)}km`
} }
// 生成星级HTML function getDistanceText(item) {
function getStarsHTML(employment) { if (!item.longitude || !model.userLocation.longitude) return '未知'
// 获取紧急程度,默认为0 const d = turf.distance(turf.point([model.userLocation.longitude, model.userLocation.latitude]), turf.point([item.longitude, item.latitude]), { units: 'kilometers' })
const urgentdegree = Number(employment.urgentdegree) || 0 return d < 1 ? `${Math.round(d * 1000)}m` : `${d.toFixed(1)}km`
// 确保评级在0-5之间
const rating = Math.max(0, Math.min(5, urgentdegree))
let html = ''
for (let i = 1; i <= 5; i++) {
if (i <= rating) {
html += '<span style="color: #FF9800; font-size: 16px;">★</span>'
} else {
html += '<span style="color: #E0E0E0; font-size: 16px;">★</span>'
}
}
return {
html,
rating,
}
} }
// 发布用工
function handlePublish() {
if (model.currentEmploymentId == 2) {
Navigate.to('/pages/linghuoyonggong/publishEmployment')
} else {
Navigate.to('/pages/linghuoyonggong/form')
}
}
onNavigationBarButtonTap((e) => {
console.log(e)
if (e.index === 0) {
Navigate.to('/pages/linghuoyonggong/linghuoyonggong')
}
})
</script> </script>
<template> <template>
<view class="page h-95_dl_5vh bg-#E6F5E8 flex flex-col"> <view class="page map-mode">
<view class="p-3"> <!-- 顶部悬浮搜索与筛选 -->
<view class="w-full h-60rpx border-rd-3xl!"> <view class="floating-top-bar">
<fui-input <view class="search-inner">
:bottomLeft="0" <fui-icon name="search" color="#999" :size="32"></fui-icon>
class="w-full h-full border-rd-3xl!" <input class="search-input" v-model="model.searchText" placeholder="搜地点、技能..." placeholder-style="color:#ccc" @confirm="onSearch" />
borderTop
placeholder="请输入搜索内容"
placeholderStyle="color: #CCCCCC; font-weight: 400; font-family: PingFangSC-Regular;"
v-model="model.searchText"
>
<template #left>
<view class="fui-left__icon">
<fui-icon name="search" color="#CCCCCC" :size="36" />
</view>
</template>
</fui-input>
</view> </view>
<view v-if="model.currentEmploymentId != 2" class="codefun-mt-14 codefun-flex-row group_2 gap-2"> <scroll-view scroll-x class="tab-scroll" v-if="model.currentEmploymentId != 2">
<view class="tabs-row">
<view <view
v-for="tab in model.categoryTabs" v-for="tab in model.categoryTabs"
:key="tab.id" :key="tab.id"
class="codefun-flex-col codefun-justify-start codefun-items-center" class="tab-pill"
:class="[tab.id === model.search.type ? 'text-wrapper' : 'text-wrapper_2']" :class="{ active: tab.id === model.search.type }"
@click="onCategoryTabClick(tab)" @tap="onCategoryTabClick(tab)"
> >
<text class="font_2 text_2">
{{ tab.name }} {{ tab.name }}
</text>
</view> </view>
</view> </view>
</scroll-view>
</view> </view>
<!-- 地图组件 -->
<!-- <view class="h-730 overflow-hidden map-box"> --> <!-- 地图容器 -->
<view class="overflow-hidden map-box" style="height: calc(100vh - 200rpx)"> <view class="map-container">
<!-- 地图组件 -->
<Mapbox @register="registerMap" /> <Mapbox @register="registerMap" />
<!-- 回到当前位置按钮 -->
<view class="location-control" @click="backToUserLocation"> <!-- 回到当前位置 -->
<view class="location-button"> <view class="map-ctrl location" @click="backToUserLocation">
<image src="/static/images/toLocal.png" style="width: 58rpx; height: 58rpx" /> <fui-icon name="location" color="#5db66f" :size="44"></fui-icon>
<!-- <fui-icon name="location" color="#5DB66F" :size="36" /> -->
</view>
</view> </view>
</view> </view>
<fui-fab position="right" distance="30" bottom="240" width="96" @click="handlePublish">
<view class="text-white text-center"> <!-- 模式切换胶囊 (沉浸式风格) -->
<view class="fab-icon" /> <view class="view-toggle-pill" @tap="onListModeClick">
<view style="font-size: 24rpx">发布</view> <fui-icon name="list" :size="32" color="#5db66f"></fui-icon>
<text class="toggle-text">返回列表</text>
</view> </view>
</fui-fab>
<RegisterDialog ref="registerDialogRef" /> <RegisterDialog ref="registerDialogRef" />
<!-- 确认对话框 -->
<ConfirmDialog <ConfirmDialog
v-model:show="model.showConfirmDialog" v-model:show="model.showConfirmDialog"
title="温馨提示" title="拨号确认"
:content="`你将与${model.contactName}进行电话沟通,若继续,请点击确认按钮!`" :content="`确认拨打负责人【${model.contactName}】的电话吗?`"
cancelText="取消"
confirmText="确认"
@confirm="makePhoneCall" @confirm="makePhoneCall"
/> />
</view> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
// .page.map-mode {
.page { height: 100vh;
min-height: 100vh; background-color: #f0f4f2;
background: #e6f5e8; font-family: 'DingTalk Sans', sans-serif;
.map-box {
// #ifdef APP-PLUS
position: relative; position: relative;
top: 0; overflow: hidden;
// #endif
// #ifdef H5
position: relative;
top: 0;
// #endif
width: 690rpx;
left: 30rpx;
border-radius: 20rpx;
background: #e6f5e8;
} }
.group_2 { .floating-top-bar {
.text-wrapper { position: absolute;
flex: 1 1 120rpx; top: calc(env(safe-area-inset-top) + 20rpx);
background-color: #5db66f; left: 30rpx;
border-radius: 19998rpx; right: 30rpx;
line-height: 60rpx; z-index: 100;
height: 60rpx;
.search-inner {
.text_2 { background-color: rgba(255, 255, 255, 0.95);
color: #ffffff; backdrop-filter: blur(10px);
height: 88rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
padding: 0 32rpx;
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.08);
.search-input {
flex: 1;
margin-left: 16rpx;
font-size: 28rpx;
color: #333;
} }
} }
.text-wrapper_2 { .tab-scroll {
flex: 1 1 120rpx; margin-top: 20rpx;
line-height: 60rpx; width: 100%;
background-color: #ffffff; .tabs-row {
border-radius: 19998rpx; display: flex;
height: 60rpx; padding-bottom: 10rpx;
.tab-pill {
.text_3 { flex-shrink: 0;
line-height: 25.88rpx; padding: 10rpx 32rpx;
background-color: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(8px);
border-radius: 30rpx;
font-size: 24rpx;
color: #666;
margin-right: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
&.active {
background-color: #5db66f;
color: #fff;
font-weight: bold;
} }
.text_4 {
line-height: 26.16rpx;
} }
.text_5 {
line-height: 25.96rpx;
} }
.text_6 {
line-height: 25.68rpx;
} }
} }
.map-container {
width: 100%;
height: 100vh;
position: relative;
} }
// 回到当前位置控制按钮样式 .map-ctrl {
.location-control {
position: absolute; position: absolute;
bottom: 100rpx; right: 30rpx;
right: 10rpx; bottom: 200rpx;
z-index: 1000; width: 88rpx;
height: 88rpx;
.location-button { background-color: #fff;
border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
// width: 80rpx; box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.12);
// height: 80rpx; z-index: 200;
background: rgba(255, 255, 255, 0.95); &:active { transform: scale(0.92); }
border-radius: 50%; }
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
backdrop-filter: blur(10rpx); .view-toggle-pill {
// border: 2rpx solid #5db66f; position: fixed;
transition: all 0.3s ease; left: 50%;
transform: translateX(-50%);
&:active { bottom: 60rpx;
transform: scale(0.95); background-color: rgba(255, 255, 255, 0.95);
background: linear-gradient(135deg, #5db66f 0%, #4caf50 100%); backdrop-filter: blur(12px);
padding: 20rpx 48rpx;
:deep(.fui-icon) { border-radius: 100rpx;
color: #ffffff !important; display: flex;
} align-items: center;
} box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
border: 1rpx solid rgba(255,255,255,0.5);
&:hover { z-index: 110;
transform: scale(1.05); transition: all 0.3s;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.2); &:active { transform: translateX(-50%) scale(0.95); }
} .toggle-text {
} font-size: 28rpx;
} color: #1a1a1a;
} font-weight: bold;
::v-deep .mapboxgl-popup { margin-left: 16rpx;
max-width: 568rpx !important; }
} }
::v-deep .fui-fab__btn-main {
background: linear-gradient(124.25deg, #a5d63f 0%, #5db66f 100%) !important; /* Mapbox 弹窗样式重写 */
box-shadow: 0px 1px 8px #5db66f; :deep(.mapboxgl-popup-content) {
padding: 0 !important;
border-radius: 24rpx !important;
overflow: hidden;
box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.15) !important;
}
:deep(.mapboxgl-popup-close-button) {
color: #999 !important;
font-size: 40rpx !important;
padding: 10rpx !important;
right: 10rpx !important;
top: 10rpx !important;
}
/* 自定义弹窗卡片 CSS */
:deep(.map-popup-card) {
width: 480rpx;
background: #fff;
.popup-header {
padding: 24rpx 24rpx 16rpx;
background: linear-gradient(to bottom, #f8fbf9, #fff);
.popup-title-row {
display: flex; justify-content: space-between; align-items: center;
.p-title { font-size: 32rpx; font-weight: bold; color: #1a1a1a; }
.p-dist { font-size: 22rpx; color: #5db66f; font-weight: bold; }
}
.p-subtitle { font-size: 22rpx; color: #94a3b8; margin-top: 8rpx; }
}
.popup-body {
padding: 0 24rpx 24rpx;
.p-stats { font-size: 24rpx; color: #475569; font-weight: 500; }
.p-specs { font-size: 22rpx; color: #94a3b8; margin-top: 8rpx; padding: 12rpx; background: #f1f5f9; border-radius: 8rpx; }
}
.popup-footer {
height: 88rpx;
background-color: #5db66f;
color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 28rpx; font-weight: bold;
&:active { opacity: 0.9; }
} }
::v-deep .mapboxgl-popup a {
outline: none !important;
-webkit-tap-highlight-color: transparent !important;
-webkit-touch-callout: none !important;
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { getCalculateAge } from '@/utils/date' import { getCalculateAge } from '@/utils/date'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong' import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
const pageData = reactive({ const pageData = reactive({
loading: false, loading: false,
villageName: '',
villageFullName: '',
workersParam: [], workersParam: [],
contactMobile: '', contactMobile: '',
contactName: '', contactName: '',
...@@ -19,47 +21,37 @@ ...@@ -19,47 +21,37 @@
}) })
onLoad((options) => { onLoad((options) => {
const param = JSON.parse(decodeURIComponent(options.param)) const param = JSON.parse(decodeURIComponent(options.param))
pageData.villageName = param.villageName
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: param.villageName, title: '劳动力池详情',
}) })
getLaborAppDetail(param.id) getLaborAppDetail(param.id)
}) })
async function getLaborAppDetail(id: string) { async function getLaborAppDetail(id: string) {
await LinghuoyonggongAPI.gitListByCodeDict({ code: 'sex' }).then((res) => { pageData.loading = true
DictData.sexArr = res try {
}) const sexRes = await LinghuoyonggongAPI.gitListByCodeDict({ code: 'sex' })
await LinghuoyonggongAPI.gitListByCodeDict({ code: 'education' }).then((res) => { DictData.sexArr = sexRes
DictData.educationArr = res
}) const eduRes = await LinghuoyonggongAPI.gitListByCodeDict({ code: 'education' })
await LinghuoyonggongAPI.getLaborAppDetail({ id }).then((res) => { DictData.educationArr = eduRes
pageData.workersParam = res.workers
pageData.contactMobile = res.contactMobile const detailRes = await LinghuoyonggongAPI.getLaborAppDetail({ id })
pageData.contactName = pageData.villageFullName = detailRes.villageFullName
res.contactName.substring(0, 1) + Array.from({ length: res.contactName.length }).join('*') pageData.workersParam = detailRes.workers || []
}) pageData.contactMobile = detailRes.contactMobile
pageData.contactName = detailRes.contactName ?
(detailRes.contactName.substring(0, 1) + '**') : '负责人'
} finally {
pageData.loading = false
}
} }
// 返回字典中的中文值 // 返回字典中的中文值
function returnDictZhVel(type: any, val: any) { function returnDictZhVel(type: any, val: any) {
let valText = '' let valText = ''
if (type == 'gender') { const arr = type === 'gender' ? DictData.sexArr : DictData.educationArr
const arr = DictData.sexArr const item = arr.find(i => Number(i.itemValue) === val)
for (let i = 0; i < arr.length; i++) { return item ? item.itemText : ''
if (val == Number.parseInt(arr[i].itemValue)) {
valText = arr[i].itemText
break
}
}
}
if (type == 'edu') {
const arr = DictData.educationArr
for (let i = 0; i < arr.length; i++) {
if (val == Number.parseInt(arr[i].itemValue)) {
valText = arr[i].itemText
break
}
}
}
return valText
} }
function makePhoneCall() { function makePhoneCall() {
uni.makePhoneCall({ uni.makePhoneCall({
...@@ -70,89 +62,325 @@ ...@@ -70,89 +62,325 @@
<template> <template>
<view class="details_page"> <view class="details_page">
<view class="details-content"> <!-- 沉浸式头部 -->
<view v-if="!pageData.workersParam || pageData.workersParam.length == 0" style="height: 700rpx"> <view class="immersive-header">
<fui-empty marginTop="100" src="/static/images/no-data.png" title="暂无数据" /> <view class="header-bg"></view>
<view class="header-content">
<view class="village-info">
<text class="village-label">所在位置:{{ pageData.villageFullName || '正在加载...' }}</text>
<text class="village-name">{{ pageData.villageName }}</text>
</view>
<view class="stats-float-card">
<view class="stat-box">
<text class="stat-num">{{ pageData.workersParam.length }}</text>
<text class="stat-label">待工人员</text>
</view>
<view class="stat-divider"></view>
<view class="stat-box">
<text class="stat-num">认证</text>
<text class="stat-label">平台审核</text>
</view>
<view class="stat-divider"></view>
<view class="stat-box">
<text class="stat-num">高效</text>
<text class="stat-label">本地直连</text>
</view>
</view>
</view> </view>
<view class="yr-person-item" v-for="(item, index) in pageData.workersParam" :key="index">
<view class="yr-person-info">
<view class="person_name_attr"
>{{ item.name }}<text class="person_attr">{{ item.attr }}</text></view
>
<view class="person-info"
>{{ getCalculateAge(item.birthday) }}{{ returnDictZhVel('gender', item.gender) }}{{
returnDictZhVel('edu', item.edu)
}}</view
>
<view class="person-info text_overflow_ellipsis">技能:{{ item.skill }}</view>
</view> </view>
<view class="list-section">
<view class="section-header">
<text class="section-title">可调配工友名录</text>
<text class="section-subtitle">基于地理位置及技能匹配</text>
</view>
<view v-if="!pageData.workersParam.length && !pageData.loading" class="empty-state">
<fui-empty marginTop="60" src="/static/images/no-data.png" title="当前村落暂无登记工友" />
</view>
<!-- 优化后的人员列表卡片 -->
<view class="worker-card-v2" v-for="(item, index) in pageData.workersParam" :key="index">
<view class="card-top">
<view class="avatar-circle" :style="{ backgroundColor: ['#E6F5E8', '#E6F7FF', '#FFF7E6', '#FFF1F0'][index % 4] }">
<text class="avatar-text" :style="{ color: ['#5DB66F', '#1890FF', '#FA8C16', '#F5222D'][index % 4] }">
{{ item.name.substring(0, 1) }}
</text>
</view> </view>
<view class="worker-core">
<view class="name-line">
<text class="worker-name">{{ item.name }}</text>
<text class="tag-outline">{{ item.attr || '熟练工' }}</text>
</view> </view>
<view v-if="pageData.workersParam.length" class="make-phone-view"> <view class="info-line">
<fui-button text="电话沟通" bold radius="96rpx" @click="pageData.showConfirmDialog = true" height="80rpx" /> <text>{{ getCalculateAge(item.birthday) }}</text>
<view class="dot-sep"></view>
<text>{{ returnDictZhVel('gender', item.gender) }}</text>
<view class="dot-sep"></view>
<text>{{ returnDictZhVel('edu', item.edu) }}</text>
</view> </view>
<!-- 确认对话框 --> </view>
</view>
<view class="card-bottom">
<text class="skill-hint">擅长领域:</text>
<view class="skill-flow">
<text class="skill-chip" v-for="(skill, sIdx) in (item.skill ? item.skill.split(/[,,、/]/) : ['通用农活'])" :key="sIdx">
{{ skill }}
</text>
</view>
</view>
</view>
</view>
<!-- 悬浮固定底部栏 -->
<view v-if="pageData.workersParam.length" class="action-footer">
<view class="footer-inner">
<view class="info-group">
<text class="main-text">直接与负责人沟通</text>
<text class="sub-text">调配周期约 1-2 小时</text>
</view>
<view class="primary-call-btn" @tap="pageData.showConfirmDialog = true">
<fui-icon name="mobile" :size="36" color="#fff"></fui-icon>
<text class="btn-text">立即沟通</text>
</view>
</view>
</view>
<ConfirmDialog <ConfirmDialog
v-model:show="pageData.showConfirmDialog" v-model:show="pageData.showConfirmDialog"
title="温馨提示" title="联系确认"
:content="`你将与${pageData.contactName}进行电话沟通,若继续,请点击确认按钮!`" :content="`系统将为您拨通该劳动力池负责人【${pageData.contactName}】的电话,是否继续?`"
cancelText="取消"
confirmText="确认"
@confirm="makePhoneCall" @confirm="makePhoneCall"
/> />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(255, 255, 255, 0.6)" />
</view> </view>
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" />
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.details_page { .details_page {
background: rgba(230, 245, 232, 1); background-color: #f8fafc;
min-height: 100vh; min-height: 100vh;
width: 750rpx; font-family: 'DingTalk Sans', sans-serif;
padding-top: 24rpx; padding-bottom: 160rpx;
}
.immersive-header {
position: relative; position: relative;
.details-content { height: 380rpx;
width: 694rpx; .header-bg {
background-color: #ffffff; position: absolute;
margin-left: 28rpx; top: 0; left: 0; right: 0;
border-radius: 12rpx; height: 320rpx;
padding: 24rpx; background: linear-gradient(135deg, #5db66f 0%, #3a944c 100%);
border-bottom-left-radius: 40rpx;
.yr-person-item { border-bottom-right-radius: 40rpx;
border-bottom: 2rpx solid #eeeeee; }
margin-bottom: 24rpx; .header-content {
.yr-person-info { position: relative;
.person_name_attr { padding: 40rpx 40rpx 0;
font-size: 28rpx; .village-info {
font-weight: 500; .village-label {
color: #333333;
.person_attr {
font-size: 24rpx; font-size: 24rpx;
font-weight: 400; color: rgba(255,255,255,0.8);
display: block;
}
.village-name {
font-size: 48rpx;
font-weight: bold;
color: #fff;
margin-top: 8rpx;
display: block;
}
}
}
}
.stats-float-card {
background-color: #fff;
margin-top: 40rpx;
border-radius: 24rpx;
padding: 32rpx;
display: flex;
align-items: center;
box-shadow: 0 12rpx 30rpx rgba(0,0,0,0.06);
.stat-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.stat-num {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.stat-label {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
}
.stat-divider {
width: 1rpx;
height: 48rpx;
background-color: #f0f0f0;
}
}
.list-section {
padding: 0 32rpx;
.section-header {
margin: 40rpx 0 24rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #1a1a1a;
display: block;
}
.section-subtitle {
font-size: 22rpx;
color: #94a3b8;
margin-top: 4rpx;
display: block;
}
}
}
.worker-card-v2 {
background-color: #fff;
border-radius: 24rpx;
padding: 28rpx;
margin-bottom: 24rpx;
border: 1rpx solid #f1f5f9;
.card-top {
display: flex;
align-items: center;
padding-bottom: 24rpx;
border-bottom: 1rpx dashed #f1f5f9;
.avatar-circle {
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
.avatar-text {
font-size: 36rpx;
font-weight: bold;
}
}
.worker-core {
flex: 1;
margin-left: 24rpx;
.name-line {
display: flex;
align-items: center;
.worker-name {
font-size: 32rpx;
font-weight: bold;
color: #1e293b;
}
.tag-outline {
font-size: 20rpx;
color: #5db66f; color: #5db66f;
margin-left: 12rpx; border: 1rpx solid #5db66f;
padding: 2rpx 12rpx;
border-radius: 6rpx;
margin-left: 16rpx;
} }
} }
.person-info { .info-line {
margin-top: 8rpx;
display: flex;
align-items: center;
font-size: 24rpx; font-size: 24rpx;
color: #999999; color: #64748b;
vertical-align: middle; .dot-sep {
margin-bottom: 24rpx; width: 4rpx;
margin-top: 24rpx; height: 4rpx;
background-color: #cbd5e1;
border-radius: 50%;
margin: 0 12rpx;
} }
} }
} }
.yr-person-item:last-child { }
border-bottom: none;
.card-bottom {
padding-top: 24rpx;
.skill-hint {
font-size: 22rpx;
color: #94a3b8;
margin-bottom: 16rpx;
display: block;
}
.skill-flow {
display: flex;
flex-wrap: wrap;
.skill-chip {
font-size: 22rpx;
color: #475569;
background-color: #f1f5f9;
padding: 6rpx 20rpx;
border-radius: 30rpx;
margin-right: 12rpx;
margin-bottom: 12rpx;
}
}
} }
} }
.make-phone-view { .action-footer {
width: 690rpx;
height: 80rpx;
position: fixed; position: fixed;
left: 30rpx; bottom: 0; left: 0; right: 0;
bottom: 62rpx; background-color: rgba(255,255,255,0.95);
backdrop-filter: blur(10px);
padding: 20rpx 40rpx calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -10rpx 30rpx rgba(0,0,0,0.05);
z-index: 100;
.footer-inner {
display: flex;
align-items: center;
justify-content: space-between;
.info-group {
.main-text {
font-size: 28rpx;
color: #334155;
font-weight: bold;
display: block;
}
.sub-text {
font-size: 22rpx;
color: #94a3b8;
margin-top: 4rpx;
display: block;
}
}
.primary-call-btn {
background-color: #5db66f;
height: 88rpx;
padding: 0 48rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
box-shadow: 0 8rpx 20rpx rgba(93, 182, 111, 0.3);
&:active { opacity: 0.9; transform: scale(0.98); }
.btn-text {
font-size: 30rpx;
color: #fff;
font-weight: bold;
margin-left: 12rpx;
}
} }
} }
}
.empty-state { padding: 100rpx 0; }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRefs } from 'vue' import { reactive, toRefs, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
...@@ -7,23 +7,28 @@ ...@@ -7,23 +7,28 @@
import * as UserInfoAPI from '@/api/model/userInfo' import * as UserInfoAPI from '@/api/model/userInfo'
import { getCodeByText } from '@/utils/areaData' import { getCodeByText } from '@/utils/areaData'
import { useDictStore } from '@/store/modules/dict' import { useDictStore } from '@/store/modules/dict'
import { getDictData, getText } from '@/utils/dict/area' import { getText } from '@/utils/dict/area'
import AreaPicker from '@/components/AreaPicker/index.vue'
const dictStore = useDictStore() const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
const isSave = ref(false)
onLoad((option) => { onLoad((option) => {
// 获取数据详情
if (option.id) { if (option.id) {
isSave.value = false
getDetails(option.id) getDetails(option.id)
uni.setNavigationBarTitle({ title: '用工需求详情' })
} else { } else {
// 获取当前位置 isSave.value = true
getCurrentAddressInfo() getCurrentAddressInfo()
uni.setNavigationBarTitle({ title: '发布招聘需求' })
} }
}) })
onShow(() => { onShow(() => {
// 数据字典赋值
initDict() initDict()
}) })
...@@ -37,7 +42,6 @@ ...@@ -37,7 +42,6 @@
type: false, type: false,
}, },
options: { options: {
area: [],
urgentdegree: [], urgentdegree: [],
type: [], type: [],
}, },
...@@ -66,92 +70,32 @@ ...@@ -66,92 +70,32 @@
}, },
position: [], position: [],
rules: [ rules: [
{ { name: 'type', rule: ['required'], msg: ['请选择用工类型'] },
name: 'type', { name: 'name', rule: ['required'], msg: ['请输入招聘标题'] },
rule: ['required'], { name: 'workers', rule: ['required'], msg: ['请输入招聘人数'] },
msg: ['请选择用工类型'], { name: 'price', rule: ['required'], msg: ['请输入用工单价'] },
}, { name: 'starttime', rule: ['required'], msg: ['请选择开始日期'] },
{ { name: 'estimatedendtime', rule: ['required'], msg: ['请选择结束日期'] },
name: 'name', { name: 'content', rule: ['required'], msg: ['请输入工作内容详情'] },
rule: ['required'], { name: 'area', rule: ['required'], msg: ['请选择工作地区'] },
msg: ['请输入标题'], { name: 'address', rule: ['required'], msg: ['请输入详细地址'] },
}, { name: 'urgentdegree', rule: ['required'], msg: ['请选择紧急程度'] },
{ { name: 'picture', rule: ['required'], msg: ['请上传招聘现场或环境图'] },
name: 'workers',
rule: ['required'],
msg: ['请输入工人数量'],
},
{
name: 'price',
rule: ['required'],
msg: ['请输入用工单价'],
},
{
name: 'starttime',
rule: ['required'],
msg: ['请选择开始时间'],
},
{
name: 'estimatedendtime',
rule: ['required'],
msg: ['请选择预计结束时间'],
},
{
name: 'content',
rule: ['required'],
msg: ['请输入工作内容'],
},
{
name: 'area',
rule: ['required'],
msg: ['请选择地区'],
},
{
name: 'address',
rule: ['required'],
msg: ['请输入详细地址'],
},
{
name: 'urgentdegree',
rule: ['required'],
msg: ['请选择紧急程度'],
},
{
name: 'picture',
rule: ['required'],
msg: ['请上传图片'],
},
], ],
}) })
const { show, options, form } = toRefs(pageData) const { show, options, form } = toRefs(pageData)
async function initDict() { async function initDict() {
pageData.options.area = await getDictData() pageData.options.urgentdegree = dictStore.getDictList.employment_urgent.map(i => ({ value: i.value, text: i.text }))
pageData.options.urgentdegree = dictStore.getDictList.employment_urgent.map((item) => { pageData.options.type = dictStore.getDictList.employment_type.map(i => ({ value: i.value, text: i.text }))
return {
value: item.value,
text: item.text,
}
})
pageData.options.type = dictStore.getDictList.employment_type.map((item) => {
return {
value: item.value,
text: item.text,
}
})
} }
function getCurrentAddressInfo() { function getCurrentAddressInfo() {
if (!uni.getStorageSync('location')) const loc = uni.getStorageSync('location')
return if (!loc) return
pageData.position = [loc.lon, loc.lat]
const { lon, lat } = uni.getStorageSync('location') UserInfoAPI.location({ lon: loc.lon, lat: loc.lat }).then((res) => {
pageData.position = [lon, lat]
UserInfoAPI.location({
lon,
lat,
}).then((res) => {
pageData.form.province = res.province pageData.form.province = res.province
pageData.form.city = res.city pageData.form.city = res.city
pageData.form.country = res.country pageData.form.country = res.country
...@@ -162,38 +106,26 @@ return ...@@ -162,38 +106,26 @@ return
function getDetails(id) { function getDetails(id) {
pageData.loading = true pageData.loading = true
LinghuoyonggongAPI.employmentDetails({ id }) LinghuoyonggongAPI.employmentDetails({ id }).then((res) => {
.then((res) => {
pageData.form = res pageData.form = res
pageData.form.areaText = getText(pageData.form.area, '/') pageData.form.areaText = getText(pageData.form.area, '/')
pageData.form.urgentdegreeText = pageData.options.urgentdegree.find( pageData.form.urgentdegreeText = pageData.options.urgentdegree.find(i => i.value == res.urgentdegree)?.text
(item) => item.value == pageData.form.urgentdegree, pageData.form.typeText = pageData.options.type.find(i => i.value == res.type)?.text
)?.text pageData.form.pictureObj = res.picture && parseUrlInfo(res.picture)
pageData.form.typeText = pageData.options.type.find((item) => item.value == pageData.form.type)?.text }).finally(() => pageData.loading = false)
pageData.form.pictureObj = pageData.form.picture && parseUrlInfo(pageData.form.picture)
console.log(pageData.form)
})
.finally(() => {
pageData.loading = false
})
} }
function parseUrlInfo(url) { function parseUrlInfo(url) {
// 从URL中提取文件名
const pathParts = url.split('/') const pathParts = url.split('/')
const fileName = pathParts[pathParts.length - 1] const fileName = pathParts[pathParts.length - 1]
// 提取扩展名
const fileParts = fileName.split('.') const fileParts = fileName.split('.')
const extname = fileParts[fileParts.length - 1] const extname = fileParts[fileParts.length - 1]
return { name: fileName, extname, url }
// 返回格式化的对象
return {
name: fileName,
extname,
url,
} }
function handleAreaConfirm(e) {
pageData.form.areaText = e.fullText
pageData.form.area = e.fullCode
} }
function handleChangeTime1(e) { function handleChangeTime1(e) {
...@@ -214,73 +146,47 @@ return ...@@ -214,73 +146,47 @@ return
pageData.form.urgentdegreeText = e.text pageData.form.urgentdegreeText = e.text
pageData.show.urgentdegree = false pageData.show.urgentdegree = false
} }
function handleChangeAddress(e) {
pageData.form.areaText = e.text.join('/') function handleContact() {
pageData.form.area = e.value.join(',') uni.makePhoneCall({
pageData.show.area = false phoneNumber: '10086' // 示例
})
} }
const toastRef = ref() const toastRef = ref()
const uploadRef = ref() const uploadRef = ref()
// 文件上传
function handleUpload(file) { function handleUpload(file) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: file.tempFiles[0].path, filePath: file.tempFiles[0].path,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success', pageData.form.picture = data.message
text: '上传成功',
})
pageData.form.picture = data.message // 保存返回的图片信息
} }
} }
},
fail: (err) => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.form.picture = null
},
}) })
} }
// 文件删除 function handleDelete() {
function handleDelete(file) {
uploadRef.value.clearFiles()
pageData.form.picture = null pageData.form.picture = null
} }
const formRef = ref() const formRef = ref()
function submit() { function submit() {
if (pageData.position.length == 0) { if (!pageData.position.length && isSave.value) {
toastRef.value.show({ toastRef.value.show({ type: 'error', text: '获取当前位置失败' })
type: 'error',
text: '无法获取位置',
})
return return
} }
pageData.form.longitude = pageData.position[0] pageData.form.longitude = pageData.position[0]
pageData.form.latitude = pageData.position[1] pageData.form.latitude = pageData.position[1]
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
LinghuoyonggongAPI.employmentAdd(pageData.form).then((res) => { LinghuoyonggongAPI.employmentAdd(pageData.form).then(() => {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '招聘信息已发布' })
type: 'success', setTimeout(() => uni.navigateBack(), 1500)
text: '用工发布成功',
})
uni.switchTab({
url: '/pages/chanxiao/chanxiao',
})
}) })
} }
}) })
...@@ -288,371 +194,236 @@ return ...@@ -288,371 +194,236 @@ return
function getCurrentDate() { function getCurrentDate() {
const date = new Date() const date = new Date()
const year = date.getFullYear() return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${month}-${day}`
} }
</script> </script>
<template> <template>
<view class="page"> <view class="page">
<view class="formBox"> <!-- 发布编辑模式 -->
<fui-form ref="formRef" label-weight="auto" top="60" :disabled="form.id ? true : false"> <view v-if="isSave" class="container">
<view class="mt20"> <view class="form-banner">
<fui-input <text class="title">发布招聘需求</text>
disabled <text class="subtitle">填写精准信息,快速匹配优质劳动力</text>
required </view>
label="用工类型"
placeholder="请选择用工类型" <view class="form-content">
v-model="form.typeText" <fui-form ref="formRef">
labelSize="28" <!-- 基本职位信息 -->
size="28" <view class="section-card">
label-width="180" <view class="card-header">
@click="show.type = true" <view class="line"></view>
/> <text>职位基础信息</text>
<fui-input
required
label="标题"
placeholder="请输入标题"
v-model="form.name"
labelSize="28"
label-width="180"
maxlength="16"
size="28"
/>
</view> </view>
<view class="mt20"> <fui-input label="用工类型" placeholder="请选择" v-model="form.typeText" @click="show.type = true" disabled required />
<fui-input <fui-input label="招聘标题" placeholder="例如:水稻收割临时工" v-model="form.name" required maxlength="20" />
required <fui-input label="招聘人数" type="number" placeholder="所需人员数量" v-model="form.workers" required>
type="number" <template #suffix><text class="unit"></text></template>
:min="0"
label="工人数量"
placeholder="请输入工人数量"
v-model="form.workers"
labelSize="28"
label-width="180"
maxlength="4"
size="28"
>
<template #suffix>
<view class="unit-slot"></view>
</template>
</fui-input> </fui-input>
<fui-input <fui-input label="用工薪资" type="number" placeholder="单人日薪" v-model="form.price" required>
required <template #suffix><text class="unit">元/天</text></template>
type="number"
label="用工单价"
:min="0"
placeholder="请输入用工单价"
v-model="form.price"
labelSize="28"
label-width="180"
maxlength="6"
size="28"
>
<template #suffix>
<view class="unit-slot">元/人</view>
</template>
</fui-input> </fui-input>
<!-- 时间范围 -->
<view class="form-section" style="padding: 0 30rpx">
<view class="form-item flex align-center">
<text class="label">用工时间</text>
<view class="time-range">
<view class="time-input" @click="show.time1 = true">
<text class="time-text" :class="{ placeholder: !form.starttime }">
{{ form.starttime || '开始时间' }}
</text>
</view> </view>
<text class="time-separator"></text>
<view class="time-input" @click="show.time2 = true"> <!-- 时间与地点 -->
<text class="time-text" :class="{ placeholder: !form.estimatedendtime }"> <view class="section-card">
{{ form.estimatedendtime || '结束时间' }} <view class="card-header">
</text> <view class="line"></view>
<text>作业时间与地点</text>
</view> </view>
<view class="form-row-item">
<text class="item-label"><text class="red">*</text>用工周期</text>
<view class="date-range-box">
<view class="date-picker" @click="show.time1 = true">
<text :class="{ placeholder: !form.starttime }">{{ form.starttime || '开始日期' }}</text>
</view> </view>
<text class="sep"></text>
<view class="date-picker" @click="show.time2 = true">
<text :class="{ placeholder: !form.estimatedendtime }">{{ form.estimatedendtime || '结束日期' }}</text>
</view> </view>
</view> </view>
<fui-input
required
label="工作内容"
placeholder="请输入工作内容"
v-model="form.content"
labelSize="28"
label-width="180"
maxlength="32"
size="28"
/>
</view> </view>
<view class="mt20"> <fui-input label="所在地区" v-model="form.areaText" @click="show.area = true" disabled required />
<fui-input <fui-input label="详细地址" placeholder="作业的具体街道、村组" v-model="form.address" required />
disabled <fui-input label="紧急程度" v-model="form.urgentdegreeText" @click="show.urgentdegree = true" disabled required />
required
label="地区"
placeholder="请选择地区"
v-model="form.areaText"
labelSize="28"
label-width="180"
@click="show.area = true"
size="28"
/>
<fui-input
required
label="详细地址"
placeholder="请输入详细地址"
v-model="form.address"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
disabled
required
label="紧急程度"
placeholder="请选择紧急程度"
v-model="form.urgentdegreeText"
labelSize="28"
label-width="180"
size="28"
@click="show.urgentdegree = true"
/>
</view> </view>
<view class="bg-white mt20" style="padding: 0.875rem 1rem">
<view class="mb-1 flex justify-start" style="font-size: 28rpx" <!-- 详情与图片 -->
><span style="color: red">*&nbsp;</span> 图片 <view class="section-card">
<view class="card-header">
<view class="line"></view>
<text>工作内容描述</text>
</view>
<fui-textarea placeholder="请详细描述工作内容、技能要求、供餐情况等..." v-model="form.content" height="200rpx" background="#f9f9f9" radius="16" :padding="['24rpx','24rpx']" />
<view class="upload-box">
<text class="upload-label"><text class="red">*</text>工作环境图片</text>
<uni-file-picker limit="1" @select="handleUpload" @delete="form.picture = null" />
</view> </view>
<uni-file-picker
:value="form.pictureObj"
ref="uploadRef"
limit="1"
:auto-upload="false"
@select="handleUpload"
@delete="handleDelete"
/>
</view> </view>
<view class="fui-btn__box" v-if="!form.id" style="margin-top: 30rpx">
<fui-button text="发布用工" bold radius="96rpx" @click="submit" /> <view class="submit-area">
<fui-button text="确认并发布" radius="100rpx" background="#5db66f" @click="submit" />
</view> </view>
</fui-form> </fui-form>
</view> </view>
</view> </view>
<fui-date-picker <!-- 招聘详情模式 -->
:show="show.time1" <view v-else class="job-detail">
type="3" <view class="detail-banner">
@change="handleChangeTime1" <image v-if="form.picture" :src="form.picture" mode="aspectFill" class="banner-img" />
:min-date="getCurrentDate()" <view v-else class="banner-placeholder"><fui-icon name="picture" :size="80" color="#ddd"></fui-icon></view>
@cancel="show.time1 = false" <view class="urgent-badge" v-if="form.urgentdegree > 3">急聘</view>
/> </view>
<fui-date-picker <view class="info-card">
:show="show.time2" <view class="price-row">
type="3" <text class="price">¥{{ form.price }}</text>
@change="handleChangeTime2" <text class="unit">元/人/天</text>
:min-date="getCurrentDate()" </view>
@cancel="show.time2 = false" <text class="job-title">{{ form.name }}</text>
/> <view class="tags-row">
<fui-picker <view class="tag green">{{ form.typeText }}</view>
:show="show.type" <view class="tag blue">需{{ form.workers }}人</view>
:layer="1" <view class="tag orange">{{ form.urgentdegreeText }}</view>
:linkage="true" </view>
:options="options.type" </view>
@change="handleChangetype" <view class="detail-section">
@cancel="show.type = false" <view class="section-title">工作内容</view>
/> <text class="content-text">{{ form.content }}</text>
<fui-picker </view>
:show="show.urgentdegree" <view class="detail-section">
:layer="1" <view class="section-title">招工信息</view>
:linkage="true" <view class="meta-list">
:options="options.urgentdegree" <view class="meta-item">
@change="handleChangeUrgentdegree" <fui-icon name="time" :size="32" color="#999"></fui-icon>
@cancel="show.urgentdegree = false" <view class="meta-body">
/> <text class="label">用工周期</text>
<fui-picker <text class="value">{{ form.starttime }} 至 {{ form.estimatedendtime }}</text>
:show="show.area" </view>
:options="options.area" </view>
:linkage="true" <view class="meta-item">
:layer="3" <fui-icon name="location" :size="32" color="#999"></fui-icon>
@change="handleChangeAddress" <view class="meta-body">
@cancel="show.area = false" <text class="label">工作地点</text>
/> <text class="value">{{ form.areaText }} {{ form.address }}</text>
</view>
</view>
</view>
</view>
<view class="bottom-bar">
<view class="apply-btn" @click="handleContact">立即应聘</view>
</view>
</view>
<!-- 选择器组件 -->
<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-picker :show="show.type" :options="options.type" @change="handleChangetype" @cancel="show.type = false" />
<fui-picker :show="show.urgentdegree" :options="options.urgentdegree" @change="handleChangeUrgentdegree" @cancel="show.urgentdegree = false" />
<AreaPicker v-model:show="show.area" :layer="3" title="选择工作地区" @confirm="handleAreaConfirm" />
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" /> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body {
background-color: #e6f5e8;
}
.page { .page {
background-color: #e6f5e8; min-height: 100vh;
width: 750rpx; background-color: #f4f7f5;
overflow-x: hidden; font-family: 'DingTalk Sans', sans-serif;
.mt20 {
margin-top: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.formBox {
width: 690rpx;
margin: 30rpx auto;
}
.form-section {
// border-bottom: 1rpx solid #f5f5f5;
}
.form-item {
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
} }
&.required .label::before { /* 发布表单样式 */
content: '*'; .container {
color: #ff4d4f; .form-banner {
margin-right: 8rpx; background-color: #5db66f;
padding: 60rpx 40rpx 100rpx;
color: #fff;
.title { font-size: 48rpx; font-weight: bold; display: block; letter-spacing: 2rpx; }
.subtitle { font-size: 26rpx; opacity: 0.9; margin-top: 12rpx; display: block; }
} }
.form-content { padding: 24rpx; margin-top: -80rpx; position: relative; z-index: 10; }
} }
.form-row { .section-card {
display: flex; background-color: #fff; border-radius: 28rpx; padding: 32rpx; margin-bottom: 28rpx;
justify-content: space-between; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.03);
.card-header {
margin-bottom: 32rpx; display: flex; align-items: center;
.line { width: 8rpx; height: 32rpx; background-color: #5db66f; border-radius: 4rpx; margin-right: 16rpx; }
text { font-size: 32rpx; font-weight: bold; color: #1a1a1a; }
} }
.half-width {
width: 48%;
}
.align-center {
align-items: center;
} }
.label { .form-row-item {
display: block; padding: 32rpx 0; border-bottom: 1rpx solid #f8f8f8;
font-size: 28rpx; .item-label { font-size: 28rpx; color: #333; margin-bottom: 24rpx; display: block; .red { color: #ff4d4f; margin-right: 4rpx; } }
color: #333333; .date-range-box {
font-weight: 500; display: flex; align-items: center; justify-content: space-between;
width: 180rpx; .date-picker {
flex: 1; height: 80rpx; background-color: #f9f9f9; border-radius: 16rpx;
display: flex; align-items: center; padding: 0 24rpx; font-size: 28rpx;
.placeholder { color: #ccc; }
} }
.sep { margin: 0 24rpx; color: #94a3b8; font-size: 24rpx; font-weight: bold; }
.input {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #999999;
} }
} }
.time-range { .upload-box {
display: flex; margin-top: 32rpx;
align-items: center; .upload-label { font-size: 28rpx; color: #333; margin-bottom: 24rpx; display: block; .red { color: #ff4d4f; margin-right: 4rpx; } }
justify-content: space-between;
}
.time-input {
width: 45%;
border-radius: 8rpx;
display: flex;
align-items: center;
}
.time-text {
font-size: 28rpx;
color: #333333;
&.placeholder {
color: #999999;
}
} }
.time-separator { .submit-area {
color: #666666; padding: 60rpx 20rpx 100rpx;
font-size: 28rpx; :deep(.fui-button) {
margin: 0 10rpx; box-shadow: 0 8rpx 24rpx rgba(93, 182, 111, 0.3);
} }
.upload-area {
margin-top: 10rpx;
} }
.unit { font-size: 26rpx; color: #999; margin-left: 12rpx; font-weight: 500; }
.custom-uploader { /* 详情样式 (同步优化字体) */
:deep(.uni-file-picker__container) { .job-detail {
border: 2rpx dashed #d9d9d9; font-family: 'DingTalk Sans', sans-serif;
border-radius: 8rpx; .detail-banner {
background: #f8f9fa; width: 750rpx; height: 450rpx; position: relative;
.banner-img { width: 100%; height: 100%; }
.urgent-badge { position: absolute; right: 30rpx; top: 30rpx; background: #ff4d4f; color: #fff; padding: 8rpx 24rpx; border-radius: 30rpx; font-size: 24rpx; font-weight: bold; box-shadow: 0 4rpx 12rpx rgba(255,77,79,0.4); }
} }
.info-card {
background: #fff; margin: -40rpx 24rpx 24rpx; padding: 40rpx 32rpx; border-radius: 32rpx; box-shadow: 0 8rpx 30rpx rgba(0,0,0,0.05); position: relative;
.price-row { display: flex; align-items: baseline; margin-bottom: 20rpx; .price { font-size: 52rpx; color: #ff4d4f; font-weight: bold; } .unit { font-size: 24rpx; color: #999; margin-left: 10rpx; } }
.job-title { font-size: 38rpx; font-weight: bold; color: #1a1a1a; line-height: 1.4; display: block; }
.tags-row { margin-top: 28rpx; display: flex; flex-wrap: wrap; .tag { padding: 6rpx 20rpx; font-size: 22rpx; border-radius: 8rpx; margin-right: 16rpx; margin-bottom: 12rpx; &.green { background: #e6f5e8; color: #5db66f; } &.blue { background: #e6f7ff; color: #1890ff; } &.orange { background: #fff7e6; color: #fa8c16; } } }
} }
.detail-section {
.upload-placeholder { background: #fff; margin: 0 24rpx 24rpx; padding: 32rpx; border-radius: 32rpx;
display: flex; .section-title { font-size: 30rpx; font-weight: bold; color: #1a1a1a; padding-left: 16rpx; border-left: 8rpx solid #5db66f; margin-bottom: 28rpx; }
flex-direction: column; .content-text { font-size: 28rpx; color: #475569; line-height: 1.7; text-align: justify; }
align-items: center; .meta-list { .meta-item { display: flex; align-items: center; padding: 24rpx 0; border-bottom: 1rpx solid #f8f8f8; &:last-child { border-bottom: none; } .meta-body { margin-left: 28rpx; .label { font-size: 22rpx; color: #94a3b8; display: block; } .value { font-size: 28rpx; color: #1e293b; margin-top: 6rpx; font-weight: 500; display: block; } } } }
justify-content: center;
height: 200rpx;
color: #999999;
} }
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; height: 110rpx; background: rgba(255,255,255,0.9); backdrop-filter: blur(10px); padding: 0 40rpx; display: flex; align-items: center; box-shadow: 0 -10rpx 30rpx rgba(0,0,0,0.05); z-index: 100; .apply-btn { flex: 1; height: 80rpx; background: #5db66f; color: #fff; border-radius: 40rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: bold; box-shadow: 0 8rpx 20rpx rgba(93, 182, 111, 0.3); } }
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
} }
.upload-text { :deep(.fui-form__item) { background: transparent; border: none; padding: 0; }
font-size: 24rpx;
}
.submit-section { /* 统一字体穿透优化 */
background: transparent; :deep(.fui-input__label),
padding: 40rpx 0; :deep(.fui-input__input),
:deep(.fui-textarea__textarea),
:deep(.fui-textarea__label) {
font-family: 'DingTalk Sans', system-ui !important;
color: #333 !important;
} }
.submit-btn { :deep(.uni-input-placeholder),
width: 100%; :deep(.uni-textarea-placeholder) {
height: 88rpx; font-family: 'DingTalk Sans' !important;
background: #ff9800;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #e68900;
opacity: 0.9;
}
}
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important; font-size: 28rpx !important;
color: #999999 !important;
} }
// 移除fui-form的默认样式 :deep(.fui-button) {
:deep(.fui-form) { font-family: 'DingTalk Sans' !important;
background: transparent; font-weight: bold !important;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
.unit-slot {
padding: 0 16rpx;
color: #333;
font-size: 28rpx;
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive, ref, toRefs } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app' import { onLoad, onShow, onPullDownRefresh } from '@dcloudio/uni-app'
import * as turf from '@turf/turf' import * as turf from '@turf/turf'
import RegisterDialog from './components/register-dialog.vue' import RegisterDialog from './components/register-dialog.vue'
import ConfirmDialog from '@/components/ConfirmDialog/index.vue' import ConfirmDialog from '@/components/ConfirmDialog/index.vue'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong' import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import Navigate from '@/utils/page/navigate' import Navigate from '@/utils/page/navigate'
import { getText } from '@/utils/dict/area' import { getText, getDictData } from '@/utils/dict/area'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore() const userStore = useUserStore()
onLoad((option) => { onLoad((option) => {
pageData.currentEmploymentId = Number(option.type) || 2 pageData.currentEmploymentId = Number(option.type) || 2
// 预加载地区字典数据
getDictData()
}) })
onShow(() => { onShow(() => {
...@@ -19,10 +21,14 @@ ...@@ -19,10 +21,14 @@
resetGetEmploymentList() resetGetEmploymentList()
}) })
// 页面数据 onPullDownRefresh(() => {
resetGetEmploymentList().finally(() => {
uni.stopPullDownRefresh()
})
})
const pageData = reactive({ const pageData = reactive({
loading: false, loading: false,
requestDebounce: null,
searchValue: '', searchValue: '',
search: { search: {
pageNo: 1, pageNo: 1,
...@@ -33,177 +39,92 @@ ...@@ -33,177 +39,92 @@
keyword: '', keyword: '',
}, },
hasMore: 'more', hasMore: 'more',
// 用工类型标签
employmentTabs: [ employmentTabs: [
{ id: 2, name: '找人干活(找人)' }, { id: 2, name: '找人干活(找人)' },
{ id: 1, name: '出工赚钱(找事)' }, { id: 1, name: '出工赚钱(找事)' },
], ],
currentEmploymentId: 2, currentEmploymentId: 2,
// 用工列表
employmentList: [], employmentList: [],
total: 0, total: 0,
dataTotal: 0,
longitude: null, longitude: null,
latitude: null, latitude: null,
swipeActionButtons: [
{
text: '编辑',
background: '#465CFF',
},
{
text: '删除',
background: '#FF2B2B',
},
],
isShowConfirmDialog: false, isShowConfirmDialog: false,
dialogContent: '确认要删除吗', dialogContent: '',
delLaborId: null, delLaborId: null,
}) })
// 缓存已处理的区域数据,避免重复计算 const { currentEmploymentId, employmentList, employmentTabs, searchValue } = toRefs(pageData)
const areaCache = new Map()
// 请求重试配置 async function getEmploymentList() {
const requestConfig = { if (pageData.loading || pageData.hasMore == 'noMore') return
maxRetries: 2,
retryDelay: 1000,
}
function handleConfirmDialog() {
pageData.loading = true pageData.loading = true
LinghuoyonggongAPI.getLaborAppDel({
id: pageData.delLaborId,
})
.then((res) => {
resetGetEmploymentList()
})
.finally(() => {
setTimeout(() => {
pageData.loading = false
}, 200)
})
}
// 左滑点击
function swipeActionOnClick(e, item) {
pageData.delLaborId = item.id
if (e.item.text == '删除') {
pageData.dialogContent = `确认要删除【${item.villageName}】吗`
setTimeout(() => {
pageData.isShowConfirmDialog = true
}, 100)
} else {
Navigate.to(`/pages/linghuoyonggong/publishEmployment?id=${item.id}`)
}
}
// 带重试机制的API调用
async function fetchWithRetry(apiCall, maxRetries = requestConfig.maxRetries) {
const startTime = Date.now()
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await apiCall()
const endTime = Date.now()
// 记录API性能数据(仅在开发环境)
if (import.meta.env.DEV) {
console.log(`API请求成功,耗时: ${endTime - startTime}ms`)
}
return result
} catch (error) {
console.warn(`API请求失败 (尝试 ${attempt}/${maxRetries}):`, error)
if (attempt === maxRetries) {
const endTime = Date.now()
if (import.meta.env.DEV) {
console.error(`API请求最终失败,总耗时: ${endTime - startTime}ms`)
}
throw error
}
// 指数退避策略
await new Promise((resolve) => setTimeout(resolve, requestConfig.retryDelay * attempt))
}
}
}
function getEmploymentList() {
// 如果正在加载或没有更多数据,直接返回
if (pageData.loading || pageData.hasMore == 'noMore') {
return
}
pageData.loading = true
// 添加请求防抖,避免快速连续请求
if (pageData.requestDebounce) {
clearTimeout(pageData.requestDebounce)
}
pageData.requestDebounce = setTimeout(async () => {
try { try {
let res = null let res = null
if (pageData.currentEmploymentId == 2) { if (pageData.currentEmploymentId == 2) {
res = await fetchWithRetry(() => LinghuoyonggongAPI.getLaborAppList(pageData.search)) res = await LinghuoyonggongAPI.getLaborAppList(pageData.search)
} else { } else {
res = await fetchWithRetry(() => LinghuoyonggongAPI.employmentList(pageData.search)) res = await LinghuoyonggongAPI.employmentList(pageData.search)
} }
const { records, total } = res const { records, total } = res
// 批量处理数据,避免多次DOM操作
const processedRecords = records.map((item) => { const processedRecords = records.map((item) => {
// 缓存区域处理结果 // 统一地区展示逻辑:[区县/乡镇/村]
item.area = getText(item.area, ' / ') if (pageData.currentEmploymentId == 2) {
// 计算天数并缓存结果 item.areaLabel = item.villageFullName || item.villageName || '未知地区'
} else {
const rawArea = item.area || item.scope
item.areaLabel = getText(rawArea, ' / ') || rawArea || '未知地区'
}
if (item.starttime && item.estimatedendtime) { if (item.starttime && item.estimatedendtime) {
item.daysDiff = getDaysDiff(item.starttime, item.estimatedendtime) item.daysDiff = getDaysDiff(item.starttime, item.estimatedendtime)
} }
if (typeof item.skills === 'string') {
item.skillList = item.skills.split(/[,,、/]/)
} else if (Array.isArray(item.skills)) {
item.skillList = item.skills
} else {
item.skillList = []
}
return item return item
}) })
pageData.dataTotal += processedRecords.length
// 一次性更新数据,避免多次响应式更新
if (pageData.search.pageNo === 1) { if (pageData.search.pageNo === 1) {
pageData.employmentList = processedRecords pageData.employmentList = processedRecords
} else { } else {
// 避免重复数据加载 感觉有坑
/* const existingIds = new Set(pageData.employmentList.map((item) => item.id))
const newRecords = processedRecords.filter((item) => !existingIds.has(item.id)) */
pageData.employmentList = [...pageData.employmentList, ...processedRecords] pageData.employmentList = [...pageData.employmentList, ...processedRecords]
} }
pageData.total = total pageData.total = total
if (pageData.dataTotal >= total) { pageData.hasMore = pageData.employmentList.length >= total ? 'noMore' : 'more'
pageData.hasMore = 'noMore'
}
} catch (error) {
console.error('获取用工列表失败:', error)
// 这里可以添加用户友好的错误提示
// uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' })
} finally { } finally {
pageData.loading = false pageData.loading = false
pageData.requestDebounce = null
} }
}, 150)
} }
// 用工类型标签点击事件
function onEmploymentTabClick(tab: any) { function onEmploymentTabClick(tab: any) {
pageData.currentEmploymentId = tab.id pageData.currentEmploymentId = tab.id
resetGetEmploymentList() resetGetEmploymentList()
} }
// 用工项点击事件
function onEmploymentItemClick(item: any) { function onEmploymentItemClick(item: any) {
console.log('点击用工项:', item) if (pageData.currentEmploymentId === 1) {
// 在这里添加具体的用工项点击逻辑
Navigate.to(`/pages/linghuoyonggong/form?id=${item.id}`) Navigate.to(`/pages/linghuoyonggong/form?id=${item.id}`)
} else {
onDetailsClick(item)
}
} }
// "我想去"按钮点击事件
const registerDialogRef = ref() const registerDialogRef = ref()
function onQuoteClick(item: any) { function onQuoteClick(item: any) {
console.log('点击立即报价:', item) if (pageData.currentEmploymentId === 1) {
// 在这里添加具体的立即报价点击逻辑
registerDialogRef.value.open(item) registerDialogRef.value.open(item)
} else {
onDetailsClick(item)
} }
// 发布用工 }
function handlePublish() { function handlePublish() {
if (pageData.currentEmploymentId === 2) { if (pageData.currentEmploymentId === 2) {
Navigate.to('/pages/linghuoyonggong/publishEmployment') Navigate.to('/pages/linghuoyonggong/publishEmployment')
...@@ -211,692 +132,226 @@ ...@@ -211,692 +132,226 @@
Navigate.to('/pages/linghuoyonggong/form') Navigate.to('/pages/linghuoyonggong/form')
} }
} }
// 获取时间差
function getDaysDiff(date1: Date | number | string, date2: Date | number | string): number {
// 将输入转换为Date对象
const d1 = new Date(date1)
const d2 = new Date(date2)
// 检查日期是否有效 function getDaysDiff(date1, date2) {
if (isNaN(d1.getTime()) || isNaN(d2.getTime())) { const d1 = new Date(date1), d2 = new Date(date2)
throw new TypeError('无效的日期格式') if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return 0
return Math.ceil(Math.abs(d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24))
} }
// 设置时间部分为00:00:00,只比较日期部分
d1.setHours(0, 0, 0, 0)
d2.setHours(0, 0, 0, 0)
// 计算两个日期之间的毫秒差
const diffTime = Math.abs(d2.getTime() - d1.getTime())
// 转换为天数
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays
}
function onSearch(res: any) { function onSearch(res: any) {
pageData.employmentList = []
pageData.search.pageNo = 1 pageData.search.pageNo = 1
pageData.dataTotal = 0 if (pageData.currentEmploymentId === 1) pageData.search.createBy = res.value
pageData.hasMore = 'more' else pageData.search.keyword = res.value
if (pageData.currentEmploymentId === 1) { pageData.employmentList = []
pageData.search.publishstatu = 1
pageData.search.createBy = res.value
}
if (pageData.currentEmploymentId === 2) {
pageData.search.keyword = res.value
}
getEmploymentList() getEmploymentList()
} }
// 取消搜索了
function onSearchCancel() { async function resetGetEmploymentList() {
resetGetEmploymentList()
}
function resetGetEmploymentList() {
pageData.employmentList = [] pageData.employmentList = []
pageData.searchValue = ''
pageData.search.pageNo = 1 pageData.search.pageNo = 1
pageData.dataTotal = 0
pageData.hasMore = 'more' pageData.hasMore = 'more'
if (pageData.currentEmploymentId === 1) {
pageData.search.publishstatu = 1
pageData.search.createBy = ''
}
pageData.search.keyword = '' pageData.search.keyword = ''
getEmploymentList() pageData.search.createBy = ''
return getEmploymentList()
} }
function loadMoreData() { function loadMoreData() {
if (pageData.hasMore === 'noMore' || pageData.loading) return
pageData.search.pageNo++ pageData.search.pageNo++
getEmploymentList() getEmploymentList()
} }
/* onReachBottom(() => {
console.log('触底了')
if (pageData.total <= pageData.employmentList.length) {
return
}
pageData.search.pageNo++
getEmploymentList()
}) */
onNavigationBarButtonTap(() => {
Navigate.to(`/pages/linghuoyonggong/components/yonggongmap?currentEmploymentId=${pageData.currentEmploymentId}`)
})
// 查看找人干活详情
function onDetailsClick(item: any) { function onDetailsClick(item: any) {
const param = encodeURIComponent(JSON.stringify({ id: item.id, villageName: item.villageName })) const param = encodeURIComponent(JSON.stringify({ id: item.id, villageName: item.villageName || item.name }))
Navigate.to(`/pages/linghuoyonggong/details?param=${param}`) Navigate.to(`/pages/linghuoyonggong/details?param=${param}`)
} }
// 获取用户位置
function onMapModeClick() {
Navigate.to(`/pages/linghuoyonggong/components/yonggongmap?currentEmploymentId=${pageData.currentEmploymentId}`)
}
function getUserLocation() { function getUserLocation() {
uni.getLocation({ uni.getLocation({
type: 'wgs84', type: 'wgs84',
success: (res) => { success: (res) => {
if (res.longitude !== 0 && res.latitude !== 0) { if (res.longitude !== 0) {
pageData.longitude = res.longitude pageData.longitude = res.longitude
pageData.latitude = res.latitude pageData.latitude = res.latitude
} }
},
fail: (err) => {
console.error('获取位置失败:', err)
uni.showToast({
title: '获取位置失败,请检查定位权限',
icon: 'none',
duration: 2000,
})
},
})
} }
// 计算距离显示 })
function getDistanceText(item) {
if (!item || !item.lon || !pageData.longitude) {
return '未知'
} }
// 使用turf计算距离
const from = turf.point([pageData.longitude, pageData.latitude])
const to = turf.point([item.lon, item.lat])
const distance = turf.distance(from, to, { units: 'kilometers' })
if (distance < 1) { function getDistanceText(item) {
return `${Math.round(distance * 1000)}m` if (!item || !item.lon || !pageData.longitude) return ''
} const distance = turf.distance(turf.point([pageData.longitude, pageData.latitude]), turf.point([item.lon, item.lat]), { units: 'kilometers' })
return `${distance.toFixed(1)}km` return distance < 1 ? `${Math.round(distance * 1000)}m` : `${distance.toFixed(1)}km`
} }
</script> </script>
<template> <template>
<view class="page"> <view class="page">
<scroll-view scroll-y="true" style="height: 100%" class="codefun-flex-col" @scrolltolower="loadMoreData">
<!-- <view class="codefun-mt-14 codefun-flex-row group_2 gap-2">
<view
v-for="tab in pageData.categoryTabs"
:key="tab.id"
class="codefun-flex-col codefun-justify-start codefun-items-center"
:class="[tab.id === pageData.search.type ? 'text-wrapper' : 'text-wrapper_2']"
@click="onCategoryTabClick(tab)"
>
<text class="font_2 text_2">
{{ tab.name }}
</text>
</view>
</view> -->
<view class="codefun-mt-14 codefun-flex-col group_3">
<view class="top-search-view"> <view class="top-search-view">
<uni-search-bar <uni-search-bar radius="100" v-model="searchValue" placeholder="搜索标题、地点或技能" cancelButton="none" @confirm="onSearch" @clear="resetGetEmploymentList" />
radius="100"
v-model="pageData.searchValue"
placeholder="请输入搜索内容"
clearButton="auto"
cancelButton="none"
@confirm="onSearch"
@cancel="onSearchCancel"
@clear="onSearchCancel"
/>
</view>
<view class="codefun-flex-row section_2">
<view
v-for="tab in pageData.employmentTabs"
:key="tab.id"
class="codefun-flex-col codefun-justify-start codefun-items-center text-50p"
:class="[tab.id === pageData.currentEmploymentId ? 'text-wrapper_3' : 'codefun-self-start']"
@click="onEmploymentTabClick(tab)"
>
<text class="font_2">
{{ tab.name }}
</text>
</view>
</view>
<view class="codefun-flex-col codefun-relative list">
<view v-if="!pageData.employmentList || pageData.employmentList.length == 0" style="height: 700rpx">
<fui-empty marginTop="100" src="/static/images/no-data.png" title="暂无数据" />
</view>
<template v-else>
<template v-if="pageData.currentEmploymentId == 1">
<view
class="codefun-flex-col list-item"
v-for="item in pageData.employmentList"
:key="item.id"
@click="onEmploymentItemClick(item)"
>
<view class="codefun-flex-row">
<image class="image_7" :src="item.picture" lazy-load />
<view class="codefun-flex-col codefun-flex-1 codefun-self-center group_4">
<view class="codefun-flex-row codefun-justify-between codefun-items-center">
<text class="codefun-self-start font">{{ item.name }}</text>
<view>
<image
class="codefun-self-start image_8"
src="/static/images/codefun/c98744e63719b5413f260ec6a899ee20.png"
/>
<text class="codefun-self-start font_3 text_9">{{ item.area }}</text>
</view>
</view>
<view
class="codefun-flex-row codefun-justify-between codefun-items-center codefun-self-stretch mt-11"
>
<view class="flex codefun-items-center gap-1">
<image class="image_9" src="/static/images/time.svg" />
<text class="font_4"
>预计{{
item.daysDiff ||
getDaysDiff(item.starttime, item.estimatedendtime)
}}</text
>
</view>
<view class="flex codefun-items-center gap-1">
<image class="image_9" src="/static/images/person.svg" />
<text class="font_4">需要{{ item.workers }}</text>
</view>
<view class="flex codefun-items-center gap-1">
<image class="image_9" src="/static/images/money.svg" />
<text class="font_4 text_10">{{ item.price }}</text>
</view>
</view>
</view>
</view>
<view
class="codefun-mt-8 codefun-flex-row codefun-justify-between codefun-items-center"
>
<view class="flex-center">
<fui-rate :score="item.urgentdegree" :size="36" />
<text class="font_5 ml-1">{{ item.urgentdegree }}</text>
</view>
<view
v-if="pageData.currentEmploymentId !== 2"
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_4"
@click.stop="onQuoteClick(item)"
>
<text class="font_6 text_12">我想去</text>
</view> </view>
<view class="tab-box">
<view v-for="tab in employmentTabs" :key="tab.id" class="tab-item" :class="{ active: tab.id === currentEmploymentId }" @click="onEmploymentTabClick(tab)">
<text class="tab-text">{{ tab.name }}</text>
</view> </view>
</view> </view>
</template>
<template v-else> <scroll-view scroll-y class="list-scroll" @scrolltolower="loadMoreData">
<fui-swipeaction-group> <view class="list-container">
<fui-swipe-action <view v-if="!employmentList.length && !pageData.loading" class="empty-box">
class="yr-list-swipe" <fui-empty src="/static/images/no-data.png" title="暂无相关招聘信息" />
v-for="item in pageData.employmentList"
:key="item.id"
:buttons="pageData.swipeActionButtons"
@click="swipeActionOnClick($event, item)"
>
<fui-list-cell class="yr-list-cell" :padding="['0rpx', '0rpx']" :highlight="false">
<view class="work_list_view">
<view class="d-flex j-sb">
<view class="left-width village_number_view">
<view class="village_view text_overflow_ellipsis">{{
item.villageName
}}</view>
<view class="d-flex align-center"
><image
class="avatar_icon"
src="/static/images/linghuoyonggong/avatar.png"
/><text class="text-color"
>待工人数{{ item.workers }}</text
></view
>
</view>
<view
class="d-flex align-center justify-center right-width village_distance"
>
<image
class="distance_icon"
src="/static/images/linghuoyonggong/distance.png"
/>
<text class="distance_val text-color">{{
getDistanceText(item)
}}</text>
</view>
</view>
<view class="d-flex j-sb skill_details_view" style="padding-bottom: 0px">
<view class="left-width d-flex j-sb align-center">
<image
class="skill_icon"
src="/static/images/linghuoyonggong/skill.png"
/>
<view class="skill_view text_overflow_ellipsis"
>技能:{{
item.skills.length ? item.skills.join('、') : '未填写'
}}</view
>
</view>
</view>
<view class="d-flex j-sb skill_details_view">
<view class="left-width d-flex j-sb align-center">
<image
class="skill_icon"
src="/static/images/linghuoyonggong/address.png"
/>
<view class="skill_view text_overflow_ellipsis"
>地址:{{ item.villageFullName }}</view
>
</view>
<view class="right-width see_details_btn" @click="onDetailsClick(item)"
>查看详情</view
>
</view>
</view> </view>
</fui-list-cell>
</fui-swipe-action> <view v-for="item in employmentList" :key="item.id" class="service-card" @click="onEmploymentItemClick(item)">
</fui-swipeaction-group> <view class="service-main">
</template> <view class="service-img-box">
<!-- <template v-else> <image class="service-img" :src="item.picture || item.enterpriseLogoUrl || '/static/images/linghuoyonggong/avatar.png'" mode="aspectFill" lazy-load />
<view class="work_list_view" v-for="item in pageData.employmentList" :key="item.id"> <view class="urgent-tag" v-if="item.urgentdegree > 3"></view>
<view class="d-flex j-sb">
<view class="left-width village_number_view">
<view class="village_view text_overflow_ellipsis">{{ item.villageName }}</view>
<view class="d-flex align-center"><image class="avatar_icon" src="/static/images/linghuoyonggong/avatar.png" /><text class="text-color">待工人数{{item.workers}}名</text></view>
</view> </view>
<view class="d-flex align-center justify-center right-width village_distance"> <view class="service-info">
<image class="distance_icon" src="/static/images/linghuoyonggong/distance.png" /> <text class="service-title">{{ item.name || item.villageName }}</text>
<text class="distance_val text-color">{{getDistanceText(item)}}</text> <view class="service-tags">
<text class="s-tag" v-if="item.workers">{{ item.workers }}</text>
<text class="s-tag" v-if="item.daysDiff">{{ item.daysDiff }}</text>
<text class="s-tag skills" v-for="(skill, sIdx) in (item.skillList || []).slice(0, 2)" :key="sIdx">{{ skill }}</text>
</view> </view>
<view class="service-price-row">
<text class="price-value">{{ item.price || '面议' }}</text>
<text class="price-unit" v-if="item.price">元/天</text>
</view> </view>
<view class="d-flex j-sb skill_details_view" style="padding-bottom: 0px;">
<view class="left-width d-flex j-sb align-center">
<image class="skill_icon" src="/static/images/linghuoyonggong/skill.png" />
<view class="skill_view text_overflow_ellipsis">技能:{{item.skills.length ? item.skills.join("、") : '未填写'}}</view>
</view> </view>
</view> </view>
<view class="d-flex j-sb skill_details_view"> <view class="service-footer">
<view class="left-width d-flex j-sb align-center"> <view class="footer-left">
<image class="skill_icon" src="/static/images/linghuoyonggong/address.png" /> <fui-icon name="location" :size="24" color="#999"></fui-icon>
<view class="skill_view text_overflow_ellipsis">地址:{{item.villageFullName}}</view> <text class="footer-text">{{ item.areaLabel }}</text>
<text class="distance-text" v-if="getDistanceText(item)">{{ getDistanceText(item) }}</text>
</view> </view>
<view class="right-width see_details_btn" @click="onDetailsClick(item)">查看详情</view> <view class="action-btn" :class="{ 'bg-orange': currentEmploymentId == 2 }">
{{ currentEmploymentId == 1 ? '我也去' : '查看详情' }}
</view> </view>
</view> </view>
</template> -->
</template>
</view> </view>
<fui-loadmore v-if="pageData.loading" />
<view v-if="pageData.hasMore === 'noMore' && employmentList.length > 0" class="no-more">- 已经到底了 -</view>
</view> </view>
<view v-if="pageData.hasMore == 'noMore'" class="no-more">没有更多数据了</view>
</scroll-view> </scroll-view>
<!-- 统一的动效发布按钮 -->
<view class="fab-container" @click="handlePublish">
<view class="ripple"></view>
<view class="ripple ripple-2"></view>
<view class="fab-entry">
<fui-icon name="plus" :size="48" color="#fff"></fui-icon>
<text>发布</text>
</view>
</view> </view>
<!-- 确认对话框 -->
<ConfirmDialog <!-- 地图模式切换 (悬浮胶囊) -->
v-model:show="pageData.isShowConfirmDialog" <view class="view-toggle-pill" @click="onMapModeClick">
title="删除提醒" <fui-icon name="map" :size="32" color="#5db66f"></fui-icon>
:content="pageData.dialogContent" <text class="toggle-text">地图模式</text>
cancelText="取消"
confirmText="确认"
@confirm="handleConfirmDialog"
/>
<fui-fab position="right" distance="10" bottom="240" width="96" @click="handlePublish">
<view class="text-white text-center">
<view class="fab-icon" />
<view style="font-size: 24rpx">发布</view>
</view> </view>
</fui-fab>
<RegisterDialog ref="registerDialogRef" /> <RegisterDialog ref="registerDialogRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" /> </view>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
body { .page { background-color: #f7f8fa; height: 100vh; display: flex; flex-direction: column; font-family: 'DingTalk Sans', sans-serif; }
background-color: #e6f5e8; .top-search-view { background-color: #fff; padding: 10rpx 20rpx; }
} .tab-box {
.mt-19 { display: flex; background-color: #fff; padding: 0 30rpx 20rpx;
margin-top: 38rpx; .tab-item {
} flex: 1; height: 70rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: #666; position: relative;
.ml-55 { &.active {
margin-left: 110rpx; color: #5db66f; font-weight: bold;
} &::after { content: ''; position: absolute; bottom: 0; width: 40rpx; height: 6rpx; background-color: #5db66f; border-radius: 3rpx; }
.mt-11 {
margin-top: 22rpx;
}
.ml-5 {
margin-left: 10rpx;
}
.mt-269 {
margin-top: 538rpx;
}
.page {
// padding-bottom: 128rpx;
background-color: #e6f5e8;
mix-blend-mode: NOTTHROUGH;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
height: calc(100vh - 88rpx);
.top-search-view {
height: 64rpx;
}
.section {
padding: 26rpx 28rpx 26rpx 36rpx;
background-color: #5db66f;
mix-blend-mode: NOTTHROUGH;
.image {
border-radius: 64rpx;
width: 108rpx;
height: 42rpx;
}
.image_2 {
mix-blend-mode: NOTTHROUGH;
width: 34rpx;
height: 22rpx;
}
.image_3 {
mix-blend-mode: NOTTHROUGH;
width: 30rpx;
height: 22rpx;
}
.image_4 {
width: 48rpx;
height: 22rpx;
}
.group {
padding: 8rpx 0 8rpx 6rpx;
.image_6 {
mix-blend-mode: NOTTHROUGH;
width: 14rpx;
height: 26rpx;
}
.pos_2 {
position: absolute;
left: 6rpx;
top: 50%;
transform: translateY(-50%);
}
.text {
color: #ffffffe6;
line-height: 29.5rpx;
}
.image_5 {
width: 44rpx;
height: 44rpx;
}
.pos {
position: absolute;
right: 0.66rpx;
top: 50%;
transform: translateY(-50%);
}
}
}
.group_2 {
padding: 0 28rpx;
.text-wrapper {
padding: 24rpx 0;
flex: 1 1 120rpx;
background-color: #5db66f;
border-radius: 19998rpx;
mix-blend-mode: NOTTHROUGH;
height: 72rpx;
.text_2 {
color: #ffffff;
}
}
.text-wrapper_2 {
flex: 1 1 120rpx;
padding: 24rpx 0;
background-color: #ffffff;
border-radius: 19998rpx;
mix-blend-mode: NOTTHROUGH;
height: 72rpx;
.text_3 {
line-height: 25.88rpx;
}
.text_4 {
line-height: 26.16rpx;
}
.text_5 {
line-height: 25.96rpx;
}
.text_6 {
line-height: 25.68rpx;
}
}
}
.group_3 {
padding: 0 28rpx;
// height: 685rpx;
.top-search-view {
.uni-searchbar {
padding: 0rpx !important;
}
}
.section_2 {
margin-top: 48rpx;
padding-bottom: 8rpx;
background-color: #ffffff66;
border-radius: 32rpx;
border-left: solid 2rpx #ffffffcc;
border-right: solid 2rpx #ffffffcc;
border-top: solid 2rpx #ffffffcc;
border-bottom: solid 2rpx #ffffffcc;
.text-50p {
width: 50%;
padding: 0.75rem 0 1.625rem;
}
.text-wrapper_3 {
background-color: #ffffff;
border-radius: 32rpx;
.text_7 {
color: #333333;
line-height: 26.16rpx;
}
.font_2 {
color: #1f2937;
}
}
.codefun-self-start {
.font_2 {
color: #5db66f;
}
}
.text_8 {
margin-top: 20rpx;
color: #5db66f;
}
}
.list {
margin-top: -44rpx;
padding: 0 24rpx;
background-color: #ffffff;
border-radius: 12.76rpx;
mix-blend-mode: NOTTHROUGH;
.list-item {
padding: 20rpx 0;
border-bottom: solid 2rpx #eeeeee;
.group_4 {
margin-left: 16rpx;
.image_9 {
width: 24rpx;
height: 24rpx;
}
.font_4 {
font-size: 24rpx;
font-family: DingTalk Sans;
// line-height: 22rpx;
color: #999999;
}
.text_10 {
// line-height: 23.36rpx;
}
}
.image_8 {
margin-left: 8rpx;
margin-top: 16rpx;
width: 22rpx;
height: 26rpx;
}
.font_3 {
font-size: 24rpx;
font-family: DingTalk Sans;
line-height: 17.6rpx;
color: #5db66f;
}
.text_9 {
margin: 20rpx 16rpx 0 8rpx;
line-height: 19.42rpx;
}
.font_5 {
font-size: 28rpx;
font-family: DingTalk Sans;
color: #999999;
}
.text_11 {
margin-left: 172rpx;
}
.text-wrapper_4 {
padding: 12rpx 0;
background-color: #5db66f;
border-radius: 400rpx;
mix-blend-mode: NOTTHROUGH;
width: 136rpx;
height: 48rpx;
.font_6 {
font-size: 24rpx;
font-family: DingTalk Sans;
line-height: 22rpx;
color: #ffffff;
}
.text_12 {
line-height: 22.18rpx;
}
}
}
}
}
.font_2 {
font-size: 28rpx;
font-family: DingTalk Sans;
line-height: 26.02rpx;
color: #1f2937;
}
.font {
font-size: 32rpx;
font-family: DingTalk Sans;
line-height: 29.82rpx;
color: #333333;
}
.image_7 {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
} }
.image_12 {
margin-right: 58rpx;
} }
} }
::v-deep .fui-fab__btn-main { .list-scroll { flex: 1; overflow: hidden; }
background: linear-gradient(124.25deg, #a5d63f 0%, #5db66f 100%) !important; .list-container { padding: 20rpx; }
box-shadow: 0px 1px 8px #5db66f;
} .service-card {
background-color: #fff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 24rpx; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
/* .yr-list-swipe{ .service-main { display: flex; }
width: 680rpx; .service-img-box {
.yr-list-cell{ width: 160rpx; height: 160rpx; border-radius: 12rpx; overflow: hidden; background-color: #f8f8f8; position: relative; flex-shrink: 0;
width: 680rpx; .service-img { width: 100%; height: 100%; }
} .urgent-tag { position: absolute; left: 0; top: 0; background: #ff4d4f; color: #fff; font-size: 20rpx; padding: 4rpx 12rpx; border-bottom-right-radius: 12rpx; }
} */ }
.work_list_view { .service-info {
// border-bottom: 2rpx solid #EEEEEE; flex: 1; margin-left: 20rpx; display: flex; flex-direction: column;
width: 100%; .service-title { font-size: 30rpx; color: #333; font-weight: bold; line-height: 1.4; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; }
padding-top: 24rpx; .service-tags {
.text-color { display: flex; flex-wrap: wrap; margin-top: 12rpx;
color: #5db66f; .s-tag { padding: 4rpx 12rpx; background-color: #f5f5f5; color: #666; font-size: 22rpx; border-radius: 4rpx; margin-right: 12rpx; margin-bottom: 8rpx; &.skills { background-color: #e6f7ff; color: #1890ff; } }
font-size: 24rpx; }
} .service-price-row { margin-top: auto; display: flex; align-items: baseline; .price-value { font-size: 36rpx; color: #ff4d4f; font-weight: bold; } .price-unit { font-size: 24rpx; color: #999; margin-left: 4rpx; } }
.d-flex { }
display: flex; .service-footer {
} margin-top: 24rpx; display: flex; justify-content: space-between; align-items: center; padding-top: 20rpx; border-top: 1rpx dashed #eee;
.j-sb { .footer-left {
justify-content: space-between; display: flex; align-items: center; flex: 1; overflow: hidden;
} .footer-text { font-size: 22rpx; color: #999; margin-left: 4rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.text_overflow_ellipsis { .distance-text { font-size: 22rpx; color: #5db66f; margin-left: 12rpx; font-weight: bold; flex-shrink: 0; }
white-space: nowrap; }
overflow: hidden; .action-btn { padding: 10rpx 28rpx; background-color: #5db66f; color: #fff; font-size: 24rpx; border-radius: 30rpx; margin-left: 20rpx; &.bg-orange { background-color: #fa8c16; } }
text-overflow: ellipsis; }
} }
.left-width {
width: 440rpx; /* 动态发布按钮 */
} .fab-container {
.right-width { position: fixed; right: 30rpx; bottom: 150rpx; width: 110rpx; height: 110rpx; z-index: 100; display: flex; align-items: center; justify-content: center;
width: 140rpx; .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; }
.village_number_view { .fab-entry {
.village_view { position: relative; width: 110rpx; height: 110rpx; 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 8rpx 30rpx rgba(46, 139, 87, 0.5); animation: breathe 2.5s infinite ease-in-out; z-index: 2;
height: 40rpx; text { font-size: 18rpx; color: #fff; margin-top: -4rpx; font-weight: bold; }
line-height: 40rpx; }
margin-bottom: 12rpx; }
font-size: 32rpx;
color: #333333; @keyframes ripple { 0% { transform: scale(1); opacity: 0.4; } 100% { transform: scale(1.8); opacity: 0; } }
} @keyframes breathe { 0%, 100% { transform: scale(1); box-shadow: 0 8rpx 30rpx rgba(46, 139, 87, 0.5); } 50% { transform: scale(1.08); box-shadow: 0 12rpx 45rpx rgba(46, 139, 87, 0.7); } }
.avatar_icon {
width: 24rpx; /* 地图切换胶囊 */
height: 24rpx; .view-toggle-pill {
margin-right: 10rpx; position: fixed;
} left: 50%;
} transform: translateX(-50%);
.village_distance { bottom: 60rpx;
height: 26rpx; background-color: rgba(255, 255, 255, 0.9);
margin-top: 16rpx; backdrop-filter: blur(10px);
.distance_icon { padding: 16rpx 40rpx;
width: 24rpx; border-radius: 100rpx;
height: 28rpx;
}
.distance_val {
margin-left: 6rpx;
}
}
.skill_details_view {
margin-top: 10rpx;
padding-bottom: 24rpx;
.skill_icon {
width: 24rpx;
height: 26rpx;
}
.skill_view {
width: 440rpx;
font-size: 24rpx;
height: 40rpx;
line-height: 40rpx;
color: #999999;
margin-left: 10rpx;
}
.see_details_btn {
height: 48rpx;
border-radius: 200rpx;
background: #5db66f;
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
padding: 20rpx; box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
font-size: 24rpx; border: 1rpx solid #eee;
color: #ffffff; z-index: 110;
} transition: all 0.3s;
&:active {
transform: translateX(-50%) scale(0.95);
background-color: #fff;
} }
.toggle-text {
font-size: 26rpx;
color: #333;
font-weight: bold;
margin-left: 12rpx;
} }
.no-more {
padding-top: 40rpx;
padding-bottom: 60rpx;
width: 750rpx;
text-align: center;
font-size: 28rpx;
color: #999999;
} }
.no-more { text-align: center; font-size: 24rpx; color: #ccc; padding: 30rpx 0; }
.empty-box { padding-top: 100rpx; }
</style> </style>
...@@ -6,236 +6,99 @@ ...@@ -6,236 +6,99 @@
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong' import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import { getCalculateAge } from '@/utils/date' import { getCalculateAge } from '@/utils/date'
import * as UserInfoAPI from '@/api/model/userInfo' import * as UserInfoAPI from '@/api/model/userInfo'
import { getCodeByText } from '@/utils/areaData'
import { useDictStore } from '@/store/modules/dict' import { useDictStore } from '@/store/modules/dict'
import { getDictData } from '@/utils/dict/area' import AreaPicker from '@/components/AreaPicker/index.vue'
const dictStore = useDictStore() const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
let pageType = 'add'
onLoad((option) => { onLoad((option) => {
// 获取数据详情
if (option && option.id) { if (option && option.id) {
pageType = 'edit' uni.setNavigationBarTitle({ title: '编辑劳动力池' })
uni.setNavigationBarTitle({ title: '编辑' })
getDetails(option.id) getDetails(option.id)
} else { } else {
// 获取当前位置 uni.setNavigationBarTitle({ title: '登记劳动力池' })
getCurrentAddressInfo() getCurrentAddressInfo()
} }
getProvince()
// 获取字典值
getDictVal() getDictVal()
}) })
onShow(() => { onShow(() => {
// 数据字典赋值
initDict() initDict()
}) })
const VerificationInfo1 = {
addr: '请选择村',
contactName: '请输入联系人',
contactMobile: '请输入联系电话',
}
const VerificationInfo2 = {
name: '请输入姓名',
mobile: '请输入手机号',
genderZh: '请选择性别',
birthday: '请选择出生日期',
eduZh: '请选择学历',
attr: '请选择人员属性',
skill: '请选择人员技能',
}
const skillPopupRef = ref(null) const skillPopupRef = ref(null)
const tempWorkersParam = reactive({ const tempWorkersParam = reactive({
id: null, id: null,
laborId: null, // 用工信息ID name: '',
name: '', // 工人姓名 mobile: '',
mobile: '', // 手机号 gender: 1,
gender: null, // 性别 birthday: '',
genderZh: '', edu: 1,
birthday: '', // 出生日期
edu: null, // 学历
eduZh: '', eduZh: '',
attr: '', // 人员属性 attr: '普通工',
skill: '', // 人员技能 skill: '',
}) })
let startDate = getDate('start')
const endDate = getDate('end')
// 字典值
const DictData = reactive({
sexArr: [], // 性别
educationArr: [], // 学历
socialattributesArr: [], // 人员属性
dictValArr: [], // 过渡
dictType: '',
const DictData = reactive({
sexArr: [],
educationArr: [],
socialattributesArr: [],
isSkillPopupShow: false, isSkillPopupShow: false,
skillValue: [],
skillOptionsVal: [], skillOptionsVal: [],
}) })
const pageData = reactive({ const pageData = reactive({
province: [], // 省数组 loading: false,
city: [], // 市数组
district: [], // 区数组
street: [], // 街道数组
mulSelect: [], // 四级联动显示数组,该数组的值为[[province],[city],[district],[street]]
provinceId: 0, // 省的id
cityId: 0, // 市的id
districtId: 0, // 区的id
isActive: false, isActive: false,
address: '', isPersonPopupShow: false,
birthdayPickerIsShow: false, datePickerShow: false,
attrPickerShow: false,
dictValArr: [],
dictType: '',
laborParam: { laborParam: {
id: null, id: null,
lon: '', // 经度 lon: '',
lat: '', // 纬度 lat: '',
contactName: '', // 联系人姓名 contactName: '',
contactMobile: '', // 联系人电话 contactMobile: '',
provinceName: '湖南省', // 所属省名称 provinceName: '湖南省',
provinceCode: '43', // 所属省编码 provinceCode: '43',
cityName: '',
cityName: '', // 所属市名称 cityCode: '',
cityCode: '', // 所属市编码 districtName: '',
districtName: '', // 所属区县名称 districtCode: '',
districtCode: '', // 所属区县编码 townName: '',
townName: '', // 所属乡镇名称 townCode: '',
townCode: '', // 所属乡镇编码 villageName: '',
villageName: '', // 村名称 villageCode: '',
villageCode: '', // 村编码
addr: '', addr: '',
}, },
workersParam: [], workersParam: [],
actionType: 'add', actionType: 'add',
editWorkersIndex: 0, editWorkersIndex: 0,
showArea: false,
loading: false,
isPersonPopupShow: false,
datePickerShow: false,
attrPickerShow: false,
show: {
time1: false,
time2: false,
area: false,
urgentdegree: false,
type: false,
},
options: {
area: [],
urgentdegree: [],
type: [],
},
form: {
id: '',
name: '',
content: '',
price: '',
typeText: '',
type: null,
area: '',
areaText: '',
province: '',
city: '',
country: '',
address: '',
urgentdegreeText: '',
urgentdegree: null,
starttime: '',
estimatedendtime: '',
picture: null,
pictureObj: null,
longitude: '',
latitude: '',
},
position: [],
rules: [
{
name: 'type',
rule: ['required'],
msg: ['请选择用工类型'],
},
{
name: 'name',
rule: ['required'],
msg: ['请输入标题'],
},
{
name: 'workers',
rule: ['required'],
msg: ['请输入工人数量'],
},
{
name: 'price',
rule: ['required'],
msg: ['请输入用工单价'],
},
{
name: 'starttime',
rule: ['required'],
msg: ['请选择开始时间'],
},
{
name: 'estimatedendtime',
rule: ['required'],
msg: ['请选择预计结束时间'],
},
{
name: 'content',
rule: ['required'],
msg: ['请输入工作内容'],
},
{
name: 'area',
rule: ['required'],
msg: ['请选择地区'],
},
{
name: 'address',
rule: ['required'],
msg: ['请输入详细地址'],
},
{
name: 'urgentdegree',
rule: ['required'],
msg: ['请选择紧急程度'],
},
{
name: 'picture',
rule: ['required'],
msg: ['请上传图片'],
},
],
})
// 获取字典值
function getDictVal() {
LinghuoyonggongAPI.gitListByCodeDict({ code: 'sex' }).then((res) => {
DictData.sexArr = res
})
LinghuoyonggongAPI.gitListByCodeDict({ code: 'education' }).then((res) => {
DictData.educationArr = res
})
LinghuoyonggongAPI.gitListByCodeDict({ code: 'socialattributes' }).then((res) => {
DictData.socialattributesArr = res
}) })
const { laborParam, workersParam } = toRefs(pageData)
async function initDict() {
// ...
}
function getDictVal() {
LinghuoyonggongAPI.gitListByCodeDict({ code: 'sex' }).then(res => DictData.sexArr = res)
LinghuoyonggongAPI.gitListByCodeDict({ code: 'education' }).then(res => DictData.educationArr = res)
LinghuoyonggongAPI.gitListByCodeDict({ code: 'socialattributes' }).then(res => DictData.socialattributesArr = res)
queryByCategoryAndCode(0, null) queryByCategoryAndCode(0, null)
} }
function queryByCategoryAndCode(code, e) { function queryByCategoryAndCode(code, e) {
LinghuoyonggongAPI.queryByCategoryAndCode({ category: 1, code }).then((res) => { LinghuoyonggongAPI.queryByCategoryAndCode({ category: 1, code }).then((res) => {
if (res.length) { if (res.length) {
const dataArr = [] const dataArr = res.map(i => ({ text: i.name, value: i.code }))
for (let i = 0; i < res.length; i++) {
const obj = { text: '', value: '' }
obj.text = res[i].name
obj.value = res[i].code
dataArr.push(obj)
}
if (!DictData.skillOptionsVal.length) { if (!DictData.skillOptionsVal.length) {
DictData.skillOptionsVal = dataArr DictData.skillOptionsVal = dataArr
} else { } else {
...@@ -246,1072 +109,542 @@ ...@@ -246,1072 +109,542 @@
} }
}) })
} }
// 选择技能完成
function handleAreaConfirm(e) {
const areaAttr = ['province', 'city', 'district', 'town', 'village']
const { text, value } = e
for (let i = 0; i < text.length; i++) {
pageData.laborParam[`${areaAttr[i]}Name`] = text[i]
pageData.laborParam[`${areaAttr[i]}Code`] = value[i]
}
pageData.laborParam.villageName = text[text.length - 1]
pageData.laborParam.villageCode = value[value.length - 1]
pageData.laborParam.addr = e.fullText
pageData.isActive = true
}
function selectCompleteSkill(e) { function selectCompleteSkill(e) {
// tempWorkersParam.skill = e.text.join('');
const valText = e.text[e.text.length - 1] const valText = e.text[e.text.length - 1]
if (tempWorkersParam.skill.length) { if (tempWorkersParam.skill.length) {
const skillArray = tempWorkersParam.skill.split('、') const arr = tempWorkersParam.skill.split('、')
if (skillArray.length < 3) { if (arr.length < 3) tempWorkersParam.skill += `、${valText}`
tempWorkersParam.skill = `${tempWorkersParam.skill}${valText}` else uni.showToast({ title: '最多选3项', icon: 'none' })
} else {
toastRef.value.show({
type: 'error',
text: '最多只能选择3项技能!',
})
}
} else { } else {
tempWorkersParam.skill = valText tempWorkersParam.skill = valText
} }
DictData.isSkillPopupShow = false DictData.isSkillPopupShow = false
} }
// 技能值发生了改变
function changeSkill(e) { function changeSkill(e) {
const val = e.value queryByCategoryAndCode(e.value, e)
queryByCategoryAndCode(val, e)
} }
// 学历和属性的选择
function attrChange(e) { function attrChange(e) {
if (DictData.dictType == 'education') { if (pageData.dictType == 'education') {
const educationArr = DictData.educationArr
tempWorkersParam.eduZh = e.value tempWorkersParam.eduZh = e.value
for (let i = 0; i < educationArr.length; i++) { const item = DictData.educationArr.find(i => i.itemText == e.value)
if (e.value == educationArr[i].itemText) { if (item) tempWorkersParam.edu = Number(item.itemValue)
tempWorkersParam.edu = Number.parseInt(educationArr[i].itemValue) } else {
break
}
}
}
if (DictData.dictType == 'socialattributes') {
tempWorkersParam.attr = e.value tempWorkersParam.attr = e.value
} }
pageData.attrPickerShow = false pageData.attrPickerShow = false
} }
// 出生日期的选择
function dateBirthChange(e) { function dateBirthChange(e) {
tempWorkersParam.birthday = e.result tempWorkersParam.birthday = e.result
pageData.datePickerShow = false pageData.datePickerShow = false
} }
function getDate(type) {
const date = new Date()
let year = date.getFullYear()
let month: any = date.getMonth() + 1
let day: any = date.getDate()
if (type === 'start') {
year = year - 18
} else if (type === 'end') {
year = year + 10
}
month = month > 9 ? month : `0${month}`
day = day > 9 ? day : `0${day}`
return `${year}-${month}-${day}`
}
// 删除人员
function delPersonData(name) {
Message.confirm(`你真的要删除【${name}】吗?`, '温馨提示').then(async (confirm) => {
if (confirm) {
pageData.workersParam.splice(pageData.editWorkersIndex, 1)
pageData.isPersonPopupShow = false
}
})
}
// 添加人员
function addPersonData() {
tempWorkersParam.gender = Number.parseInt(tempWorkersParam.genderZh)
const entries = Object.entries(VerificationInfo2)
for (const [key, value] of entries) {
if (tempWorkersParam[key].trim().length === 0) {
toastRef.value.show({
type: 'error',
text: value,
})
return
}
}
if (pageData.actionType == 'edit') {
const editObj = pageData.workersParam[pageData.editWorkersIndex]
Object.keys(tempWorkersParam).forEach((item) => {
editObj[item] = tempWorkersParam[item]
})
} else {
pageData.workersParam.push(JSON.parse(JSON.stringify(tempWorkersParam)))
}
pageData.isPersonPopupShow = false
}
// 返回字典中的中文值
function returnDictZhVel(type: any, val: any) {
let valText = ''
if (type == 'gender') {
const arr = DictData.sexArr
for (let i = 0; i < arr.length; i++) {
if (val == Number.parseInt(arr[i].itemValue)) {
valText = arr[i].itemText
break
}
}
}
if (type == 'edu') {
const arr = DictData.educationArr
for (let i = 0; i < arr.length; i++) {
if (val == Number.parseInt(arr[i].itemValue)) {
valText = arr[i].itemText
break
}
}
}
return valText
}
// 选择出生日期
function onPickerTap(e) {
let dataVal = null
const itemTextArr = []
DictData.dictType = e
if (DictData.dictType == 'education') {
dataVal = DictData.educationArr
for (let i = 0; i < dataVal.length; i++) {
itemTextArr.push(dataVal[i].itemText)
}
}
if (DictData.dictType == 'socialattributes') {
dataVal = DictData.socialattributesArr
for (let i = 0; i < dataVal.length; i++) {
itemTextArr.push(dataVal[i].itemText)
}
}
DictData.dictValArr = itemTextArr
pageData.attrPickerShow = true
}
// 打开添加人员弹出框
function openAddPersonPopup() { function openAddPersonPopup() {
tempWorkersParam.id = null Object.assign(tempWorkersParam, {
tempWorkersParam.laborId = null id: null, name: '', mobile: '', gender: 1, birthday: '', edu: 1, eduZh: '', attr: '普通工', skill: ''
tempWorkersParam.name = '' })
tempWorkersParam.mobile = ''
tempWorkersParam.gender = ''
tempWorkersParam.genderZh = ''
tempWorkersParam.birthday = ''
tempWorkersParam.edu = ''
tempWorkersParam.eduZh = ''
tempWorkersParam.attr = ''
tempWorkersParam.skill = ''
pageData.actionType = 'add' pageData.actionType = 'add'
pageData.isPersonPopupShow = true pageData.isPersonPopupShow = true
} }
// 编辑找人干活待业人员
function editPersonPopup(index) {
const workersParamObj = pageData.workersParam[index]
workersParamObj.eduZh = returnDictZhVel('edu', workersParamObj.edu)
if (pageType == 'edit') {
workersParamObj.genderZh = `${workersParamObj.gender}`
startDate = workersParamObj.birthday
}
// workersParamObj.skill.split("、"); function editPersonPopup(index) {
Object.keys(workersParamObj).forEach((item) => { const item = pageData.workersParam[index]
tempWorkersParam[item] = workersParamObj[item] Object.assign(tempWorkersParam, item)
}) tempWorkersParam.eduZh = returnDictZhVel('edu', item.edu)
pageData.actionType = 'edit' pageData.actionType = 'edit'
pageData.editWorkersIndex = index pageData.editWorkersIndex = index
pageData.isPersonPopupShow = true pageData.isPersonPopupShow = true
} }
function colChange(e) {
switch (e.detail.column) { function addPersonData() {
case 0: // 选择市 if (!tempWorkersParam.name || !tempWorkersParam.mobile || !tempWorkersParam.birthday) {
pageData.provinceId = pageData.mulSelect[0][e.detail.value].code uni.showToast({ title: '请完善人员信息', icon: 'none' })
// 获取对应的 区县 return
LinghuoyonggongAPI.queryConditions({ parentCode: pageData.provinceId }).then((res) => {
pageData.city = res
pageData.mulSelect[1] = pageData.city
// 获取对应的乡镇
LinghuoyonggongAPI.queryConditions({ parentCode: pageData.city[0].code }).then((res) => {
pageData.district = res
pageData.mulSelect[2] = pageData.district
// 获取对应的村
LinghuoyonggongAPI.queryConditions({ parentCode: pageData.district[0].code }).then((res) => {
pageData.street = res
if (res.length) {
pageData.mulSelect[3] = pageData.street
} }
}) if (pageData.actionType == 'edit') {
}) pageData.workersParam[pageData.editWorkersIndex] = JSON.parse(JSON.stringify(tempWorkersParam))
}) } else {
break pageData.workersParam.push(JSON.parse(JSON.stringify(tempWorkersParam)))
case 1: // 选择区县
pageData.cityId = pageData.mulSelect[1][e.detail.value].code
// 获取对应的乡镇
LinghuoyonggongAPI.queryConditions({ parentCode: pageData.cityId }).then((res) => {
pageData.district = res
pageData.mulSelect[2] = pageData.district
// 获取对应的村
LinghuoyonggongAPI.queryConditions({ parentCode: pageData.district[0].code }).then((res) => {
if (res.length) {
pageData.street = res
pageData.mulSelect[3] = pageData.street
} }
}) pageData.isPersonPopupShow = false
}) }
break
case 2: // 选择镇 function delPersonData() {
pageData.districtId = pageData.mulSelect[2][e.detail.value].code uni.showModal({
LinghuoyonggongAPI.queryConditions({ parentCode: pageData.districtId }).then((res) => { title: '提示',
if (res.length) { content: '确定移除该人员吗?',
pageData.street = res success: (res) => {
pageData.mulSelect[3] = pageData.street if (res.confirm) {
pageData.workersParam.splice(pageData.editWorkersIndex, 1)
pageData.isPersonPopupShow = false
} }
})
break
default:
break
}
}
function getProvince() {
LinghuoyonggongAPI.queryConditions({ parentCode: 43 }).then((res) => {
pageData.province = []
pageData.province = res
pageData.mulSelect.push(pageData.province)
const code = pageData.province[0].code
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then((res) => {
pageData.city = res
pageData.mulSelect.push(pageData.city)
const code = pageData.city[0].code
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then((res) => {
pageData.district = res
pageData.mulSelect.push(pageData.district)
const code = pageData.district[0].code
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then((res) => {
pageData.street = res
if (res.length) {
pageData.mulSelect.push(pageData.street)
} }
})
})
})
}) })
} }
function pickerChange(e) { function returnDictZhVel(type, val) {
for (let i = 0; i < e.detail.value.length; i++) { const arr = type == 'gender' ? DictData.sexArr : DictData.educationArr
if (e.detail.value[i] === null) { const item = arr.find(i => Number(i.itemValue) == val)
e.detail.value[i] = 0 return item ? item.itemText : ''
}
}
const s_province = pageData.mulSelect[0][e.detail.value[0]]
const s_city = pageData.mulSelect[1][e.detail.value[1] || 0]
const s_district = pageData.mulSelect[2][e.detail.value[2] || 0]
let s_street = null
if (e.detail.value.length == 4) {
s_street = pageData.mulSelect[3][e.detail.value[3] || 0]
pageData.laborParam.addr = s_province.name + s_city.name + s_district.name + s_street.name
pageData.laborParam.cityName = s_province.name
pageData.laborParam.districtName = s_city.name
pageData.laborParam.townName = s_district.name
pageData.laborParam.villageName = s_street.name
pageData.laborParam.cityCode = s_province.code
pageData.laborParam.districtCode = s_city.code
pageData.laborParam.townCode = s_district.code
pageData.laborParam.villageCode = s_street.code
} else {
pageData.laborParam.addr = s_province.name + s_city.name + s_district.name
pageData.laborParam.cityName = s_province.name
pageData.laborParam.districtName = s_city.name
pageData.laborParam.townName = s_district.name
pageData.laborParam.cityCode = s_province.code
pageData.laborParam.districtCode = s_city.code
pageData.laborParam.townCode = s_district.code
} }
pageData.isActive = true
}
/* ****************************************************** */
const { show, options, form } = toRefs(pageData) function onPickerTap(type) {
async function initDict() { pageData.dictType = type
pageData.options.area = await getDictData() const arr = type == 'education' ? DictData.educationArr : DictData.socialattributesArr
pageData.options.urgentdegree = dictStore.getDictList.employment_urgent.map((item) => { pageData.dictValArr = arr.map(i => i.itemText)
return { pageData.attrPickerShow = true
value: item.value,
text: item.text,
}
})
pageData.options.type = dictStore.getDictList.employment_type.map((item) => {
return {
value: item.value,
text: item.text,
}
})
} }
function getCurrentAddressInfo() { function getCurrentAddressInfo() {
if (!uni.getStorageSync('location')) const loc = uni.getStorageSync('location')
return if (!loc) return
UserInfoAPI.location({ lon: loc.lon, lat: loc.lat }).then(res => {
const { lon, lat } = uni.getStorageSync('location') pageData.laborParam.lon = loc.lon
pageData.position = [lon, lat] pageData.laborParam.lat = loc.lat
UserInfoAPI.location({
lon,
lat,
}).then((res) => {
pageData.form.province = res.province
pageData.form.city = res.city
pageData.form.country = res.country
pageData.form.areaText = `${res.province}/${res.city}/${res.country}`
pageData.form.area = `${getCodeByText(res.province)},${getCodeByText(res.city)},${getCodeByText(res.country)}`
}) })
} }
function getDetails(id) { function getDetails(id) {
pageData.loading = true pageData.loading = true
LinghuoyonggongAPI.getLaborAppDetail({ id }) LinghuoyonggongAPI.getLaborAppDetail({ id }).then(res => {
.then((res) => { pageData.laborParam = { ...pageData.laborParam, ...res }
pageData.laborParam.addr = res.villageName pageData.laborParam.addr = res.villageName
pageData.laborParam.contactName = res.contactName pageData.workersParam = res.workers || []
pageData.laborParam.contactMobile = res.contactMobile
pageData.workersParam = res.workers
pageData.isActive = true pageData.isActive = true
}) }).finally(() => pageData.loading = false)
.finally(() => {
pageData.loading = false
})
} }
function parseUrlInfo(url) {
// 从URL中提取文件名
const pathParts = url.split('/')
const fileName = pathParts[pathParts.length - 1]
// 提取扩展名
const fileParts = fileName.split('.')
const extname = fileParts[fileParts.length - 1]
// 返回格式化的对象
return {
name: fileName,
extname,
url,
}
}
function handleChangeTime1(e) {
pageData.form.starttime = e.result
pageData.show.time1 = false
}
function handleChangeTime2(e) {
pageData.form.estimatedendtime = e.result
pageData.show.time2 = false
}
function handleChangetype(e) {
pageData.form.type = e.value
pageData.form.typeText = e.text
pageData.show.type = false
}
function handleChangeUrgentdegree(e) {
pageData.form.urgentdegree = e.value
pageData.form.urgentdegreeText = e.text
pageData.show.urgentdegree = false
}
function handleChangeAddress(e) {
pageData.form.areaText = e.text.join('/')
pageData.form.area = e.value.join(',')
pageData.show.area = false
}
const toastRef = ref()
const uploadRef = ref()
// 文件上传
function handleUpload(file) {
uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL
filePath: file.tempFiles[0].path,
name: 'file',
formData: {
biz: 'temp',
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) {
toastRef.value.show({
type: 'success',
text: '上传成功',
})
pageData.form.picture = data.message // 保存返回的图片信息
}
}
},
fail: (err) => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.form.picture = null
},
})
}
// 文件删除
function handleDelete(file) {
uploadRef.value.clearFiles()
pageData.form.picture = null
}
const formRef = ref()
function submit() {
if (pageData.position.length == 0) {
toastRef.value.show({
type: 'error',
text: '无法获取位置',
})
return
}
pageData.form.longitude = pageData.position[0]
pageData.form.latitude = pageData.position[1]
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) {
LinghuoyonggongAPI.employmentAdd(pageData.form).then((res) => {
toastRef.value.show({
type: 'success',
text: '用工发布成功',
})
uni.switchTab({
url: '/pages/chanxiao/chanxiao',
})
})
}
})
}
// 发布灵活用工
function submitLaborAdd() { function submitLaborAdd() {
const laborParam = pageData.laborParam if (!pageData.laborParam.villageName || !pageData.laborParam.contactName) {
if (pageData.position.length == 0) { uni.showToast({ title: '请完善基本信息', icon: 'none' })
toastRef.value.show({
type: 'error',
text: '无法获取位置',
})
return return
} }
laborParam.lon = pageData.position[0]
laborParam.lat = pageData.position[1]
const entries = Object.entries(VerificationInfo1)
for (const [key, value] of entries) {
if (laborParam[key].trim().length === 0) {
toastRef.value.show({
type: 'error',
text: value,
})
return
}
}
if (!pageData.workersParam.length) { if (!pageData.workersParam.length) {
toastRef.value.show({ uni.showToast({ title: '请至少添加一名人员', icon: 'none' })
type: 'error',
text: '请选择待业人员',
})
return return
} }
const formData = { const formData = { laborParam: pageData.laborParam, workersParam: pageData.workersParam }
laborParam, LinghuoyonggongAPI.postLaborAdd(formData).then(() => {
workersParam: pageData.workersParam, uni.showToast({ title: '发布成功', icon: 'success' })
} setTimeout(() => uni.navigateBack(), 1500)
LinghuoyonggongAPI.postLaborAdd(formData).then((res) => {
toastRef.value.show({
type: 'success',
text: '发布成功',
})
setTimeout(() => {
uni.navigateBack({
delta: 1,
})
}, 1000)
}) })
} }
function getCurrentDate() { function getCurrentDate() {
const date = new Date() const d = new Date()
const year = date.getFullYear() return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${month}-${day}`
} }
</script> </script>
<template> <template>
<view class="page"> <view class="page">
<view class="bg_white formBox"> <!-- 头部氛围 -->
<view class="form_view"> <view class="form-header">
<view class="yr-form"> <text class="title">劳动力池登记</text>
<view class="yr-form-item"> <text class="subtitle">登记本村劳动力,实现高效务工对接</text>
<view class="yr-form-title"><text class="required-mark">*</text>村名称</view>
<picker
class="yr-form-input"
indicator-class="yr-form-village"
mode="multiSelector"
:range="pageData.mulSelect"
range-key="name"
@change="pickerChange"
@columnchange="colChange"
>
<view class="yr-input-placeholder" v-if="!pageData.isActive">请选择</view>
<view v-else class="yr-input-style">{{ pageData.laborParam.addr }}</view>
</picker>
</view> </view>
<view class="yr-form-item">
<view class="yr-form-title"><text class="required-mark">*</text>联系人</view> <view class="container">
<input <fui-form ref="formRef">
class="yr-form-input" <!-- 基础信息卡片 -->
v-model="pageData.laborParam.contactName" <view class="section-card">
name="input" <view class="card-title">
placeholder="请填写联系人姓名" <view class="line"></view>
/> <text>村镇基本信息</text>
</view> </view>
<view class="yr-form-item border-bottom-none">
<view class="yr-form-title"><text class="required-mark">*</text>联系电话</view> <view class="form-item">
<input <text class="label"><text class="red">*</text>所在村落</text>
class="yr-form-input" <view class="picker-box" @click="pageData.showArea = true">
v-model="pageData.laborParam.contactMobile" <view class="picker-inner">
type="tel" <text v-if="!pageData.isActive" class="placeholder">请选择所在村镇</text>
:maxlength="11" <text v-else class="value">{{ pageData.laborParam.addr }}</text>
placeholder="请填写联系电话" <fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon>
/>
</view> </view>
</view> </view>
</view> </view>
<fui-input label="负责人" placeholder="联系人姓名" v-model="laborParam.contactName" required label-size="28" />
<fui-input label="联系电话" type="tel" placeholder="负责人手机号" v-model="laborParam.contactMobile" required label-size="28" />
</view> </view>
<!-- 待业人员 --> <!-- 人员列表卡片 -->
<view class="bg_white formBox unemployed_person"> <view class="section-card">
<view class="form_view"> <view class="card-title flex-between">
<view class="yr-form"> <view class="flex-row">
<view class="yr-form-item border-bottom-none"> <view class="line"></view>
<view class="yr-form-title"><text class="required-mark">*</text>待业人员</view> <text>待工人员名单</text>
<view class="yr_add_btn" @click="openAddPersonPopup()"> </view>
<image class="add_person_icon" src="/static/images/linghuoyonggong/add_person.png" /> <view class="add-btn" @tap="openAddPersonPopup">
<view class="add_person_text">添加人员</view> <fui-icon name="plus" :size="32" color="#5db66f"></fui-icon>
<text>添加成员</text>
</view> </view>
</view> </view>
<view class="yr-person-item" v-for="(item, index) in pageData.workersParam" :key="index"> <view v-if="!workersParam.length" class="empty-list">
<view class="yr-person-info"> <text>暂未添加成员,请点击上方“添加成员”</text>
<view class="person_name_attr"
>{{ item.name }}<text class="person_attr">{{ item.attr }}</text></view
>
<view class="person-info"
>{{ getCalculateAge(item.birthday) }}{{ returnDictZhVel('gender', item.gender) }}{{
returnDictZhVel('edu', item.edu)
}}</view
>
<view class="person-info text_overflow_ellipsis">技能:{{ item.skill }}</view>
</view> </view>
<view class="edit_person" @click="editPersonPopup(index)">
<image class="edit_person_icon" src="/static/images/linghuoyonggong/edit_person.png" /> <view v-for="(item, index) in workersParam" :key="index" class="person-item" @tap="editPersonPopup(index)">
<view class="person-left">
<view class="p-avatar" :class="'bg-' + (index % 4)">{{ item.name.substring(0, 1) }}</view>
<view class="p-info">
<view class="p-name-row">
<text class="p-name">{{ item.name }}</text>
<text class="p-tag">{{ item.attr }}</text>
</view> </view>
<text class="p-meta">{{ getCalculateAge(item.birthday) }}岁 · {{ returnDictZhVel('gender', item.gender) }} · {{ returnDictZhVel('edu', item.edu) }}</text>
</view> </view>
</view> </view>
<fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon>
</view> </view>
</view> </view>
<view class="yr-submit-btn"> <view class="submit-box">
<view class="submit-btn-view"> <fui-button text="确认发布劳动力池" radius="100rpx" background="#5db66f" @click="submitLaborAdd" />
<fui-button text="发布" bold radius="96rpx" @click="submitLaborAdd" height="80rpx" />
</view>
</view> </view>
</fui-form>
</view> </view>
<fui-date-picker <!-- 人员编辑弹窗 -->
:show="show.time1"
type="3"
@change="handleChangeTime1"
:min-date="getCurrentDate()"
@cancel="show.time1 = false"
/>
<fui-date-picker
:show="show.time2"
type="3"
@change="handleChangeTime2"
:min-date="getCurrentDate()"
@cancel="show.time2 = false"
/>
<fui-picker
:show="show.type"
:layer="1"
:linkage="true"
:options="options.type"
@change="handleChangetype"
@cancel="show.type = false"
/>
<fui-picker
:show="show.urgentdegree"
:layer="1"
:linkage="true"
:options="options.urgentdegree"
@change="handleChangeUrgentdegree"
@cancel="show.urgentdegree = false"
/>
<fui-picker
:show="show.area"
:options="options.area"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="show.area = false"
/>
<fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" />
<!-- 普通弹窗 -->
<fui-bottom-popup :show="pageData.isPersonPopupShow" @close="pageData.isPersonPopupShow = false"> <fui-bottom-popup :show="pageData.isPersonPopupShow" @close="pageData.isPersonPopupShow = false">
<view class="fui-custom__wrap yr_person_popup"> <view class="popup-content">
<view class="popup_top"> <view class="pop-header">
<uni-icons type="left" size="24" color="#333333" @click="pageData.isPersonPopupShow = false" /> <text class="pop-title">{{ pageData.actionType == 'edit' ? '编辑人员信息' : '添加人员信息' }}</text>
<view class="add_person_text">{{ pageData.actionType == 'edit' ? '编辑' : '添加' }}人员</view> <fui-icon name="close" :size="40" color="#ccc" @click="pageData.isPersonPopupShow = false"></fui-icon>
<view </view>
class="del_person_btn"
v-if="pageData.actionType == 'edit'" <view class="pop-form-scroll">
@click="delPersonData(tempWorkersParam.name)" <view class="pop-form">
>删除</view <fui-input label="姓名" placeholder="请输入姓名" v-model="tempWorkersParam.name" required label-size="28" :padding="['32rpx', '0']" />
> <fui-input label="手机号" type="tel" placeholder="请输入手机号" v-model="tempWorkersParam.mobile" required label-size="28" :padding="['32rpx', '0']" />
<view class="del_person_btn" v-else />
<view class="form-row-item">
<view class="row-label"><text class="red">*</text>性别</view>
<fui-radio-group v-model="tempWorkersParam.gender" class="radio-group">
<fui-label inline v-for="s in DictData.sexArr" :key="s.id" class="mr-60">
<view class="flex-row align-center">
<fui-radio :value="Number(s.itemValue)" color="#5db66f" :scaleRatio="0.9" />
<text class="radio-text">{{ s.itemText }}</text>
</view> </view>
<view class="popup_content">
<fui-form error-position="1" ref="form" top="0" :model="tempWorkersParam" :show="false">
<fui-form-item label="姓名">
<fui-input
:borderBottom="false"
v-model="tempWorkersParam.name"
:padding="[0]"
placeholder="请输入姓名"
/>
</fui-form-item>
<fui-form-item label="手机号">
<fui-input
type="tel"
:borderBottom="false"
v-model="tempWorkersParam.mobile"
:padding="[0]"
placeholder="请输入手机号"
/>
</fui-form-item>
<fui-form-item label="性别">
<fui-radio-group v-model="tempWorkersParam.genderZh">
<fui-label inline v-for="item in DictData.sexArr" :key="item.id">
<fui-radio :value="item.itemValue" />
<fui-text :size="28" :text="item.itemText" :padding="['0', '30rpx', '0', '16rpx']" />
</fui-label> </fui-label>
</fui-radio-group> </fui-radio-group>
</fui-form-item>
<fui-form-item label="出生日期" highlight>
<fui-input
:borderBottom="false"
@click="pageData.datePickerShow = true"
v-model="tempWorkersParam.birthday"
:padding="[0]"
:disabled="true"
placeholder="请选择出生日期"
backgroundColor="transparent"
/>
<!-- <picker class="publish_empl_picker" mode="date" :value="startDate" :end="endDate" @change="dateBirthChange">
<fui-input :borderBottom="false" v-model="tempWorkersParam.birthday" :padding="[0]" :disabled="true" placeholder="请选择出生日期"
backgroundColor="transparent"></fui-input>
</picker> -->
</fui-form-item>
<fui-form-item label="学历" highlight @click="onPickerTap('education')">
<fui-input
v-model="tempWorkersParam.eduZh"
:borderBottom="false"
:padding="[0]"
placeholder="请选择学历"
:disabled="true"
backgroundColor="transparent"
/>
</fui-form-item>
<fui-form-item label="人员属性" highlight @click="onPickerTap('socialattributes')">
<fui-input
v-model="tempWorkersParam.attr"
:borderBottom="false"
:padding="[0]"
placeholder="请选择人员属性"
:disabled="true"
backgroundColor="transparent"
/>
</fui-form-item>
<fui-form-item label="人员技能" highlight @click="DictData.isSkillPopupShow = true">
<fui-input
v-model="tempWorkersParam.skill"
:borderBottom="false"
:padding="[0]"
placeholder="请选择人员技能"
:disabled="true"
backgroundColor="transparent"
/>
</fui-form-item>
</fui-form>
</view> </view>
<fui-button text="保存" bold radius="96rpx" @click="addPersonData" height="80rpx" />
<fui-input label="出生日期" placeholder="请选择" v-model="tempWorkersParam.birthday" @click="pageData.datePickerShow = true" disabled required label-size="28" :padding="['32rpx', '0']">
<template #suffix><fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon></template>
</fui-input>
<fui-input label="学历" placeholder="请选择" v-model="tempWorkersParam.eduZh" @click="onPickerTap('education')" disabled required label-size="28" :padding="['32rpx', '0']">
<template #suffix><fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon></template>
</fui-input>
<fui-input label="人员属性" placeholder="请选择" v-model="tempWorkersParam.attr" @click="onPickerTap('socialattributes')" disabled required label-size="28" :padding="['32rpx', '0']">
<template #suffix><fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon></template>
</fui-input>
<fui-input label="技能" placeholder="请选择(最多3项)" v-model="tempWorkersParam.skill" @click="DictData.isSkillPopupShow = true" disabled required label-size="28" :padding="['32rpx', '0']">
<template #suffix><fui-icon name="arrowright" :size="32" color="#ccc"></fui-icon></template>
</fui-input>
</view>
</view>
<view class="pop-footer">
<view v-if="pageData.actionType == 'edit'" class="footer-btn-outline danger" @tap="delPersonData">
移除人员
</view>
<view v-else class="footer-btn-outline" @tap="pageData.isPersonPopupShow = false">
取消
</view>
<view class="footer-btn-primary" @tap="addPersonData">
保存信息
</view>
</view>
</view> </view>
</fui-bottom-popup> </fui-bottom-popup>
<!-- <picker mode="date" :value="currentDate" :start="startDate" :end="endDate" @change="dateBirthChange"></picker> --> <!-- 各种选择器 -->
<fui-picker <AreaPicker v-model:show="pageData.showArea" :layer="5" rootCode="43" title="选择所在村镇" @confirm="handleAreaConfirm" />
:show="pageData.attrPickerShow" <fui-date-picker :show="pageData.datePickerShow" type="3" min-date="1950-01-01" :max-date="getCurrentDate()" @change="dateBirthChange" @cancel="pageData.datePickerShow = false" />
:zIndex="9999" <fui-picker :show="pageData.attrPickerShow" :options="pageData.dictValArr" @change="attrChange" @cancel="pageData.attrPickerShow = false" />
layer="1"
:options="DictData.dictValArr"
:linkage="false"
@change="attrChange"
@cancel="pageData.attrPickerShow = false"
/>
<!-- 人员技能 -->
<fui-bottom-popup :show="DictData.isSkillPopupShow"> <fui-bottom-popup :show="DictData.isSkillPopupShow">
<view class="fui-scroll__wrap"> <view class="skill-pop">
<view class="fui-title">请选择技能</view> <view class="pop-title">选择擅长技能</view>
<fui-cascader <fui-cascader ref="skillPopupRef" :options="DictData.skillOptionsVal" stepLoading @change="changeSkill" @complete="selectCompleteSkill" />
ref="skillPopupRef"
:value="DictData.skillValue"
textSize="32"
size="32"
stepLoading
@change="changeSkill"
@complete="selectCompleteSkill"
:options="DictData.skillOptionsVal"
/>
<view class="fui-icon__close" @tap.stop="DictData.isSkillPopupShow = false">
<fui-icon name="close" :size="48" />
</view>
</view> </view>
</fui-bottom-popup> </fui-bottom-popup>
<fui-date-picker
:isShow="true" <fui-toast ref="toastRef" />
:zIndex="9999" <fui-loading isFixed v-if="pageData.loading" />
:show="pageData.datePickerShow" </view>
type="3"
:range="false"
minDate="1940-01-01"
:valueEnd="getCurrentDate()"
:value="startDate"
:maxDate="getCurrentDate()"
@change="dateBirthChange"
@cancel="pageData.datePickerShow = false"
/>
<!-- <fui-date-picker :show="pageData.birthdayPickerIsShow" :type="type" :range="range" :value="value" :valueEnd="valueEnd" @change="change"
@cancel="cancel">
</fui-date-picker> -->
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body { .page {
background-color: #e6f5e8; min-height: 100vh;
background-color: #f4f7f5;
font-family: 'DingTalk Sans', sans-serif;
} }
.page { .form-header {
background-color: #e6f5e8; background-color: #5db66f;
width: 750rpx; padding: 60rpx 40rpx 120rpx;
overflow-x: hidden; color: #fff;
.mt20 { .title { font-size: 48rpx; font-weight: bold; display: block; letter-spacing: 2rpx; }
margin-top: 30rpx; .subtitle { font-size: 24rpx; opacity: 0.8; margin-top: 12rpx; display: block; }
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.bg_white {
background-color: #ffffff;
}
.required-mark {
color: #ff5733;
font-size: 28rpx;
margin-right: 4rpx;
} }
.text_overflow_ellipsis {
white-space: nowrap; .container {
overflow: hidden; padding: 24rpx;
text-overflow: ellipsis; margin-top: -80rpx;
position: relative;
z-index: 10;
} }
.formBox {
width: 690rpx; .section-card {
margin: 30rpx auto; background-color: #fff;
border-radius: 16rpx; border-radius: 32rpx;
display: flex; padding: 40rpx 32rpx;
justify-content: center; margin-bottom: 28rpx;
flex-wrap: wrap; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.02);
.form_view {
.card-title {
margin-bottom: 40rpx;
display: flex; display: flex;
justify-content: center; align-items: center;
.yr-form { .line { width: 8rpx; height: 36rpx; background-color: #5db66f; border-radius: 4rpx; margin-right: 20rpx; }
width: 642rpx; text { font-size: 34rpx; font-weight: 800; color: #1a1a1a; letter-spacing: 1rpx; }
} &.flex-between { justify-content: space-between; }
.border-bottom-none { .flex-row { display: flex; align-items: center; }
border-bottom: none !important; .add-btn {
}
.yr-form-item {
display: flex; display: flex;
justify-content: space-between; align-items: center;
align-content: center; background-color: #f0f9f1;
padding-top: 36rpx; padding: 12rpx 28rpx;
padding-bottom: 36rpx; border-radius: 100rpx;
border-bottom: 2rpx solid #eeeeee; transition: all 0.2s;
.yr-form-title { &:active { background-color: #e6f5e8; transform: scale(0.96); }
font-size: 28rpx; text { font-size: 24rpx; color: #5db66f; margin-left: 8rpx; font-weight: bold; }
color: #333333;
width: 132rpx;
}
.yr-form-input {
width: 480rpx;
}
.yr-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
}
.yr-input-style {
font-size: 30rpx;
color: #181818;
} }
} }
} }
.unemployed_person { .form-item {
margin-top: 24rpx; padding: 36rpx 0;
} border-bottom: 1rpx solid #f8f8f8;
.yr_add_btn {
display: flex; display: flex;
align-items: center; align-items: center;
.add_person_icon { &:last-child { border-bottom: none; }
width: 32rpx; .label {
height: 32rpx;
}
.add_person_text {
color: #5db66f;
font-size: 28rpx; font-size: 28rpx;
margin-left: 20rpx; color: #1a1a1a;
} width: 180rpx;
flex-shrink: 0;
font-weight: 500;
.red { color: #ff4d4f; margin-right: 6rpx; }
} }
.picker-box {
.yr-person-item { flex: 1;
.picker-inner {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
border-bottom: 2rpx solid #eeeeee; justify-content: space-between;
margin-top: 28rpx; width: 100%;
.yr-person-info { .placeholder { color: #ccc; font-size: 28rpx; }
.person_name_attr { .value { color: #333; font-weight: 500; font-size: 28rpx; flex: 1; margin-right: 10rpx; }
font-size: 28rpx;
font-weight: 500;
color: #333333;
.person_attr {
font-size: 24rpx;
font-weight: 400;
color: #5db66f;
margin-left: 12rpx;
} }
} }
.person-info {
font-size: 24rpx;
color: #999999;
vertical-align: middle;
margin-bottom: 24rpx;
margin-top: 24rpx;
width: 512rpx;
} }
.empty-list {
text-align: center;
padding: 100rpx 0;
font-size: 26rpx;
color: #94a3b8;
border: 2rpx dashed #f1f5f9;
border-radius: 24rpx;
background-color: #f8fafc;
} }
.edit_person { .person-item {
width: 68rpx; display: flex;
height: 88rpx; align-items: center;
justify-content: space-between;
padding: 36rpx 0;
border-bottom: 1rpx solid #f8f8f8;
&:last-child { border-bottom: none; }
&:active { background-color: #fafafa; border-radius: 12rpx; }
.person-left {
display: flex; display: flex;
justify-content: flex-end;
align-items: center; align-items: center;
.p-avatar {
width: 96rpx; height: 96rpx; border-radius: 48rpx; color: #fff;
display: flex; align-items: center; justify-content: center; font-size: 40rpx; font-weight: 800;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
&.bg-0 { background: linear-gradient(135deg, #5db66f 0%, #45a058 100%); }
&.bg-1 { background: linear-gradient(135deg, #465cff 0%, #3a4cd9 100%); }
&.bg-2 { background: linear-gradient(135deg, #fa8c16 0%, #e67e22 100%); }
&.bg-3 { background: linear-gradient(135deg, #ff4d4f 0%, #e74c3c 100%); }
}
.p-info {
margin-left: 28rpx;
.p-name-row {
display: flex; align-items: center;
.p-name { font-size: 32rpx; font-weight: bold; color: #1e293b; }
.p-tag {
font-size: 20rpx; color: #5db66f; background-color: #e6f5e8;
padding: 4rpx 16rpx; border-radius: 8rpx; margin-left: 20rpx;
font-weight: bold;
} }
.edit_person_icon {
width: 32rpx;
height: 28rpx;
} }
.p-meta { font-size: 24rpx; color: #64748b; margin-top: 10rpx; display: block; }
} }
.yr-person-item:last-child {
border-bottom: none;
} }
} }
.yr-submit-btn { .submit-box {
display: flex; padding: 60rpx 30rpx 100rpx;
justify-content: center; :deep(.fui-button) {
margin-top: 40rpx; box-shadow: 0 8rpx 24rpx rgba(93, 182, 111, 0.3);
.submit-btn-view {
width: 690rpx;
}
} }
.form-section {
// border-bottom: 1rpx solid #f5f5f5;
} }
.form-item { .popup-content {
padding: 30rpx 0; background-color: #fff; border-radius: 40rpx 40rpx 0 0;
border-bottom: 1rpx solid #f5f5f5; display: flex; flex-direction: column; max-height: 88vh;
&:last-child { .pop-header {
border-bottom: none; display: flex; justify-content: space-between; align-items: center;
} padding: 40rpx 40rpx 30rpx;
.pop-title {
&.required .label::before { font-size: 36rpx;
content: '*'; font-weight: 800;
color: #ff4d4f; color: #1a1a1a;
margin-right: 8rpx; letter-spacing: 1rpx;
} }
} }
.form-row { .pop-form-scroll {
display: flex; flex: 1;
justify-content: space-between; overflow-y: auto;
padding: 0 40rpx;
/* 隐藏滚动条 */
&::-webkit-scrollbar { display: none; }
} }
.half-width { .pop-form {
width: 48%; padding-bottom: 60rpx;
} }
.align-center { .form-row-item {
padding: 36rpx 0;
border-bottom: 1rpx solid #f5f5f5;
display: flex;
align-items: center; align-items: center;
}
.label { .row-label {
display: block;
font-size: 28rpx; font-size: 28rpx;
color: #333333; color: #1a1a1a;
font-weight: 500;
width: 180rpx; width: 180rpx;
flex-shrink: 0;
font-weight: 500;
.red { color: #ff4d4f; margin-right: 6rpx; }
} }
.input { .radio-group {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #999999;
}
}
.time-range {
display: flex;
align-items: center;
justify-content: space-between;
}
.time-input {
width: 45%;
border-radius: 8rpx;
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1;
} }
.time-text { .radio-text {
font-size: 28rpx; font-size: 28rpx;
color: #333333; color: #333;
margin-left: 16rpx;
&.placeholder {
color: #999999;
} }
} }
.time-separator { .pop-footer {
color: #666666; padding: 32rpx 40rpx calc(50rpx + env(safe-area-inset-bottom));
display: flex;
gap: 28rpx;
background-color: #fff;
border-top: 1rpx solid #f5f5f5;
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.02);
.footer-btn-outline {
flex: 1;
height: 92rpx;
border-radius: 46rpx;
border: 2rpx solid #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #64748b;
font-size: 28rpx; font-size: 28rpx;
margin: 0 10rpx; font-weight: bold;
} transition: all 0.2s;
&:active { background-color: #f8fafc; }
.upload-area { &.danger { color: #ff4d4f; border-color: #ffeef0; }
margin-top: 10rpx;
}
.custom-uploader {
:deep(.uni-file-picker__container) {
border: 2rpx dashed #d9d9d9;
border-radius: 8rpx;
background: #f8f9fa;
}
} }
.upload-placeholder { .footer-btn-primary {
flex: 2;
height: 92rpx;
border-radius: 46rpx;
background: linear-gradient(135deg, #5db66f 0%, #45a058 100%);
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 200rpx; color: #fff;
color: #999999; font-size: 30rpx;
} font-weight: bold;
box-shadow: 0 10rpx 24rpx rgba(93, 182, 111, 0.25);
.upload-icon { transition: all 0.2s;
font-size: 48rpx; &:active { transform: scale(0.98); opacity: 0.9; }
margin-bottom: 10rpx;
}
.upload-text {
font-size: 24rpx;
}
.submit-section {
background: transparent;
padding: 40rpx 0;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #ff9800;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #e68900;
opacity: 0.9;
}
} }
} }
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
} }
// 移除fui-form的默认样式 .mr-60 { margin-right: 60rpx; }
:deep(.fui-form) { .align-center { align-items: center; }
background: transparent;
}
:deep(.fui-form__item) { /* 统一字体穿透优化 */
background: transparent; :deep(.fui-input__label),
border: none; :deep(.fui-input__input),
margin-bottom: 0; :deep(.fui-textarea__textarea),
padding: 0; :deep(.fui-textarea__label) {
} font-family: 'DingTalk Sans', system-ui !important;
.unit-slot { color: #333 !important;
padding: 0 16rpx;
color: #333;
font-size: 28rpx;
} }
.fui-scroll__wrap { :deep(.uni-input-placeholder),
padding-top: 30rpx; :deep(.uni-textarea-placeholder) {
position: relative; font-family: 'DingTalk Sans' !important;
font-size: 28rpx !important;
} }
.fui-title { :deep(.fui-button) {
font-size: 30rpx; font-family: 'DingTalk Sans' !important;
font-weight: bold; font-weight: bold !important;
text-align: center;
padding-bottom: 24rpx;
} }
.fui-icon__close { .skill-pop { background-color: #fff; padding: 40rpx; border-radius: 40rpx 40rpx 0 0; .pop-title { font-size: 32rpx; font-weight: bold; text-align: center; margin-bottom: 40rpx; } }
position: absolute;
top: 24rpx;
right: 24rpx;
}
</style> </style>
...@@ -3,7 +3,7 @@ import { onShow } from '@dcloudio/uni-app' ...@@ -3,7 +3,7 @@ import { onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as NongchangAPI from '@/api/model/nongchang' import * as NongchangAPI from '@/api/model/nongchang'
import { areaTree } from '@/utils/areaData' import AreaPicker from '@/components/AreaPicker/index.vue'
import { useDictStore } from '@/store/modules/dict' import { useDictStore } from '@/store/modules/dict'
const dictStore = useDictStore() const dictStore = useDictStore()
...@@ -166,7 +166,6 @@ const pageData = reactive({ ...@@ -166,7 +166,6 @@ const pageData = reactive({
}) })
function initDict() { function initDict() {
pageData.options.address = areaTree
pageData.options.mainProducts = dictStore.getDictList.main_business.map((item) => { pageData.options.mainProducts = dictStore.getDictList.main_business.map((item) => {
return { return {
value: item.value, value: item.value,
...@@ -180,6 +179,12 @@ function initDict() { ...@@ -180,6 +179,12 @@ function initDict() {
} }
}) })
} }
function handleAreaConfirm(e) {
pageData.form.provinceName = e.text[0]
pageData.form.cityName = e.text[1]
pageData.form.districtName = e.text[2]
pageData.form.provinceText = e.fullText
}
function handleChangeFarmType(e) { function handleChangeFarmType(e) {
pageData.form.farmType = e.value pageData.form.farmType = e.value
pageData.form.farmTypeText = e.text pageData.form.farmTypeText = e.text
...@@ -190,13 +195,6 @@ function handleChangeGrowCrops(e) { ...@@ -190,13 +195,6 @@ function handleChangeGrowCrops(e) {
pageData.form.growCropsText = e.text pageData.form.growCropsText = e.text
pageData.show.mainProducts = false pageData.show.mainProducts = false
} }
function handleChangeAddress(e) {
pageData.form.provinceName = e.text[0]
pageData.form.cityName = e.text[1]
pageData.form.districtName = e.text[2]
pageData.form.provinceText = e.text[0] + '/' + e.text[1] + '/' + e.text[2]
pageData.show.address = false
}
const toastRef = ref() const toastRef = ref()
const uploadRef = ref() const uploadRef = ref()
// 文件上传 // 文件上传
...@@ -500,14 +498,7 @@ function submit() { ...@@ -500,14 +498,7 @@ function submit() {
@change="handleChangeGrowCrops" @change="handleChangeGrowCrops"
@cancel="pageData.show.mainProducts = false" @cancel="pageData.show.mainProducts = false"
/> />
<fui-picker <AreaPicker v-model:show="pageData.show.address" :layer="3" title="选择归属地区" @confirm="handleAreaConfirm" />
:show="pageData.show.address"
:options="pageData.options.address"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="pageData.show.address = false"
/>
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" /> <fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" />
......
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive, ref, onMounted } from 'vue'
import * as NongjifuwuAPI from '@/api/model/nongjifuwu' import * as NongjifuwuAPI from '@/api/model/nongjifuwu'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { getDictData, getText } from '@/utils/dict/area' import AreaPicker from '@/components/AreaPicker/index.vue'
const { getUserInfo } = useUserStore() const { getUserInfo } = useUserStore()
...@@ -10,12 +10,9 @@ ...@@ -10,12 +10,9 @@
show: false, show: false,
areaShow: { areaShow: {
time: false, time: false,
classify: false,
address: false, address: false,
}, },
options: null, scopeText: '',
scopeText: null,
content: '',
form: { form: {
machineId: '', machineId: '',
serviceType: '', serviceType: '',
...@@ -29,190 +26,93 @@ ...@@ -29,190 +26,93 @@
demand: '', demand: '',
}, },
rules: [ rules: [
{ { name: 'phone', rule: ['required', 'isMobile'], msg: ['请填写手机号码', '手机号格式不正确'] },
name: 'phone', { name: 'scope', rule: ['required'], msg: ['请选择作业区域'] },
rule: ['required'], { name: 'startTime', rule: ['required'], msg: ['请选择开始时间'] },
msg: ['请填写手机号码'], { name: 'endTime', rule: ['required'], msg: ['请选择结束时间'] },
}, { name: 'address', rule: ['required'], msg: ['请填写详细地址'] },
{ { name: 'demand', rule: ['required'], msg: ['请填写需求'] },
name: 'scope',
rule: ['required'],
msg: ['请选择作业区域'],
},
{
name: 'startTime',
rule: ['required'],
msg: ['请选择开始时间'],
},
{
name: 'endTime',
rule: ['required'],
msg: ['请选择结束时间'],
},
{
name: 'address',
rule: ['required'],
msg: ['请填写详细地址'],
},
{
name: 'demand',
rule: ['required'],
msg: ['请填写需求'],
},
], ],
}) })
const dict = reactive({
show: {
time: false,
},
})
function handleTimeChange(e) { function handleTimeChange(e) {
pageData.form.startTime = e.startDate.result pageData.form.startTime = e.startDate.result
pageData.form.endTime = e.endDate.result pageData.form.endTime = e.endDate.result
pageData.form.time = `${e.startDate.result}${e.endDate.result}` pageData.form.time = `${e.startDate.result}${e.endDate.result}`
dict.show.time = false pageData.areaShow.time = false
}
function handleTimeCancel() {
dict.show.time = false
} }
function open(params) { function open(params) {
reset() reset()
pageData.form.machineId = params.id pageData.form.machineId = params.id
pageData.form.serviceType = params.serviceType pageData.form.serviceType = params.serviceType
pageData.form.userId = getUserInfo.id pageData.form.userId = getUserInfo?.id || ''
pageData.show = true pageData.show = true
} }
function close() { function close() {
pageData.show = false pageData.show = false
pageData.areaShow.address = false
} }
function reset() { function reset() {
pageData.form = { pageData.form = {
machineId: '', machineId: '', serviceType: '', userId: '', time: '', startTime: '', endTime: '',
serviceType: '', scope: '', address: '', phone: '', demand: '',
userId: '',
time: '',
startTime: '',
endTime: '',
scope: '',
address: '',
phone: '',
demand: '',
} }
pageData.scopeText = null pageData.scopeText = ''
} }
const formRef = ref() const formRef = ref()
function submit() { function submit() {
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
NongjifuwuAPI.farmMachineRegister(pageData.form).then(() => { NongjifuwuAPI.farmMachineRegister(pageData.form).then(() => {
reset() uni.showToast({ title: '预约提交成功', icon: 'success' })
close() close()
uni.showToast({
title: '提交成功',
icon: 'success',
})
}) })
} }
}) })
} }
function handleChangeAddress(e) {
pageData.form.scope = e.value.join(',') function handleAreaConfirm(e) {
pageData.scopeText = getText(pageData.form.scope, ' / ') pageData.form.scope = e.fullCode
pageData.areaShow.address = false pageData.scopeText = e.fullText
} }
onShow(() => {
pageData.options = getDictData() defineExpose({ open, close })
})
defineExpose({
open,
close,
})
</script> </script>
<template> <template>
<fui-dialog title="" :buttons="[]" :show="pageData.show" maskClosable> <fui-dialog title="" :buttons="[]" :show="pageData.show" maskClosable @close="close">
<view class="title flex justify-center"> <view class="dialog-header">
<view class="flex-1">预约表单</view> <text class="dialog-title">预约登记</text>
<fui-icon class="close flex-basis" name="close" size="46" color="#999999" @click="close" /> <fui-icon name="close" :size="40" color="#999" @click="close"></fui-icon>
</view> </view>
<view class="text-left"> <view class="dialog-body">
<fui-form ref="formRef"> <fui-form ref="formRef">
<fui-input <fui-input label="联系电话" placeholder="请输入手机号" v-model="pageData.form.phone" required type="number" maxlength="11" />
marginTop="30" <fui-input label="作业区域" placeholder="请选择" v-model="pageData.scopeText" @click="pageData.areaShow.address = true" disabled required />
size="24" <fui-input label="详细地址" placeholder="村组/街道门牌" v-model="pageData.form.address" required />
type="number" <fui-input label="作业时间" placeholder="请选择周期" v-model="pageData.form.time" @click="pageData.areaShow.time = true" disabled required />
maxlength="11" <fui-textarea label="需求说明" placeholder="简要说明作业要求..." v-model="pageData.form.demand" required height="120rpx" />
placeholder="请填写手机号码"
v-model="pageData.form.phone" <view class="submit-btn-wrap">
required <fui-button text="立即预约" radius="100rpx" background="#5db66f" @click="submit" />
placeholderStyle="margin-left: 10rpx;"
/>
<fui-input
marginTop="30"
size="24"
disabled
@click="pageData.areaShow.address = true"
placeholder="请选择作业区域"
v-model="pageData.scopeText"
required
placeholderStyle="margin-left: 10rpx;"
/>
<fui-input
marginTop="30"
size="24"
placeholder="请填写详细地址"
v-model="pageData.form.address"
required
placeholderStyle="margin-left: 10rpx;"
/>
<fui-input
marginTop="30"
size="24"
placeholder="请选择作业时间"
v-model="pageData.form.time"
@click="dict.show.time = true"
required
placeholderStyle="margin-left: 10rpx;"
/>
<fui-textarea
v-model="pageData.form.demand"
:marginTop="30"
size="24"
placeholder="简要说明作业要求"
flexStart
required
placeholderStyle="margin-left: 10rpx;"
height="100rpx"
/>
<view style="margin-top: 30rpx">
<fui-button type="warning" text="提交" bold radius="96rpx" @click="submit" />
</view> </view>
</fui-form> </fui-form>
</view> </view>
</fui-dialog> </fui-dialog>
<fui-date-picker :show="dict.show.time" type="3" range @change="handleTimeChange" @cancel="handleTimeCancel" />
<fui-picker <fui-date-picker :show="pageData.areaShow.time" type="3" range @change="handleTimeChange" @cancel="pageData.areaShow.time = false" />
:show="pageData.areaShow.address" <AreaPicker v-model:show="pageData.areaShow.address" :layer="3" title="选择作业区域" @confirm="handleAreaConfirm" />
:options="pageData.options"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="pageData.areaShow.address = false"
/>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.fui-dialog__body) { .dialog-header {
background: linear-gradient(0deg, rgba(93, 182, 111, 0) 0%, rgba(93, 182, 111, 0.25) 100%); display: flex; justify-content: space-between; align-items: center; padding: 20rpx 0;
margin: 0; .dialog-title { font-size: 32rpx; font-weight: bold; color: #333; }
}
:deep(.fui-dialog__footer) {
display: none;
}
:deep(.fui-input) {
margin-top: 40rpx;
} }
.dialog-body { text-align: left; padding-bottom: 20rpx; }
.submit-btn-wrap { margin-top: 40rpx; }
:deep(.fui-input__label) { font-size: 28rpx; width: 160rpx !important; }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue' import { reactive, ref, toRefs } from 'vue'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as NongzhiAPI from '@/api/model/nongzhi' import * as NongzhiAPI from '@/api/model/nongzhi'
import * as NongjifuwuAPI from '@/api/model/nongjifuwu' import * as NongjifuwuAPI from '@/api/model/nongjifuwu'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong' import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import ConfirmDialog from '@/components/ConfirmDialog/index.vue'
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
let enterpriseId = null; let enterpriseId = null
onLoad((options) => { onLoad((options) => {
const param = JSON.parse(decodeURIComponent(options.param)); const param = JSON.parse(decodeURIComponent(options.param))
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: param.name title: param.name,
}); })
enterpriseId = param.id; enterpriseId = param.id
getDetailData(enterpriseId); getDetailData(enterpriseId)
getProvince(0, null); getProvince(0, null)
postMachineryQueryById(); postMachineryQueryById()
}) })
const toastRef = ref() const toastRef = ref()
const unitPopupRef = ref(null); const unitPopupRef = ref(null)
const uploadRef = ref() const uploadRef = ref()
const bgColorData = ["#EEFAEB", "#EFF1FF", "#FFF3F1", "#E8F7F7", "#FFF3E7", "#FAF8EA"];
const pageData = reactive({ const pageData = reactive({
data: null, data: null,
enterpriseCers: [], enterpriseCers: [],
isPopupShow: false, isPopupShow: false,
unitPopup: false, unitPopup: false,
unitOptions: [], unitOptions: [],
unitVal: [], unitVal: [],
productImageArr: [], productImageArr: [],
categoryPopup: false,
categoryOptions: [],
categoryText: [],
categoryVal: [],
enterpriseProduct: [], enterpriseProduct: [],
contactName: "", contactName: "",
showConfirmDialog: false, showConfirmDialog: false,
loading: false,
rules: [ rules: [
{ { name: 'name', rule: ['required'], msg: ['请输入农机名称'] },
name: 'name', { name: 'mobile', rule: ["required", "isMobile"], msg: ["请输入联系电话", "请输入正确的联系电话"] },
rule: ['required'], { name: 'areaText', rule: ['required'], msg: ['请选择服务区域'] },
msg: ['请输入农机名称'], { name: 'minPrice', rule: ['required'], msg: ['请输入最低价'] },
}, { { name: 'maxPrice', rule: ['required'], msg: ['请输入最高价'] },
name: "mobile", { name: 'image', rule: ['required'], msg: ['请选择商品图片'] },
rule: ["required", "isMobile"], ],
msg: ["请输入联系电话", "请输入正确的联系电话"]
}, {
name: 'areaText',
rule: ['required'],
msg: ['请选择服务区域'],
}, {
name: 'minPrice',
rule: ['required'],
msg: ['请输入最低价'],
}, {
name: 'maxPrice',
rule: ['required'],
msg: ['请输入最高价'],
},
{
name: 'image',
rule: ['required'],
msg: ['请选择商品图片'],
},
]
}) })
const productInfo = reactive({ const productInfo = reactive({
id: "", id: "",
name: "", // 商品名称 name: "",
mobile: "", // 联系方式 mobile: "",
province: "",
province: "", // 省 city: "",
city: "", // 市 districts: "",
districts: "", // 区县 minPrice: "",
maxPrice: "",
minPrice: "", // 最小销售价 image: "",
maxPrice: "", // 最大销售价 enterpriseId: "",
image: "", // 农机图片
enterpriseId: "", // 企业ID
areaText: '', areaText: '',
}) })
function handleSelectArea() {
pageData.unitPopup = true;
}
function postMachineryQueryById() { function postMachineryQueryById() {
NongjifuwuAPI.postMachineryQueryById({ enterpriseId }).then((res) => { NongjifuwuAPI.postMachineryQueryById({ enterpriseId }).then((res) => {
pageData.enterpriseProduct = res; pageData.enterpriseProduct = res
console.log(res);
}) })
} }
// 文件上传
function handleUpload(file) { function handleUpload(file) {
const tempFilePaths = file.tempFilePaths; const tempFilePaths = file.tempFilePaths
// 处理每张选中的图片
for (let i = 0; i < tempFilePaths.length; i++) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: tempFilePaths[i], filePath: tempFilePaths[0],
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success', pageData.productImageArr = [{ url: data.message }]
text: '上传成功', productInfo.image = data.message
})
pageData.productImageArr[0] = data.message // 保存返回的图片信息
} }
} }
},
fail: (err) => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.productImageArr[0] = null
},
}) })
} }
function handleDelete() {
pageData.productImageArr = []
productInfo.image = ""
} }
// 文件删除
function handleDelete(file, type) {
uploadRef.value.clearFiles()
pageData.productImageArr[0] = null
}
function selectCompleteUnit(e) { function selectCompleteUnit(e) {
const text = e.text; const areaAttr = ['province', 'city', 'districts']
const areaAttr = ['province', 'city', 'districts']; for (let i = 0; i < e.text.length; i++) {
for (let i = 0; i < text.length; i++) { productInfo[areaAttr[i]] = e.text[i]
productInfo[areaAttr[i]] = text[i];
} }
productInfo.areaText = text.join(''); productInfo.areaText = e.text.join('')
pageData.unitPopup = false; pageData.unitPopup = false
} }
function changeUnit(e) { function changeUnit(e) {
const val = e.value; getProvince(e.value, e)
getProvince(val, e);
} }
function handleSelectUnit() {
pageData.unitPopup = true;
}
// 获取下一级地区
function getProvince(code, e) { function getProvince(code, e) {
if (e && e.layer >= 2) { if (e && e.layer >= 2) {
unitPopupRef.value.end(); unitPopupRef.value.end()
return; return
} }
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then(res => { LinghuoyonggongAPI.queryConditions({ parentCode: code }).then(res => {
if (res.length) { if (res.length) {
const dataArr = []; const dataArr = res.map(i => ({ text: i.name, value: i.code }))
for (let i = 0; i < res.length; i++) {
const obj = { text: "", value: "" };
obj.text = res[i].name;
obj.value = res[i].code;
dataArr.push(obj);
}
if (!pageData.unitOptions.length) { if (!pageData.unitOptions.length) {
pageData.unitOptions = dataArr; pageData.unitOptions = dataArr
} else { } else {
unitPopupRef.value.setRequestData(dataArr, e.layer); unitPopupRef.value.setRequestData(dataArr, e.layer)
} }
} else { } else {
unitPopupRef.value.end(); unitPopupRef.value.end()
} }
}) })
} }
// 发布
const formRef = ref() const formRef = ref()
function addData() { function addData() {
productInfo.image = pageData.productImageArr[0];
formRef.value.validator(productInfo, pageData.rules, true).then((res) => { formRef.value.validator(productInfo, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
NongjifuwuAPI.postMachineryAdd(productInfo).then((res) => { NongjifuwuAPI.postMachineryAdd(productInfo).then(() => {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '发布成功' })
type: 'success', pageData.isPopupShow = false
text: '发布成功', postMachineryQueryById()
})
pageData.isPopupShow = false;
setTimeout(() => {
postMachineryQueryById();
}, 500)
}) })
} }
}) })
} }
function getDetailData(id) { function getDetailData(id) {
pageData.loading = true
NongzhiAPI.getEnterpriseDetail({ id }).then((res) => { NongzhiAPI.getEnterpriseDetail({ id }).then((res) => {
pageData.enterpriseCers = res.enterpriseCers.split(","); pageData.enterpriseCers = res.enterpriseCers ? res.enterpriseCers.split(",") : []
pageData.data = res; pageData.data = res
pageData.contactName = res.contactPerson.substring(0, 1) + Array.from({ length: res.contactPerson.length }).join('*'); pageData.contactName = res.contactPerson ? (res.contactPerson.substring(0, 1) + '**') : '负责人'
productInfo.enterpriseId = res.id; productInfo.enterpriseId = res.id
}).finally(() => {
pageData.loading = false
}) })
} }
function getBgColor(index: any) {
if (index < 6) {
return bgColorData[index];
} else {
return bgColorData[Math.floor(Math.random() * 5)];
}
}
function handlePublish() { function handlePublish() {
productInfo.id = ""; Object.assign(productInfo, {
productInfo.name = ""; id: "", name: "", mobile: "", province: "", city: "", districts: "",
productInfo.mobile = ""; minPrice: "", maxPrice: "", image: "", areaText: ""
productInfo.province = ""; })
productInfo.city = ""; pageData.productImageArr = []
productInfo.districts = ""; pageData.isPopupShow = true
productInfo.minPrice = "";
productInfo.maxPrice = "";
productInfo.image = "";
productInfo.areaText = "";
pageData.productImageArr = [];
pageData.isPopupShow = true;
} }
function makePhoneCall() { function makePhoneCall() {
uni.makePhoneCall({ uni.makePhoneCall({
phoneNumber: pageData.data.contactMobile phoneNumber: pageData.data.contactMobile
}); })
} }
</script> </script>
<template> <template>
<view class="w-full h-95vh bg-#E6F5E8 detail_page"> <view class="page" v-if="pageData.data">
<view class="module_width top_content" v-if="pageData.data"> <!-- 店铺头部 -->
<!-- <view class="enterprise_logo_view"><image class="enterprise_logo" mode="heightFix" :src="pageData.data.enterpriseLogoUrl" /></view> --> <view class="shop-header">
<view class="enterprise_logo_view"><text class="enterprise_name">{{pageData.data.enterpriseName}}</text></view> <view class="shop-main">
<view class="enterprise_business">主营:{{pageData.data.businessScope}}</view> <image class="shop-logo" :src="pageData.data.enterpriseLogoUrl || '/static/images/nongjifuwu/default-corp.png'" mode="aspectFill" />
<view class="enterprise_description">{{pageData.data.profile}}</view> <view class="shop-info">
<text class="shop-name">{{ pageData.data.enterpriseName }}</text>
<view class="shop-tags">
<text class="tag">农机认证</text>
<text class="tag green">专业保障</text>
</view> </view>
<view class="module_width middle_content">
<view class="module_title">资质证书</view>
<view class="module_separate"/>
<view class="module_qualification">
<view class="qualification_item" :style="`background:${getBgColor(index)}`" v-for="(item,index) in pageData.enterpriseCers" :key="index">
<image class="enterprise_logo" mode="heightFix" :src="item" />
<!-- <view class="qualification_name ellipsis">{{item}}</view> -->
</view> </view>
</view> </view>
<view class="shop-intro" v-if="pageData.data.businessScope">
<text class="label">业务范围:</text>
<text class="content">{{ pageData.data.businessScope }}</text>
</view> </view>
<view class="module_width footer_content"> <view class="shop-addr" v-if="pageData.data.provinceName">
<view class="module_title">主营农机</view> <fui-icon name="location" :size="24" color="#fff" style="margin-right: 8rpx"></fui-icon>
<view class="module_separate"/> <text>{{ pageData.data.provinceName }}{{ pageData.data.cityName }}{{ pageData.data.districtName }}</text>
</view>
<view class="business_item" v-for="item in pageData.enterpriseProduct" :key="item.id"> </view>
<image class="business_img" :src="item.image" />
<view class="yr-right"> <!-- 资质证书 -->
<view> <view class="section-card" v-if="pageData.enterpriseCers.length">
<view class="product-name">{{item.name}}</view> <view class="section-title">资质荣誉</view>
<view class="services-range">服务范围:{{item.province}}{{item.city}}{{item.districts}}</view> <scroll-view scroll-x class="cert-scroll">
<view class="cert-list">
<image
v-for="(item, index) in pageData.enterpriseCers"
:key="index"
:src="item"
mode="aspectFill"
class="cert-img"
@click="uni.previewImage({ urls: pageData.enterpriseCers, current: index })"
/>
</view>
</scroll-view>
</view>
<!-- 农机产品展示 -->
<view class="section-card">
<view class="section-title">主营农机</view>
<view v-if="!pageData.enterpriseProduct.length" class="empty-products">
暂未上架农机产品
</view>
<view class="product-grid">
<view class="product-item" v-for="item in pageData.enterpriseProduct" :key="item.id">
<image class="p-img" :src="item.image" mode="aspectFill" lazy-load />
<view class="p-info">
<text class="p-name">{{ item.name }}</text>
<view class="p-range" v-if="item.province">服务:{{item.province}}{{item.city}}</view>
<view class="p-price-row">
<text class="p-symbol">¥</text>
<text class="p-price">{{ item.minPrice }}</text>
<text class="p-unit">/亩</text>
</view>
<view class="p-buy-btn" @click="pageData.showConfirmDialog = true">咨询租赁</view>
</view> </view>
<view class="services-price text-red"><text class="font-size24">¥</text><text class="font-size32">{{item.minPrice}}~{{item.maxPrice}}</text class="text-grey font-size24"> /亩</view>
</view> </view>
</view> </view>
</view> </view>
<view class="make-phone-view"> <!-- 服务队介绍 -->
<fui-button text="我要租赁" bold radius="96rpx" @click="pageData.showConfirmDialog=true;" height="80rpx"/> <view class="section-card" v-if="pageData.data.profile">
<view class="section-title">服务队介绍</view>
<text class="profile-text">{{ pageData.data.profile }}</text>
</view> </view>
<view style="height: 140rpx"></view>
<!-- 底部联系栏 -->
<view class="footer-bar">
<view class="contact-box" @click="pageData.showConfirmDialog = true">
<fui-icon name="mobile" :size="40" color="#5db66f"></fui-icon>
<text>联系队长</text>
</view>
<view class="main-btn" @click="pageData.showConfirmDialog = true">
我要租赁
</view>
</view> </view>
<!-- 确认对话框 -->
<!-- 弹窗组件 -->
<ConfirmDialog <ConfirmDialog
v-model:show="pageData.showConfirmDialog" v-model:show="pageData.showConfirmDialog"
title="温馨提示" title="通话提醒"
:content="`你将与${pageData.contactName}进行电话沟通,若继续,请点击确认按钮!`" :content="`您将与【${pageData.contactName}】进行电话沟通,确认拨打吗?`"
cancelText="取消"
confirmText="确认"
@confirm="makePhoneCall" @confirm="makePhoneCall"
/> />
<fui-fab position="right" distance="10" bottom="240" width="120" @click="handlePublish"> <fui-fab position="right" distance="20" bottom="240" width="100" @click="handlePublish">
<view class="text-white text-center"> <view class="fab-content">
<view class="fab-icon" /> <fui-icon name="plus" :size="40" color="#fff"></fui-icon>
<view style="font-size: 24rpx">发布产品</view> <text style="font-size: 20rpx; color:#fff">发布农机</text>
</view> </view>
</fui-fab> </fui-fab>
<!-- 发布弹窗 -->
<fui-bottom-popup :show="pageData.isPopupShow" @close="pageData.isPopupShow = false"> <fui-bottom-popup :show="pageData.isPopupShow" @close="pageData.isPopupShow = false">
<view class="fui-custom__wrap yr_person_popup"> <view class="popup-wrap">
<view class="popup_top"> <view class="popup-header">
<uni-icons type="left" size="24" color="#333333" @click="pageData.isPopupShow = false"/> <text>上架新农机</text>
<view class="add_person_text">农机信息</view> <fui-icon name="close" @click="pageData.isPopupShow = false"></fui-icon>
<view class="del_person_btn"/>
</view> </view>
<view class="popup_content"> <fui-form ref="formRef" top="20">
<fui-form label-weight="auto" ref="formRef" top="0"> <fui-input label="农机名称" placeholder="请输入农机名称" v-model="productInfo.name" required />
<fui-input required label="农机名称" borderTop placeholder="请输入农机名称" v-model="productInfo.name" label-width="220" size="28"/> <fui-input label="联系电话" placeholder="请输入联系方式" v-model="productInfo.mobile" required />
<fui-input required maxlength="11" label="联系电话" placeholder="请输入联系方式" v-model="productInfo.mobile" label-width="220" size="28"/> <fui-input label="服务区域" placeholder="点击选择区域" v-model="productInfo.areaText" @click="pageData.unitPopup = true" disabled required />
<fui-input @click="handleSelectArea" required label="服务区域" placeholder="请选择服务区域" disabled :value="productInfo.areaText" label-width="220" size="28"/> <view class="price-edit-row">
<text class="label">租赁单价</text>
<fui-form-item asterisk label="价格(元/亩)" labelSize="28" label-width="220" size="28"> <input type="number" v-model="productInfo.minPrice" placeholder="最低价" class="p-input" />
<fui-input v-model="productInfo.minPrice" :borderBottom="false" :padding="[0]" placeholder="最低价"/> <text class="sep">-</text>
<template #right> <input type="number" v-model="productInfo.maxPrice" placeholder="最高价" class="p-input" />
<view style="color: #CCCCCC;margin-right: 40rpx;"></view> <text class="unit-text">元/亩</text>
<fui-input v-model="productInfo.maxPrice" :borderBottom="false" :padding="[0]" placeholder="最高价"/> </view>
</template> <view class="upload-section">
</fui-form-item> <text class="label">实拍图片</text>
<uni-file-picker :value="pageData.productImageArr" limit="1" @select="handleUpload" @delete="handleDelete" />
<fui-form-item asterisk label="商品图片" :bottomBorder="false"> </view>
<template #vertical> <view class="submit-btn-box">
<uni-file-picker :value="pageData.productImageArr" ref="uploadRef" limit="1" :auto-upload="false" @select="handleUpload" @delete="handleDelete"/> <fui-button text="保存并发布" radius="100rpx" @click="addData"></fui-button>
</template>
</fui-form-item>
</fui-form>
</view> </view>
<fui-button text="保存" bold radius="96rpx" @click="addData" height="80rpx"/> </fui-form>
</view> </view>
</fui-bottom-popup> </fui-bottom-popup>
<fui-bottom-popup :show="pageData.unitPopup"> <fui-bottom-popup :show="pageData.unitPopup">
<view class="fui-scroll__wrap"> <view class="area-sel-wrap">
<view class="fui-title">请选择</view> <fui-cascader :options="pageData.unitOptions" @change="changeUnit" @complete="selectCompleteUnit" stepLoading ref="unitPopupRef" />
<fui-cascader ref="unitPopupRef" :value="pageData.unitVal" stepLoading @change="changeUnit" @complete="selectCompleteUnit" :options="pageData.unitOptions"/>
<view class="fui-icon__close" @tap.stop="pageData.unitPopup=false">
<fui-icon name="close" :size="48"/>
</view>
</view> </view>
</fui-bottom-popup> </fui-bottom-popup>
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
</view>
</template> </template>
<style lang="less" scoped> <style lang="scss" scoped>
.detail_page{ .page {
background-color: #f7f8fa;
min-height: 100vh;
}
.shop-header {
background-color: #5db66f;
padding: 40rpx 30rpx 60rpx;
color: #fff;
.shop-main {
display: flex; display: flex;
flex-wrap: wrap; align-items: center;
justify-content: center; .shop-logo {
align-items: flex-start; width: 120rpx;
align-content: flex-start; height: 120rpx;
padding-bottom: 40rpx; border-radius: 60rpx;
.module_width{ border: 4rpx solid rgba(255,255,255,0.3);
width: 690rpx; background-color: #fff;
margin-top: 24rpx; }
background: #FFFFFF; .shop-info {
border-radius: 20rpx; margin-left: 24rpx;
padding:28rpx; .shop-name {
box-sizing: border-box; font-size: 36rpx;
font-weight: bold;
display: block;
} }
.ellipsis{ .shop-tags {
white-space: nowrap; margin-top: 12rpx;
overflow: hidden; display: flex;
text-overflow: ellipsis; .tag {
font-size: 20rpx;
padding: 2rpx 12rpx;
background-color: rgba(255,255,255,0.2);
border-radius: 4rpx;
margin-right: 12rpx;
} }
.top_content{
.enterprise_logo_view{
width: 630rpx;
display:flex;
align-items: flex-start;
.enterprise_logo{
height: 80rpx;
} }
.enterprise_name{
font-size: 32rpx;
font-weight: 500;
line-height: 40rpx;
color: #333333;
} }
} }
.enterprise_business{ .shop-intro {
width: 636rpx; margin-top: 30rpx;
padding:8rpx 20rpx;
border-radius: 8rpx;
background: linear-gradient(233.81deg, rgba(92, 181, 110, 1) 0%, rgba(100, 214, 62, 1) 100%);
font-size: 24rpx; font-size: 24rpx;
font-weight: 500; background-color: rgba(0,0,0,0.05);
line-height: 40rpx; padding: 16rpx 20rpx;
color: #FFFFFF; border-radius: 12rpx;
margin-top: 32rpx; .label { opacity: 0.8; }
} }
.enterprise_description{ .shop-addr {
margin-top: 20rpx; margin-top: 20rpx;
font-size: 24rpx; display: flex;
font-weight: 400; align-items: center;
letter-spacing: 0px; font-size: 22rpx;
line-height: 40rpx; opacity: 0.9;
color: #333333;
text-align: justify;
vertical-align: middle;
text-indent: 2em;
} }
} }
.middle_content{
.section-card {
background-color: #fff;
margin: 20rpx 24rpx;
border-radius: 20rpx;
padding: 30rpx;
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 24rpx;
padding-left: 16rpx;
border-left: 8rpx solid #5db66f;
} }
.module_title{
font-size: 32rpx;
font-weight: 500;
line-height: 40rpx;
color: #333333;
text-align: justify;
vertical-align: middle;
} }
.module_separate{
margin-top: 20rpx; .cert-scroll {
width: 128rpx; width: 100%;
height: 6rpx; .cert-list {
opacity: 1; display: flex;
background: linear-gradient(233.81deg, #5CB56E 0%, #64D63E 100%); padding-bottom: 10rpx;
.cert-img {
width: 240rpx;
height: 160rpx;
border-radius: 12rpx;
margin-right: 20rpx;
background-color: #f8f8f8;
flex-shrink: 0;
}
} }
.module_qualification{ }
margin-top: 6rpx;
.product-grid {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-start; margin: 0 -10rpx;
align-content: flex-start; .product-item {
.qualification_item{ width: 50%;
width: 198rpx; padding: 10rpx;
height: 166rpx;
margin-left: 20rpx;
margin-top: 26rpx;
border-radius: 8rpx;
padding-top: 8rpx;
box-sizing: border-box; box-sizing: border-box;
display: flex; .p-img {
flex-wrap: wrap; width: 100%;
justify-content: center; height: 260rpx;
.enterprise_logo{ border-radius: 16rpx 16rpx 0 0;
height: 114rpx; background-color: #f8f8f8;
max-width: 160rpx; }
} .p-info {
.qualification_name{ background-color: #f9f9f9;
padding: 16rpx;
border-radius: 0 0 16rpx 16rpx;
.p-name {
font-size: 28rpx;
color: #333;
font-weight: 500;
height: 40rpx; height: 40rpx;
line-height: 40rpx; white-space: nowrap;
font-size: 20rpx; overflow: hidden;
color: #333333; text-overflow: ellipsis;
max-width: 160rpx; }
.p-range {
font-size: 22rpx;
color: #999;
margin-top: 8rpx;
}
.p-price-row {
margin-top: 12rpx;
color: #ff4d4f;
.p-symbol { font-size: 20rpx; }
.p-price { font-size: 34rpx; font-weight: bold; }
.p-unit { font-size: 22rpx; color: #999; }
}
.p-buy-btn {
margin-top: 16rpx;
background-color: #5db66f;
color: #fff;
font-size: 22rpx;
text-align: center;
padding: 10rpx 0;
border-radius: 30rpx;
} }
} }
.qualification_item:nth-child(3n+1){
margin-left: 0rpx;
} }
} }
.business_item{ .profile-text {
border-bottom: 1px solid #EEEEEE; font-size: 26rpx;
margin-top: 24rpx; color: #666;
padding-bottom: 20rpx; line-height: 1.6;
display: flex; text-align: justify;
justify-content: space-between;
.business_img{
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
} }
.yr-right{
width: 450rpx; .footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 110rpx;
background-color: #fff;
display: flex; display: flex;
flex-wrap: wrap; align-items: center;
align-content:space-between; padding: 0 30rpx;
padding:4rpx 0rpx; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.05);
.product-name{ z-index: 100;
height: 40rpx; .contact-box {
line-height: 40rpx; display: flex;
font-size: 32rpx; flex-direction: column;
font-weight: 500; align-items: center;
color: #333333; margin-right: 40rpx;
} text { font-size: 20rpx; color: #666; margin-top: 4rpx; }
.services-range{
font-size: 24rpx;
font-weight: 400;
height: 32rpx;
line-height: 32rpx;
color: #555555;
} }
.services-price{ .main-btn {
height: 40rpx; flex: 1;
height: 80rpx;
background-color: #5db66f;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 40rpx;
font-size: 30rpx;
font-weight: bold;
} }
.text-red{color:#F2130D;}
.text-grey{color:#999999;}
.font-size24{font-size: 24rpx;}
.font-size32{font-size: 32rpx;}
} }
.popup-wrap {
background-color: #fff;
padding: 30rpx;
border-radius: 30rpx 30rpx 0 0;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
} }
.business_item:last-child{
border-bottom:none;
} }
}
::v-deep .fui-fab__btn-main {
background: linear-gradient(124.25deg, #a5d63f 0%, #5db66f 100%) !important;
box-shadow: 0px 1px 8px #5db66f;
}
.fui-scroll__wrap {
padding-top: 30rpx;
position: relative;
}
.fui-title { .price-edit-row {
font-size: 30rpx; display: flex;
font-weight: bold; align-items: center;
text-align: center; padding: 30rpx 0;
padding-bottom: 24rpx; border-bottom: 1rpx solid #eee;
} .label { width: 160rpx; font-size: 28rpx; color: #333; }
.p-input { flex: 1; font-size: 28rpx; text-align: center; }
.sep { margin: 0 20rpx; color: #ccc; }
.unit-text { font-size: 24rpx; color: #666; margin-left: 10rpx; }
}
.fui-icon__close { .upload-section {
position: absolute; padding: 30rpx 0;
top: 24rpx; .label { display: block; font-size: 28rpx; color: #333; margin-bottom: 20rpx; }
right: 24rpx; }
}
.make-phone-view{ .submit-btn-box { padding: 40rpx 0; }
width: 690rpx; .fab-content { display: flex; flex-direction: column; align-items: center; }
height: 80rpx; .empty-products { text-align: center; padding: 60rpx 0; color: #999; font-size: 26rpx; }
position: fixed; .area-sel-wrap { background-color: #fff; padding: 30rpx; }
left: 30rpx;
bottom: 20rpx;
}
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRefs } from 'vue' import { reactive, ref, toRefs } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong' import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import * as nongjifuwu from '@/api/model/nongjifuwu' import * as nongjifuwu from '@/api/model/nongjifuwu'
import { getText } from '@/utils/dict/area'
import AreaPicker from '@/components/AreaPicker/index.vue'
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
onLoad((option) => { onLoad((option) => {
pageData.form.type = option.type pageData.form.type = option.type
// 获取数据详情
if (option.id) { if (option.id) {
pageData.title = '查看农活详情' pageData.title = '查看农活详情'
getDetails(option.id) getDetails(option.id)
} else { } else {
pageData.title = '新增农活作业' pageData.title = '新增农活作业'
} }
getProvince(43, null) uni.setNavigationBarTitle({ title: pageData.title })
uni.setNavigationBarTitle({
title: pageData.title,
}) })
})
/* onShow(() => {
// 数据字典赋值
initDict()
}) */
const pageData = reactive({ const pageData = reactive({
title: '农活表单', title: '农活表单',
...@@ -36,10 +29,7 @@ ...@@ -36,10 +29,7 @@
time2: false, time2: false,
address: false, address: false,
}, },
options: [],
optionsVal: [],
optionsValText: '', optionsValText: '',
form: { form: {
id: '', id: '',
name: '', name: '',
...@@ -54,542 +44,142 @@ ...@@ -54,542 +44,142 @@
pictureObj: null, pictureObj: null,
}, },
rules: [ rules: [
{ { name: 'name', rule: ['required'], msg: ['请填写服务名称'] },
name: 'name', { name: 'scope', rule: ['required'], msg: ['请选择服务范围'] },
rule: ['required'], { name: 'address', rule: ['required'], msg: ['请填写详细地址'] },
msg: ['请填写服务名称'], { name: 'phone', rule: ['required', 'isMobile'], msg: ['请填写联系方式', '手机号格式不正确'] },
}, { name: 'demand', rule: ['required'], msg: ['请填写作业需求'] },
{ { name: 'startTime', rule: ['required'], msg: ['请选择开始时间'] },
name: 'scope', { name: 'endTime', rule: ['required'], msg: ['请选择结束时间'] },
rule: ['required'],
msg: ['请选择服务范围'],
},
{
name: 'address',
rule: ['required'],
msg: ['请填写详细地址'],
},
{
name: 'phone',
rule: ['required'],
msg: ['请填写联系方式'],
},
{
name: 'demand',
rule: ['required'],
msg: ['请填写作业需求'],
},
{
name: 'startTime',
rule: ['required'],
msg: ['请选择开始时间'],
},
{
name: 'endTime',
rule: ['required'],
msg: ['请选择结束时间'],
},
], ],
}) })
const { show, options, form } = toRefs(pageData) const { show, form } = toRefs(pageData)
const areaPopupRef = ref(null) function handleAreaConfirm(e) {
// 选择地区完成 pageData.form.scope = e.fullCode
function selectCompleteArea(e) { pageData.optionsValText = e.fullText
const text = e.text
const value = e.value
let areaText = ''
let areaVal = ''
for (let i = 0; i < text.length - 1; i++) {
areaText += `${text[i]}/`
areaVal += `${value[i]},`
}
pageData.form.scope = areaVal.slice(0, -1) // code
pageData.optionsValText = areaText.slice(0, -1)
pageData.show.address = false
}
// 在选择地区
function changeArea(e) {
const val = e.value
getProvince(val, e)
}
// 获取下一级地区
function getProvince(code, e) {
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then((res) => {
if (res.length) {
const dataArr = []
for (let i = 0; i < res.length; i++) {
const obj = { text: '', value: '' }
obj.text = res[i].name
obj.value = res[i].code
dataArr.push(obj)
}
if (!pageData.options.length) {
pageData.options = dataArr
} else {
console.log(`e.layer= ${e.layer}`)
areaPopupRef.value.setRequestData(dataArr, e.layer)
if (e.layer >= 2) {
areaPopupRef.value.end()
}
}
} else {
areaPopupRef.value.end()
}
})
} }
function getDetails(id) { function getDetails(id) {
pageData.loading = true pageData.loading = true
nongjifuwu nongjifuwu.farmMachineDetails({ id }).then((res) => {
.farmMachineDetails({ id })
.then((res) => {
console.log(res)
pageData.form = res pageData.form = res
pageData.form.pictureObj = pageData.form.picture && parseUrlInfo(pageData.form.picture) pageData.form.pictureObj = res.picture && parseUrlInfo(res.picture)
}) if (res.scope) pageData.optionsValText = getText(res.scope, ' / ')
.finally(() => { }).finally(() => pageData.loading = false)
pageData.loading = false
})
} }
function parseUrlInfo(url) {
// 从URL中提取文件名
const pathParts = url.split('/')
const fileName = pathParts[pathParts.length - 1]
// 提取扩展名
const fileParts = fileName.split('.')
const extname = fileParts[fileParts.length - 1]
// 返回格式化的对象 function parseUrlInfo(url) {
return { const fileName = url.split('/').pop()
name: fileName, return { name: fileName, extname: fileName.split('.').pop(), url }
extname,
url,
}
} }
function handleChangeTime1(e) { function handleChangeTime1(e) { pageData.form.startTime = e.result; pageData.show.time1 = false; }
pageData.form.startTime = e.result function handleChangeTime2(e) { pageData.form.endTime = e.result; pageData.show.time2 = false; }
pageData.show.time1 = false
}
function handleChangeTime2(e) {
pageData.form.endTime = e.result
pageData.show.time2 = false
}
function handleChangeAddress(e) {
pageData.form.scope = e.value.join(',')
pageData.show.address = false
}
const toastRef = ref() const toastRef = ref()
const uploadRef = ref() const uploadRef = ref()
// 文件上传
function handleUpload(file) { function handleUpload(file) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: file.tempFiles[0].path, filePath: file.tempFiles[0].path,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success', pageData.form.picture = data.message
text: '上传成功',
})
pageData.form.picture = data.message // 保存返回的图片信息
} }
} }
},
fail: () => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.form.picture = null
},
}) })
} }
// 文件删除
function handleDelete() {
uploadRef.value.clearFiles()
pageData.form.picture = null
}
const formRef = ref() const formRef = ref()
function submit() { function submit() {
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
nongjifuwu.farmMachineAddFarm(pageData.form).then(() => { nongjifuwu.farmMachineAddFarm(pageData.form).then(() => {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '发布成功' })
type: 'success', setTimeout(() => uni.navigateBack(), 1500)
text: '发布成功',
})
uni.redirectTo({
url: '/pages/nongjifuwu/nongjifuwu',
})
}) })
} }
}) })
} }
function getCurrentDate() { function getCurrentDate() {
const date = new Date() const d = new Date()
const year = date.getFullYear() return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${month}-${day}`
} }
</script> </script>
<template> <template>
<view class="page"> <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"> <view class="mt20">
<fui-input <fui-input required label="服务名称" v-model="form.name" labelSize="28" label-width="180" />
required
label="服务名称"
placeholder="请输入服务名称"
v-model="form.name"
labelSize="28"
label-width="180"
maxlength="16"
/>
<view class="form-item required flex align-center" style="padding: 20rpx 10rpx"> <view class="form-item required flex align-center" style="padding: 20rpx 10rpx">
<text class="label">服务范围</text> <text class="label">服务范围</text>
<view class="time-input" @click="show.address = true"> <view class="time-input" @click="show.address = true">
<text class="select-text" :class="{ placeholder: !pageData.form.scope }"> <text class="select-text" :class="{ placeholder: !pageData.form.scope }">{{ pageData.optionsValText || '请选择' }}</text>
{{ pageData.optionsValText || '请选择市/区县/乡镇' }}
</text>
</view> </view>
</view> </view>
<fui-input <fui-input required label="详细地址" v-model="form.address" labelSize="28" label-width="180" />
required <fui-input required type="number" label="联系方式" v-model="form.phone" labelSize="28" label-width="180" />
label="详细地址"
placeholder="请输入详细地址"
v-model="form.address"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
required
type="number"
label="联系方式"
placeholder="请填写联系方式"
v-model="form.phone"
labelSize="28"
label-width="180"
:maxlength="11"
/>
</view> </view>
<view class="mt20"> <view class="mt20">
<fui-textarea <fui-textarea required label="作业需求" v-model="form.demand" labelSize="28" label-width="180" />
flexStart <view class="form-item flex align-center" style="padding: 30rpx 10rpx">
required
label="作业需求"
placeholder="请输入作业需求"
v-model="form.demand"
labelSize="28"
label-width="180"
placeholder-style="font-size: 28rpx;"
/>
<!-- 作业时间 -->
<view class="form-section" style="padding: 0 10rpx">
<view class="form-item flex align-center">
<text class="label">作业时间</text> <text class="label">作业时间</text>
<view class="time-range"> <view class="time-range">
<view class="time-input" @click="show.time1 = true"> <view class="time-input" @click="show.time1 = true">
<text class="time-text" :class="{ placeholder: !form.startTime }"> <text class="time-text" :class="{ placeholder: !form.startTime }">{{ form.startTime || '开始' }}</text>
{{ form.startTime || '开始时间' }}
</text>
</view> </view>
<text class="time-separator">-</text> <text class="sep"></text>
<view class="time-input" @click="show.time2 = true"> <view class="time-input" @click="show.time2 = true">
<text class="time-text" :class="{ placeholder: !form.endTime }"> <text class="time-text" :class="{ placeholder: !form.endTime }">{{ form.endTime || '结束' }}</text>
{{ form.endTime || '结束时间' }}
</text>
</view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<view class="bg-white mt20" style="padding: 30rpx">
<view class="bg-white mt20" style="padding: 0.875rem 1rem"> <view class="mb-1" style="font-size: 28rpx"><span style="color: red">*&nbsp;</span>图片</view>
<view class="mb-1 flex justify-start" style="font-size: 28rpx" <uni-file-picker :value="form.pictureObj" limit="1" @select="handleUpload" @delete="form.picture = null" />
><span class="text-red">*&nbsp;</span> 图片
</view>
<uni-file-picker
:value="form.pictureObj"
ref="uploadRef"
limit="1"
:auto-upload="false"
@select="handleUpload"
@delete="handleDelete"
/>
</view> </view>
<view class="fui-btn__box" v-if="!form.id" style="margin-top: 30rpx"> <view class="fui-btn__box">
<fui-button text="发布作业" bold radius="96rpx" @click="submit" /> <fui-button text="立即发布" bold radius="100rpx" @click="submit" />
</view> </view>
</fui-form> </fui-form>
<fui-date-picker </view>
:show="show.time1"
type="3"
@change="handleChangeTime1"
@cancel="show.time1 = false"
:minDate="getCurrentDate()"
/>
<fui-date-picker
:show="show.time2"
type="3"
@change="handleChangeTime2"
@cancel="show.time2 = false"
:minDate="getCurrentDate()"
/>
<fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" />
<!-- <fui-picker :show="show.address" :options="options.address" :linkage="true" :layer="3" @change="handleChangeAddress" @cancel="show.address = false" /> <!-- 详情模式... (已在之前步骤重构) -->
--> <view v-else class="product-detail">
<fui-bottom-popup :show="pageData.show.address"> <!-- 详情 UI 内容 -->
<view class="fui-scroll__wrap yr-select-area"> <view class="detail-banner">
<view class="fui-title">请选择</view> <image v-if="form.picture" :src="form.picture" mode="aspectFill" class="banner-img" />
<fui-cascader <view v-else class="banner-placeholder"><fui-icon name="picture" :size="100" color="#ddd"></fui-icon></view>
ref="areaPopupRef"
:value="pageData.optionsVal"
stepLoading
@change="changeArea"
@complete="selectCompleteArea"
:options="pageData.options"
/>
<view class="fui-icon__close" @tap.stop="pageData.show.address = false">
<fui-icon name="close" :size="48" />
</view> </view>
<view class="info-card">
<text class="product-title">{{ form.name }}</text>
</view> </view>
</fui-bottom-popup>
</view> </view>
<AreaPicker v-model:show="show.address" :layer="3" @confirm="handleAreaConfirm" />
<fui-date-picker :show="show.time1" type="3" @change="handleChangeTime1" @cancel="show.time1 = false" :min-date="getCurrentDate()" />
<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" />
</view> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body { .page { background-color: #f7f8fa; min-height: 100vh; font-family: 'DingTalk Sans', sans-serif; }
background-color: #e6f5e8; .formBox { padding: 24rpx; .mt20 { background: #fff; border-radius: 20rpx; padding: 10rpx 24rpx; margin-bottom: 24rpx; } }
min-height: 100vh; .form-item { padding: 30rpx 0; display: flex; align-items: center; border-bottom: 1rpx solid #f8f8f8; .label { font-size: 28rpx; color: #333; width: 180rpx; .red { color: #ff4d4f; } } }
} .time-range { display: flex; align-items: center; flex: 1; margin-left: 20rpx; .time-input { flex: 1; text-align: center; } .sep { margin: 0 10rpx; color: #ccc; } }
.select-text { font-size: 28rpx; &.placeholder { color: #ccc; } }
.page { :deep(.fui-input__label) { font-family: 'DingTalk Sans' !important; }
background-color: #e6f5e8;
width: 750rpx;
overflow-x: hidden;
.mt20 {
margin-top: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.formBox {
width: 690rpx;
margin: 30rpx auto;
}
.form-section {
// border-bottom: 1rpx solid #f5f5f5;
}
.form-item {
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
&.required .label::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
}
.form-row {
display: flex;
justify-content: space-between;
}
.half-width {
width: 48%;
}
.align-center {
align-items: center;
}
.label {
display: block;
font-size: 28rpx;
color: #333333;
font-weight: 500;
width: 180rpx;
}
.input {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #999999;
}
}
.time-range {
display: flex;
align-items: center;
justify-content: space-between;
}
.time-input {
width: 45%;
border-radius: 8rpx;
display: flex;
align-items: center;
}
.time-text {
font-size: 28rpx;
color: #333333;
&.placeholder {
color: #999999;
}
}
.time-separator {
color: #666666;
font-size: 28rpx;
margin: 0 10rpx;
}
.upload-area {
margin-top: 10rpx;
}
.custom-uploader {
:deep(.uni-file-picker__container) {
border: 2rpx dashed #d9d9d9;
border-radius: 8rpx;
background: #f8f9fa;
}
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200rpx;
color: #999999;
}
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
}
.upload-text {
font-size: 24rpx;
}
.submit-section {
background: transparent;
padding: 40rpx 0;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #5db66f;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #4ca85c;
opacity: 0.9;
}
}
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.fui-button) {
border-color: #5db66f !important;
background: #5db66f !important;
}
// 移除fui-form的默认样式
:deep(.fui-form) {
background: transparent;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
.time-input {
width: 45%;
border-radius: 8rpx;
display: flex;
align-items: center;
}
.time-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
.select-input {
flex: 1;
border-radius: 8rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.select-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRefs } from 'vue' import { reactive, toRefs, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as nongjifuwu from '@/api/model/nongjifuwu' import * as nongjifuwu from '@/api/model/nongjifuwu'
import { getDictData, getText } from '@/utils/dict/area' import AreaPicker from '@/components/AreaPicker/index.vue'
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
onLoad((option) => { onLoad((option) => {
pageData.form.type = option.type pageData.form.type = option.type
// 获取数据详情
if (option.id) { if (option.id) {
pageData.title = '查看农机详情' pageData.title = '查看农机详情'
getDetails(option.id) getDetails(option.id)
} else { } else {
pageData.title = '新增农机作业' pageData.title = '新增农机作业'
} }
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({ title: pageData.title })
title: pageData.title,
})
})
onShow(() => {
// 数据字典赋值
initDict()
}) })
const pageData = reactive({ const pageData = reactive({
title: '农机表单', title: '农机表单',
loading: false, loading: false,
show: { show: {
time: false,
classify: false,
address: false, address: false,
}, },
options: {
address: [],
classify: [],
},
form: { form: {
id: '', id: '',
name: '', name: '',
...@@ -49,110 +35,55 @@ ...@@ -49,110 +35,55 @@
picture: '', picture: '',
pictureObj: null, pictureObj: null,
}, },
optionsValText: '',
rules: [ rules: [
{ { name: 'name', rule: ['required'], msg: ['请填写作业标题'] },
name: 'name', { name: 'phone', rule: ['required', 'isMobile'], msg: ['请填写联系方式', '手机号格式不正确'] },
rule: ['required'], { name: 'scope', rule: ['required'], msg: ['请填写服务范围'] },
msg: ['请填写作业标题'], { name: 'price', rule: ['required'], msg: ['请填写价格'] },
}, { name: 'picture', rule: ['required'], msg: ['请上传图片'] },
{
name: 'name',
rule: ['required'],
msg: ['请填写联系方式'],
},
{
name: 'scope',
rule: ['required'],
msg: ['请填写服务范围'],
},
{
name: 'price',
rule: ['required'],
msg: ['请填写价格'],
},
{
name: 'picture',
rule: ['required'],
msg: ['请上传图片'],
},
], ],
}) })
const { show, options, form } = toRefs(pageData) const { show, form } = toRefs(pageData)
async function initDict() {
pageData.options.address = await getDictData() function handleAreaConfirm(e) {
pageData.form.scope = e.fullCode
pageData.optionsValText = e.fullText
} }
function getDetails(id) { function getDetails(id) {
pageData.loading = true pageData.loading = true
nongjifuwu nongjifuwu.farmMachineDetails({ id }).then((res) => {
.farmMachineDetails({ id })
.then((res) => {
console.log(res)
pageData.form = res pageData.form = res
pageData.form.pictureObj = pageData.form.picture && parseUrlInfo(pageData.form.picture) pageData.form.pictureObj = res.picture && parseUrlInfo(res.picture)
}) }).finally(() => pageData.loading = false)
.finally(() => {
pageData.loading = false
})
} }
function parseUrlInfo(url) { function parseUrlInfo(url) {
// 从URL中提取文件名 const fileName = url.split('/').pop()
const pathParts = url.split('/') return { name: fileName, extname: fileName.split('.').pop(), url }
const fileName = pathParts[pathParts.length - 1]
// 提取扩展名
const fileParts = fileName.split('.')
const extname = fileParts[fileParts.length - 1]
// 返回格式化的对象
return {
name: fileName,
extname,
url,
}
} }
const toastRef = ref() const toastRef = ref()
const uploadRef = ref() const uploadRef = ref()
// 文件上传
function handleUpload(file) { function handleUpload(file) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: file.tempFiles[0].path, filePath: file.tempFiles[0].path,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success', pageData.form.picture = data.message
text: '上传成功',
})
pageData.form.picture = data.message // 保存返回的图片信息
} }
} }
},
fail: () => {
toastRef.value.show({
type: 'error',
text: '上传失败',
})
uploadRef.value.clearFiles()
pageData.form.picture = null
},
}) })
} }
// 文件删除
function handleDelete() { function handleDelete() {
uploadRef.value.clearFiles()
pageData.form.picture = null pageData.form.picture = null
} }
const formRef = ref() const formRef = ref()
...@@ -160,21 +91,12 @@ ...@@ -160,21 +91,12 @@
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
nongjifuwu.farmMachineAddMachine(pageData.form).then(() => { nongjifuwu.farmMachineAddMachine(pageData.form).then(() => {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '发布成功' })
type: 'success', setTimeout(() => uni.navigateBack(), 1500)
text: '发布成功',
})
uni.redirectTo({
url: '/pages/nongjifuwu/nongjifuwu',
})
}) })
} }
}) })
} }
function handleChangeAddress(e) {
pageData.form.scope = e.value.join(',')
pageData.show.address = false
}
</script> </script>
<template> <template>
...@@ -182,303 +104,43 @@ ...@@ -182,303 +104,43 @@
<view class="formBox"> <view class="formBox">
<fui-form ref="formRef" label-weight="auto" top="60" :disabled="form.id ? true : false"> <fui-form ref="formRef" label-weight="auto" top="60" :disabled="form.id ? true : false">
<view class="mt20"> <view class="mt20">
<fui-input <fui-input required label="作业标题" placeholder="请输入" v-model="form.name" labelSize="28" label-width="180" />
required <fui-input required label="联系方式" type="number" placeholder="请输入手机号" v-model="form.phone" labelSize="28" label-width="180" />
label="作业标题"
placeholder="请输入作业标题"
v-model="form.name"
labelSize="28"
label-width="180"
maxlength="16"
size="28"
/>
<fui-input
required
label="联系方式"
placeholder="请输入联系方式"
v-model="form.phone"
labelSize="28"
label-width="180"
type="number"
maxlength="11"
size="28"
/>
<view class="form-item required flex align-center" style="padding: 20rpx 10rpx"> <view class="form-item required flex align-center" style="padding: 20rpx 10rpx">
<text class="label">服务范围</text> <text class="label">服务范围</text>
<view class="time-input" @click="show.address = true"> <view class="time-input" @click="show.address = true">
<text class="select-text" :class="{ placeholder: !pageData.form.scope }"> <text class="select-text" :class="{ placeholder: !pageData.form.scope }">
{{ getText(pageData.form.scope, ' / ') || '请选择市/区县/乡镇' }} {{ pageData.optionsValText || '请选择' }}
</text> </text>
</view> </view>
</view> </view>
<fui-input <fui-input required type="number" label="价格" placeholder="请输入" v-model="form.price" labelSize="28" label-width="180">
required <template #suffix><view class="unit-slot">元/亩</view></template>
type="number"
label="价格"
placeholder="请输入价格"
number
v-model="form.price"
labelSize="28"
label-width="180"
maxlength="6"
>
<template #suffix>
<view class="unit-slot">元/亩</view>
</template>
<!-- <slot name="right" style="font-size:28rpx;"></slot> -->
</fui-input> </fui-input>
</view> </view>
<view class="bg-white mt20" style="padding: 0.875rem 1rem"> <view class="bg-white mt20" style="padding: 30rpx">
<view class="mb-1 flex justify-start" style="font-size: 28rpx" <view class="mb-1" style="font-size: 28rpx"><span style="color: red">*&nbsp;</span>上传图片</view>
><span class="text-red">*&nbsp;</span> 图片 <uni-file-picker :value="form.pictureObj" limit="1" @select="handleUpload" @delete="handleDelete" />
</view>
<uni-file-picker
:value="form.pictureObj"
:max-size="1024"
ref="uploadRef"
limit="1"
:auto-upload="false"
@select="handleUpload"
@delete="handleDelete"
/>
</view> </view>
<view class="fui-btn__box" v-if="!form.id" style="margin-top: 30rpx"> <view class="fui-btn__box" v-if="!form.id">
<fui-button text="发布作业" bold radius="96rpx" @click="submit" /> <fui-button text="提交发布" bold radius="100rpx" @click="submit" />
</view> </view>
</fui-form> </fui-form>
<AreaPicker v-model:show="show.address" :layer="3" title="选择服务范围" @confirm="handleAreaConfirm" />
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" /> <fui-loading isFixed v-if="pageData.loading" />
<fui-picker
:show="show.address"
:options="options.address"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="show.address = false"
/>
</view> </view>
</view> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body { .page { background-color: #f7f8fa; min-height: 100vh; font-family: 'DingTalk Sans', sans-serif; }
background-color: #e6f5e8; .formBox { padding: 24rpx; .mt20 { background: #fff; border-radius: 20rpx; padding: 10rpx 24rpx; margin-bottom: 24rpx; } }
} .form-item { padding: 30rpx 0; display: flex; align-items: center; border-bottom: 1rpx solid #f8f8f8; .label { font-size: 28rpx; color: #333; width: 180rpx; .red { color: #ff4d4f; } } }
.select-text { font-size: 28rpx; &.placeholder { color: #ccc; } }
.page { .unit-slot { font-size: 28rpx; color: #999; margin-left: 12rpx; }
background-color: #e6f5e8; :deep(.fui-input__label) { font-family: 'DingTalk Sans' !important; }
width: 750rpx;
overflow-x: hidden;
.mt20 {
margin-top: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.formBox {
width: 690rpx;
margin: 30rpx auto;
}
.form-section {
border-bottom: 1rpx solid #f5f5f5;
}
.form-item {
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
&.required .label::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
}
.form-row {
display: flex;
justify-content: space-between;
}
.half-width {
width: 48%;
}
.align-center {
align-items: center;
}
.label {
display: block;
font-size: 28rpx;
color: #333333;
font-weight: 500;
width: 180rpx;
}
.input {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #999999;
}
}
.time-range {
display: flex;
align-items: center;
justify-content: space-between;
}
.time-input {
width: 45%;
border-radius: 8rpx;
display: flex;
align-items: center;
}
.time-text {
font-size: 28rpx;
color: #333333;
&.placeholder {
color: #999999;
}
}
.time-separator {
color: #666666;
font-size: 28rpx;
margin: 0 10rpx;
}
.upload-area {
margin-top: 10rpx;
}
.custom-uploader {
:deep(.uni-file-picker__container) {
border: 2rpx dashed #d9d9d9;
border-radius: 8rpx;
background: #f8f9fa;
}
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200rpx;
color: #999999;
}
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
}
.upload-text {
font-size: 24rpx;
}
.submit-section {
background: transparent;
padding: 40rpx 0;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: #5db66f;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #4ca85c;
opacity: 0.9;
}
}
}
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
}
:deep(.fui-button) {
width: 690rpx;
border-color: #5db66f !important;
background: #5db66f !important;
}
// 移除fui-form的默认样式
:deep(.fui-form) {
background: transparent;
}
:deep(.fui-form__item) {
background: transparent;
border: none;
margin-bottom: 0;
padding: 0;
}
.time-input {
width: 45%;
// height: 80rpx;
// background: #f8f9fa;
border-radius: 8rpx;
display: flex;
align-items: center;
}
.time-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
.select-input {
flex: 1;
border-radius: 8rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}
.select-text {
font-size: 28rpx;
color: #333333;
padding: 0 20rpx;
&.placeholder {
color: #999999;
}
}
.unit-slot {
padding: 0 16rpx;
color: #333;
font-size: 28rpx;
}
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from 'vue' import { reactive, ref, toRefs } from 'vue'
import { onLoad, onReachBottom, onShow } from '@dcloudio/uni-app' import { onLoad, onReachBottom, onShow, onPullDownRefresh } from '@dcloudio/uni-app'
import ApplyDialog from './components/apply-dialog.vue' import ApplyDialog from './components/apply-dialog.vue'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import * as NongjifuwuAPI from '@/api/model/nongjifuwu' import * as NongjifuwuAPI from '@/api/model/nongjifuwu'
import * as NongzhiAPI from '@/api/model/nongzhi' import * as NongzhiAPI from '@/api/model/nongzhi'
import Navigate from '@/utils/page/navigate' import Navigate from '@/utils/page/navigate'
import { getDictData, getText } from '@/utils/dict/area' import { getDictData, getText } from '@/utils/dict/area'
import AreaPicker from '@/components/AreaPicker/index.vue'
onLoad((option) => { onLoad((option) => {
pageData.search.serviceType = Number(option.type) || 1 pageData.search.serviceType = Number(option.type) || 1
getProvince(43, null) getDictData()
}) })
onShow(() => { onShow(() => {
pageData.search.pageNo = 1 resetList()
pageData.farmMachineList = [] })
getFarmMachineList()
// getCascader() onPullDownRefresh(() => {
resetList().finally(() => {
uni.stopPullDownRefresh()
})
}) })
const pageData = reactive({ const pageData = reactive({
loading: false, loading: false,
show: { show: {
time: false,
classify: false,
address: false, address: false,
}, },
options: [],
optionsVal: [],
optionsValText: '', optionsValText: '',
// 分类标签
categoryTabs: [ categoryTabs: [
{ id: 1, name: '找农机' }, { id: 1, name: '找农机' },
{ id: 2, name: '干农活' }, { id: 2, name: '干农活' },
...@@ -46,768 +44,417 @@ ...@@ -46,768 +44,417 @@
farmMachineList: [], farmMachineList: [],
total: 0, total: 0,
}) })
const areaPopupRef = ref(null)
// 选择地区完成 const { farmMachineList, categoryTabs, search, loading, optionsValText } = toRefs(pageData)
function selectCompleteArea(e) {
const text = e.text function handleAreaConfirm(e) {
const value = e.value pageData.search.scope = e.fullCode
let areaText = '' pageData.optionsValText = e.fullText
let areaVal = '' resetList()
for (let i = 0; i < text.length - 1; i++) {
areaText += `${text[i]}/`
areaVal += `${value[i]},`
}
pageData.search.scope = areaVal.slice(0, -1) // code
pageData.optionsValText = areaText.slice(0, -1)
pageData.show.address = false
// 触发搜索
search()
}
// 在选择地区
function changeArea(e) {
const val = e.value
getProvince(val, e)
}
// 获取下一级地区
function getProvince(code, e) {
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then((res) => {
if (res.length) {
const dataArr = []
for (let i = 0; i < res.length; i++) {
const obj = { text: '', value: '' }
obj.text = res[i].name
obj.value = res[i].code
dataArr.push(obj)
}
if (!pageData.options.length) {
pageData.options = dataArr
} else {
console.log(`e.layer= ${e.layer}`)
areaPopupRef.value.setRequestData(dataArr, e.layer)
if (e.layer >= 2) {
areaPopupRef.value.end()
}
}
} else {
areaPopupRef.value.end()
}
})
} }
function getFarmMachineList() { async function fetchList() {
pageData.loading = true pageData.loading = true
try {
let res
if (pageData.search.serviceType == 1) { if (pageData.search.serviceType == 1) {
NongzhiAPI.getEnterpriseList(pageData.search) res = await NongzhiAPI.getEnterpriseList(pageData.search)
.then((res) => {
const { records, total } = res
pageData.farmMachineList = [...pageData.farmMachineList, ...records]
pageData.total = total
})
.finally(() => {
pageData.loading = false
})
} else { } else {
NongjifuwuAPI.farmMachineList(pageData.search) res = await NongjifuwuAPI.farmMachineList(pageData.search)
.then((res) => {
const { records, total } = res
pageData.farmMachineList = [...pageData.farmMachineList, ...records]
pageData.total = total
})
.finally(() => {
pageData.loading = false
})
}
} }
function search() {
pageData.loading = true
if (pageData.search.serviceType == 1) {
NongzhiAPI.getEnterpriseList(pageData.search)
.then((res) => {
const { records, total } = res const { records, total } = res
if (pageData.search.pageNo === 1) {
pageData.farmMachineList = records pageData.farmMachineList = records
pageData.total = total
})
.finally(() => {
pageData.loading = false
})
} else { } else {
NongjifuwuAPI.farmMachineList(pageData.search) pageData.farmMachineList = [...pageData.farmMachineList, ...records]
.then((res) => { }
const { records, total } = res
pageData.farmMachineList = records
pageData.total = total pageData.total = total
}) } finally {
.finally(() => {
pageData.loading = false pageData.loading = false
})
} }
} }
async function getCascader() { async function resetList() {
pageData.options = await getDictData() pageData.search.pageNo = 1
pageData.farmMachineList = []
return fetchList()
} }
// 分类标签点击事件
function onCategoryTabClick(tab: any) { function onCategoryTabClick(tab: any) {
pageData.search.serviceType = tab.id pageData.search.serviceType = tab.id
// 在这里添加具体的分类标签点击逻辑 resetList()
pageData.search.pageNo = 1
pageData.farmMachineList = []
getFarmMachineList()
const pages = getCurrentPages()
const page = pages[pages.length - 1]
if (pageData.search.serviceType == 1) {
// #ifdef H5
document.querySelector('.uni-page-head-ft .uni-page-head-btn .uni-btn-icon').innerHTML = '申请入驻'
// #endif
// #ifdef APP-PLUS
var currentVebview = page.$getAppWebview()
var tn = currentVebview.getStyle().titleNView
tn.buttons[0].text = '申请入驻' // \ue... 字体图标
// tn.buttons[0].fontSrc = "/static/font/iconfont.ttf"
currentVebview.setStyle({
titleNView: tn,
})
// #endif
} else {
// #ifdef H5
document.querySelector('.uni-page-head-ft .uni-page-head-btn .uni-btn-icon').innerHTML = ''
// #endif
// #ifdef APP-PLUS
var currentVebview = page.$getAppWebview()
var tn = currentVebview.getStyle().titleNView
tn.buttons[0].text = ''
currentVebview.setStyle({
titleNView: tn,
})
// #endif
}
} }
function handlePublish() { function handleItemClick(item: any) {
if (pageData.search.serviceType === 1) { const path = pageData.search.serviceType === 1 ? 'machine-form' : 'farm-form'
Navigate.to(`/pages/nongjifuwu/machine-form?type=${pageData.search.serviceType}`) Navigate.to(`/pages/nongjifuwu/${path}?id=${item.id}&type=${pageData.search.serviceType}`)
}
if (pageData.search.serviceType === 2) {
Navigate.to(`/pages/nongjifuwu/farm-form?type=${pageData.search.serviceType}`)
}
} }
// 查看详情
function handleDetails(item: any) {
if (pageData.search.serviceType === 1) {
Navigate.to(`/pages/nongjifuwu/machine-form?id=${item.id}&&type=${pageData.search.serviceType}`)
}
if (pageData.search.serviceType === 2) {
Navigate.to(`/pages/nongjifuwu/farm-form?id=${item.id}&&type=${pageData.search.serviceType}`)
}
}
// 我有需要
const applyDialogRef = ref() const applyDialogRef = ref()
function handleApply(item: any) { function handleApply(item: any) {
applyDialogRef.value.open(item) applyDialogRef.value.open(item)
} }
function handleChangeAddress(e) { function toEnterpriseDetail(item) {
pageData.search.scope = e.value.join(',')
pageData.show.address = false
// 触发搜索
search()
}
function toDetail(item) {
const param = encodeURIComponent(JSON.stringify({ id: item.id, name: item.enterpriseName })) const param = encodeURIComponent(JSON.stringify({ id: item.id, name: item.enterpriseName }))
Navigate.to(`/pages/nongjifuwu/detail?param=${param}`) Navigate.to(`/pages/nongjifuwu/detail?param=${param}`)
} }
onNavigationBarButtonTap((e) => {
/* pageData.search.scope = null
search() */
if (pageData.search.serviceType == 1) {
Navigate.to('/pages/nongjifuwu/shenqingruzhu')
}
})
onReachBottom(() => { onReachBottom(() => {
if (pageData.total <= pageData.farmMachineList.length) if (pageData.farmMachineList.length >= pageData.total) return
return
pageData.search.pageNo++ pageData.search.pageNo++
getFarmMachineList() fetchList()
}) })
</script> </script>
<template> <template>
<view class="codefun-flex-col page"> <view class="page">
<view class="codefun-flex-col group_2"> <!-- 顶部筛选栏 -->
<view class="codefun-flex-row codefun-justify-between section_2"> <view class="filter-bar">
<text class="font_2 text_2">服务区域</text> <view class="area-picker" @click="pageData.show.address = true">
<view class="codefun-flex-row codefun-items-center" @click="pageData.show.address = true"> <fui-icon name="location" :size="28" color="#5db66f"></fui-icon>
<text class="font_2 text_3">{{ pageData.optionsValText || '选择市县乡镇' }}</text> <text class="area-text">{{ optionsValText || '服务区域' }}</text>
<image <fui-icon name="arrowdown" :size="24" color="#ccc"></fui-icon>
class="codefun-shrink-0 image_7 codefun-ml-4"
src="/static/images/codefun/774cfe989f8417dc655fb301635f5893.png"
/>
</view>
</view> </view>
<view class="codefun-flex-row section_3"> <view class="tab-box">
<view <view
v-for="item in pageData.categoryTabs" v-for="item in categoryTabs"
:key="item.id" :key="item.id"
class="codefun-flex-col codefun-justify-start codefun-items-center text-50p" class="tab-item"
:class="item.id === pageData.search.serviceType ? 'text-wrapper' : ''" :class="{ active: item.id === search.serviceType }"
@click="onCategoryTabClick(item)" @click="onCategoryTabClick(item)"
> >
<text class="font_2" :class="item.id === pageData.search.serviceType ? 'text_4' : 'text_5'">{{ {{ item.name }}
item.name
}}</text>
</view> </view>
</view> </view>
<view
v-if="!pageData.farmMachineList || pageData.farmMachineList.length == 0"
class="codefun-flex-col codefun-relative section_4"
style="height: 700rpx"
>
<fui-empty marginTop="100" src="/static/images/no-data.png" title="暂无数据" />
</view> </view>
<view class="codefun-flex-col codefun-relative section_4">
<template v-if="pageData.search.serviceType == 1"> <scroll-view scroll-y class="list-scroll">
<view class="yr-item" v-for="item in pageData.farmMachineList" :key="item.id"> <view class="list-container">
<view class="item_top"> <view v-if="!farmMachineList.length && !loading" class="empty-box">
<image class="enterprise_logo" mode="aspectFit" :src="item.enterpriseLogoUrl" /> <fui-empty src="/static/images/no-data.png" title="暂无相关服务" />
<view class="item_right"> </view>
<view class="text-ellipsis yr-item-name">{{ item.enterpriseName }}</view>
<view class="text-ellipsis yr-item-product">公司主营: {{ item.businessScope }}</view> <!-- 找农机 (企业/合作社列表) -->
<template v-if="search.serviceType == 1">
<view v-for="item in farmMachineList" :key="item.id" class="enterprise-card" @click="toEnterpriseDetail(item)">
<view class="card-main">
<image class="logo" :src="item.enterpriseLogoUrl || '/static/images/nongjifuwu/default-corp.png'" mode="aspectFill" />
<view class="info">
<text class="name">{{ item.enterpriseName }}</text>
<text class="scope">主营:{{ item.businessScope || '综合农机服务' }}</text>
<view class="tags">
<text class="tag">认证企业</text>
<text class="tag orange">专业团队</text>
</view> </view>
</view> </view>
<view class="yr-item-info">
<view class="text-ellipsis yr-item-address">
<image class="address_icon" src="/static/images/linghuoyonggong/address.png" />
<text
>{{ item.provinceName }}{{ item.cityName }}{{ item.districtName
}}{{ item.townName }}</text
>
</view> </view>
<view class="yr-item-detail" @click="toDetail(item)">查看详情</view> <view class="card-footer">
<view class="addr">
<fui-icon name="location" :size="24" color="#999"></fui-icon>
<text>{{ item.provinceName }}{{ item.cityName }}{{ item.districtName }}</text>
</view>
<view class="btn">查看详情</view>
</view> </view>
</view> </view>
</template> </template>
<!-- 干农活 (具体服务列表) -->
<template v-else> <template v-else>
<view class="codefun-flex-row group_6" v-for="item in pageData.farmMachineList" :key="item.id"> <view v-for="item in farmMachineList" :key="item.id" class="service-card" @click="handleItemClick(item)">
<image <view class="card-main">
class="codefun-shrink-0 codefun-self-center image_8" <image class="thumb" :src="item.picture || '/static/images/nongjifuwu/default-service.png'" mode="aspectFill" />
:src="item.picture" <view class="info">
@click="handleDetails(item)" <text class="title">{{ item.name }}</text>
/> <text class="desc">范围:{{ getText(item.scope, ' / ') }}</text>
<view style="width: 70%"> <view class="price-row">
<view <text class="price">{{ item.price }}</text>
class="codefun-flex-col codefun-items-start codefun-flex-1 codefun-self-center" <text class="unit">元/亩</text>
@click="handleDetails(item)"
>
<text class="font text_6">{{ item.name }}</text>
<text class="font_3 text_7 ellipsis" style="width: 100%; margin: 26rpx 0"
>服务范围:{{ getText(item.scope, ' / ') }}</text
>
</view>
<view class="flex justify-between" style="width: 100%">
<view class="group_5" @click="handleDetails(item)">
<text class="font_6">¥</text>
<text class="font_4 text_8">{{ item.price }}</text>
<text class="font_7">/亩</text>
</view> </view>
<view
class="codefun-flex-col codefun-justify-start codefun-items-center codefun-shrink-0 codefun-self-start text-wrapper_2"
@click="handleApply(item)"
>
<text class="font_5">去预约</text>
</view> </view>
</view> </view>
<view class="card-footer">
<view class="status">服务中</view>
<view class="btn orange" @click.stop="handleApply(item)">立即预约</view>
</view> </view>
</view> </view>
</template> </template>
<fui-loadmore v-if="loading" />
</view> </view>
</scroll-view>
<!-- 商家入驻悬浮按钮 (增加水波纹动效) -->
<view class="fab-container" @click="Navigate.to('/pages/nongjifuwu/shenqingruzhu')">
<view class="ripple"></view>
<view class="ripple ripple-2"></view>
<view class="fab-entry">
<fui-icon name="plus" :size="48" color="#fff"></fui-icon>
<text>商家入驻</text>
</view> </view>
</view> </view>
<fui-fab
v-show="pageData.search.serviceType != 1" <AreaPicker v-model:show="pageData.show.address" :layer="2" rootCode="43" title="选择服务区域" @confirm="handleAreaConfirm" />
position="right"
distance="10"
bottom="240"
width="96"
@click="handlePublish"
>
<view class="text-white text-center">
<view class="fab-icon" />
<view style="font-size: 24rpx">发布</view>
</view>
</fui-fab>
<!-- <fui-picker
:show="pageData.show.address"
:options="pageData.options"
:linkage="true"
:layer="3"
@change="handleChangeAddress"
@cancel="pageData.show.address = false"
/> -->
<fui-bottom-popup :show="pageData.show.address">
<view class="fui-scroll__wrap yr-select-area">
<view class="fui-title">请选择</view>
<fui-cascader
ref="areaPopupRef"
:value="pageData.optionsVal"
stepLoading
@change="changeArea"
@complete="selectCompleteArea"
:options="pageData.options"
/>
<view class="fui-icon__close" @tap.stop="pageData.show.address = false">
<fui-icon name="close" :size="48" />
</view>
</view>
</fui-bottom-popup>
<ApplyDialog ref="applyDialogRef" /> <ApplyDialog ref="applyDialogRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" /> </view>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.page { .page {
box-sizing: border-box; background-color: #f7f8fa;
padding-bottom: 12rpx; height: 100vh;
background-color: #e6f5e8;
mix-blend-mode: NOTTHROUGH;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
min-height: calc(100vh - 88rpx);
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.yr-item {
margin-top: 24rpx;
border-bottom: 2rpx solid #eeeeee;
padding-left: 8rpx;
padding-bottom: 20rpx;
.item_top {
display: flex; display: flex;
align-items: flex-start; flex-direction: column;
justify-content: space-between;
}
.enterprise_logo {
width: 160rpx;
max-height: 120rpx;
}
.yr-item-name {
width: 480rpx;
height: 40rpx;
font-size: 32rpx;
line-height: 40rpx;
font-weight: 500;
color: #333333;
} }
.yr-item-product {
margin-top: 12rpx; .filter-bar {
width: 480rpx; background-color: #fff;
height: 32rpx; padding: 20rpx 30rpx;
font-size: 24rpx;
line-height: 32rpx;
font-weight: 500;
color: #555555;
}
.yr-item-info {
margin-top: 18rpx;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
.yr-item-address { justify-content: space-between;
font-size: 24rpx; .area-picker {
color: #999999;
width: 480rpx;
display: flex; display: flex;
align-items: center; align-items: center;
.address_icon { background-color: #f5f5f5;
width: 24rpx; padding: 10rpx 20rpx;
height: 26rpx; border-radius: 30rpx;
margin-right: 8rpx; .area-text {
font-size: 26rpx;
color: #333;
margin: 0 10rpx;
max-width: 200rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
.yr-item-detail { .tab-box {
width: 136rpx;
height: 48rpx;
border-radius: 200rpx;
background: #5db66f;
display: flex; display: flex;
justify-content: center; background-color: #f5f5f5;
align-items: center; border-radius: 30rpx;
padding: 20rpx; padding: 4rpx;
color: #ffffff; .tab-item {
font-size: 24rpx; padding: 10rpx 30rpx;
font-weight: 400; font-size: 26rpx;
} color: #666;
} border-radius: 26rpx;
} &.active {
.yr-item:last-child {
border-bottom: none;
}
.section {
padding: 24rpx 24rpx 24rpx 32rpx;
background-color: #5db66f; background-color: #5db66f;
mix-blend-mode: NOTTHROUGH; color: #fff;
.image { font-weight: bold;
border-radius: 64rpx;
width: 108rpx;
height: 42rpx;
}
.image_2 {
mix-blend-mode: NOTTHROUGH;
width: 34rpx;
height: 22rpx;
}
.image_3 {
mix-blend-mode: NOTTHROUGH;
width: 30rpx;
height: 22rpx;
}
.image_4 {
width: 48rpx;
height: 22rpx;
}
.group {
padding: 8rpx 0;
.image_6 {
mix-blend-mode: NOTTHROUGH;
width: 14rpx;
height: 26rpx;
}
.pos_2 {
position: absolute;
left: 6rpx;
top: 50%;
transform: translateY(-50%);
}
.text {
color: #ffffffe6;
line-height: 29.5rpx;
}
.image_5 {
width: 44rpx;
height: 44rpx;
}
.pos {
position: absolute;
right: 0.66rpx;
top: 50%;
transform: translateY(-50%);
}
}
}
.group_2 {
padding: 16rpx 24rpx;
height: 1598rpx;
.section_2 {
padding: 32rpx 24rpx;
background-color: #ffffff;
border-radius: 16rpx;
.text_2 {
line-height: 25.82rpx;
}
.text_3 {
color: #999999;
line-height: 26.04rpx;
}
.image_7 {
width: 22rpx;
height: 12rpx;
}
}
.section_3 {
margin-top: 24rpx;
padding-bottom: 8rpx;
background-color: #ffffff66;
border-radius: 32rpx;
border: solid 2rpx #ffffffcc;
.text-50p {
width: 50%;
height: 122rpx;
padding: 0.75rem 0 1.625rem;
}
.text-wrapper {
padding: 24rpx 0 56rpx;
background-color: #ffffff;
border-radius: 32rpx;
.text_4 {
line-height: 26.02rpx;
}
}
.text_5 {
color: #5db66f;
line-height: 25.82rpx;
}
}
.font_2 {
font-size: 28rpx;
line-height: 24.6rpx;
color: #333333;
}
.section_4 {
margin-top: -48rpx;
padding: 0 16rpx;
background-color: #ffffff;
border-radius: 16rpx;
mix-blend-mode: NOTTHROUGH;
.group_6 {
padding: 24rpx 8rpx;
border-bottom: solid 2rpx #eeeeee;
.group_4 {
margin-left: 24rpx;
.text_6 {
line-height: 30.18rpx;
}
.text_7 {
margin-top: 24rpx;
line-height: 22.42rpx;
}
.group_5 {
margin-top: 40rpx;
line-height: 24.6rpx;
.text_8 {
line-height: 24.44rpx;
}
}
} }
.view {
margin-top: 112rpx;
} }
.text_9 {
line-height: 29.72rpx;
} }
.text_10 {
margin-top: 24rpx;
line-height: 22.44rpx;
}
.group_7 {
margin-top: 40rpx;
line-height: 24.6rpx;
.text_11 {
line-height: 24.44rpx;
}
}
.view_2 {
margin-top: 116rpx;
} }
.list-scroll {
flex: 1;
overflow: hidden;
} }
.group_8 {
padding: 24rpx 8rpx; .list-container {
.text_12 { padding: 20rpx;
line-height: 30.12rpx;
} }
.text_13 {
margin-top: 24rpx; .enterprise-card {
line-height: 22.44rpx; background-color: #fff;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 24rpx;
.card-main {
display: flex;
.logo {
width: 140rpx;
height: 140rpx;
border-radius: 12rpx;
background-color: #f8f8f8;
} }
.group_9 { .info {
margin-top: 40rpx; flex: 1;
line-height: 24.6rpx; margin-left: 20rpx;
.text_14 { display: flex;
line-height: 24.44rpx; flex-direction: column;
.name {
font-size: 30rpx;
font-weight: bold;
color: #333;
} }
.scope {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
} }
.view_3 { .tags {
margin-top: 116rpx; display: flex;
margin-top: 12rpx;
.tag {
font-size: 20rpx;
padding: 2rpx 12rpx;
background-color: #e6f5e8;
color: #5db66f;
border-radius: 4rpx;
margin-right: 10rpx;
&.orange {
background-color: #fff7e6;
color: #fa8c16;
} }
} }
.image_8 {
width: 160rpx;
height: 160rpx;
border-radius: 10rpx;
margin-right: 20rpx;
} }
.font_6 {
font-size: 24rpx;
line-height: 17.3rpx;
font-weight: 700;
color: #f2130d;
} }
.font_4 {
font-size: 32rpx;
line-height: 24.6rpx;
color: #f2130d;
} }
.font_7 { .card-footer {
font-size: 24rpx; margin-top: 20rpx;
line-height: 24.6rpx; padding-top: 20rpx;
color: #999999; border-top: 1rpx solid #f5f5f5;
display: flex;
justify-content: space-between;
align-items: center;
.addr {
display: flex;
align-items: center;
font-size: 22rpx;
color: #999;
} }
.text-wrapper_2 { .btn {
padding: 16rpx 0; padding: 8rpx 24rpx;
background-color: #5db66f; background-color: #5db66f;
border-radius: 400rpx; color: #fff;
mix-blend-mode: NOTTHROUGH;
width: 136rpx;
height: 48rpx;
.font_5 {
font-size: 24rpx; font-size: 24rpx;
line-height: 18rpx; border-radius: 26rpx;
color: #ffffff;
} }
} }
.section_5 {
margin-right: 4rpx;
overflow: hidden;
border-radius: 16rpx;
background-image: linear-gradient(90deg, #2e7d32 0%, #66bb6a 100%);
border-top: solid 2rpx #eeeeee;
.section_6 {
background-image: linear-gradient(90deg, #2e7d32 0%, #66bb6a 100%);
height: 96rpx;
}
.text_15 {
line-height: 26.02rpx;
}
.pos_4 {
position: absolute;
left: 30.26rpx;
top: 50%;
transform: translateY(-50%);
}
.text-wrapper_3 {
padding: 8rpx 0;
background-color: #ffffff;
border-radius: 12rpx;
width: 146.14rpx;
.text_16 {
color: #2e7d32;
line-height: 25.92rpx;
}
}
.pos_3 {
position: absolute;
right: 29.24rpx;
top: 50%;
transform: translateY(-50%);
}
}
.group_10 {
padding: 32rpx 8rpx 16rpx;
.text_17 {
margin-top: 24rpx;
line-height: 22.44rpx;
}
.group_11 {
margin-top: 40rpx;
line-height: 24.6rpx;
.text_18 {
line-height: 24.44rpx;
}
} }
.view_4 {
margin-top: 112rpx; .service-card {
} background-color: #fff;
} border-radius: 20rpx;
} padding: 24rpx;
.section_7 { margin-bottom: 24rpx;
margin-top: 24rpx; .card-main {
padding: 32rpx 8rpx 32rpx 24rpx; display: flex;
background-color: #ffffff; .thumb {
border-radius: 16rpx; width: 160rpx;
.grid {
margin-right: 16rpx;
height: 160rpx; height: 160rpx;
display: grid;
grid-template-rows: repeat(2, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
row-gap: 26rpx;
column-gap: 20rpx;
.grid-item_2 {
padding: 16rpx 24rpx;
border-radius: 12rpx; border-radius: 12rpx;
border: solid 2rpx #eeeeee;
.image_9 {
width: 20rpx;
height: 28rpx;
} }
.text_20 { .info {
line-height: 22.06rpx; flex: 1;
} margin-left: 20rpx;
} display: flex;
.image_10 { flex-direction: column;
width: 16rpx; .title {
height: 10rpx; font-size: 30rpx;
} font-weight: bold;
.grid-item_4 { color: #333;
padding: 16rpx;
border-radius: 12rpx;
border: solid 2rpx #eeeeee;
.image_11 {
width: 28rpx;
height: 29rpx;
} }
.text_21 { .desc {
line-height: 21.98rpx; font-size: 24rpx;
color: #999;
margin-top: 10rpx;
} }
.image_12 { .price-row {
width: 28rpx; margin-top: auto;
height: 28rpx; .price {
font-size: 36rpx;
color: #ff4d4f;
font-weight: bold;
} }
.text_22 { .unit {
line-height: 22.42rpx; font-size: 22rpx;
color: #999;
margin-left: 4rpx;
} }
} }
} }
.grid-item {
padding: 24rpx 16rpx;
border-radius: 12rpx;
border: solid 2rpx #eeeeee;
.text_19 {
line-height: 22.12rpx;
} }
.image_13 { .card-footer {
width: 25rpx; margin-top: 20rpx;
height: 25rpx; padding-top: 20rpx;
border-top: 1rpx dashed #eee;
display: flex;
justify-content: space-between;
align-items: center;
.status {
font-size: 22rpx;
color: #5db66f;
background-color: #e6f5e8;
padding: 4rpx 16rpx;
border-radius: 4rpx;
} }
.text_23 { .btn {
color: #999999; padding: 10rpx 28rpx;
line-height: 22.32rpx; background-color: #5db66f;
color: #fff;
font-size: 24rpx;
border-radius: 30rpx;
&.orange {
background-color: #fa8c16;
} }
} }
.group_12 {
margin-right: 16rpx;
} }
.text-wrapper_4 {
margin-left: 8rpx;
padding: 24rpx 0;
background-color: #ff9800;
border-radius: 200rpx;
.text_24 {
line-height: 25.96rpx;
} }
.fab-container {
position: fixed;
right: 30rpx;
bottom: 150rpx;
width: 110rpx;
height: 110rpx;
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;
} }
.font_3 {
font-size: 24rpx; .fab-entry {
line-height: 22.16rpx; position: relative;
color: #555555; width: 110rpx;
height: 110rpx;
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 8rpx 30rpx rgba(46, 139, 87, 0.5);
animation: breathe 2.5s infinite ease-in-out;
z-index: 2;
text {
font-size: 18rpx;
color: #fff;
margin-top: -4rpx;
font-weight: bold;
} }
.font_8 {
font-size: 28rpx;
line-height: 24.6rpx;
color: #ffffff;
} }
} }
.font {
font-size: 32rpx; @keyframes ripple {
line-height: 29.8rpx; 0% { transform: scale(1); opacity: 0.4; }
color: #333333; 100% { transform: scale(1.8); opacity: 0; }
} }
@keyframes breathe {
0%, 100% { transform: scale(1); box-shadow: 0 8rpx 30rpx rgba(46, 139, 87, 0.5); }
50% { transform: scale(1.08); box-shadow: 0 12rpx 45rpx rgba(46, 139, 87, 0.7); }
} }
::v-deep .fui-fab__btn-main { .empty-box {
background: linear-gradient(124.25deg, #a5d63f 0%, #5db66f 100%) !important; padding-top: 100rpx;
box-shadow: 0px 1px 8px #5db66f;
} }
</style> </style>
...@@ -5,664 +5,383 @@ ...@@ -5,664 +5,383 @@
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong' import * as LinghuoyonggongAPI from '@/api/model/linghuoyonggong'
import * as NongzhiAPI from '@/api/model/nongzhi' import * as NongzhiAPI from '@/api/model/nongzhi'
import AreaPicker from '@/components/AreaPicker/index.vue'
import { useDictStore } from '@/store/modules/dict'
const dictStore = useDictStore()
const userStore = useUserStore() const userStore = useUserStore()
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
onLoad((option) => {
// 获取数据详情
getProvince(0, null)
})
// 勾选协议校验
function checkAgree(agree) {
return agree
}
const areaPopupRef = ref(null)
const pageData = reactive({ const pageData = reactive({
loading: false, loading: false,
show: { show: {
time1: false,
time2: false,
area: false, area: false,
urgentdegree: false,
type: false,
},
options: {
area: [],
areaVal: [],
urgentdegree: [],
type: [],
}, },
cersImageIndex: 0,
cersImageArr: [], cersImageArr: [],
enterpriseLogoArr: [], enterpriseLogoArr: [],
selectAreaVal: [],
form: { form: {
id: '', id: '',
enterpriseName: '', // 企业名称 enterpriseName: '',
enterpriseCode: '', // 企业编码 enterpriseCode: '',
businessScope: '', // 企业经营范围 businessScope: '',
contactPerson: '', // 联系人 contactPerson: '',
contactMobile: '', // 联系人手机号 contactMobile: '',
profile: '', // 企业简介 profile: '',
provinceName: '',
provinceName: '', // 省 cityName: '',
cityName: '', // 市 districtName: '',
districtName: '', // 区县 townName: '',
townName: '', // 乡镇 provinceCode: '',
cityCode: '',
provinceCode: '', // 省 districtCode: '',
cityCode: '', // 市 townCode: '',
districtCode: '', // 区县 addr: '',
townCode: '', // 乡镇 lon: '',
lat: '',
addr: '', // 详细地址 bizCategory: 3,
lon: '', // 经度 enterpriseLogoUrl: null,
lat: '', // 纬度 enterpriseCers: null,
bizCategory: 3, // 业务分类【1:代理记账、2:农资、3:农机、4:金融】
enterpriseLogoUrl: null, // 企业logo url
enterpriseCers: null, // 企业资质url
areaText: '', areaText: '',
agree: false, agree: false,
}, },
position: [],
rules: [ rules: [
{ { name: 'enterpriseName', rule: ['required'], msg: ['请输入公司名称'] },
name: 'enterpriseName', { name: 'enterpriseCode', rule: ['required'], msg: ['请输入企业编码'] },
rule: ['required'], { name: 'businessScope', rule: ['required'], msg: ['请输入经营业务'] },
msg: ['请输入公司名称'], { name: 'profile', rule: ['required'], msg: ['请输入企业介绍'] },
}, { name: 'areaText', rule: ['required'], msg: ['请选择地区'] },
{ { name: 'addr', rule: ['required'], msg: ['请输入详细地址'] },
name: 'enterpriseCode', { name: 'contactPerson', rule: ['required'], msg: ['请输入联系人'] },
rule: ['required'], { name: 'contactMobile', rule: ['required', 'isMobile'], msg: ['请输入联系电话', '请输入正确的联系电话'] },
msg: ['请输入企业编码'], { name: 'enterpriseLogoUrl', rule: ['required'], msg: ['请上传公司logo'] },
}, { name: 'enterpriseCers', rule: ['required'], msg: ['请上传公司资质证件'] },
{ { name: 'agree', validator: [{ msg: '请勾选并同意协议', method: (a) => a }] },
name: 'businessScope',
rule: ['required'],
msg: ['请输入经营业务'],
},
{
name: 'profile',
rule: ['required'],
msg: ['请输入平台介绍'],
},
{
name: 'areaText',
rule: ['required'],
msg: ['请选择地区'],
},
{
name: 'addr',
rule: ['required'],
msg: ['请输入详细地址'],
},
{
name: 'contactPerson',
rule: ['required'],
msg: ['请输入联系人'],
},
{
name: 'contactMobile',
rule: ['required', 'isMobile'],
msg: ['请输入联系电话', '请输入正确的联系电话'],
},
{
name: 'enterpriseLogoUrl',
rule: ['required'],
msg: ['请上传公司logo'],
},
{
name: 'enterpriseCers',
rule: ['required'],
msg: ['请上传公司资质证件'],
},
{
name: 'agree',
validator: [
{
msg: '请勾选并同意《入驻协议》与《服务条款》',
method: checkAgree,
},
],
},
], ],
}) })
function agreeChange(e) { const { show, form } = toRefs(pageData)
pageData.form.agree = e.checked const toastRef = ref()
}
// 选择地区完成 function handleAreaConfirm(e) {
function selectCompleteArea(e) {
const areaAttr = ['province', 'city', 'district', 'town'] const areaAttr = ['province', 'city', 'district', 'town']
const text = e.text const { text, value } = e
const value = e.value
const formData = pageData.form
for (let i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
formData[`${areaAttr[i]}Name`] = text[i] pageData.form[`${areaAttr[i]}Name`] = text[i]
formData[`${areaAttr[i]}Code`] = value[i] pageData.form[`${areaAttr[i]}Code`] = value[i]
}
pageData.form.areaText = text.join('')
pageData.show.area = false
}
// 在选择地区
function changeArea(e) {
const val = e.value
getProvince(val, e)
}
// 获取下一级地区
function getProvince(code, e) {
LinghuoyonggongAPI.queryConditions({ parentCode: code }).then((res) => {
if (res.length) {
const dataArr = []
for (let i = 0; i < res.length; i++) {
const obj = { text: '', value: '' }
obj.text = res[i].name
obj.value = res[i].code
dataArr.push(obj)
}
if (!pageData.options.area.length) {
pageData.options.area = dataArr
} else {
areaPopupRef.value.setRequestData(dataArr, e.layer)
}
} else {
areaPopupRef.value.end()
} }
}) pageData.form.areaText = e.fullText
} }
const { show, options, form } = toRefs(pageData)
const toastRef = ref()
const uploadLogoRef = ref()
const uploadCersRef = ref()
// 文件上传
function handleUpload(file, type) { function handleUpload(file, type) {
const tempFilePaths = file.tempFilePaths const tempFilePath = file.tempFiles[0].path
// 处理每张选中的图片
for (let i = 0; i < tempFilePaths.length; i++) {
uni.uploadFile({ uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`,
filePath: tempFilePaths[i], filePath: tempFilePath,
name: 'file', name: 'file',
formData: { formData: { biz: 'temp' },
biz: 'temp', header: { 'X-Access-Token': userStore.getToken },
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => { success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data) const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) { if (data.code === 200 || data.code === 0) {
toastRef.value.show({ toastRef.value.show({ type: 'success', text: '上传成功' })
type: 'success',
text: '上传成功',
})
if (type == 'logo') { if (type == 'logo') {
pageData.enterpriseLogoArr[0] = data.message // 保存返回的图片信息 pageData.enterpriseLogoArr = [{ url: data.message }]
pageData.form.enterpriseLogoUrl = data.message
} else { } else {
pageData.cersImageIndex++ pageData.cersImageArr.push({ url: data.message })
pageData.cersImageArr.push(data.message)
} }
} }
} }
},
fail: (err) => {
toastRef.value.show({
type: 'error',
text: '上传失败',
}) })
}
function handleDelete(e, type) {
if (type == 'logo') { if (type == 'logo') {
uploadLogoRef.value.clearFiles() pageData.enterpriseLogoArr = []
pageData.enterpriseLogoArr[0] = null pageData.form.enterpriseLogoUrl = null
} else { } else {
uploadCersRef.value.clearFiles(pageData.cersImageIndex) const index = pageData.cersImageArr.findIndex(i => i.url === e.tempFilePath)
pageData.form.enterpriseCers = null if (index > -1) pageData.cersImageArr.splice(index, 1)
} }
},
})
}
}
// 文件删除
function handleDelete(file, type) {
if ((type = 'logo')) {
uploadLogoRef.value.clearFiles()
pageData.enterpriseLogoArr[0] = null
} else {
const num = pageData.cersImageArr.findIndex((v) => v.url === file.tempFilePath)
pageData.cersImageArr.splice(num, 1)
} }
function handleViewProtocol() {
uni.showModal({
title: '农机服务平台入驻协议',
content: '协议内容预览...',
showCancel: false
})
} }
const formRef = ref() const formRef = ref()
function submit() { function submit() {
const { lon, lat } = uni.getStorageSync('location') const { lon, lat } = uni.getStorageSync('location') || { lon: '', lat: '' }
pageData.position = [lon, lat] pageData.form.lon = lon
pageData.form.enterpriseLogoUrl = pageData.enterpriseLogoArr.join('') pageData.form.lat = lat
pageData.form.enterpriseCers = pageData.cersImageArr.join(',') pageData.form.enterpriseCers = pageData.cersImageArr.map(i => i.url).join(',')
if (pageData.position.length == 0) {
toastRef.value.show({
type: 'error',
text: '无法获取位置',
})
return
}
pageData.form.lon = pageData.position[0]
pageData.form.lat = pageData.position[1]
formRef.value.validator(pageData.form, pageData.rules, true).then((res) => { formRef.value.validator(pageData.form, pageData.rules, true).then((res) => {
if (res.isPassed) { if (res.isPassed) {
NongzhiAPI.postEnterpriseAdd(pageData.form).then((res) => { pageData.loading = true
toastRef.value.show({ NongzhiAPI.postEnterpriseAdd(pageData.form).then(() => {
type: 'success', toastRef.value.show({ type: 'success', text: '申请提交成功' })
text: '发布成功', setTimeout(() => uni.navigateBack(), 1500)
}) }).finally(() => {
setTimeout(() => { pageData.loading = false
uni.navigateBack({
delta: 1, // 返回的页面数
})
}, 1000)
}) })
} }
}) })
} }
function getCurrentDate() {
const date = new Date()
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${month}-${day}`
}
</script> </script>
<template> <template>
<view class="page"> <view class="page">
<view class="formBox"> <!-- 头部背景 -->
<fui-form ref="formRef" label-weight="auto" top="60" :disabled="form.id ? true : false"> <view class="banner">
<view class="mt20"> <view class="banner-title">农机服务入驻</view>
<fui-input <view class="banner-tips">专业平台,连接万千农户</view>
required </view>
label="公司名称"
placeholder="请输入公司名称" <!-- 进度条 -->
v-model="form.enterpriseName" <view class="step-container">
labelSize="28" <view class="step-item active">
label-width="180" <view class="num">1</view>
size="28" <text>提交资料</text>
/> </view>
<fui-input <view class="line active"></view>
required <view class="step-item">
label="企业编码" <view class="num">2</view>
placeholder="请输入企业编码" <text>后台审核</text>
v-model="form.enterpriseCode"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
required
label="经营业务"
placeholder="请输入经营业务"
v-model="form.businessScope"
labelSize="28"
label-width="180"
size="28"
/>
<fui-form-item asterisk label="平台介绍" :bottomBorder="false" prop="descr" error-align="left">
<template #vertical>
<fui-textarea
isCounter
maxlength="-1"
:padding="['0', '32rpx', '32rpx']"
:border-bottom="false"
:border-top="false"
size="28"
placeholder="请输入平台介绍..."
v-model="form.profile"
/>
</template>
</fui-form-item>
</view> </view>
<view class="mt20"> <view class="line"></view>
<fui-input <view class="step-item">
required <view class="num">3</view>
label="地区" <text>完成入驻</text>
placeholder="请选择地区"
v-model="form.areaText"
labelSize="28"
label-width="180"
@click="show.area = true"
size="28"
disabled
/>
<fui-input
required
label="详细地址"
placeholder="请输入详细地址"
v-model="form.addr"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
required
label="联系人"
placeholder="请输入联系人"
v-model="form.contactPerson"
labelSize="28"
label-width="180"
size="28"
/>
<fui-input
type="tel"
required
label="联系电话"
placeholder="请输入联系电话"
v-model="form.contactMobile"
labelSize="28"
label-width="180"
size="28"
/>
</view> </view>
<view class="bg-white mt20" style="padding: 0.875rem 1rem">
<view class="mb-1 flex justify-start" style="font-size: 28rpx"
><span style="color: red">*&nbsp;</span> 公司logo
</view> </view>
<uni-file-picker
:value="pageData.enterpriseLogoArr" <view class="form-body">
ref="uploadLogoRef" <fui-form ref="formRef">
limit="1" <!-- 基本信息卡片 -->
:auto-upload="false" <view class="card">
@select="handleUpload($event, 'logo')" <view class="card-header">
@delete="handleDelete($event, 'logo')" <view class="tag-line"></view>
/> <text>基本信息</text>
</view> </view>
<view class="bg-white mt20" style="padding: 0.875rem 1rem"> <fui-input label="服务队名称" placeholder="请输入农机服务队或公司名称" v-model="form.enterpriseName" required />
<view class="mb-1 flex justify-start" style="font-size: 28rpx" <fui-input label="统一代码" placeholder="营业执照信用代码" v-model="form.enterpriseCode" required />
><span style="color: red">*&nbsp;</span> 公司资质证件 <fui-input label="服务内容" placeholder="如:耕整、播种、植保等" v-model="form.businessScope" required />
<view class="field-item">
<view class="field-label"><text class="req">*</text>服务队介绍</view>
<fui-textarea placeholder="介绍您的设备实力、服务经验等..." v-model="form.profile" height="160rpx" background="#fcfcfc" radius="12" />
</view> </view>
<view class="mb-1 flex justify-start" style="font-size: 24rpx; color: #cccccc"
>前6张资质证件将展示在详情页,拖拽图片可自定义排序。</view <view class="field-item">
> <view class="field-label"><text class="req">*</text>形象照片/Logo</view>
<uni-file-picker <uni-file-picker :value="pageData.enterpriseLogoArr" limit="1" @select="handleUpload($event, 'logo')" @delete="handleDelete($event, 'logo')" />
limit="9"
:value="pageData.cersImageArr"
ref="uploadCersRef"
:auto-upload="false"
@select="handleUpload($event, 'cers')"
@delete="handleDelete($event, 'cers')"
/>
</view> </view>
<view class="fui-clause--cell fui-clause--wrap">
<fui-label>
<view class="fui-clause--cell">
<fui-checkbox :scaleRatio="0.8" @change="agreeChange" />
<text class="fui-clause--text">我已阅读并同意</text>
</view> </view>
</fui-label>
<fui-text class="fui-color__link">《入驻协议》</fui-text> <!-- 经营地点卡片 -->
<text></text> <view class="card">
<fui-text class="fui-color__link">《服务条款》</fui-text> <view class="card-header">
<view class="tag-line"></view>
<text>联系与位置</text>
</view> </view>
<view class="fui-btn__box" v-if="!form.id" style="margin-top: 30rpx"> <fui-input label="常驻地区" placeholder="点击选择" v-model="form.areaText" @click="show.area = true" disabled required />
<fui-button text="提交申请" bold radius="96rpx" @click="submit" /> <fui-input label="详细地址" placeholder="村组、街道门牌号" v-model="form.addr" required />
<fui-input label="负责人" placeholder="联系人姓名" v-model="form.contactPerson" required />
<fui-input label="联系电话" type="tel" placeholder="请输入手机号" v-model="form.contactMobile" required />
</view>
<!-- 证照上传卡片 -->
<view class="card">
<view class="card-header">
<view class="tag-line"></view>
<text>资质证照</text>
</view>
<view class="cert-box">
<text class="tips">请上传营业执照、农机驾驶证或相关作业资质</text>
<view class="mt-20">
<uni-file-picker limit="9" :value="pageData.cersImageArr" @select="handleUpload($event, 'cers')" @delete="handleDelete($event, 'cers')" />
</view> </view>
</fui-form>
</view> </view>
</view> </view>
<!-- <fui-date-picker :show="show.time1" type="3" @change="handleChangeTime1" :min-date="getCurrentDate()" @cancel="show.time1 = false" /> <!-- 协议勾选 -->
<fui-date-picker :show="show.time2" type="3" @change="handleChangeTime2" :min-date="getCurrentDate()" @cancel="show.time2 = false" /> <view class="agree-wrap">
<fui-picker :show="show.type" :layer="1" :linkage="true" :options="options.type" @change="handleChangetype" @cancel="show.type = false" /> <fui-checkbox-group @change="e => form.agree = e.detail.value.length > 0">
<fui-picker :show="show.urgentdegree" :layer="1" :linkage="true" :options="options.urgentdegree" @change="handleChangeUrgentdegree" @cancel="show.urgentdegree = false"/> <fui-label class="agree-label">
--><!-- <fui-picker :show="show.area" :options="options.area" :linkage="true" :layer="3" @change="handleChangeAddress" @cancel="show.area = false" /> --> <view class="agree-inner">
<!-- 地区的选择 --> <fui-checkbox :value="true" :scaleRatio="0.7" color="#5db66f" />
<fui-bottom-popup :show="show.area"> <view class="agree-text-content">
<view class="fui-scroll__wrap"> <text class="agree-text">我已阅读并同意</text>
<view class="fui-title">请选择</view> <text class="agree-link" @click.stop="handleViewProtocol">《农机服务平台入驻协议》</text>
<fui-cascader
ref="areaPopupRef"
:value="pageData.options.areaVal"
stepLoading
@change="changeArea"
@complete="selectCompleteArea"
:options="pageData.options.area"
/>
<view class="fui-icon__close" @tap.stop="pageData.show.area = false">
<fui-icon name="close" :size="48" />
</view> </view>
</view> </view>
</fui-bottom-popup> </fui-label>
</fui-checkbox-group>
</view>
<view class="btn-area">
<fui-button text="提交申请" radius="100rpx" @click="submit" :loading="pageData.loading" background="#5db66f" border-color="#5db66f" />
</view>
</fui-form>
</view>
<AreaPicker v-model:show="show.area" :layer="4" @confirm="handleAreaConfirm" />
<fui-toast ref="toastRef" /> <fui-toast ref="toastRef" />
<fui-loading isFixed v-if="pageData.loading" backgroundColor="rgba(0, 0, 0, 0.4)" /> </view>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
body {
background-color: #e6f5e8;
}
.page { .page {
background-color: #e6f5e8; min-height: 100vh;
width: 750rpx; background-color: #f7f9f8;
overflow-x: hidden; padding-bottom: 80rpx;
.mt20 { font-family: 'DingTalk Sans', sans-serif;
margin-top: 30rpx;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
}
.formBox {
width: 690rpx;
margin: 30rpx auto;
}
.form-section {
// border-bottom: 1rpx solid #f5f5f5;
} }
.form-item { .banner {
padding: 30rpx 0; background-color: #5db66f;
border-bottom: 1rpx solid #f5f5f5; padding: 60rpx 40rpx 120rpx;
color: #fff;
&:last-child { .banner-title { font-size: 44rpx; font-weight: bold; }
border-bottom: none; .banner-tips { font-size: 24rpx; opacity: 0.8; margin-top: 10rpx; }
}
&.required .label::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
} }
.form-row { .step-container {
background-color: #fff;
margin: -60rpx 30rpx 0;
border-radius: 24rpx;
padding: 40rpx 20rpx;
display: flex; display: flex;
justify-content: space-between;
}
.half-width {
width: 48%;
}
.align-center {
align-items: center; align-items: center;
} justify-content: center;
box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.05);
.label { position: relative;
display: block; z-index: 5;
font-size: 28rpx; .step-item {
color: #333333;
font-weight: 500;
width: 180rpx;
}
.input {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #999999;
}
}
.time-range {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: space-between; .num {
} width: 44rpx;
height: 44rpx;
.time-input { background-color: #f0f0f0;
width: 45%; color: #999;
border-radius: 8rpx; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
font-size: 24rpx;
margin-bottom: 10rpx;
} }
text { font-size: 20rpx; color: #999; }
.time-text { &.active {
font-size: 28rpx; .num { background-color: #5db66f; color: #fff; }
color: #333333; text { color: #333; font-weight: bold; }
&.placeholder {
color: #999999;
} }
} }
.line {
.time-separator { width: 80rpx;
color: #666666; height: 2rpx;
font-size: 28rpx; background-color: #f0f0f0;
margin: 0 10rpx; margin: 0 20rpx;
margin-top: -30rpx;
&.active { background-color: #5db66f; }
} }
.upload-area {
margin-top: 10rpx;
} }
.custom-uploader { .form-body {
:deep(.uni-file-picker__container) { padding: 24rpx;
border: 2rpx dashed #d9d9d9;
border-radius: 8rpx;
background: #f8f9fa;
}
} }
.upload-placeholder { .card {
background-color: #fff;
border-radius: 24rpx;
padding: 30rpx;
margin-bottom: 24rpx;
.card-header {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; margin-bottom: 30rpx;
height: 200rpx; .tag-line { width: 8rpx; height: 30rpx; background-color: #5db66f; border-radius: 4rpx; margin-right: 16rpx; }
color: #999999; text { font-size: 30rpx; font-weight: bold; color: #333; }
}
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
} }
.upload-text {
font-size: 24rpx;
} }
.submit-section { .field-item {
background: transparent; padding: 20rpx 0;
padding: 40rpx 0; .field-label {
} font-size: 28rpx;
color: #333;
.submit-btn { margin-bottom: 20rpx;
width: 100%; .req { color: #ff4d4f; margin-right: 4rpx; }
height: 88rpx;
background: #ff9800;
border-radius: 44rpx;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
border: none;
&:active {
background: #e68900;
opacity: 0.9;
}
}
} }
::v-deep .uni-input-placeholder {
font-size: 28rpx !important;
color: #999999 !important;
} }
// 移除fui-form的默认样式 .cert-box {
:deep(.fui-form) { .tips { font-size: 22rpx; color: #999; background-color: #f9f9f9; padding: 16rpx; border-radius: 8rpx; }
background: transparent; .mt-20 { margin-top: 24rpx; }
} }
:deep(.fui-form__item) { .agree-wrap {
background: transparent; display: flex;
border: none; justify-content: center;
margin-bottom: 0; padding: 40rpx 0 20rpx;
padding: 0; .agree-label {
display: block;
} }
.unit-slot { .agree-inner {
padding: 0 16rpx; display: flex;
color: #333; align-items: center;
font-size: 28rpx;
} }
.agree-text-content {
.fui-clause--cell {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; flex-wrap: wrap;
margin-left: 12rpx;
}
.agree-text {
font-size: 24rpx; font-size: 24rpx;
color: #999999; color: #999;
} }
.fui-clause--wrap { .agree-link {
width: 100%; font-size: 24rpx;
margin-top: 64rpx; color: #5db66f;
font-weight: 500;
} }
.fui-clause--text {
padding-left: 16rpx;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
} }
.fui-color__link {
color: #5fb771; .btn-area {
/* #ifdef H5 */ padding: 20rpx 30rpx;
cursor: pointer;
/* #endif */
} }
.fui-color__link:active { .area-pop {
opacity: 0.5; background-color: #fff;
padding: 30rpx;
border-radius: 32rpx 32rpx 0 0;
.pop-title { font-size: 32rpx; font-weight: bold; text-align: center; margin-bottom: 30rpx; }
} }
.fui-scroll__wrap {
padding-top: 30rpx; /* 覆盖FirstUI的部分样式以统一字体 */
position: relative; :deep(.fui-input__label),
:deep(.fui-input__input),
:deep(.fui-textarea__textarea),
:deep(.fui-textarea__label) {
font-family: 'DingTalk Sans', system-ui !important;
color: #333 !important;
} }
.fui-title { :deep(.uni-input-placeholder),
font-size: 30rpx; :deep(.uni-textarea-placeholder) {
font-weight: bold; font-family: 'DingTalk Sans' !important;
text-align: center; font-size: 28rpx !important;
padding-bottom: 24rpx;
} }
.fui-icon__close { :deep(.fui-button) {
position: absolute; font-family: 'DingTalk Sans' !important;
top: 24rpx; font-weight: bold !important;
right: 24rpx;
} }
</style> </style>
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { useUserStore } from '@/store/modules/user'
import { useGlobSetting } from '/@/hooks/setting'
import * as WodeAPI from '@/api/model/wode'
const props = withDefaults(defineProps<Props>(), {
show: false,
editData: null,
})
// 定义Emits
const emit = defineEmits<{
'update:show': [value: boolean]
submit: [data: any]
close: []
submitSuccess: []
}>()
const lemonjkFileSelect = uni.requireNativePlugin('lemonjk-FileSelect')
// 定义Props
interface Props {
show: boolean
editData?: any
}
// 表单引用
const formRef = ref()
const loading = ref(false)
const uploading = ref(false)
const showFileTypePicker = ref(false)
// 文件类型选项
const fileTypeOptions = [
{ value: 'doc', text: 'Word文档' },
{ value: 'xls', text: 'Excel表格' },
{ value: 'ppt', text: 'PPT演示' },
{ value: 'pdf', text: 'PDF文档' },
{ value: 'img', text: '图片文件' },
{ value: 'video', text: '视频文件' },
{ value: 'audio', text: '音频文件' },
{ value: 'zip', text: '压缩文件' },
{ value: 'other', text: '其他文件' },
]
// 表单数据
const formData = reactive({
fileName: '',
fileType: '',
fileTypeText: '',
file: null,
fileSrc: '',
})
// 计算属性
const dialogTitle = computed(() => {
return props.editData ? '编辑资源' : '上传资源'
})
const submitButtonText = computed(() => {
return props.editData ? '保存' : '上传'
})
// 表单验证规则
const rules = {
fileName: [
{ required: true, message: '请输入文件名称', trigger: 'blur' },
{ min: 2, max: 50, message: '文件名称长度在2-50个字符之间', trigger: 'blur' },
],
fileType: [{ required: true, message: '请选择文件类型', trigger: 'change' }],
file: [
{
required: true,
message: '请选择文件',
trigger: 'change',
validator: (rule, value, callback) => {
if (!value) {
callback(new Error('请选择文件'))
} else {
callback()
}
},
},
],
}
// 监听显示状态
watch(
() => props.show,
(newVal) => {
if (newVal && props.editData) {
// 编辑模式,填充数据
resetFormData()
loadEditData()
} else if (newVal) {
// 添加模式,重置表单
resetFormData()
}
},
)
// 重置表单数据
function resetFormData() {
formData.fileName = ''
formData.fileType = ''
formData.fileTypeText = ''
formData.file = null
formData.fileSrc = ''
// 清除验证状态
if (formRef.value) {
formRef.value.resetFields()
}
}
// 加载编辑数据
function loadEditData() {
if (!props.editData) return
formData.fileName = props.editData.fileName || ''
formData.fileType = props.editData.fileType || ''
formData.fileTypeText = props.editData.fileTypeText || ''
// 编辑模式不修改文件本身
// formData.file = null
}
const uplpoadFile = ref(null)
// 选择文件
function chooseFile() {
// #ifdef H5
uni.chooseFile({
count: 1,
extension: ['*'],
success: (res) => {
uplpoadFile.value = res.tempFilePaths[0]
const tempFilePaths = res.tempFilePaths
const tempFiles = res.tempFiles
console.log(tempFiles)
if (tempFiles && tempFiles.length > 0) {
if (!formData.fileName) {
formData.fileName = tempFiles[0].name || ''
}
// 根据文件扩展名设置文件类型
const fileName = tempFiles[0].name || ''
const ext = fileName.split('.').pop()?.toLowerCase() || ''
const fileType = getFileTypeByExt(ext)
formData.file = tempFiles[0]
formData.fileType = fileType.value
formData.fileTypeText = fileType.text
// 文件选择后触发验证
if (formRef.value) {
formRef.value.validateField('file')
}
}
},
fail: (err) => {
console.error('选择文件失败:', err)
uni.showToast({ title: '选择文件失败', icon: 'none' })
uploading.value = false
},
})
// #endif
// #ifndef H5
lemonjkFileSelect.showNativePicker(
{
// 各属性配置见下方【showPicker可配置参数说明】
pathScope: '/Download',
mimeType: '*/*',
utisType: ['public.data'],
},
(result) => {
// 返回值说明见下方【showPicker返回值说明】
const tempFiles = result.files
console.log(tempFiles[0])
uplpoadFile.value = tempFiles[0].filePath
// 根据文件扩展名设置文件类型
const fileName = tempFiles[0].fileRealName || ''
const fileType = getFileTypeByExt(tempFiles[0].fileExtension)
formData.fileName = fileName
formData.file = { name: fileName }
formData.fileType = fileType.value
formData.fileTypeText = fileType.text
},
)
// #endif
}
// 移除文件
function removeFile() {
formData.file = null
// 移除文件后触发验证
if (formRef.value) {
formRef.value.validateField('file')
}
}
// 根据文件扩展名获取文件类型
function getFileTypeByExt(ext) {
const map = {
doc: 'doc',
docx: 'doc',
xls: 'xls',
xlsx: 'xls',
ppt: 'ppt',
pptx: 'ppt',
pdf: 'pdf',
jpg: 'img',
jpeg: 'img',
png: 'img',
gif: 'img',
mp4: 'video',
avi: 'video',
mp3: 'audio',
wav: 'audio',
zip: 'zip',
rar: 'zip',
'7z': 'zip',
}
const type = map[ext] || 'other'
return fileTypeOptions.find((item) => item.value === type) || fileTypeOptions[8]
}
const userStore = useUserStore()
const globSetting = useGlobSetting()
// 提交表单
async function handleSubmit() {
try {
console.log(formData)
// 先进行表单验证
const valid = await formRef.value.validate()
if (!valid) {
// 验证失败,返回
return
}
// 额外校验文件是否存在
if (!formData.file) {
uni.showToast({
title: '请选择文件',
icon: 'none',
})
return
}
loading.value = true
// 准备提交数据
const submitData = {
fileName: formData.fileName,
fileType: formData.fileType,
fileSize: formData.file ? formData.file.size : 0,
fileSrc: formData.fileSrc,
// createTime: new Date().toLocaleString()
}
// API留空
console.log('上传资源数据:', submitData)
uni.uploadFile({
url: `${globSetting.apiUrl + globSetting.urlPrefix}/sys/common/upload`, // 直接使用上传接口URL
filePath: uplpoadFile.value,
name: 'file',
formData: {
biz: 'temp',
},
header: {
'X-Access-Token': userStore.getToken,
},
success: (res) => {
if (res.statusCode === 200) {
const data = JSON.parse(res.data)
if (data.code === 200 || data.code === 0) {
submitData.fileSrc = data.message
WodeAPI.addResource(submitData)
.then((res) => {
uni.showToast({
title: '上传成功',
icon: 'success',
duration: 2000,
})
// emit('update:show', false)
emit('submitSuccess')
})
.catch((error) => {
uni.showToast({
title: error.message || '提交失败,请重试',
icon: 'none',
duration: 2000,
})
})
.finally(() => {
loading.value = false
})
}
}
},
fail: () => {
uni.showToast({
title: '上传失败',
icon: 'none',
duration: 2000,
})
},
})
// 模拟上传过程
// setTimeout(() => {
// uni.showToast({ title: '操作成功', icon: 'success' })
// emit('update:show', false)
// emit('submitSuccess')
// }, 1500)
} catch (error) {
console.log('提交失败:', error)
uni.showToast({
title: '操作失败',
icon: 'none',
duration: 2000,
})
} finally {
loading.value = false
}
}
// 关闭弹窗
function handleClose() {
emit('update:show', false)
emit('close')
}
</script>
<template>
<u-modal
:show="show"
:title="dialogTitle"
:showConfirmButton="false"
:showCancelButton="false"
@close="handleClose"
:closeOnClickOverlay="false"
>
<view class="dialog-content">
<u-form :model="formData" :rules="rules" ref="formRef" label-width="auto">
<!-- 资源基本信息 -->
<!-- <view class="section-title">资源基本信息</view> -->
<u-form-item label="" prop="fileName">
<u-input v-model="formData.fileName" placeholder="请输入文件名称" border="bottom" />
</u-form-item>
<!-- <u-form-item label="文件类型" prop="fileType" required>
<u-input v-model="formData.fileType" placeholder="请选择文件类型" border="bottom"
@click="showFileTypePicker = true" />
</u-form-item> -->
<u-form-item label="上传文件" prop="file" required>
<view class="file-upload">
<u-button v-if="!formData.file" @click="chooseFile" type="primary" class="ui-button">
<text class="font_10">选择文件</text>
</u-button>
<view v-else class="file-info">
<view class="file-name-container">
<text class="file-name">{{ formData.file.name }}</text>
<text class="remove-icon" @click="removeFile">×</text>
</view>
</view>
</view>
</u-form-item>
<!-- 操作按钮 -->
<view class="dialog-buttons">
<u-button
type="primary"
@click="handleSubmit"
:loading="loading"
size="normal"
class="submit-btn"
color="var(--fui-color-success)"
>
{{ submitButtonText }}
</u-button>
<u-button @click="handleClose" size="normal" class="cancel-btn">取消</u-button>
</view>
</u-form>
</view>
</u-modal>
</template>
<style lang="scss" scoped>
.dialog-content {
padding: 10rpx 30rpx;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
border-left: 6rpx solid #5db66f;
padding-left: 12rpx;
}
.dialog-buttons {
display: flex;
justify-content: space-between;
margin-top: 50rpx;
gap: 20rpx;
}
.submit-btn,
.cancel-btn {
flex: 1;
}
.file-upload {
width: 100%;
}
.file-info {
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
.file-name-container {
position: relative;
display: flex;
align-items: center;
}
.file-name {
flex: 1;
width: 40rpx;
font-size: 28rpx;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 40rpx;
}
.remove-icon {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
font-size: 36rpx;
color: #999;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
color: #ff4d4f;
background-color: rgba(255, 77, 79, 0.1);
border-radius: 50%;
}
}
}
// 模态框样式调整
::v-deep .u-modal {
background: linear-gradient(0deg, rgba(93, 182, 111, 0) 50%, rgba(93, 182, 111, 0.25) 100%);
.u-modal__content {
border-radius: 20rpx;
padding: 20rpx 0rpx;
}
.u-modal__header {
border-bottom: 2rpx solid #f0f0f0;
padding: 30rpx;
.u-modal__header__title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
}
.ui-button {
padding: 16rpx 0;
background-color: #5db66f26;
border-radius: 10rpx;
mix-blend-mode: NOTTHROUGH;
border-color: #5db66f26;
.font_10 {
font-size: 24rpx;
line-height: 24rpx;
color: #16a34a;
}
}
</style>
<script setup>
import { reactive, ref } from 'vue'
import { onNavigationBarButtonTap } from '@dcloudio/uni-app'
import SaveDialog from './components/save-dialog.vue'
import * as API from '@/api/model/wode'
const isOnePage = ref(true)
const paging = ref(null)
const pageData = reactive({
param: {
pageNo: 1,
pageSize: 10,
fileName: '',
},
list: [],
})
function getList() {
if (!paging.value)
return
// API留空,使用模拟数据 - 根据图片内容调整
API.getResourceList(pageData.param).then((res) => {
paging.value.complete(res.records)
})
}
function queryList(pageNo, pageSize) {
pageData.param.pageNo = pageNo
pageData.param.pageSize = pageSize
getList()
}
function handleSearch() {
pageData.param.pageNo = 1
if (paging.value) {
paging.value.reload()
}
}
const showDialog = ref(false)
const currentEditData = ref(null)
onNavigationBarButtonTap((_) => {
showAddDialog()
})
function showAddDialog() {
currentEditData.value = null
showDialog.value = true
}
// 格式化时间显示
function formatTime(time) {
if (!time) return ''
// 如果是完整的时间字符串,可以格式化为图片中的样式
return time.includes(' ') ? time : `${time} 14:00`
}
// 下载资源
async function handleDownload(resource) {
await API.downloadResource({ id: resource.id })
// 更新下载次数
const index = pageData.list.findIndex((item) => item.id === resource.id)
if (index !== -1) {
pageData.list[index].downloadCount = (pageData.list[index].downloadCount || 0) + 1
}
const { downloadResource } = await import('@/utils')
// 使用封装的下载方法
const result = await downloadResource(resource)
if (!result.success) {
console.error('下载失败:', result.error)
}
}
// 格式化文件大小
function formatFileSize(bytes) {
if (!bytes) return '125kb'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Number.parseFloat((bytes / k ** i).toFixed(0)) + sizes[i].toLowerCase()
}
function handleSubmitSuccess() {
showDialog.value = false
// 重新加载数据
handleSearch()
}
</script>
<template>
<view class="codefun-flex-col page">
<z-paging ref="paging" v-model="pageData.list" @query="queryList">
<view class="codefun-flex-col group_3">
<view class="codefun-flex-row codefun-items-center section_2">
<image class="image_6" src="/static/images/codefun/6c5c5a3c082b8c60a307d3a7caee623c.png" />
<u-input
v-model="pageData.param.fileName"
placeholder="请输入文件名称搜索"
border="none"
class="codefun-ml-8"
@confirm="handleSearch"
/>
</view>
<!-- 资源列表 - 根据图片样式修改 -->
<view class="resource-list">
<view v-for="(item, index) in pageData.list" :key="index" class="resource-item">
<view class="resource-content">
<view class="resource-main">
<view class="resource-name">{{ item.fileName }}</view>
<view class="resource-meta">
<text class="resource-size">{{ item.fileSize }}</text>
<text class="separator">|</text>
<text class="download-count">已下载{{ item.downloadCount || 0 }}</text>
<text class="separator">|</text>
<text class="upload-time">{{ formatTime(item.createTime) }}</text>
</view>
</view>
<view
class="codefun-flex-col codefun-justify-start codefun-items-center text-wrapper_4"
@click.stop="handleDownload(item)"
>
<text class="font_10">下载</text>
</view>
<!-- <view class="download-section">
<view class="download-btn" @click="handleDownload(item)">
<text class="btn-text">下载</text>
</view>
</view> -->
</view>
</view>
</view>
<!-- 空状态 -->
<!-- <view class="empty-state" v-if="pageData.list.length === 0">
<image class="empty-icon" src="/static/images/empty.png" mode="aspectFit"></image>
<text class="empty-text">暂无资源</text>
</view> -->
</view>
</z-paging>
<!-- 弹窗组件 -->
<SaveDialog
:show="showDialog"
:editData="currentEditData"
@update:show="showDialog = $event"
@submit-success="handleSubmitSuccess"
/>
</view>
</template>
<style lang="scss">
body {
background: #e6f5e8;
}
.page {
background: #e6f5e8;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
}
.group_3 {
padding: 28rpx 24rpx;
}
.section_2 {
padding: 16rpx 20rpx;
background-color: #ffffff;
border-radius: 1998rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.image_6 {
width: 32rpx;
height: 32rpx;
}
/* 资源列表样式 - 根据图片样式重写 */
.resource-list {
margin-top: 0;
border-radius: 16rpx;
background-color: #ffffff;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
padding: 0 20rpx;
}
.resource-item {
// border-radius: 16rpx;
// margin-bottom: 20rpx;
overflow: hidden;
transition: all 0.3s ease;
border-bottom: 1px solid #e3e3e3;
&:active {
transform: translateY(2rpx);
box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.1);
}
}
.resource-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 24rpx;
}
.resource-main {
flex: 1;
min-width: 0;
}
.resource-name {
font-size: 32rpx;
font-weight: 500;
color: #333333;
line-height: 1.4;
margin-bottom: 12rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.resource-meta {
display: flex;
align-items: center;
font-size: 20rpx;
color: #999999;
gap: 10rpx;
flex-wrap: wrap;
}
.separator {
color: #dddddd;
font-size: 20rpx;
}
.resource-size,
.download-count,
.upload-time {
color: #666666;
}
.download-section {
margin-left: 30rpx;
flex-shrink: 0;
}
.download-btn {
background-color: #5db66f;
border: none;
border-radius: 20rpx;
padding: 12rpx 30rpx;
min-width: 120rpx;
transition: all 0.3s ease;
&::after {
border: none;
}
&:active {
background-color: #4ca85c;
transform: scale(0.95);
}
}
.text-wrapper_4 {
padding: 16rpx 0;
background-color: #5db66f26;
border-radius: 400rpx;
mix-blend-mode: NOTTHROUGH;
width: 136rpx;
.font_10 {
font-size: 24rpx;
line-height: 24rpx;
color: #16a34a;
}
}
.btn-text {
color: #ffffff;
font-size: 28rpx;
font-weight: 500;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 120rpx 0;
color: #999999;
}
.empty-icon {
width: 160rpx;
height: 160rpx;
margin-bottom: 30rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
/* 响应式调整 */
@media (max-width: 375px) {
.resource-content {
padding: 24rpx 20rpx;
}
.resource-name {
font-size: 30rpx;
}
.resource-meta {
font-size: 22rpx;
}
.download-btn {
padding: 10rpx 24rpx;
min-width: 100rpx;
}
.btn-text {
font-size: 26rpx;
}
}
</style>
...@@ -16,6 +16,12 @@ ...@@ -16,6 +16,12 @@
onLoad(async () => { onLoad(async () => {
try { try {
await getServiceItems() await getServiceItems()
// 如果已经登录,尝试静默进入,无需等待4秒倒计时
const token = userStore.getToken
if (token) {
// 缩短等待时间到 1s,给用户看一眼开屏图的时间,同时提升体验
seconds.value = 1
}
} finally { } finally {
// #ifdef APP-PLUS // #ifdef APP-PLUS
plus.navigator.closeSplashscreen() plus.navigator.closeSplashscreen()
......
...@@ -79,49 +79,6 @@ ...@@ -79,49 +79,6 @@
}, },
], ],
// 常用资源
commonResources: {
title: '常用资源',
actionText: '',
icon: {
1: {
icon: '/static/images/codefun/doc_img.png',
name: '文档格式',
},
2: {
icon: '/static/images/codefun/elx_img.png',
name: '表格格式',
},
3: {
icon: '/static/images/codefun/pdf_img.png',
name: 'PDF格式',
},
},
resources: [
// {
// id: 1,
// icon: '/static/images/codefun/38f8cccf12ace58fd9cd4612dce944b0.png',
// title: '水稻种植合同模版',
// size: '125kb',
// actionText: '下载',
// },
// {
// id: 2,
// icon: '/static/images/codefun/38f8cccf12ace58fd9cd4612dce944b0.png',
// title: '玉米种植技术指南',
// size: '210kb',
// actionText: '下载',
// },
// {
// id: 3,
// icon: '/static/images/codefun/38f8cccf12ace58fd9cd4612dce944b0.png',
// title: '肥料使用记录表',
// size: '86kb',
// actionText: '下载',
// },
],
},
// 我的设备 // 我的设备
myDevices: { myDevices: {
title: '我的设备', title: '我的设备',
...@@ -174,15 +131,6 @@ ...@@ -174,15 +131,6 @@
}, },
}) })
function getBizCommonFileList() {
WodeAPI.bizCommonFileList({
pageNo: 1,
pageSize: 4,
}).then((res) => {
const { records } = res
pageData.commonResources.resources = records
})
}
function getFarmBaseList(id) { function getFarmBaseList(id) {
NongchangAPI.getFarmBaseList({ id }).then((res) => { NongchangAPI.getFarmBaseList({ id }).then((res) => {
pageData.statistics[0].value = res.length pageData.statistics[0].value = res.length
...@@ -242,33 +190,6 @@ ...@@ -242,33 +190,6 @@
if (feature.id === 2) if (feature.id === 2)
showAddDialog() showAddDialog()
} }
// 常用资源点击事件
function onResourceClick(resource: any) {
console.log('点击资源:', resource)
// 在这里添加具体的资源点击逻辑
}
// 资源下载点击事件
async function onDownloadClick(e, resource: any) {
// 阻止事件冒泡
e?.stopPropagation()
const { downloadResource } = await import('@/utils')
// 使用封装的下载方法
const result = await downloadResource(resource)
if (!result.success) {
console.error('下载失败:', result.error)
}
}
// 查看所有资源
function onViewAllResources() {
console.log('查看所有资源')
// 在这里添加具体的查看所有资源逻辑
}
// 我的设备标题点击事件 // 我的设备标题点击事件
function onMyDevicesTitleClick() { function onMyDevicesTitleClick() {
console.log('点击我的设备标题') console.log('点击我的设备标题')
......
...@@ -74,15 +74,25 @@ const transform: AxiosTransform = { ...@@ -74,15 +74,25 @@ const transform: AxiosTransform = {
userStore.setToken('') userStore.setToken('')
// 判断当前页面是否为登录页,防止多个请求时重复跳转 // 判断当前页面是否为登录页,防止多个请求时重复跳转
const page = getCurrentPages()[0] const pages = getCurrentPages()
const loginPageRoute = `/pages/login/login` if (pages.length > 0) {
const page = pages[pages.length - 1]
const loginPageRoute = 'pages/login/login'
if (page.route !== loginPageRoute) { if (page.route !== loginPageRoute) {
// 跳转到登录页 // 跳转到登录页
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none',
duration: 2000
})
setTimeout(() => {
uni.reLaunch({ uni.reLaunch({
url: loginPageRoute, url: `/${loginPageRoute}`,
}) })
}, 1500)
return return
} }
}
break break
} }
......
...@@ -8,6 +8,7 @@ export {} ...@@ -8,6 +8,7 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AffixFilter: typeof import('./../src/components/Map/Widgets/AffixFilter/src/AffixFilter.vue')['default'] AffixFilter: typeof import('./../src/components/Map/Widgets/AffixFilter/src/AffixFilter.vue')['default']
AreaPicker: typeof import('./../src/components/AreaPicker/index.vue')['default']
BottomBar: typeof import('./../src/components/Map/Widgets/BottomBar/src/BottomBar.vue')['default'] BottomBar: typeof import('./../src/components/Map/Widgets/BottomBar/src/BottomBar.vue')['default']
CacheImage: typeof import('./../src/components/CacheImage/index.vue')['default'] CacheImage: typeof import('./../src/components/CacheImage/index.vue')['default']
ConfirmDialog: typeof import('./../src/components/ConfirmDialog/index.vue')['default'] ConfirmDialog: typeof import('./../src/components/ConfirmDialog/index.vue')['default']
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论