diff --git a/backend/application/api/controller/Demand.php b/backend/application/api/controller/Demand.php
index 8e70585..7b20f0b 100644
--- a/backend/application/api/controller/Demand.php
+++ b/backend/application/api/controller/Demand.php
@@ -120,6 +120,9 @@ class Demand extends Api
$button = ['name' => '查看详情', 'theme' => 'light'];
// key 为按钮动作标识(复用 DemandService::BUTTON_* 常量),
// 前端据此判断按钮类型,避免依赖中文文案;无动作的展示型按钮 key 为空
+ if ((int)$item->status === DemandService::STATUS_FINISHED) {
+ return ['key' => DemandService::BUTTON_FINISHED, 'name' => '交易完成', 'theme' => 'light'];
+ }
if ($opType == 'all') {
if ($item->status == 1 && $item->isMyWin && $item->winBidding->bidding_status == 2 && $item->winBidding->process_status == 1) {
$button = ['key' => DemandService::BUTTON_CONFIRM, 'name' => '确认接单', 'theme' => 'danger'];
@@ -196,9 +199,12 @@ class Demand extends Api
$bidUserIds = array_column($demand_bidding, 'user_id');
$item->demand_biddings_count = count($demand_bidding);
$item->is_bid = in_array($user_id, $bidUserIds);
+ $item->isMy = $item->user_id == $user_id;
$item->isMyWin = $item->winBidding && $item->winBidding->user_id == $user_id;
$item->hasDelivery = in_array($item->id, $deliveredDemandIds);
$item->button = $btnNameFun($opType, $item);
+ // 列表不返回报价、合同和中标明细,避免其它开发者看到成交价与交付相关信息
+ $item->hidden(['demandBiddings', 'demandContracts', 'winBidding']);
});
$this->success('', $list);
} catch (\Exception $e) {
@@ -222,6 +228,13 @@ class Demand extends Api
if (!$info) {
throw new \Exception('该需求不存在');
}
+ $info->isMy = $user_id == $info->user_id;//我发布
+ $info->isMyWin = $info->winBidding && $info->winBidding->user_id == $user_id;//我中标
+ $canSeeDeal = $info->isMy || $info->isMyWin;
+ // 交易完成的订单,只有发布者和接单开发者可以查看
+ if ((int)$info->status === DemandService::STATUS_FINISHED && !$canSeeDeal) {
+ throw new \Exception('仅发布者和接单开发者可查看');
+ }
//更新浏览次数
DemandService::where($where)->setInc('views');
$steps = [
@@ -239,12 +252,10 @@ class Demand extends Api
'steps' => $steps,
'current' => max($ins - 1, 0)
];
- //我的报价
- $info->myBidding = DemandBiddingService::getOne(['demand_id' => $id, 'user_id' => $user_id]);
- //中标报价
-// $info->winBidding = DemandBiddingService::getOne(['demand_id' => $id, 'bidding_status' => DemandBiddingService::STATUS_BIDDING_WIN]);
- $info->isMy = $user_id == $info->user_id;//我发布
- $info->isMyWin = $info->winBidding && $info->winBidding->user_id == $user_id;//我中标
+ // 招标中返回自己的报价,便于继续投标;成交报价只给发布者和接单开发者
+ $info->myBidding = ((int)$info->status === DemandService::STATUS_BIDDING || $canSeeDeal)
+ ? DemandBiddingService::getOne(['demand_id' => $id, 'user_id' => $user_id])
+ : null;
//小程序内无法直接识别二维码,需先长按保存再用微信扫一扫
$qrcodeText = '请使用微信扫码进群沟通';
if ($user_id == $info->user_id) {
@@ -257,8 +268,13 @@ class Demand extends Api
'url' => $fl,
'name' => urldecode(basename($fl))
], $info->files);
- //交付内容(订单发货表,开发者发货后展示)
- $info->delivery = OrderShipService::getDemandDelivery($id);
+ // 成交报价、合同和交付内容仅发布者与接单开发者可见
+ if ($canSeeDeal) {
+ $info->delivery = OrderShipService::getDemandDelivery($id);
+ } else {
+ $info->delivery = null;
+ $info->hidden(['winBidding', 'demandContracts']);
+ }
$this->success('', $info);
} catch (\Exception $e) {
@@ -273,8 +289,17 @@ class Demand extends Api
{
try {
$demandId = $this->request->param('demandId', 0);
+ $userId = $this->auth->id ?? 0;
+ $demand = DemandService::getOne(['id' => $demandId], ['winBidding']);
+ if (!$demand) {
+ $this->error('该需求不存在');
+ }
+ $isMy = $demand->user_id == $userId;
+ $isMyWin = $demand->winBidding && $demand->winBidding->user_id == $userId;
+ if (!$isMy && !$isMyWin) {
+ $this->error('仅发布者和接单开发者可查看');
+ }
$info = ContractService::getWinBiddingContract($demandId);
- $demand = DemandService::getOne(['id' => $demandId]);
$platform = $demand ? VirtualPayService::normalizePayPlatform($demand->pay_platform) : '';
$info->pay_platform = $platform !== '' ? $platform : 'android';
if (empty($info->data_json['customData']) && ($firstContract = ContractService::getFirstContract($demandId))) {
diff --git a/backend/application/common/command/DemandDeal.php b/backend/application/common/command/DemandDeal.php
index e39e004..2189220 100644
--- a/backend/application/common/command/DemandDeal.php
+++ b/backend/application/common/command/DemandDeal.php
@@ -96,7 +96,7 @@ class DemandDeal extends Command
Db::transaction(function () use ($item) {
//根据订单列表查找冻结金额日志,根据冻结日志解冻冻结金额到余额
$userId = $item->winBidding->user_id;
- $settleTotalAmount = 0;
+ $settleTotalAmount = '0';
foreach ($item->orders as $order) {
$logWhere = [
'user_id' => $userId,
@@ -109,7 +109,7 @@ class DemandDeal extends Command
$log = AccountLogService::where($logWhere)->find();
if ($log) {
$amount = $log->amount;
- $settleTotalAmount += $amount;
+ $settleTotalAmount = bcadd($settleTotalAmount, (string)$amount, 2);
//写入余额日志
$title = OrderService::ORDER_TYPE_MAP[$order->order_type] ?? '需求收益';
$log_res = AccountLogService::addLog(AccountLogService::ACCOUNT_TYPE_FREEZE, $userId, $amount, $title . '解冻-冻结金额扣除', AccountLogService::TYPE_UNFREEZE, 0, AccountLogService::INC_EXP_EXPEND, $order->id, $order->total_amount);
@@ -134,7 +134,7 @@ class DemandDeal extends Command
$msgData = [
'values' => [
$item->parent_order_no,
- round($settleTotalAmount, 2),
+ $settleTotalAmount,
SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_DEMAND_SETTLE]['remark'],
]
];
diff --git a/backend/application/common/service/DemandService.php b/backend/application/common/service/DemandService.php
index 47cef90..9f5024e 100644
--- a/backend/application/common/service/DemandService.php
+++ b/backend/application/common/service/DemandService.php
@@ -71,6 +71,7 @@ class DemandService
const BUTTON_ACCEPT_PASSED = 'acceptPassed'; //验收通过
const BUTTON_CONFIRM_RECEIPT = 'confirmReceipt'; //确认收货
const BUTTON_DELIVER_GOODS = 'deliverGoods'; //开发者发货
+ const BUTTON_FINISHED = 'finished'; //交易完成,仅发布者和接单开发者可进入详情
/**
* 获取平台服务费比例(0~1),后台填写大于 1 时按百分比处理,如 10 表示 10%
diff --git a/backend/application/common/service/MessageService.php b/backend/application/common/service/MessageService.php
index bbc48b6..3d9249c 100644
--- a/backend/application/common/service/MessageService.php
+++ b/backend/application/common/service/MessageService.php
@@ -69,11 +69,17 @@ class MessageService
//按模板字段名组装可读内容:工单号:xxx;服务项目:xxx;...;备注:xxx
$values = $msgData['values'] ?? [];
$valueNames = $template['valueNames'] ?? [];
+ $fields = $template['fields'] ?? [];
$lines = [];
foreach ($valueNames as $key => $name) {
- if (isset($values[$key]) && $values[$key] !== '') {
- $lines[] = $name . ':' . $values[$key];
+ if (!isset($values[$key]) || $values[$key] === '') {
+ continue;
}
+ $value = $values[$key];
+ if (strpos($fields[$key] ?? '', 'amount') === 0) {
+ $value = SubscribeMessageService::formatAmount($value);
+ }
+ $lines[] = $name . ':' . $value;
}
// 备注处理:避免正文出现两行「备注」或语义冲突
// 1) 模板字段里已含「备注」时,发送方自定义了备注文案,不再追加模板默认 remark
diff --git a/backend/application/common/service/SubscribeMessageService.php b/backend/application/common/service/SubscribeMessageService.php
index 5794313..def129c 100644
--- a/backend/application/common/service/SubscribeMessageService.php
+++ b/backend/application/common/service/SubscribeMessageService.php
@@ -58,7 +58,7 @@ class SubscribeMessageService
'remark' => '该工单已结算完成',
/*
订单编号:200912162851392635fdbf4149
- 结算金额:99.9元
+ 结算金额:¥99.9
结算说明:订单已收货结算完成
*/
'fields' => ['character_string4', 'amount1', 'thing2'],
@@ -231,11 +231,46 @@ class SubscribeMessageService
public static function packData($template_id, $values): array
{
$fields = self::MSG_MAP[$template_id]['fields'] ?? [];
+ $param_data = [];
foreach ($fields as $key => $field) {
- $param_data[$field] = ['value' => $values[$key] ?? ''];
+ $value = $values[$key] ?? '';
+ //微信 amount 类型不带币种符号时会固定展示 5 位小数,如 2.37 显示成 2.37000
+ if (strpos($field, 'amount') === 0) {
+ $value = self::formatAmount($value);
+ }
+ $param_data[$field] = ['value' => $value];
}
- return $param_data ?? [];
+ return $param_data;
+ }
+
+ /**
+ * 订阅消息金额:最多两位小数,去掉末尾多余的 0,并加上币种符号。
+ * 微信 amount 类型不带币种符号时会把 2.37 展示成 2.37000。
+ * @param mixed $amount
+ * @return string 如 ¥2.37、¥2.3、¥2
+ */
+ public static function formatAmount($amount): string
+ {
+ if ($amount === '' || $amount === null || !is_numeric($amount)) {
+ return (string)$amount;
+ }
+ $number = (string)$amount;
+ if (stripos($number, 'e') !== false) {
+ $number = sprintf('%.8F', (float)$amount);
+ }
+ $negative = strncmp($number, '-', 1) === 0;
+ if ($negative) {
+ $number = substr($number, 1);
+ }
+ //先加 0.005 再截到 2 位,等价于四舍五入,避免 bcmath 直接截断
+ $rounded = bcadd($number, '0.005', 2);
+ $rounded = rtrim(rtrim($rounded, '0'), '.');
+ if ($rounded === '') {
+ $rounded = '0';
+ }
+
+ return '¥' . ($negative ? '-' : '') . $rounded;
}
}
diff --git a/frontend/miniapp/pages/demand/contract/index.js b/frontend/miniapp/pages/demand/contract/index.js
index 104c857..204ece2 100644
--- a/frontend/miniapp/pages/demand/contract/index.js
+++ b/frontend/miniapp/pages/demand/contract/index.js
@@ -531,41 +531,53 @@ Page({
return !!error;
},
- // 表单验证
+ // 表单验证:按页面从上到下的顺序,提示第一个未填字段
validateForm() {
const { formData } = this.data;
+ const missing = (title) => {
+ wx.showToast({
+ title,
+ icon: 'none'
+ });
+ return false;
+ };
+ const blank = (value) => String(value === undefined || value === null ? '' : value).trim() === '';
+ const emptyAmount = (value) => blank(value) || Number(value) === 0;
const contractType = formData.contractType;
if (contractType === 'personal') {
// 身份证号暂不采集
- if (!formData.name || !formData.phone) {
- wx.showToast({
- title: '请填写完整的个人信息',
- icon: 'none'
- });
- return false;
+ if (blank(formData.name)) {
+ return missing('请填写个人姓名');
+ }
+ if (blank(formData.phone)) {
+ return missing('请填写联系电话');
}
if (this.hasPhoneError(formData.phone, 'phoneError')) {
return false;
}
} else {
- if (!formData.companyName || !formData.legalPerson || !formData.companyPhone) {
- wx.showToast({
- title: '请填写完整的企业信息',
- icon: 'none'
- });
- return false;
+ if (blank(formData.companyName)) {
+ return missing('请填写企业名称');
+ }
+ if (blank(formData.legalPerson)) {
+ return missing('请填写法定代表人');
+ }
+ if (blank(formData.companyPhone)) {
+ return missing('请填写联系电话');
}
if (this.hasPhoneError(formData.companyPhone, 'companyPhoneError')) {
return false;
}
}
- if (!formData.taskContent || !formData.needDays || !formData.totalCost) {
- wx.showToast({
- title: '请填写完整的开发信息',
- icon: 'none'
- });
- return false;
+ if (blank(formData.taskContent)) {
+ return missing('请填写开发内容');
+ }
+ if (emptyAmount(formData.totalCost)) {
+ return missing('请填写总费用');
+ }
+ if (emptyAmount(formData.needDays)) {
+ return missing('请填写开发工期');
}
if (this.data.attachFiles.some(item => item.status !== 'done')) {
diff --git a/frontend/miniapp/pages/demand/detail/index.js b/frontend/miniapp/pages/demand/detail/index.js
index 477653d..bcf0a8f 100644
--- a/frontend/miniapp/pages/demand/detail/index.js
+++ b/frontend/miniapp/pages/demand/detail/index.js
@@ -39,7 +39,16 @@ Page({
return new Promise((resolve) => {
fetchDemandDetail({ id: id }).then(
(res) => {
- console.log(res);
+ if (!res || res.code !== 1 || !res.data) {
+ wx.showToast({ title: (res && res.msg) || '加载失败', icon: 'none' });
+ if (!this.data.demandInfo) {
+ setTimeout(() => {
+ wx.navigateBack({ delta: 1 });
+ }, 1500);
+ }
+ resolve();
+ return;
+ }
const demandInfo = res.data;
this.setData({
demandInfo,
@@ -47,7 +56,7 @@ Page({
}, () => resolve()); // setData 回调:页面已重新渲染
},
() => {
- //接口异常处理
+ wx.showToast({ title: '网络异常,请重试', icon: 'none' });
resolve();
},
);
diff --git a/frontend/miniapp/pages/demand/detail/index.wxml b/frontend/miniapp/pages/demand/detail/index.wxml
index 867ebf0..bb683cf 100644
--- a/frontend/miniapp/pages/demand/detail/index.wxml
+++ b/frontend/miniapp/pages/demand/detail/index.wxml
@@ -42,7 +42,7 @@
暂无附件
-
+
报价
@@ -53,8 +53,8 @@
-
-
+
+
开发完成凭证截图
diff --git a/frontend/miniapp/pages/demand/detail/index.wxs b/frontend/miniapp/pages/demand/detail/index.wxs
index f82c005..3728148 100644
--- a/frontend/miniapp/pages/demand/detail/index.wxs
+++ b/frontend/miniapp/pages/demand/detail/index.wxs
@@ -82,6 +82,20 @@ var getDelivery = function (demandInfo) {
return demandInfo.delivery || null;
}
+// 成交报价仅发布者和接单开发者可见;招标中其它开发者只看到自己的报价表单
+var canSeeQuote = function (demandInfo) {
+ if (!demandInfo) return false;
+ if (demandInfo.isMy || demandInfo.isMyWin) return true;
+ return demandInfo.status == 1;
+}
+
+// 交付内容仅发布者和接单开发者可见
+var canSeeDelivery = function (demandInfo) {
+ if (!demandInfo) return false;
+ if (!demandInfo.isMy && !demandInfo.isMyWin) return false;
+ return !!getDelivery(demandInfo);
+}
+
// 开发者待发货:需求待收货(客户已付尾款)、自己中标且尚未交付
var canDeliverGoods = function (demandInfo) {
if (!demandInfo) return false;
@@ -134,6 +148,8 @@ module.exports = {
canConfirmReceipt: canConfirmReceipt,
isWaitingDeliver: isWaitingDeliver,
getDelivery: getDelivery,
+ canSeeQuote: canSeeQuote,
+ canSeeDelivery: canSeeDelivery,
canDeliverGoods: canDeliverGoods,
isDelivered: isDelivered,
isDevView: isDevView,
diff --git a/frontend/miniapp/pages/demand/list/index.js b/frontend/miniapp/pages/demand/list/index.js
index 39b8e08..339ab1e 100644
--- a/frontend/miniapp/pages/demand/list/index.js
+++ b/frontend/miniapp/pages/demand/list/index.js
@@ -283,6 +283,14 @@ Page({
if (!item) {
return;
}
+ // 交易完成只允许发布者和接单开发者进入详情
+ if (item.button && item.button.key === 'finished' && !item.isMy && !item.isMyWin) {
+ wx.showToast({
+ title: '仅发布者和接单开发者可查看',
+ icon: 'none',
+ });
+ return;
+ }
// 参与投标仅开发者可进入报价;未入驻先提示,确定后再去申请
if (item.button && item.button.key === 'bid') {
const role = (wx.getStorageSync('userInfo') || {}).role || '';
diff --git a/frontend/miniapp/pages/home/home.js b/frontend/miniapp/pages/home/home.js
index 7d7b249..732673d 100644
--- a/frontend/miniapp/pages/home/home.js
+++ b/frontend/miniapp/pages/home/home.js
@@ -80,7 +80,7 @@ Page({
/**
* 拉取最新角色并写回本地缓存
- * 入驻审核通过后,用户回到小程序触发 onShow 即可看到「进入接单大厅」入口
+ * 未入驻显示「开发者入驻」;审核通过后回到首页即可看到「进入接单大厅」
*/
refreshRole() {
if (this._roleLoading) {
@@ -165,6 +165,12 @@ Page({
});
},
+ toDeveloperJoin() {
+ wx.navigateTo({
+ url: '/pages/developer/join/index',
+ });
+ },
+
onBannerChange(e) {
const current = Number((e.detail && e.detail.current) || 0);
if (current === this.data.bannerCurrent) {
diff --git a/frontend/miniapp/pages/home/home.wxml b/frontend/miniapp/pages/home/home.wxml
index 6dfebc4..9795ee2 100644
--- a/frontend/miniapp/pages/home/home.wxml
+++ b/frontend/miniapp/pages/home/home.wxml
@@ -48,6 +48,11 @@
进入接单大厅
+
+
+ 开发者入驻
+
+
diff --git a/frontend/miniapp/pages/usercenter/components/order-group/index.js b/frontend/miniapp/pages/usercenter/components/order-group/index.js
index 16b6b87..60f8c16 100644
--- a/frontend/miniapp/pages/usercenter/components/order-group/index.js
+++ b/frontend/miniapp/pages/usercenter/components/order-group/index.js
@@ -10,7 +10,7 @@ Component({
},
title: {
type: String,
- value: '我的支付订单',
+ value: '我的订单',
},
desc: {
type: String,