feat(zjmfmanager_reserve): 完善商品管理功能,优化展示与同步逻辑
6 个文件变更
+89
-27
13172193298@163.com
| •app_plugins/zjmfmanager_reserve/assets/style.css | +4 -2 |
| •app_plugins/zjmfmanager_reserve/bootstrap.php | +18 -5 |
| •app_plugins/zjmfmanager_reserve/lib/upstream.php | +29 -4 |
| •app_plugins/zjmfmanager_reserve/views/admin/products.php | +36 -14 |
| •app_plugins/zjmfmanager_reserve/views/order.php | +1 -1 |
| •app_plugins/zjmfmanager_reserve/views/shop.php | +1 -1 |
变更内容
diff --git a/app_plugins/zjmfmanager_reserve/assets/style.css b/app_plugins/zjmfmanager_reserve/assets/style.css
index 08396ad..f9c88c6 100644
--- a/app_plugins/zjmfmanager_reserve/assets/style.css
+++ b/app_plugins/zjmfmanager_reserve/assets/style.css
@@ -23,8 +23,10 @@
.zj-card-head h2{font-size:18px;color:#222;margin:0;}
.zj-tag{display:inline-block;padding:2px 10px;font-size:11px;background:#e6f4ff;
color:#1e9fff;border-radius:10px;margin-top:6px;}
-.zj-card-desc{padding:14px 20px;font-size:13px;color:#666;line-height:1.7;}
-.zj-desc{padding:10px 2px 18px;font-size:13px;color:#666;line-height:1.7;}
+.zj-card-desc{padding:14px 20px;font-size:13px;color:#666;line-height:1.7;overflow-wrap:break-word;}
+.zj-card-desc img{max-width:100%;}
+.zj-desc{padding:10px 2px 18px;font-size:13px;color:#666;line-height:1.7;overflow-wrap:break-word;}
+.zj-desc img{max-width:100%;}
.zj-price{display:flex;gap:14px;padding:12px 20px;background:#fafbfc;border-top:1px solid #f0f0f0;flex-wrap:wrap;}
.zj-price-item{display:flex;align-items:center;gap:4px;font-size:12px;}
.zj-price-label{background:#e8e8e8;color:#666;padding:1px 6px;border-radius:3px;}
diff --git a/app_plugins/zjmfmanager_reserve/bootstrap.php b/app_plugins/zjmfmanager_reserve/bootstrap.php
index ec6d809..477f0f3 100644
--- a/app_plugins/zjmfmanager_reserve/bootstrap.php
+++ b/app_plugins/zjmfmanager_reserve/bootstrap.php
@@ -253,6 +253,8 @@ mnbt_register_ajax('admin', 'p_zjmf_admin_upstream_products', function () {
'id' => (int)($item['id'] ?? 0),
'name' => (string)($item['name'] ?? ''),
'description' => (string)($item['description'] ?? ''),
+ 'module' => ZjmfUpstream::itemModule($item),
+ 'price' => ZjmfUpstream::itemPrice($item),
'synced' => isset($existing[(int)($item['id'] ?? 0)]),
];
}
@@ -331,7 +333,7 @@ mnbt_register_ajax('admin', 'p_zjmf_admin_add_product', function () {
json_exit_success('商品已添加,价格同步完成');
});
-// 保存商品加价/排序/状态
+// 保存商品名称/简介/加价/排序/状态
mnbt_register_ajax('admin', 'p_zjmf_admin_save_product', function () {
mnbt_plugin_require_admin();
@@ -341,6 +343,18 @@ mnbt_register_ajax('admin', 'p_zjmf_admin_save_product', function () {
json_exit_error('商品不存在');
}
+ $name = trim((string)($_POST['name'] ?? ''));
+ $description = (string)($_POST['description'] ?? '');
+ if ($name === '') {
+ json_exit_error('请填写商品名称');
+ }
+ if (mb_strlen($name) > 100) {
+ json_exit_error('商品名称过长');
+ }
+ if (mb_strlen($description) > 65535) {
+ json_exit_error('商品简介过长');
+ }
+
$markupType = in_array((string)($_POST['markup_type'] ?? ''), ['0', '1'], true)
? (int)$_POST['markup_type'] : 0;
$markupValue = max(0, (int)($_POST['markup_value'] ?? 0));
@@ -352,9 +366,9 @@ mnbt_register_ajax('admin', 'p_zjmf_admin_save_product', function () {
$now = $date ?: date('Y-m-d H:i:s');
$ok = $DB->query_prepare(
"UPDATE MN_plugin_zjmf_product
- SET markup_type=?, markup_value=?, sort=?, status=?, updated_at=?
+ SET name=?, description=?, markup_type=?, markup_value=?, sort=?, status=?, updated_at=?
WHERE id=?",
- [$markupType, $markupValue, $sort, $status, $now, $id]
+ [$name, $description, $markupType, $markupValue, $sort, $status, $now, $id]
);
if (!$ok) {
json_exit_error('保存失败');
@@ -388,9 +402,8 @@ mnbt_register_ajax('admin', 'p_zjmf_admin_toggle_product', function () {
* 用户端页面路由
* ============================================================ */
-// 商品列表
+// 商品列表(对游客开放,无需登录)
mnbt_register_route('GET', '/reserve/shop', function ($params, $ctx) {
- zjmf_require_user();
zjmf_render('shop', [
'page_title' => '商品选购',
'products' => zjmf_product_list_active(),
diff --git a/app_plugins/zjmfmanager_reserve/lib/upstream.php b/app_plugins/zjmfmanager_reserve/lib/upstream.php
index 1490a0b..539e951 100644
--- a/app_plugins/zjmfmanager_reserve/lib/upstream.php
+++ b/app_plugins/zjmfmanager_reserve/lib/upstream.php
@@ -225,14 +225,12 @@ class ZjmfUpstream
$existing = zjmf_product_get_by_up($supplierId, $upId);
if ($existing) {
+ // 重复同步仅刷新价格/周期,不覆盖本地已修改的名称与简介
$ok = $DB->query_prepare(
"UPDATE MN_plugin_zjmf_product
- SET name=?, description=?, currency=?, agent_price_cents=?,
- cycles=?, synced_at=?, updated_at=?
+ SET currency=?, agent_price_cents=?, cycles=?, synced_at=?, updated_at=?
WHERE id=?",
[
- (string)($item['name'] ?? ''),
- (string)($item['description'] ?? ''),
$currency,
$agentCents,
$cyclesJson,
@@ -584,6 +582,33 @@ class ZjmfUpstream
return (int)round((float)$val * 100);
}
+ /** 上游列表项价格(分),支持嵌套 pricing 结构,无价格返回 0。 */
+ public static function itemPrice($item)
+ {
+ if (!is_array($item)) {
+ return 0;
+ }
+ $cents = self::toCents(self::pickPrice($item));
+ if ($cents <= 0 && isset($item['pricing']) && is_array($item['pricing'])) {
+ foreach ($item['pricing'] as $v) {
+ $cents = self::toCents(is_array($v) ? self::pickPrice($v) : $v);
+ if ($cents > 0) {
+ break;
+ }
+ }
+ }
+ return $cents;
+ }
+
+ /** 上游列表项模块类型(魔方财务列表返回字段为 type),无则返回空串。 */
+ public static function itemModule($item)
+ {
+ if (!is_array($item)) {
+ return '';
+ }
+ return (string)($item['type'] ?? $item['module'] ?? $item['module_name'] ?? '');
+ }
+
/** 从 data 中找通用 ID(订单 ID)。 */
protected static function findId($arr)
{
diff --git a/app_plugins/zjmfmanager_reserve/views/admin/products.php b/app_plugins/zjmfmanager_reserve/views/admin/products.php
index 82692cc..b3be28b 100644
--- a/app_plugins/zjmfmanager_reserve/views/admin/products.php
+++ b/app_plugins/zjmfmanager_reserve/views/admin/products.php
@@ -89,11 +89,13 @@ function zjmf_admin_markup_label($product)
</th>
<th>上游ID</th>
<th>名称</th>
+ <th>价格</th>
+ <th>模块</th>
<th>状态</th>
</tr>
</thead>
<tbody id="zjf-sync-list">
- <tr><td colspan="4" class="text-center text-muted">
+ <tr><td colspan="6" class="text-center text-muted">
请先选择供应商并加载商品列表
</td></tr>
</tbody>
@@ -123,11 +125,12 @@ function zjmf_admin_markup_label($product)
</div>
<div class="col-md-3 mb-2">
<label class="small text-muted">商品名称 *</label>
- <input type="text" id="zjf-add-name" class="form-control form-control-sm">
+ <input type="text" id="zjf-add-name" class="form-control form-control-sm" maxlength="100">
</div>
<div class="col-md-4 mb-2">
- <label class="small text-muted">描述</label>
- <input type="text" id="zjf-add-desc" class="form-control form-control-sm">
+ <label class="small text-muted">描述(支持 HTML)</label>
+ <textarea id="zjf-add-desc" class="form-control form-control-sm" rows="2"
+ placeholder="支持 HTML 标签,如 <b>加粗</b>"></textarea>
</div>
</div>
<div class="small text-muted mb-2">
@@ -143,8 +146,9 @@ function zjmf_admin_markup_label($product)
<input type="hidden" id="zjf-edit-id" value="0">
<div class="form-row">
<div class="col-md-4 mb-2">
- <label class="small text-muted">商品名</label>
- <div id="zjf-edit-name" class="pt-1"></div>
+ <label class="small text-muted">商品名 *</label>
+ <input type="text" id="zjf-edit-name" class="form-control form-control-sm"
+ maxlength="100">
</div>
<div class="col-md-2 mb-2">
<label class="small text-muted">加价方式</label>
@@ -170,9 +174,14 @@ function zjmf_admin_markup_label($product)
<option value="0">下架</option>
</select>
</div>
+ <div class="col-md-12 mb-2">
+ <label class="small text-muted">商品简介(支持 HTML)</label>
+ <textarea id="zjf-edit-desc" class="form-control form-control-sm" rows="4"
+ placeholder="支持 HTML 标签,如 <b>加粗</b>、<br> 换行。同步商品不会覆盖此处内容。"></textarea>
+ </div>
</div>
<div class="small text-muted mb-2">
- 留空加价数值表示使用所属供应商的加价规则
+ 留空加价数值表示使用所属供应商的加价规则;重新同步上游仅更新价格,不覆盖本地名称与简介。
</div>
<button type="button" class="btn btn-sm btn-primary" id="zjf-edit-save">保存</button>
<button type="button" class="btn btn-sm btn-secondary" id="zjf-edit-cancel">取消</button>
@@ -225,6 +234,7 @@ function zjmf_admin_markup_label($product)
<button type="button" class="btn btn-sm btn-outline-primary zjf-edit"
data-id="<?= (int)$p['id'] ?>"
data-name="<?= htmlspecialchars($p['name'], ENT_QUOTES) ?>"
+ data-desc="<?= htmlspecialchars($p['description'] ?? '', ENT_QUOTES) ?>"
data-type="<?= (int)$p['markup_type'] ?>"
data-value="<?= (int)$p['markup_value'] ?>"
data-sort="<?= (int)$p['sort'] ?>"
@@ -259,6 +269,11 @@ function zjmf_admin_markup_label($product)
}
if (ok && reload) setTimeout(function () { location.reload(); }, 600);
}
+ function esc(s) {
+ return String(s == null ? '' : s)
+ .replace(/&/g, '&').replace(/</g, '<')
+ .replace(/>/g, '>').replace(/"/g, '"');
+ }
function post(data, reload) {
$.post('ajax.php', data, function (r) { notify(res(r), reload); });
}
@@ -284,7 +299,7 @@ function zjmf_admin_markup_label($product)
btn.disabled = true;
btn.textContent = '加载中...';
syncTip.textContent = '正在拉取上游商品列表...';
- syncList.innerHTML = '<tr><td colspan="4" class="text-center text-muted">加载中...</td></tr>';
+ syncList.innerHTML = '<tr><td colspan="6" class="text-center text-muted">加载中...</td></tr>';
$.post('ajax.php', {gn: 'p_zjmf_admin_upstream_products', id: supplierId},
function (r) {
var d = res(r);
@@ -292,26 +307,28 @@ function zjmf_admin_markup_label($product)
btn.textContent = '加载商品列表';
if (d.qk != 1 && !d.success) {
syncTip.textContent = '';
- syncList.innerHTML = '<tr><td colspan="4" class="text-center text-muted">'
+ syncList.innerHTML = '<tr><td colspan="6" class="text-center text-muted">'
+ (d.msg || d.code || '拉取失败') + '</td></tr>';
return;
}
var list = d.list || (d.data && d.data.list) || [];
if (!list.length) {
- syncList.innerHTML = '<tr><td colspan="4" class="text-center text-muted">'
+ syncList.innerHTML = '<tr><td colspan="6" class="text-center text-muted">'
+ '该供应商暂无商品</td></tr>';
return;
}
syncTip.textContent = '共 ' + list.length + ' 个商品,勾选要同步的项目后点击「开始同步」'
- + '(已同步过的将更新价格,不覆盖加价与上架状态)';
+ + '(已同步过的将更新价格,不覆盖本地名称/简介与加价配置)';
var html = '';
for (var i = 0; i < list.length; i++) {
var it = list[i];
- html += '<tr class="zjf-up-row" data-name="' + (it.name || '').toLowerCase() + '">'
+ html += '<tr class="zjf-up-row" data-name="' + esc((it.name || '').toLowerCase()) + '">'
+ '<td><input type="checkbox" class="zjf-up-check" value="' + it.id + '"'
+ (it.synced ? ' checked' : '') + '></td>'
+ '<td>' + it.id + '</td>'
- + '<td>' + (it.name || '-') + '</td>'
+ + '<td>' + esc(it.name || '-') + '</td>'
+ + '<td>' + (it.price ? '¥' + (it.price / 100).toFixed(2) : '-') + '</td>'
+ + '<td>' + (it.module ? '<span class="badge badge-info">' + esc(it.module) + '</span>' : '-') + '</td>'
+ '<td>' + (it.synced ? '<span class="badge badge-success">已同步</span>'
: '<span class="badge badge-secondary">未同步</span>') + '</td>'
+ '</tr>';
@@ -399,6 +416,7 @@ function zjmf_admin_markup_label($product)
/* ---------------- 编辑商品 ---------------- */
var editId = document.getElementById('zjf-edit-id');
var editName = document.getElementById('zjf-edit-name');
+ var editDesc = document.getElementById('zjf-edit-desc');
var editType = document.getElementById('zjf-edit-type');
var editValue = document.getElementById('zjf-edit-value');
var editSort = document.getElementById('zjf-edit-sort');
@@ -409,7 +427,8 @@ function zjmf_admin_markup_label($product)
syncWrap.style.display = 'none';
addWrap.style.display = 'none';
editId.value = btn.getAttribute('data-id');
- editName.textContent = btn.getAttribute('data-name');
+ editName.value = btn.getAttribute('data-name');
+ editDesc.value = btn.getAttribute('data-desc') || '';
editType.value = btn.getAttribute('data-type');
editValue.value = btn.getAttribute('data-value');
editSort.value = btn.getAttribute('data-sort');
@@ -426,9 +445,12 @@ function zjmf_admin_markup_label($product)
document.getElementById('zjf-edit-save').addEventListener('click', function () {
var id = parseInt(editId.value, 10);
if (!id) return;
+ if (!editName.value) { alert('请填写商品名称'); return; }
post({
gn: 'p_zjmf_admin_save_product',
id: id,
+ name: editName.value,
+ description: editDesc.value,
markup_type: editType.value,
markup_value: editValue.value,
sort: editSort.value,
diff --git a/app_plugins/zjmfmanager_reserve/views/order.php b/app_plugins/zjmfmanager_reserve/views/order.php
index d8c5bf0..3f7a2ea 100644
--- a/app_plugins/zjmfmanager_reserve/views/order.php
+++ b/app_plugins/zjmfmanager_reserve/views/order.php
@@ -19,7 +19,7 @@ ob_start();
<a class="layui-btn layui-btn-xs layui-btn-primary" href="<?= zjmf_url('reserve/shop') ?>">返回商品列表</a>
</div>
- <div class="zj-desc"><?= nl2br(htmlspecialchars($product['description'])) ?></div>
+ <div class="zj-desc"><?= $product['description'] ?></div>
<form class="zj-order-form" id="zjf-order-form">
<div class="layui-form-item">
diff --git a/app_plugins/zjmfmanager_reserve/views/shop.php b/app_plugins/zjmfmanager_reserve/views/shop.php
index 2f8dcf9..4d8b588 100644
--- a/app_plugins/zjmfmanager_reserve/views/shop.php
+++ b/app_plugins/zjmfmanager_reserve/views/shop.php
@@ -49,7 +49,7 @@ ob_start();
<span class="zj-tag"><?= htmlspecialchars($product['currency']) ?></span>
<?php endif; ?>
</div>
- <div class="zj-card-desc"><?= nl2br(htmlspecialchars($product['description'])) ?></div>
+ <div class="zj-card-desc"><?= $product['description'] ?></div>
<div class="zj-price">
<?php $cycles = zjmf_product_cycles($product); ?>
<?php foreach ($cycles as $cycle => $cfg): ?>