diff --git a/backend/application/api/controller/Demand.php b/backend/application/api/controller/Demand.php
index 0bbbc9f..dc329d3 100644
--- a/backend/application/api/controller/Demand.php
+++ b/backend/application/api/controller/Demand.php
@@ -7,6 +7,7 @@ use app\common\service\CommonService;
use app\common\service\ContractService;
use app\common\service\DemandBiddingService;
use app\common\service\DemandService;
+use app\common\service\OrderShipService;
use think\exception\HttpResponseException;
use think\Lang;
use think\Validate;
@@ -128,6 +129,13 @@ class Demand extends Api
}
} elseif ($item->status == 3 && $item->isMyWin && $item->winBidding->bidding_status == 2 && $item->winBidding->process_status == 2) {
$button = ['name' => '工作完成', 'theme' => 'danger'];
+ } elseif ($item->status == 5 && $item->isMyWin && $item->winBidding->bidding_status == 2 && $item->winBidding->process_status == 2) {
+ //客户支付尾款后,开发者发货交付
+ if ($item->hasDelivery) {
+ $button = ['name' => '已发货', 'theme' => 'light'];
+ } else {
+ $button = ['name' => '去发货', 'theme' => 'danger'];
+ }
} elseif ($item->is_bid) {
$button = ['name' => '已投标', 'theme' => 'light'];
} elseif ($item->status == 1) {
@@ -147,6 +155,13 @@ class Demand extends Api
$button = ['name' => '工作完成', 'theme' => 'danger'];
} elseif ($item->status == 4 && $item->accept_status == 1) {
$button = ['name' => '待验收', 'theme' => 'light'];
+ } elseif ($item->status == 5 && $item->isMyWin && $item->winBidding->bidding_status == 2 && $item->winBidding->process_status == 2) {
+ //客户支付尾款后,开发者发货交付
+ if ($item->hasDelivery) {
+ $button = ['name' => '已发货', 'theme' => 'light'];
+ } else {
+ $button = ['name' => '去发货', 'theme' => 'danger'];
+ }
} elseif ($item->status == 5 && $item->accept_status == 2) {
$button = ['name' => '待收货', 'theme' => 'light'];
}
@@ -166,12 +181,15 @@ class Demand extends Api
return $button;
};
$list = DemandService::getList($where, $page_size, 'id desc', '', ['user', 'demandContracts', 'demandBiddings', 'winBidding']);
- $list = $list->each(function ($item, $key) use ($user_id, $opType, $btnNameFun) {
+ //已发货的需求集合(发货表),用于开发者视角的发货按钮展示
+ $deliveredDemandIds = OrderShipService::where(['demand_id' => ['in', $list->column('id')]])->column('demand_id');
+ $list = $list->each(function ($item, $key) use ($user_id, $opType, $btnNameFun, $deliveredDemandIds) {
$demand_bidding = $item->demand_biddings;
$bidUserIds = array_column($demand_bidding, 'user_id');
$item->demand_biddings_count = count($demand_bidding);
$item->is_bid = in_array($user_id, $bidUserIds);
$item->isMyWin = $item->winBidding && $item->winBidding->user_id == $user_id;
+ $item->hasDelivery = in_array($item->id, $deliveredDemandIds);
$item->button = $btnNameFun($opType, $item);
});
$this->success('', $list);
@@ -229,6 +247,14 @@ class Demand extends Api
'url' => $fl,
'name' => urldecode(basename($fl))
], $info->files);
+ //交付内容(订单发货表,开发者发货后展示)
+ $ship = OrderShipService::where(['demand_id' => $id])->order('id', 'desc')->find();
+ $info->delivery = $ship ? [
+ 'images' => json_decode($ship->images, true) ?: [],
+ 'files' => json_decode($ship->files, true) ?: [],
+ 'remark' => $ship->remark,
+ 'delivery_time' => $ship->createtime,
+ ] : null;
$this->success('', $info);
} catch (\Exception $e) {
diff --git a/backend/application/common/command/DemandDeal.php b/backend/application/common/command/DemandDeal.php
index c5be525..c28a84a 100644
--- a/backend/application/common/command/DemandDeal.php
+++ b/backend/application/common/command/DemandDeal.php
@@ -6,6 +6,7 @@ use app\common\service\AccountLogService;
use app\common\service\AccountService;
use app\common\service\EasyWeChatService;
use app\common\service\OrderService;
+use app\common\service\OrderShipService;
use app\common\service\SubscribeMessageService;
use think\console\Command;
use think\console\Input;
@@ -45,8 +46,13 @@ class DemandDeal extends Command
foreach ($demandList as $item) {
try {
Db::transaction(function () use ($item, $confirmReceiptTimeoutDay) {
- //如果七天未确认收货,则自动确认收货
- if (time() > strtotime("+$confirmReceiptTimeoutDay day", $item->accept_time)) {
+ //未发货的需求不自动确认收货,等待开发者发货交付
+ $ship = OrderShipService::where(['demand_id' => $item->id])->order('id', 'desc')->find();
+ if (!$ship) {
+ return;
+ }
+ //发货后N天未确认收货,则自动确认收货
+ if (time() > strtotime("+$confirmReceiptTimeoutDay day", intval($ship->createtime))) {
$item->status = DemandService::STATUS_FINISHED;
$item->settle_status = DemandService::$Common::ONGOING;
$saveRes = $item->save();
diff --git a/backend/application/common/service/DemandService.php b/backend/application/common/service/DemandService.php
index 109d7fe..2c39093 100644
--- a/backend/application/common/service/DemandService.php
+++ b/backend/application/common/service/DemandService.php
@@ -70,6 +70,7 @@ class DemandService
const BUTTON_COMPLETE_WORK = 'completeWork'; //完成工作
const BUTTON_ACCEPT_PASSED = 'acceptPassed'; //验收通过
const BUTTON_CONFIRM_RECEIPT = 'confirmReceipt'; //确认收货
+ const BUTTON_DELIVER_GOODS = 'deliverGoods'; //开发者发货
/**
* 创建需求
@@ -130,6 +131,9 @@ class DemandService
case self::BUTTON_CONFIRM_RECEIPT:
$res = self::confirmReceipt($requestData, $userId);
break;
+ case self::BUTTON_DELIVER_GOODS:
+ $res = self::deliverGoods($requestData, $userId);
+ break;
default:
throw new \Exception('非法操作');
}
@@ -434,6 +438,70 @@ class DemandService
}
}
+ /**
+ * 开发者发货(交付开发成果)
+ * 凭证截图、交付附件与备注写入订单发货表,尾款订单转为已发货(待收货)
+ * @param $requestData
+ * @param $userId
+ * @return bool
+ * @throws Exception
+ */
+ public static function deliverGoods($requestData, $userId)
+ {
+ try {
+ $demandId = $requestData['demandId'] ?? 0;
+ $images = array_values($requestData['images'] ?? []);
+ $files = array_values($requestData['files'] ?? []);
+ $remark = trim($requestData['remark'] ?? '');
+ Db::startTrans();
+ if (!$images || !$files) {
+ throw new \Exception('请上传开发完成的凭证截图和交付附件');
+ }
+ $demand = self::getOne(['id' => $demandId], ['winBidding']);
+ if (!$demand) {
+ throw new \Exception('该需求不存在');
+ }
+ if ($demand->status != self::STATUS_CONFIRM_RECEIPT) {
+ throw new \Exception('该需求为' . self::STATUS_MAP[$demand->status] . ',不能发货');
+ }
+ if (!$demand->winBidding || $demand->winBidding->user_id != $userId) {
+ throw new \Exception('只有中标开发者才能发货');
+ }
+ //尾款订单必须已支付(待发货)才能发货
+ $finalOrder = OrderService::getDemandOrder($demand->parent_order_no, OrderService::ORDER_TYPE_FINAL_PAYMENT);
+ if (!$finalOrder) {
+ throw new \Exception('获取尾款订单失败');
+ }
+ if ($finalOrder->order_status != OrderService::ORDER_STATUS_WAIT_SHIP) {
+ throw new \Exception('该订单为' . OrderService::ORDER_STATUS_MAP[$finalOrder->order_status] . ',不能发货');
+ }
+ //交付内容写入订单发货表
+ $spuId = $finalOrder->orderGoods[0]->spu_id ?? 0;
+ OrderShipService::createDemandDelivery($finalOrder->order_no, $spuId, $demandId, $images, $files, $remark);
+ //尾款订单改为已发货(待收货)
+ $upRes = OrderService::where(['id' => $finalOrder->id])->update(['order_status' => OrderService::ORDER_STATUS_SHIPPED]);
+ if (!$upRes) {
+ exception('更新发货状态失败');
+ }
+ Db::commit();
+ //给客户发送发货通知
+ $msgData = [
+ 'values' => [
+ $finalOrder->order_no,
+ mb_substr($demand->title, 0, 20),
+ '已发货',
+ '开发成果已交付,请到需求详情查看交付内容并确认收货',
+ ]
+ ];
+ (new EasyWeChatService)->sendMsg($demand->user_id, SubscribeMessageService::MSG_TEMP_ORDER_SHIPPED, $msgData);
+
+ return true;
+ } catch (\Exception $e) {
+ Db::rollback();
+ throw $e;
+ }
+ }
+
/**
* 确认收货
* @param $requestData
@@ -461,6 +529,11 @@ class DemandService
if (!$saveRes) {
exception('更新需求状态失败');
}
+ //关联订单状态改为已完成
+ $upRes = OrderService::where(['parent_order_no' => $demand->parent_order_no])->update(['order_status' => OrderService::ORDER_STATUS_FINISHED]);
+ if (!$upRes) {
+ exception('更新订单状态失败');
+ }
Db::commit();
return $demand;
} catch (\Exception $e) {
diff --git a/backend/application/common/service/OrderService.php b/backend/application/common/service/OrderService.php
index 801cde7..f8f2407 100644
--- a/backend/application/common/service/OrderService.php
+++ b/backend/application/common/service/OrderService.php
@@ -398,6 +398,23 @@ class OrderService
if (!$upRes) {
throw new \Exception('更新订单状态失败');
}
+ //需求订单(首付款/尾款):同步需求状态与关联订单
+ if (in_array($order->order_type, [self::ORDER_TYPE_FIRST_PAYMENT, self::ORDER_TYPE_FINAL_PAYMENT])) {
+ $demand = DemandService::getOne(['parent_order_no' => $order->parent_order_no]);
+ if ($demand && $demand->status == DemandService::STATUS_CONFIRM_RECEIPT) {
+ $demand->status = DemandService::STATUS_FINISHED;
+ $demand->settle_status = CommonService::ONGOING; //结算中
+ $saveRes = $demand->save();
+ if (!$saveRes) {
+ throw new \Exception('更新需求状态失败');
+ }
+ //同一需求下的关联订单全部改为已完成
+ $upRes = self::where(['parent_order_no' => $order->parent_order_no])->update(['order_status' => self::ORDER_STATUS_FINISHED]);
+ if (!$upRes) {
+ throw new \Exception('更新关联订单状态失败');
+ }
+ }
+ }
Db::commit();
$info = OrderService::getOne(['id' => $orderId]);
diff --git a/backend/application/common/service/OrderShipService.php b/backend/application/common/service/OrderShipService.php
index e0d4d30..b66af90 100644
--- a/backend/application/common/service/OrderShipService.php
+++ b/backend/application/common/service/OrderShipService.php
@@ -35,4 +35,38 @@ class OrderShipService
return $res;
}
+
+ /**
+ * 需求交付发货单(开发者发货:凭证截图+交付附件+备注)
+ * @param $order_no
+ * @param $order_goods_spu_id
+ * @param $demand_id
+ * @param array $images
+ * @param array $files
+ * @param string $remark
+ * @return mixed
+ * @throws \Exception
+ */
+ public static function createDemandDelivery($order_no, $order_goods_spu_id, $demand_id, array $images, array $files, $remark = '')
+ {
+ $imagesStr = json_encode($images, JSON_UNESCAPED_UNICODE);
+ $filesStr = json_encode($files, JSON_UNESCAPED_UNICODE);
+ if (mb_strlen($imagesStr) > 1000 || mb_strlen($filesStr) > 1000) {
+ exception('附件内容过多,请精简后重新发货');
+ }
+ $data = [
+ 'order_no' => $order_no,
+ 'order_goods_spu_id' => $order_goods_spu_id,
+ 'demand_id' => $demand_id,
+ 'images' => $imagesStr,
+ 'files' => $filesStr,
+ 'remark' => $remark,
+ ];
+ $res = self::create($data);
+ if (!$res) {
+ exception('创建发货单数据失败');
+ }
+
+ return $res;
+ }
}
\ No newline at end of file
diff --git a/frontend/miniapp/app.json b/frontend/miniapp/app.json
index 3c46d9a..e1518fb 100644
--- a/frontend/miniapp/app.json
+++ b/frontend/miniapp/app.json
@@ -35,6 +35,7 @@
"pages/demand/list/index",
"pages/demand/detail/index",
"pages/demand/contract/index",
+ "pages/demand/deliver/index",
"pages/demand/agreement/index",
"pages/usercenter/balance/index"
],
diff --git a/frontend/miniapp/pages/demand/deliver/index.js b/frontend/miniapp/pages/demand/deliver/index.js
new file mode 100644
index 0000000..0b5a013
--- /dev/null
+++ b/frontend/miniapp/pages/demand/deliver/index.js
@@ -0,0 +1,299 @@
+// pages/demand/deliver/index.js
+import { demandOperate } from '../../../services/demand/fetchDemand';
+Page({
+
+ /**
+ * 页面的初始数据
+ */
+ data: {
+ demandId: 0,
+ remark: '',
+ // 开发完成凭证截图(软件成品展示图等)
+ imageFiles: [],
+ imageMax: 9,
+ imageSizeLimit: {
+ size: 20, unit: 'MB', message: '图片大小不超过 {sizeLimit} MB'
+ },
+ // 交付附件(源码或软件成品文件)
+ attachFiles: [],
+ attachMax: 5,
+ attachConfig: {
+ count: 5,
+ },
+ attachSizeLimit: {
+ size: 20, unit: 'MB', message: '附件大小不超过 {sizeLimit} MB'
+ },
+ gridConfig: {
+ column: 4,
+ width: 160,
+ height: 160,
+ },
+ },
+
+ /**
+ * 生命周期函数--监听页面加载
+ */
+ onLoad(options) {
+ const demandId = options?.demandId;
+ this.setData({ demandId });
+ },
+
+ // 备注变更
+ onRemarkChange(e) {
+ this.setData({ remark: e.detail.value });
+ },
+
+ // 凭证截图选择成功
+ handleSuccessImage(e) {
+ const files = e.detail?.files || [];
+ const { imageFiles, imageMax } = this.data;
+ const remain = imageMax - imageFiles.length;
+ const accepted = files.slice(0, remain);
+ if (files.length > remain) {
+ wx.showToast({
+ title: `最多上传${imageMax}张截图`,
+ icon: 'none'
+ });
+ }
+ if (!accepted.length) {
+ return;
+ }
+ this.setData({
+ imageFiles: [...imageFiles, ...accepted]
+ });
+ this.uploadFiles(accepted, 'image');
+ },
+
+ // 移除凭证截图
+ handleRemoveImage(e) {
+ const { index } = e.detail;
+ const { imageFiles } = this.data;
+ imageFiles.splice(index, 1);
+ this.setData({ imageFiles });
+ },
+
+ // 交付附件选择成功
+ handleSuccessAttach(e) {
+ const files = e.detail?.files || [];
+ const { attachFiles, attachMax } = this.data;
+ const remain = attachMax - attachFiles.length;
+ const accepted = files.slice(0, remain);
+ if (files.length > remain) {
+ wx.showToast({
+ title: `最多上传${attachMax}个附件`,
+ icon: 'none'
+ });
+ }
+ if (!accepted.length) {
+ return;
+ }
+ const list = [...attachFiles, ...accepted];
+ this.setData({
+ attachFiles: list,
+ attachConfig: { count: attachMax - list.length }
+ });
+ this.uploadFiles(accepted, 'attach');
+ },
+
+ // 移除交付附件
+ handleRemoveAttach(e) {
+ const { index } = e.currentTarget.dataset;
+ const { attachFiles, attachMax } = this.data;
+ attachFiles.splice(index, 1);
+ this.setData({
+ attachFiles,
+ attachConfig: { count: attachMax - attachFiles.length }
+ });
+ },
+
+ // 上传文件到服务器
+ uploadFiles(files, type) {
+ files.forEach((file, index) => {
+ if (file.status === 'done') {
+ return;
+ }
+ wx.uploadFile({
+ url: wx.$api.UploadFile,
+ filePath: file.url,
+ name: 'file',
+ formData: {
+ 'name': file.name,
+ 'type': file.type
+ },
+ header: {
+ 'Accept': 'application/json',
+ 'token': wx.getStorageSync('token')
+ },
+ success: (res) => {
+ if (res.statusCode == 413) {
+ wx.showToast({
+ title: '文件大小超出限制',
+ icon: 'none'
+ });
+ this.removeFileByUrl(file.url, type);
+ return;
+ }
+ let response = {};
+ try {
+ response = JSON.parse(res.data);
+ } catch (err) {
+ response = {};
+ }
+ if (response.code === 1 && response.data && response.data.length > 0) {
+ this.updateFile(type, {
+ ...file,
+ fullUrl: response.data[0].url,
+ status: 'done'
+ });
+ if (index === files.length - 1) {
+ wx.showToast({
+ title: '上传成功',
+ icon: 'success'
+ });
+ }
+ } else {
+ wx.showToast({
+ title: response.msg || '上传失败',
+ icon: 'none'
+ });
+ this.removeFileByUrl(file.url, type);
+ }
+ },
+ fail: () => {
+ wx.showToast({
+ title: '上传失败',
+ icon: 'none'
+ });
+ this.removeFileByUrl(file.url, type);
+ }
+ });
+ });
+ },
+
+ // 上传完成后回写
+ updateFile(type, newFile) {
+ if (type === 'image') {
+ const { imageFiles } = this.data;
+ const idx = imageFiles.findIndex(item => item.url === newFile.url);
+ if (idx === -1) {
+ return;
+ }
+ const list = [...imageFiles];
+ list[idx] = newFile;
+ this.setData({ imageFiles: list });
+ } else {
+ const { attachFiles, attachMax } = this.data;
+ const idx = attachFiles.findIndex(item => item.url === newFile.url);
+ if (idx === -1) {
+ return;
+ }
+ const list = [...attachFiles];
+ list[idx] = newFile;
+ this.setData({
+ attachFiles: list,
+ attachConfig: { count: attachMax - list.length }
+ });
+ }
+ },
+
+ // 上传失败时移除
+ removeFileByUrl(url, type) {
+ if (type === 'image') {
+ this.setData({
+ imageFiles: this.data.imageFiles.filter(item => item.url !== url)
+ });
+ } else {
+ const { attachFiles, attachMax } = this.data;
+ const list = attachFiles.filter(item => item.url !== url);
+ this.setData({
+ attachFiles: list,
+ attachConfig: { count: attachMax - list.length }
+ });
+ }
+ },
+
+ // 表单验证
+ validateForm() {
+ const { imageFiles, attachFiles } = this.data;
+ if (!imageFiles.length) {
+ wx.showToast({
+ title: '请上传开发完成的凭证截图',
+ icon: 'none'
+ });
+ return false;
+ }
+ if (!attachFiles.length) {
+ wx.showToast({
+ title: '请上传源码或软件附件',
+ icon: 'none'
+ });
+ return false;
+ }
+ if (imageFiles.some(item => item.status !== 'done') || attachFiles.some(item => item.status !== 'done')) {
+ wx.showToast({
+ title: '文件上传中,请稍候',
+ icon: 'none'
+ });
+ return false;
+ }
+ return true;
+ },
+
+ // 提交发货
+ onSubmit() {
+ if (!this.validateForm()) {
+ return;
+ }
+ wx.showLoading({
+ title: '提交中...'
+ });
+ const { demandId, imageFiles, attachFiles, remark } = this.data;
+ // 只提交服务端地址与文件名,避免提交本地临时路径
+ const params = {
+ demandId,
+ opType: 'deliverGoods',
+ images: imageFiles.map(file => ({
+ url: file.fullUrl || file.url,
+ name: file.name || ''
+ })),
+ files: attachFiles.map(file => ({
+ url: file.fullUrl || file.url,
+ name: file.name
+ })),
+ remark
+ };
+ demandOperate(params).then(
+ (res) => {
+ console.log(res);
+ wx.hideLoading();
+ if (res.code == 1) {
+ wx.showModal({
+ title: '提示',
+ showCancel: false,
+ content: '发货成功,等待客户确认收货',
+ success: (res2) => {
+ if (res2.confirm) {
+ //返回上一页(需求详情)并刷新
+ wx.$util.backAndRefresh();
+ console.log('用户点击确定');
+ }
+ }
+ });
+ } else {
+ wx.showToast({
+ title: res.msg || '发货失败',
+ icon: 'none'
+ });
+ }
+ },
+ () => {
+ //接口异常处理
+ wx.hideLoading();
+ wx.showToast({
+ title: '操作失败',
+ icon: 'none'
+ });
+ },
+ );
+ }
+})
diff --git a/frontend/miniapp/pages/demand/deliver/index.json b/frontend/miniapp/pages/demand/deliver/index.json
new file mode 100644
index 0000000..3227b1d
--- /dev/null
+++ b/frontend/miniapp/pages/demand/deliver/index.json
@@ -0,0 +1,8 @@
+{
+ "navigationBarTitleText": "发货",
+ "usingComponents": {
+ "t-upload": "tdesign-miniprogram/upload/upload",
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "t-textarea": "tdesign-miniprogram/textarea/textarea"
+ }
+}
diff --git a/frontend/miniapp/pages/demand/deliver/index.wxml b/frontend/miniapp/pages/demand/deliver/index.wxml
new file mode 100644
index 0000000..be42d43
--- /dev/null
+++ b/frontend/miniapp/pages/demand/deliver/index.wxml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ 开发完成凭证截图
+ *
+
+
+ 上传软件成品展示图等开发完成凭证,供客户核对
+
+
+
+
+
+
+ 交付附件(源码或软件成品)
+ *
+
+
+ 上传源码或软件成品文件,客户确认收货后可下载,从聊天记录选择文件
+
+ {{item.name}}
+
+
+
+
+
+
+
+ 发货备注
+
+
+
+
+
+
+
+
+
+
+
+ 确认发货
+
+
+
diff --git a/frontend/miniapp/pages/demand/deliver/index.wxss b/frontend/miniapp/pages/demand/deliver/index.wxss
new file mode 100644
index 0000000..d7181ca
--- /dev/null
+++ b/frontend/miniapp/pages/demand/deliver/index.wxss
@@ -0,0 +1,103 @@
+/* pages/demand/deliver/index.wxss */
+.page-content {
+ padding: 24rpx 24rpx calc(112rpx + env(safe-area-inset-bottom) + 24rpx);
+ box-sizing: border-box;
+}
+
+/* ================= 分组卡片 ================= */
+.form-card {
+ background: var(--bg-card);
+ border-radius: var(--radius-lg);
+ padding: 32rpx;
+ margin-bottom: 24rpx;
+ box-shadow: var(--shadow-card);
+}
+
+.form-card__head {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: 16rpx;
+}
+
+.form-card__title {
+ font-size: 30rpx;
+ font-weight: 600;
+ line-height: 44rpx;
+ color: var(--text-primary);
+}
+
+.form-card__require {
+ margin-left: 4rpx;
+ font-size: 28rpx;
+ line-height: 44rpx;
+ color: var(--error);
+}
+
+.form-card__extra {
+ margin-left: auto;
+ font-size: 22rpx;
+ line-height: 40rpx;
+ color: var(--text-tertiary);
+}
+
+.form-card__desc {
+ margin-bottom: 24rpx;
+ font-size: 24rpx;
+ line-height: 36rpx;
+ color: var(--text-tertiary);
+}
+
+/* ================= 附件标签 ================= */
+.attach-list {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.file-tag-margin {
+ margin: 8rpx 16rpx 8rpx 0;
+}
+
+/* ================= 底部提交栏 ================= */
+.wx-pay-cover {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 10;
+ background: var(--bg-card);
+ height: 112rpx;
+ padding-bottom: env(safe-area-inset-bottom);
+ box-shadow: 0 -4rpx 20rpx rgba(29, 33, 41, 0.05);
+}
+
+.wx-pay-cover .wx-pay {
+ width: 100%;
+ height: 100rpx;
+ box-sizing: border-box;
+ padding: 0rpx 32rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.wx-pay-cover .wx-pay .submit-btn {
+ height: 80rpx;
+ width: 50%;
+ border-radius: 40rpx;
+ background: var(--brand-gradient);
+ box-shadow: var(--shadow-brand);
+ color: #ffffff;
+ line-height: 80rpx;
+ font-weight: 500;
+ font-size: 28rpx;
+ text-align: center;
+}
+
+.wx-pay-cover .wx-pay .submit-btn:active {
+ opacity: 0.88;
+}
+
+.wx-pay-cover .wx-pay .btn-gray {
+ background: var(--text-disabled);
+ box-shadow: none;
+}
diff --git a/frontend/miniapp/pages/demand/detail/index.js b/frontend/miniapp/pages/demand/detail/index.js
index 2de8e20..681bd6d 100644
--- a/frontend/miniapp/pages/demand/detail/index.js
+++ b/frontend/miniapp/pages/demand/detail/index.js
@@ -11,7 +11,7 @@ Page({
needDays: '',
remark: '',
actualAccount: 0,
- activeValues: [0, 1, 2, 3],
+ activeValues: [0, 1, 2, 3, 4],
demandInfo: null
},
@@ -103,6 +103,24 @@ Page({
urls: images
});
},
+ //交付凭证截图预览
+ onDeliveryImagePreview(e) {
+ const { images, index } = e.currentTarget.dataset;
+ const urls = (images || []).map(item => item.url);
+ if (!urls.length) {
+ return;
+ }
+ wx.previewImage({
+ current: urls[index],
+ urls
+ });
+ },
+ //去发货(交付开发成果)
+ deliverGoods() {
+ wx.navigateTo({
+ url: `/pages/demand/deliver/index?demandId=${this.data.id}`,
+ });
+ },
//参与竞标
submitBidding() {
const { id, quotedPrice, needDays, remark } = this.data;
diff --git a/frontend/miniapp/pages/demand/detail/index.wxml b/frontend/miniapp/pages/demand/detail/index.wxml
index e423852..94ccbbb 100644
--- a/frontend/miniapp/pages/demand/detail/index.wxml
+++ b/frontend/miniapp/pages/demand/detail/index.wxml
@@ -53,6 +53,33 @@
+
+
+
+
+ 开发完成凭证截图
+
+
+
+ 交付附件
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+ 发货备注
+
+
+
+
+
@@ -73,6 +100,10 @@
完成失败
工作完成
+
+ 去发货
+
+ 已发货(等待客户确认收货)
diff --git a/frontend/miniapp/pages/demand/detail/index.wxs b/frontend/miniapp/pages/demand/detail/index.wxs
index 5783916..2d6ef53 100644
--- a/frontend/miniapp/pages/demand/detail/index.wxs
+++ b/frontend/miniapp/pages/demand/detail/index.wxs
@@ -64,6 +64,36 @@ var canConfirmReceipt = function (demandInfo) {
demandInfo.accept_status == 2;
}
+// 获取交付内容(订单发货表,后端 detail 接口挂载的 delivery 字段,未发货返回 null)
+var getDelivery = function (demandInfo) {
+ if (!demandInfo) {
+ return null;
+ }
+ return demandInfo.delivery || null;
+}
+
+// 开发者待发货:需求待收货(客户已付尾款)、自己中标且尚未交付
+var canDeliverGoods = function (demandInfo) {
+ if (!demandInfo) return false;
+ return demandInfo.status == 5 &&
+ demandInfo.isMyWin &&
+ demandInfo.winBidding &&
+ demandInfo.winBidding.bidding_status == 2 &&
+ demandInfo.winBidding.process_status == 2 &&
+ !getDelivery(demandInfo);
+}
+
+// 开发者已发货、等待客户确认收货
+var isDelivered = function (demandInfo) {
+ if (!demandInfo) return false;
+ return demandInfo.status == 5 &&
+ demandInfo.isMyWin &&
+ demandInfo.winBidding &&
+ demandInfo.winBidding.bidding_status == 2 &&
+ demandInfo.winBidding.process_status == 2 &&
+ !!getDelivery(demandInfo);
+}
+
// 数值拼接单位,空值时不显示单位
var withUnit = function (value, unit) {
if (value === undefined || value === null || value === '') {
@@ -81,5 +111,8 @@ module.exports = {
canCompleteWork: canCompleteWork,
canAcceptPassed: canAcceptPassed,
canConfirmReceipt: canConfirmReceipt,
+ getDelivery: getDelivery,
+ canDeliverGoods: canDeliverGoods,
+ isDelivered: isDelivered,
withUnit: withUnit,
};
\ No newline at end of file
diff --git a/frontend/miniapp/pages/demand/detail/index.wxss b/frontend/miniapp/pages/demand/detail/index.wxss
index d808d00..a66971a 100644
--- a/frontend/miniapp/pages/demand/detail/index.wxss
+++ b/frontend/miniapp/pages/demand/detail/index.wxss
@@ -68,6 +68,40 @@ page {
color: var(--text-tertiary);
}
+/* 交付内容 */
+.delivery-section {
+ padding-bottom: 8rpx;
+}
+
+.delivery-title {
+ margin: 16rpx 0 12rpx;
+ font-size: 26rpx;
+ font-weight: 500;
+ color: var(--text-primary);
+}
+
+.delivery-images {
+ display: flex;
+ flex-wrap: wrap;
+ margin-right: -16rpx;
+}
+
+.delivery-image {
+ width: 160rpx;
+ height: 160rpx;
+ margin: 0 16rpx 16rpx 0;
+ border-radius: var(--radius-md, 12rpx);
+ background: var(--bg-grey);
+}
+
+.delivery-remark {
+ font-size: 26rpx;
+ line-height: 40rpx;
+ color: var(--text-tertiary);
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
.button-submit {
margin: 32rpx;
}