文件
yjyf/server/application/admin/view/service/edit.html
YuanWen 127b606e75 feat(admin): 重构服务管理模块新增增值服务功能
- 新增增值服务管理模块,支持按类目筛选绑定
- 新增工项服务定价和规格独立定价功能
- 优化前端订单页增值服务展示逻辑
- 升级版本号至1.0.19
2026-11-04 16:14:44 +08:00

473 行
27 KiB
HTML

<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<div class="nav-tabs-custom" style="box-shadow: none;">
<ul class="nav nav-tabs">
<li class="active"><a href="#tab-basic" data-toggle="tab"><i class="fa fa-info-circle text-primary"></i> 基础信息</a></li>
<li><a href="#tab-service-types" data-toggle="tab"><i class="fa fa-wrench text-warning"></i> 工项服务定价</a></li>
<li><a href="#tab-specs" data-toggle="tab"><i class="fa fa-cubes text-success"></i> 规格独立一口价</a></li>
<li><a href="#tab-addons" data-toggle="tab"><i class="fa fa-plus-square text-danger"></i> 增项施工服务绑定</a></li>
</ul>
<div class="tab-content" style="padding: 15px 0;">
<!-- Tab 1: 基础信息 -->
<div class="tab-pane active" id="tab-basic">
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">所属类目:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-cat_id" data-rule="required" min="0" data-source="service/cat/index" class="form-control selectpage" name="row[cat_id]" type="text" value="{$row.cat_id|htmlentities}" placeholder="请选择所属服务类目">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">服务/商品名称:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-name" data-rule="required" class="form-control" name="row[name]" type="text" value="{$row.name|htmlentities}" placeholder="如:智能门锁安装/净水器拆旧">
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">主图缩略图:</label>
<div class="col-xs-12 col-sm-10">
<div class="input-group">
<input id="c-image" class="form-control" size="50" name="row[image]" type="text" value="{$row.image|htmlentities}">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-image" class="btn btn-danger plupload" data-input-id="c-image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp,image/webp" data-multiple="false" data-preview-id="p-image"><i class="fa fa-upload"></i> 上传</button></span>
<span><button type="button" id="fachoose-image" class="btn btn-primary fachoose" data-input-id="c-image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> 选择</button></span>
</div>
<span class="msg-box n-right" for="c-image"></span>
</div>
<ul class="row list-inline plupload-preview" id="p-image"></ul>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">计价模式:</label>
<div class="col-xs-12 col-sm-8">
<select id="c-price_type" data-rule="required" class="form-control selectpicker" name="row[price_type]">
{foreach name="priceTypeList" item="vo"}
<option value="{$key}" {in name="key" value="$row.price_type"}selected{/in}>{$vo}</option>
{/foreach}
</select>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">计价单位:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-unit" data-rule="required" class="form-control" name="row[unit]" type="text" value="{$row.unit|htmlentities}" placeholder="如:件、台、套、次">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">基准参考价(元):</label>
<div class="col-xs-12 col-sm-8">
<div class="input-group">
<span class="input-group-addon">¥</span>
<input id="c-base_price" data-rule="required" class="form-control" step="0.01" name="row[base_price]" type="number" value="{$row.base_price|htmlentities}">
</div>
<span class="help-block text-muted" style="margin-bottom:0; font-size:12px;"><i class="fa fa-info-circle"></i> 各工项未单独定价时默认采用此价</span>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">楼层步梯费:</label>
<div class="col-xs-12 col-sm-8">
<div class="row">
<div class="col-xs-6">
<div class="input-group">
<span class="input-group-addon">¥</span>
<input id="c-floor_fee" data-rule="required" class="form-control" step="0.01" name="row[floor_fee]" type="number" value="{$row.floor_fee|htmlentities}">
<span class="input-group-addon">/层</span>
</div>
</div>
<div class="col-xs-6">
<div class="input-group">
<span class="input-group-addon">免</span>
<input id="c-free_floor" data-rule="required" class="form-control" name="row[free_floor]" type="number" value="{$row.free_floor|htmlentities}">
<span class="input-group-addon">层</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">服务简介说明:</label>
<div class="col-xs-12 col-sm-10">
<textarea id="c-description" class="form-control" rows="2" name="row[description]" placeholder="服务施工范围、注意事项及验收标准">{$row.description|htmlentities}</textarea>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">排序权重:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-weigh" data-rule="required" class="form-control" name="row[weigh]" type="number" value="{$row.weigh|htmlentities}">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-4">上架状态:</label>
<div class="col-xs-12 col-sm-8">
<div class="radio">
{foreach name="statusList" item="vo"}
<label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="$row.status"}checked{/in} /> {$vo}</label>
{/foreach}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tab 2: 工项服务定价 -->
<div class="tab-pane" id="tab-service-types">
<div class="alert alert-info-light">
<i class="fa fa-info-circle"></i> 勾选此商品支持的服务工项(如安装、维修、拆旧等),并可为每个工项设定独立一口价。
</div>
<table class="table table-bordered table-striped" id="service-types-table">
<thead>
<tr class="active">
<th style="width: 60px; text-align: center;">启用</th>
<th style="width: 120px;">工项标识</th>
<th style="width: 160px;">工项显示名称</th>
<th style="width: 140px;">工项一口价(元)</th>
<th style="width: 80px; text-align: center;">默认勾选</th>
</tr>
</thead>
<tbody>
<?php
$defaultTypes = [
'install' => ['name' => '安装', 'price' => $row['base_price'], 'default' => 1],
'repair' => ['name' => '维修', 'price' => round($row['base_price'] * 0.7, 2), 'default' => 0],
'remove' => ['name' => '拆旧', 'price' => round($row['base_price'] * 0.5, 2), 'default' => 0],
'deliver' => ['name' => '送货到家', 'price' => 50.00, 'default' => 0],
'deliver_install' => ['name' => '送货到家并安装', 'price' => $row['base_price'] + 50.00, 'default' => 0],
'measure' => ['name' => '坑距测量(含拆旧)', 'price' => 40.00, 'default' => 0],
'maintain' => ['name' => '保养', 'price' => 60.00, 'default' => 0],
];
$savedMap = [];
foreach ($serviceTypes as $st) {
if (isset($st['code'])) {
$savedMap[$st['code']] = $st;
}
}
?>
{foreach name="defaultTypes" item="cfg" key="code"}
<?php
$isCheck = !empty($savedMap) ? isset($savedMap[$code]) : true;
$curName = isset($savedMap[$code]['name']) ? $savedMap[$code]['name'] : $cfg['name'];
$curPrice = isset($savedMap[$code]['price']) ? $savedMap[$code]['price'] : $cfg['price'];
$isDef = isset($savedMap[$code]['is_default']) ? $savedMap[$code]['is_default'] : $cfg['default'];
?>
<tr>
<td style="text-align: center; vertical-align: middle;">
<input type="checkbox" class="st-enable" data-code="{$code}" {if $isCheck}checked{/if}>
</td>
<td style="vertical-align: middle;"><code>{$code}</code></td>
<td>
<input type="text" class="form-control input-sm st-name" data-code="{$code}" value="{$curName}">
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon">¥</span>
<input type="number" step="0.01" class="form-control st-price" data-code="{$code}" value="{$curPrice}">
</div>
</td>
<td style="text-align: center; vertical-align: middle;">
<input type="radio" name="st_default_radio" class="st-default" data-code="{$code}" {if $isDef}checked{/if}>
</td>
</tr>
{/foreach}
</tbody>
</table>
<input type="hidden" name="row[service_types_raw]" id="service_types_raw" value="">
</div>
<!-- Tab 3: 规格独立定价 -->
<div class="tab-pane" id="tab-specs">
<div class="alert alert-success-light">
<i class="fa fa-info-circle"></i> 为该商品增设不同规格(如:单眼灶/双眼灶、普通坐便器/智能马桶),并分别设置规格基准一口价。
</div>
<table class="table table-bordered table-striped" id="specs-table">
<thead>
<tr class="active">
<th style="width: 200px;">规格名称</th>
<th style="width: 150px;">规格独立一口价(元)</th>
<th>规格施工描述</th>
<th style="width: 80px; text-align: center;">操作</th>
</tr>
</thead>
<tbody id="specs-tbody">
{foreach name="specs" item="sp" key="idx"}
<tr data-id="{$sp.id}">
<td>
<input type="hidden" name="specs[{$idx}][id]" value="{$sp.id}">
<input type="text" name="specs[{$idx}][name]" class="form-control input-sm" value="{$sp.name|htmlentities}" placeholder="如:智能一体机">
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon">¥</span>
<input type="number" step="0.01" name="specs[{$idx}][base_price]" class="form-control" value="{$sp.base_price}">
</div>
</td>
<td>
<input type="text" name="specs[{$idx}][description]" class="form-control input-sm" value="{$sp.description|htmlentities}" placeholder="施工要求说明">
</td>
<td style="text-align: center;">
<button type="button" class="btn btn-xs btn-danger btn-del-spec"><i class="fa fa-trash"></i></button>
</td>
</tr>
{/foreach}
</tbody>
</table>
<button type="button" class="btn btn-sm btn-success" id="btn-add-spec"><i class="fa fa-plus"></i> 添加新规格</button>
</div>
<!-- Tab 4: 增值服务绑定 -->
<div class="tab-pane" id="tab-addons">
<div class="alert alert-info" style="margin-bottom: 12px; padding: 10px 15px; border-radius: 4px;">
<i class="fa fa-info-circle"></i> <strong>增值服务按类目精准隔离与绑定:</strong>
系统已自动为您匹配该商品所属类目的专属增项池。不同品类拥有独立的工法与增项标准,支持为本商品单独设置专属覆写单价(留空则采用平台指导价)。
</div>
<!-- 增值服务工具栏与品类联动过滤 -->
<div class="well well-sm" style="background: #fafafa; border: 1px solid #e7eaec; margin-bottom: 12px; padding: 8px 12px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<span class="text-muted" style="font-size: 13px;"><i class="fa fa-filter"></i> 视图筛选:</span>
<div class="btn-group btn-group-sm" id="addon-filter-group">
<button type="button" class="btn btn-primary active" id="btn-filter-category"><i class="fa fa-check-circle"></i> 仅显示本类目与通用增项</button>
<button type="button" class="btn btn-default" id="btn-filter-all"><i class="fa fa-th-list"></i> 显示全部增值服务库</button>
</div>
</div>
<div style="display: flex; align-items: center; gap: 6px;">
<button type="button" class="btn btn-xs btn-success" id="btn-check-cat-addons"><i class="fa fa-check-square-o"></i> 一键勾选本类目专属增项</button>
<button type="button" class="btn btn-xs btn-info" id="btn-check-common-addons"><i class="fa fa-check-square"></i> 一键勾选通用增项</button>
<button type="button" class="btn btn-xs btn-default" id="btn-uncheck-all-addons"><i class="fa fa-square-o"></i> 全部取消勾选</button>
</div>
</div>
<table class="table table-bordered table-striped table-hover" id="addons-table">
<thead>
<tr class="active">
<th style="width: 50px; text-align: center;">绑定</th>
<th style="width: 130px; text-align: center;">所属品类</th>
<th style="min-width: 180px;">增值服务名称</th>
<th style="width: 80px; text-align: center;">单位</th>
<th style="width: 130px;">平台指导价</th>
<th style="width: 180px;">针对本商品专属单价(元)</th>
</tr>
</thead>
<tbody>
<?php
$assignedMap = [];
foreach ($assignedAddons as $ad) {
if (isset($ad['id'])) {
$assignedMap[$ad['id']] = $ad;
}
}
?>
{foreach name="addonsPool" item="ad"}
<?php
$isBind = isset($assignedMap[$ad['id']]);
$customPrice = $isBind && isset($assignedMap[$ad['id']]['price']) ? $assignedMap[$ad['id']]['price'] : $ad['default_price'];
$isCommon = ($ad['cat_id'] == 0);
?>
<tr class="addon-row" data-cat-id="{$ad.cat_id}">
<td style="text-align: center; vertical-align: middle;">
<input type="checkbox" class="ad-bind" data-id="{$ad.id}" data-name="{$ad.name}" data-unit="{$ad.unit}" data-cat-id="{$ad.cat_id}" {if $isBind}checked{/if}>
</td>
<td style="text-align: center; vertical-align: middle;">
{if $isCommon}
<span class="label label-default" style="font-weight: normal; font-size: 11px;"><i class="fa fa-globe"></i> 全平台通用</span>
{else}
<span class="label label-primary" style="font-weight: normal; font-size: 11px;"><i class="fa fa-tag"></i> {$ad.cat_name}</span>
{/if}
</td>
<td style="vertical-align: middle;">
<strong>{$ad.name}</strong>
</td>
<td style="text-align: center; vertical-align: middle;">
<span class="badge" style="background-color: #f3f3f4; color: #555; border: 1px solid #e0e0e0;">{$ad.unit}</span>
</td>
<td style="vertical-align: middle; color: #888;">
¥{$ad.default_price}
</td>
<td>
<div class="input-group input-group-sm">
<span class="input-group-addon">¥</span>
<input type="number" step="0.01" class="form-control ad-price" data-id="{$ad.id}" value="{$customPrice}" placeholder="覆盖单价">
</div>
</td>
</tr>
{/foreach}
</tbody>
</table>
<input type="hidden" name="row[addons_raw]" id="addons_raw" value="">
</div>
</div>
</div>
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-primary btn-embossed disabled" id="btn-submit-service">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</form>
<script>
// 动态组装工项与增项 JSON
function collectServiceConfig() {
// 1. 收集工项
var serviceTypes = [];
$('#service-types-table tbody tr').each(function () {
var $row = $(this);
var $enable = $row.find('.st-enable');
if ($enable.is(':checked')) {
var code = $enable.data('code');
var name = $row.find('.st-name').val();
var price = parseFloat($row.find('.st-price').val()) || 0;
var isDef = $row.find('.st-default').is(':checked') ? 1 : 0;
serviceTypes.push({
code: code,
name: name,
price: price,
is_default: isDef
});
}
});
$('#service_types_raw').val(JSON.stringify(serviceTypes));
// 2. 收集增项
var addons = [];
$('#addons-table tbody tr').each(function () {
var $row = $(this);
var $bind = $row.find('.ad-bind');
if ($bind.is(':checked')) {
var id = parseInt($bind.data('id'), 10);
var name = $bind.data('name');
var unit = $bind.data('unit');
var price = parseFloat($row.find('.ad-price').val()) || 0;
addons.push({
id: id,
name: name,
unit: unit,
price: price
});
}
});
$('#addons_raw').val(JSON.stringify(addons));
}
// 增值服务按所属类目筛选
function applyAddonsFilter(onlyCategory) {
var currentCatId = parseInt($('#c-cat_id').val(), 10) || 0;
$('#addons-table tbody tr.addon-row').each(function () {
var rowCatId = parseInt($(this).data('cat-id'), 10) || 0;
if (onlyCategory) {
// 仅显示属于当前商品类目或者通用的增值服务
if (rowCatId === 0 || rowCatId === currentCatId) {
$(this).show();
} else {
$(this).hide();
}
} else {
$(this).show();
}
});
}
// 初始化时仅显示本类目与通用增项
$(function () {
applyAddonsFilter(true);
});
// 切换筛选视图
$(document).on('click', '#btn-filter-category', function () {
$('#btn-filter-group .btn').removeClass('active btn-primary').addClass('btn-default');
$(this).removeClass('btn-default').addClass('active btn-primary');
applyAddonsFilter(true);
});
$(document).on('click', '#btn-filter-all', function () {
$('#btn-filter-group .btn').removeClass('active btn-primary').addClass('btn-default');
$(this).removeClass('btn-default').addClass('active btn-primary');
applyAddonsFilter(false);
});
// 一键勾选本类目专属增项
$(document).on('click', '#btn-check-cat-addons', function () {
var currentCatId = parseInt($('#c-cat_id').val(), 10) || 0;
$('#addons-table tbody tr.addon-row').each(function () {
var rowCatId = parseInt($(this).data('cat-id'), 10) || 0;
if (rowCatId > 0 && rowCatId === currentCatId) {
$(this).find('.ad-bind').prop('checked', true);
}
});
});
// 一键勾选全平台通用增项
$(document).on('click', '#btn-check-common-addons', function () {
$('#addons-table tbody tr.addon-row').each(function () {
var rowCatId = parseInt($(this).data('cat-id'), 10) || 0;
if (rowCatId === 0) {
$(this).find('.ad-bind').prop('checked', true);
}
});
});
// 全部取消勾选
$(document).on('click', '#btn-uncheck-all-addons', function () {
$('#addons-table tbody tr.addon-row .ad-bind').prop('checked', false);
});
// 当所属类目变更时动态联动过滤
$(document).on('change', '#c-cat_id', function () {
if ($('#btn-filter-category').hasClass('active')) {
applyAddonsFilter(true);
}
});
// 监听表单提交事件,在提交前组装 JSON
$(document).on('submit', '#edit-form', function () {
collectServiceConfig();
});
// 动态添加规格
var specIndex = 1000;
$(document).on('click', '#btn-add-spec', function () {
specIndex++;
var html = '<tr>' +
'<td><input type="text" name="specs[' + specIndex + '][name]" class="form-control input-sm" placeholder="规格名称(如:四眼炒灶)"></td>' +
'<td><div class="input-group input-group-sm"><span class="input-group-addon">¥</span><input type="number" step="0.01" name="specs[' + specIndex + '][base_price]" class="form-control" value="0.00"></div></td>' +
'<td><input type="text" name="specs[' + specIndex + '][description]" class="form-control input-sm" placeholder="施工说明"></td>' +
'<td style="text-align:center;"><button type="button" class="btn btn-xs btn-danger btn-del-spec"><i class="fa fa-trash"></i></button></td>' +
'</tr>';
$('#specs-tbody').append(html);
});
// 删除规格
$(document).on('click', '.btn-del-spec', function () {
$(this).closest('tr').remove();
});
</script>