添加开发者发货功能
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "发货",
|
||||
"usingComponents": {
|
||||
"t-upload": "tdesign-miniprogram/upload/upload",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"t-textarea": "tdesign-miniprogram/textarea/textarea"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<!--pages/demand/deliver/index.wxml-->
|
||||
<view class="page-content">
|
||||
|
||||
<!-- 开发完成凭证截图 -->
|
||||
<view class="form-card">
|
||||
<view class="form-card__head">
|
||||
<text class="form-card__title">开发完成凭证截图</text>
|
||||
<text class="form-card__require">*</text>
|
||||
<text class="form-card__extra">最多 {{imageMax}} 张</text>
|
||||
</view>
|
||||
<view class="form-card__desc">上传软件成品展示图等开发完成凭证,供客户核对</view>
|
||||
<t-upload
|
||||
media-type="{{['image']}}"
|
||||
files="{{imageFiles}}"
|
||||
gridConfig="{{gridConfig}}"
|
||||
max="{{imageMax}}"
|
||||
size-limit="{{imageSizeLimit}}"
|
||||
bind:success="handleSuccessImage"
|
||||
bind:remove="handleRemoveImage"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 交付附件 -->
|
||||
<view class="form-card">
|
||||
<view class="form-card__head">
|
||||
<text class="form-card__title">交付附件(源码或软件成品)</text>
|
||||
<text class="form-card__require">*</text>
|
||||
<text class="form-card__extra">{{attachFiles.length}}/{{attachMax}}</text>
|
||||
</view>
|
||||
<view class="form-card__desc">上传源码或软件成品文件,客户确认收货后可下载,从聊天记录选择文件</view>
|
||||
<view class="attach-list" wx:if="{{attachFiles.length}}">
|
||||
<t-tag
|
||||
wx:for="{{attachFiles}}"
|
||||
wx:for-index="index"
|
||||
wx:key="index"
|
||||
class="file-tag-margin"
|
||||
max-width="{{220}}"
|
||||
size="medium"
|
||||
variant="light"
|
||||
data-index="{{index}}"
|
||||
bind:close="handleRemoveAttach"
|
||||
closable
|
||||
>{{item.name}}</t-tag>
|
||||
</view>
|
||||
<t-upload
|
||||
wx:if="{{attachFiles.length < attachMax}}"
|
||||
files="{{[]}}"
|
||||
gridConfig="{{gridConfig}}"
|
||||
config="{{attachConfig}}"
|
||||
size-limit="{{attachSizeLimit}}"
|
||||
source="messageFile"
|
||||
bind:success="handleSuccessAttach"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 发货备注 -->
|
||||
<view class="form-card">
|
||||
<view class="form-card__head">
|
||||
<text class="form-card__title">发货备注</text>
|
||||
<text class="form-card__extra">选填</text>
|
||||
</view>
|
||||
<t-textarea
|
||||
autosize="{{ { maxHeight: 120, minHeight: 50 } }}"
|
||||
disableDefaultPadding="{{true}}"
|
||||
placeholder="填写交付说明,如软件安装方式、源码目录结构、注意事项等"
|
||||
value="{{remark}}"
|
||||
bindchange="onRemarkChange"
|
||||
maxlength="255"
|
||||
/>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 底部提交栏 -->
|
||||
<view class="wx-pay-cover">
|
||||
<view class="wx-pay">
|
||||
<view class="submit-btn {{(imageFiles.length && attachFiles.length) ? '' : 'btn-gray'}}" bindtap="onSubmit">
|
||||
确认发货
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -53,6 +53,33 @@
|
||||
<t-input label="{{opType=='quote' ? '我的优势' : 'Ta的优势'}}" placeholder="{{opType=='quote' ? '请填写您的优势技能' : '优势技能'}}" disabled="{{opType=='quote' && !demandInfo.myBidding ? false : true}}" value="{{opType=='quote' ? demandInfo.myBidding.remark : demandInfo.winBidding.remark}}" data-field="remark" bindchange="onInputChange" />
|
||||
</view>
|
||||
</t-collapse-panel>
|
||||
<!-- 交付内容:开发者发货后展示,客户确认收货前可查看 -->
|
||||
<block wx:if="{{_calc.getDelivery(demandInfo)}}">
|
||||
<t-collapse-panel header="交付内容" value="{{4}}" expandIcon>
|
||||
<view class="delivery-section">
|
||||
<view class="delivery-title">开发完成凭证截图</view>
|
||||
<view class="delivery-images">
|
||||
<image wx:for="{{_calc.getDelivery(demandInfo).images}}" wx:key="index" class="delivery-image" src="{{item.url}}" mode="aspectFill" data-index="{{index}}" data-images="{{_calc.getDelivery(demandInfo).images}}" bindtap="onDeliveryImagePreview" />
|
||||
</view>
|
||||
<view class="delivery-title">交付附件</view>
|
||||
<view class="file-list">
|
||||
<view class="file-item" wx:for="{{_calc.getDelivery(demandInfo).files}}" wx:key="index">
|
||||
<view class="file-item__name">
|
||||
<t-tag max-width="420rpx" variant="light">{{item.name}}</t-tag>
|
||||
</view>
|
||||
<view class="file-item__ops">
|
||||
<button class="mini-btn" type="default" size="mini" data-url="{{item.url}}" bindtap="fileUrlCopy">复制</button>
|
||||
<button class="mini-btn mini-btn--primary" size="mini" data-url="{{item.url}}" bindtap="fileView">预览</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<block wx:if="{{_calc.getDelivery(demandInfo).remark}}">
|
||||
<view class="delivery-title">发货备注</view>
|
||||
<view class="delivery-remark">{{_calc.getDelivery(demandInfo).remark}}</view>
|
||||
</block>
|
||||
</view>
|
||||
</t-collapse-panel>
|
||||
</block>
|
||||
</t-collapse>
|
||||
|
||||
|
||||
@@ -73,6 +100,10 @@
|
||||
<t-button theme="light" size="large" data-status="0" bind:tap="completeWork">完成失败</t-button>
|
||||
<t-button theme="danger" size="large" data-status="1" bind:tap="completeWork" block>工作完成</t-button>
|
||||
</view>
|
||||
<view class="button-layout-flex" wx:elif="{{_calc.canDeliverGoods(demandInfo)}}">
|
||||
<t-button theme="danger" size="large" bind:tap="deliverGoods" block>去发货</t-button>
|
||||
</view>
|
||||
<t-button wx:elif="{{_calc.isDelivered(demandInfo)}}" theme="light" size="large" disabled block>已发货(等待客户确认收货)</t-button>
|
||||
</block>
|
||||
</view>
|
||||
<!-- 需求方 -->
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user