在市场竞争白热化的今天,小微企业的生存发展往往受制于资源有限、流程繁琐等问题。尤其在客户对接的关键环节,报价效率的高低直接决定了订单的成败。随着移动互联网技术的普及,一套适配手机端的自动报价系统,正成为零售批发、餐饮服务、小型制造等小微企业密集行业的 "效率神器",重新定义着业务拓展的节奏与精度。
零售批发业:实时响应,抢占市场先机
社区超市、五金建材店、服装批发商等零售批发类小微企业,日常需面对大量碎片化的采购咨询。传统报价模式中,业务员在外奔波时需手动记录客户需求,返回门店后才能整理成报价单,往往错失最佳成交时机。
移动报价系统彻底改变了这一现状。当业务员在客户店铺沟通时,可通过手机即时调取商品库数据,根据采购量自动计算折扣价格。例如某食品批发商的业务员在超市洽谈时,能当场根据订单量生成包含运输费用的报价单,系统自动关联最近的仓库库存信息,标注 "24 小时送达" 等服务承诺。这种即时响应能力,使成交率提升 40% 以上,尤其适合生鲜、快消品等时效敏感型行业。
餐饮服务业:套餐组合灵活,提升客单价
中小型餐厅、咖啡馆、外卖作坊等餐饮服务企业,常需设计不同档次的套餐方案。传统 Excel 报价表难以直观展示套餐差异,客户选择时容易产生困惑。
代码
移动报价系统的横向对比功能在此场景下价值凸显。经营者可预设 "双人餐"" 家庭套餐 " 等多个方案,系统以表格形式清晰呈现菜品组成、分量、价格差异。某披萨店业务员在写字楼推广时,通过手机向企业行政展示三种团餐方案:基础版含 10 寸披萨 + 饮料,升级版增加小吃拼盘,豪华版包含定制 logo 的蛋糕。客户能快速识别不同套餐的性价比,业务员可现场调整菜品组合,系统自动更新总价,使客单价平均提升 25%。
小型制造业:参数可视化,消除沟通壁垒
精密零件加工、小型设备组装等制造类小微企业,报价时需详细标注材质、工艺、公差等技术参数。传统纸质报价单常因参数模糊导致客户误解,反复沟通耗费大量精力。
移动报价系统通过标准化模板解决了这一痛点。机械加工坊的业务员在客户工厂现场,可通过手机选择 "不锈钢材质"" 精度等级 IT7" 等参数,系统自动匹配对应的加工费用计算模型。对于复杂订单,能生成包含 3D 示意图的报价单,直观展示产品细节。某模具加工厂使用该系统后,报价沟通周期从平均 5 天缩短至 1 天,图纸误解率下降 70%,尤其适合技术型小微企业的业务拓展。
装饰维修业:明细透明化,赢得客户信任
家装公司、家电维修店、家政服务社等服务类小微企业,报价透明度直接影响客户信任度。传统 "打包报价" 常因项目模糊引发后期纠纷,成为业务拓展的隐形障碍。
移动报价系统的明细项管理功能,让服务内容一目了然。装修公司业务员在业主家中测量时,可逐项添加 "墙面处理"" 地板铺设 "等服务项目,系统自动带出施工时长、材料品牌等明细。某家政公司为办公楼报价时,通过横向对比展示" 日常保洁 ""深度清洁"" 开荒服务 "的具体差异,使客户清晰了解" 每平方米 3 元 " 的报价包含 7 项服务内容。这种透明化呈现,使客户签约周期缩短至传统模式的 1/3。
技术服务类:版本清晰化,满足个性化需求
网站建设、软件定制、设计咨询等技术服务型小微企业,常需根据客户需求提供不同档次的服务方案。传统报价方式难以清晰区分服务边界,易引发后期需求蔓延。
移动报价系统的差异记录功能在此场景大放异彩。某网页设计工作室设置 "基础版"" 营销版 ""电商版" 三个方案,系统明确标注基础版含 5 个页面设计,营销版增加 SEO 优化,电商版包含支付接口开发。当客户提出定制需求时,业务员可在手机上勾选 "增加 2 个专题页" 等选项,系统自动计算附加费用。这种模块化报价方式,使需求变更率降低 60%,尤其适合知识密集型服务行业。
对于资源有限的小微企业而言,移动报价系统不仅是工具的升级,更是经营思维的革新。它将业务员从繁琐的文书工作中解放出来,把时间重新投入到客户沟通中;通过标准化流程减少人为失误,降低管理成本;借助数据沉淀优化定价策略,提升盈利空间。在零售、餐饮、制造等核心行业的实践证明,一套适配手机端的智能报价系统,正成为小微企业突破增长瓶颈的关键支点,让有限的资源创造出更大的市场价值。
代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>共享明细字段的标准报价系统</title>
<style>
/* 基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
}
/* 顶部导航 */
.header {
background-color: #165DFF;
color: white;
padding: 15px;
position: relative;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 1.2rem;
font-weight: 600;
text-align: center;
}
.header-actions {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
display: flex;
gap: 10px;
}
.action-btn {
color: white;
background: none;
border: none;
font-size: 1rem;
cursor: pointer;
padding: 5px 10px;
display: inline-flex;
align-items: center;
gap: 5px;
}
/* 主容器 */
.container {
max-width: 500px;
margin: 0 auto;
padding: 15px;
}
/* 产品信息卡片 */
.product-card {
background-color: white;
border-radius: 10px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.product-header {
display: flex;
margin-bottom: 15px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.product-image {
width: 80px;
height: 80px;
background-color: #e8f0ff;
border-radius: 8px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
color: #165DFF;
font-weight: bold;
}
.product-info h2 {
font-size: 1.1rem;
margin-bottom: 5px;
color: #333;
}
.product-meta {
font-size: 0.9rem;
color: #666;
margin-bottom: 3px;
}
/* 业务关联信息 */
.business-info {
background-color: #f9fbff;
border-radius: 8px;
padding: 12px;
margin-bottom: 15px;
font-size: 0.9rem;
}
.business-info h3 {
font-size: 0.95rem;
color: #165DFF;
margin-bottom: 8px;
font-weight: 600;
}
.business-fields {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.business-field {
background-color: white;
padding: 6px 10px;
border-radius: 6px;
border: 1px solid #e0e8ff;
}
.business-field label {
display: block;
font-size: 0.75rem;
color: #666;
margin-bottom: 2px;
}
.business-field input {
width: 100%;
border: none;
font-size: 0.9rem;
color: #333;
padding: 2px 0;
}
.business-field input:focus {
outline: none;
color: #165DFF;
}
/* 共享明细管理 */
.shared-features {
background-color: white;
border-radius: 10px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.shared-features h3 {
font-size: 1rem;
color: #333;
margin-bottom: 10px;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.feature-item {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
}
.feature-item:last-child {
border-bottom: none;
}
.feature-name {
flex: 2;
font-size: 0.95rem;
}
.feature-default {
flex: 1;
font-size: 0.9rem;
color: #666;
}
.feature-default input {
width: 100%;
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px 8px;
font-size: 0.9rem;
}
.feature-actions {
width: 60px;
text-align: center;
}
.remove-feature-btn {
background: none;
border: none;
color: #ff5630;
cursor: pointer;
font-size: 1rem;
width: 24px;
height: 24px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
}
.add-feature-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
color: #165DFF;
background: none;
border: 1px dashed #165DFF;
width: 100%;
padding: 8px 0;
border-radius: 6px;
font-size: 0.9rem;
cursor: pointer;
margin-top: 10px;
}
/* 套餐选择区域 */
.packages-section {
margin-bottom: 20px;
}
.section-title {
font-size: 1rem;
color: #333;
margin-bottom: 10px;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title .add-btn {
font-size: 0.85rem;
color: #165DFF;
background: none;
border: none;
cursor: pointer;
padding: 0;
display: inline-flex;
align-items: center;
gap: 3px;
}
.package-list {
margin-bottom: 15px;
}
/* 套餐卡片 */
.package-card {
background-color: white;
border-radius: 10px;
margin-bottom: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
overflow: hidden;
}
.package-header {
padding: 12px 15px;
background-color: #f5f8ff;
border-bottom: 1px solid #e8f0ff;
/* display: flex;*/
justify-content: space-between;
align-items: center;
}
.package-title {
font-size: 1rem;
font-weight: 600;
color: #333;
flex: 1;
}
.package-title input {
width: 100%;
border: 1px solid transparent;
background: transparent;
font-size: 1rem;
font-weight: 600;
color: #333;
padding: 3px 5px;
border-radius: 4px;
}
.package-title input:focus {
outline: none;
border-color: #165DFF;
background-color: white;
}
.package-price {
font-size: 1rem;
font-weight: 700;
color: #165DFF;
margin: 0 10px;
min-width: 100px;
text-align: right;
}
.package-price input {
width: 100px;
border: 1px solid transparent;
background: transparent;
font-size: 1rem;
font-weight: 700;
color: #165DFF;
padding: 3px 5px;
border-radius: 4px;
text-align: right;
}
.package-price input:focus {
outline: none;
border-color: #165DFF;
background-color: white;
}
.package-actions {
display: flex;
gap: 8px;
}
.package-action-btn {
background: none;
border: none;
color: #666;
cursor: pointer;
font-size: 0.9rem;
padding: 3px 5px;
border-radius: 4px;
}
.package-action-btn:hover {
background-color: rgba(0,0,0,0.05);
}
.package-action-btn.delete {
color: #ff5630;
}
.package-action-btn.toggle {
color: #666;
}
/* 套餐明细差异 */
.package-details {
padding: 15px;
border-top: 1px solid #f0f0f0;
}
.package-items {
margin-bottom: 15px;
border: 1px solid #f0f0f0;
border-radius: 6px;
overflow: hidden;
}
.items-header {
display: flex;
background-color: #f9f9f9;
padding: 8px 12px;
font-size: 0.85rem;
color: #666;
border-bottom: 1px solid #f0f0f0;
}
.header-name {
flex: 2;
}
.header-default {
flex: 1;
text-align: center;
color: #999;
font-style: italic;
}
.header-value {
flex: 1;
text-align: center;
}
.header-actions {
width: 60px;
text-align: center;
}
.package-item {
display: flex;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
font-size: 0.9rem;
}
.package-item:last-child {
border-bottom: none;
}
.item-name {
flex: 2;
padding-right: 10px;
}
.item-default {
flex: 1;
text-align: center;
font-size: 0.85rem;
color: #999;
}
.item-value {
flex: 1;
text-align: center;
}
.item-value input {
width: 100%;
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px 8px;
font-size: 0.9rem;
text-align: center;
}
.item-value input:focus {
outline: none;
border-color: #165DFF;
}
.item-value input.different {
border-color: #165DFF;
font-weight: 500;
}
.item-actions {
width: 60px;
text-align: center;
}
.use-default-btn {
background: none;
border: none;
color: #666;
cursor: pointer;
font-size: 0.8rem;
color: #999;
}
.use-default-btn:hover {
color: #165DFF;
}
.package-description {
margin-top: 15px;
}
.package-description textarea {
width: 100%;
border: 1px solid #ddd;
border-radius: 6px;
padding: 10px;
font-size: 0.9rem;
min-height: 80px;
resize: vertical;
line-height: 1.5;
}
.package-description textarea:focus {
outline: none;
border-color: #165DFF;
}
/* 按钮样式 */
.btn {
display: inline-block;
padding: 12px 0;
background-color: #165DFF;
color: white;
border: none;
border-radius: 6px;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
text-align: center;
width: 100%;
}
.btn:hover {
background-color: #0E4CD1;
}
.btn-secondary {
background-color: white;
color: #165DFF;
border: 1px solid #165DFF;
}
.btn-secondary:hover {
background-color: #f0f5ff;
}
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: white;
padding: 12px 15px;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
max-width: 500px;
margin: 0 auto;
display: flex;
gap: 10px;
}
.bottom-actions .btn {
flex: 1;
}
/* 提示信息 */
.toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
padding: 10px 15px;
border-radius: 6px;
color: white;
font-size: 0.9rem;
z-index: 1001;
display: none;
}
.toast.show {
display: block;
animation: fadeInUp 0.3s, fadeOut 0.3s 2.7s;
}
.toast.success {
background-color: rgba(54, 179, 126, 0.9);
}
.toast.error {
background-color: rgba(255, 86, 48, 0.9);
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translate(-50%, 20px);
}
to {
opacity: 1;
transform: translate(-50%, 0);
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translate(-50%, 0);
}
to {
opacity: 0;
transform: translate(-50%, -20px);
}
}
/* 加载状态 */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 确认对话框 */
.confirm-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
display: none;
}
.confirm-modal.show {
display: flex;
}
.modal-content {
background-color: white;
border-radius: 10px;
width: 90%;
max-width: 300px;
padding: 20px;
}
.modal-content h3 {
font-size: 1.1rem;
margin-bottom: 15px;
text-align: center;
color: #333;
}
.modal-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
.modal-buttons button {
flex: 1;
padding: 8px 0;
border-radius: 6px;
font-size: 0.95rem;
cursor: pointer;
}
/* 预览模态框样式 */
.preview-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.7);
z-index: 2000;
display: none;
overflow-y: auto;
padding: 20px 0;
}
.preview-modal.show {
display: block;
}
.preview-content {
background-color: white;
border-radius: 10px;
width: 95%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
position: relative;
}
.close-preview {
position: absolute;
top: 15px;
right: 15px;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #f5f5f5;
}
.preview-header {
text-align: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid #f0f0f0;
}
.preview-title {
font-size: 1.5rem;
color: #165DFF;
margin-bottom: 10px;
}
.preview-subtitle {
color: #666;
font-size: 0.9rem;
}
/* 横向比对表格样式 */
.comparison-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
min-width: 600px;
}
.comparison-table th,
.comparison-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #f0f0f0;
}
.comparison-table th {
background-color: #f5f8ff;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
}
.comparison-table tr:last-child td {
border-bottom: none;
}
.comparison-table tr:hover {
background-color: #f9f9f9;
}
.feature-column {
width: 30%;
font-weight: 500;
background-color: #f9f9f9;
position: sticky;
left: 0;
z-index: 5;
}
.package-column {
width: calc(70% / var(--package-count, 3));
}
.package-header {
color: #165DFF;
text-align: center;
font-size: 1.1rem;
}
.price-cell {
font-weight: 700;
color: #165DFF;
text-align: center;
}
.different-value {
color: #165DFF;
font-weight: 500;
}
.package-description-cell {
padding-top: 15px;
font-size: 0.9rem;
color: #555;
border-top: 2px solid #f0f0f0;
}
/* 滚动容器 */
.table-scroll-container {
overflow-x: auto;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.preview-footer {
text-align: center;
color: #666;
font-size: 0.9rem;
padding-top: 15px;
border-top: 1px solid #f0f0f0;
}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="header">
<h1>产品标准报价</h1>
<div class="header-actions">
<button class="action-btn" id="save-all-btn">
<span>保存</span>
</button>
</div>
</div>
<!-- 主内容区 -->
<div class="container">
<!-- 产品信息 -->
<div class="product-card">
<div class="product-header">
<div class="product-image">产品图</div>
<div class="product-info">
<h2>智能办公系统 V3.0</h2>
<div class="product-meta">产品ID: PROD-2023-0589</div>
<div class="product-meta">版本: 企业级</div>
</div>
</div>
<p>新一代智能办公解决方案,集成考勤、审批、协作等功能,提升企业办公效率。</p>
</div>
<!-- 业务关联信息 -->
<div class="business-info">
<h3>业务关联信息</h3>
<div class="business-fields">
<div class="business-field">
<label for="mei_id">项目ID (mei_id)</label>
<input type="number" id="mei_id" placeholder="输入项目ID">
</div>
<div class="business-field">
<label for="store_id">门店ID (store_id)</label>
<input type="number" id="store_id" placeholder="输入门店ID">
</div>
<div class="business-field">
<label for="staff_id">员工ID (staff_id)</label>
<input type="number" id="staff_id" placeholder="输入员工ID">
</div>
<div class="business-field">
<label for="cyber_id">业务线ID (cyber_id)</label>
<input type="number" id="cyber_id" placeholder="输入业务线ID">
</div>
</div>
</div>
<!-- 共享明细管理 -->
<div class="shared-features">
<h3>
<span>共享明细项管理</span>
<button class="add-btn" id="add-feature-btn">
<span>+</span>
<span>添加明细</span>
</button>
</h3>
<div id="features-list">
<!-- 共享明细项将通过JavaScript动态生成 -->
</div>
</div>
<!-- 套餐列表 -->
<div class="packages-section">
<div class="section-title">
<span>标准套餐报价</span>
<button class="add-btn" id="add-package-btn">
<span>+</span>
<span>添加套餐</span>
</button>
</div>
<div class="package-list" id="package-list">
<!-- 套餐内容将通过JavaScript动态生成 -->
</div>
</div>
<!-- 底部留出空间 -->
<div style="height: 80px;"></div>
</div>
<!-- 底部操作按钮 -->
<div class="bottom-actions">
<button class="btn btn-secondary" id="preview-btn">预览报价单</button>
<button class="btn" id="export-btn">导出报价</button>
</div>
<!-- 确认删除对话框 -->
<div class="confirm-modal" id="confirm-modal">
<div class="modal-content">
<h3>确定要删除吗?</h3>
<div class="modal-buttons">
<button class="btn-secondary" id="cancel-delete">取消</button>
<button class="btn" id="confirm-delete">删除</button>
</div>
</div>
</div>
<!-- 预览对话框 -->
<div class="preview-modal" id="preview-modal">
<div class="preview-content">
<button class="close-preview" id="close-preview">×</button>
<div class="preview-header">
<h2 class="preview-title">智能办公系统 V3.0 - 套餐报价对比</h2>
<div class="preview-subtitle">更新时间: <span id="preview-update-time"></span></div>
</div>
<div class="table-scroll-container">
<table class="comparison-table" id="comparison-table">
<!-- 表格内容将通过JavaScript动态生成 -->
</table>
</div>
<div class="preview-footer">
<p>业务关联信息: 项目ID: <span id="preview-mei-id">-</span> | 门店ID: <span id="preview-store-id">-</span> | 员工ID: <span id="preview-staff-id">-</span></p>
</div>
</div>
</div>
<!-- 提示框 -->
<div class="toast" id="toast"></div>
<script>
// 共享的明细项列表
let features = [
{ id: 1, name: "核心办公模块", defaultValue: "1套" },
{ id: 2, name: "用户授权", defaultValue: "50用户" },
{ id: 3, name: "云存储", defaultValue: "50GB" },
{ id: 4, name: "技术支持", defaultValue: "3个月" },
{ id: 5, name: "移动端应用", defaultValue: "不包含" }
];
// 套餐数据 - 只存储与默认值不同的差异
let packages = [
{
id: 1,
name: "基础版套餐",
price: 3800.00,
// 基础版使用所有默认值,所以差异为空
differences: {},
description: "适合小型团队使用,包含基础办公功能和有限的用户授权,提供3个月技术支持服务。"
},
{
id: 2,
name: "标准版套餐",
price: 8600.00,
// 标准版只记录与默认值不同的项
differences: {
2: "200用户", // 用户授权
3: "200GB", // 云存储
4: "12个月", // 技术支持
5: "包含" // 移动端应用
},
description: "适合中小型企业,包含完整功能和扩展用户授权,提供1年技术支持和移动端应用访问。"
},
{
id: 3,
name: "企业版套餐",
price: 19800.00,
// 企业版只记录与默认值不同的项
differences: {
2: "500用户", // 用户授权
3: "1TB", // 云存储
4: "36个月", // 技术支持
5: "包含" // 移动端应用
},
description: "适合中大型企业,包含全部功能和高级支持服务,提供3年技术支持。"
}
];
// 当前要删除的项ID和类型
let itemToDelete = { id: null, type: null };
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 渲染共享明细项
renderFeatures();
// 渲染套餐列表
renderPackages();
// 添加明细项按钮
document.getElementById('add-feature-btn').addEventListener('click', addNewFeature);
// 添加套餐按钮
document.getElementById('add-package-btn').addEventListener('click', addNewPackage);
// 保存所有按钮
document.getElementById('save-all-btn').addEventListener('click', saveAllData);
// 预览和导出按钮
document.getElementById('preview-btn').addEventListener('click', showPreview);
document.getElementById('export-btn').addEventListener('click', () => showToast('报价已导出', 'success'));
// 关闭预览按钮
document.getElementById('close-preview').addEventListener('click', () => {
document.getElementById('preview-modal').classList.remove('show');
});
// 确认删除相关事件
document.getElementById('cancel-delete').addEventListener('click', () => {
document.getElementById('confirm-modal').classList.remove('show');
itemToDelete = { id: null, type: null };
});
document.getElementById('confirm-delete').addEventListener('click', () => {
if (itemToDelete.id !== null && itemToDelete.type) {
if (itemToDelete.type === 'feature') {
deleteFeature(itemToDelete.id);
} else if (itemToDelete.type === 'package') {
deletePackage(itemToDelete.id);
}
document.getElementById('confirm-modal').classList.remove('show');
itemToDelete = { id: null, type: null };
}
});
});
// 渲染共享明细项
function renderFeatures() {
const featuresList = document.getElementById('features-list');
featuresList.innerHTML = '';
if (features.length === 0) {
featuresList.innerHTML = '<div style="text-align: center; padding: 20px; color: #666;">暂无明细项,请添加</div>';
return;
}
features.forEach(feature => {
const featureItem = document.createElement('div');
featureItem.className = 'feature-item';
featureItem.dataset.id = feature.id;
featureItem.innerHTML = `
<div class="feature-name">${feature.name}</div>
<div class="feature-default">
<input type="text" value="${feature.defaultValue}" onchange="updateFeatureDefault(${feature.id}, this.value)">
</div>
<div class="feature-actions">
<button class="remove-feature-btn" onclick="showDeleteConfirm(${feature.id}, 'feature')">×</button>
</div>
`;
featuresList.appendChild(featureItem);
});
// 当明细项变化时,更新所有套餐的显示
renderPackages();
}
// 渲染套餐列表
function renderPackages() {
const packageList = document.getElementById('package-list');
packageList.innerHTML = '';
if (packages.length === 0) {
packageList.innerHTML = '<div style="text-align: center; padding: 20px; color: #666;">暂无套餐,请添加套餐</div>';
return;
}
packages.forEach(pkg => {
// 创建套餐卡片
const packageCard = document.createElement('div');
packageCard.className = 'package-card';
packageCard.dataset.id = pkg.id;
// 套餐标题栏
const header = document.createElement('div');
header.className = 'package-header';
// 套餐名称(可编辑)
const titleDiv = document.createElement('div');
titleDiv.className = 'package-title';
titleDiv.innerHTML = `<input type="text" value="${pkg.name}" onchange="updatePackageName(${pkg.id}, this.value)">`;
// 套餐价格(可编辑)
const priceDiv = document.createElement('div');
priceDiv.className = 'package-price';
priceDiv.innerHTML = `<input type="number" step="0.01" min="0" value="${pkg.price}" onchange="updatePackagePrice(${pkg.id}, this.value)">`;
// 套餐操作按钮
const actionsDiv = document.createElement('div');
actionsDiv.className = 'package-actions';
actionsDiv.innerHTML = `
<button class="package-action-btn toggle" onclick="togglePackageDetails(${pkg.id})">
<span id="toggle-icon-${pkg.id}">▼</span>
</button>
<button class="package-action-btn delete" onclick="showDeleteConfirm(${pkg.id}, 'package')">删除</button>
`;
// 组装标题栏
header.appendChild(titleDiv);
header.appendChild(priceDiv);
header.appendChild(actionsDiv);
// 套餐明细差异区域
const detailsDiv = document.createElement('div');
detailsDiv.className = 'package-details';
detailsDiv.id = `package-details-${pkg.id}`;
detailsDiv.style.display = 'none'; // 默认隐藏
// 明细项列表
let itemsHtml = `
<div class="package-items">
<div class="items-header">
<div class="header-name">项目名称</div>
<div class="header-default">默认值</div>
<div class="header-value">套餐值</div>
<div class="header-actions">操作</div>
</div>
`;
// 明细项(只显示差异或使用默认值)
features.forEach(feature => {
// 检查是否有差异值,否则使用默认值
const hasDifference = pkg.differences && pkg.differences[feature.id] !== undefined;
const value = hasDifference ? pkg.differences[feature.id] : feature.defaultValue;
itemsHtml += `
<div class="package-item">
<div class="item-name">${feature.name}</div>
<div class="item-default">${feature.defaultValue}</div>
<div class="item-value">
<input type="text"
value="${value}"
class="${hasDifference ? 'different' : ''}"
onchange="updatePackageFeature(${pkg.id}, ${feature.id}, this.value)">
</div>
<div class="item-actions">
${hasDifference ?
`<button class="use-default-btn" onclick="useDefaultValue(${pkg.id}, ${feature.id})">默认</button>` : ''}
</div>
</div>
`;
});
itemsHtml += '</div>'; // 关闭package-items
// 套餐说明(可编辑)
itemsHtml += `
<div class="package-description">
<textarea onchange="updatePackageDescription(${pkg.id}, this.value)" placeholder="请输入套餐说明...">${pkg.description || ''}</textarea>
</div>
`;
detailsDiv.innerHTML = itemsHtml;
// 组装套餐卡片
packageCard.appendChild(header);
packageCard.appendChild(detailsDiv);
// 添加到列表
packageList.appendChild(packageCard);
});
}
// 切换套餐明细显示/隐藏
function togglePackageDetails(packageId) {
const detailsElement = document.getElementById(`package-details-${packageId}`);
const iconElement = document.getElementById(`toggle-icon-${packageId}`);
if (detailsElement.style.display === 'none' || detailsElement.style.display === '') {
detailsElement.style.display = 'block';
iconElement.textContent = '▲';
} else {
detailsElement.style.display = 'none';
iconElement.textContent = '▼';
}
}
// 添加新明细项
function addNewFeature() {
const newId = features.length > 0 ? Math.max(...features.map(f => f.id)) + 1 : 1;
const newFeature = {
id: newId,
name: "新明细项",
defaultValue: "1个"
};
features.push(newFeature);
renderFeatures();
// 聚焦到新明细项的名称
const featureElement = document.querySelector(`.feature-item[data-id="${newId}"] .feature-name`);
if (featureElement) {
// 创建临时输入框以便修改名称
const originalContent = featureElement.textContent;
featureElement.innerHTML = `<input type="text" value="${originalContent}"
onblur="updateFeatureName(${newId}, this.value)">`;
const input = featureElement.querySelector('input');
input.focus();
}
showToast('新明细项已添加', 'success');
}
// 更新明细项名称
function updateFeatureName(featureId, newName) {
const feature = features.find(f => f.id === featureId);
if (feature && newName.trim() !== '') {
feature.name = newName.trim();
renderFeatures();
} else {
renderFeatures(); // 恢复原始显示
}
}
// 更新明细项默认值
function updateFeatureDefault(featureId, newValue) {
const feature = features.find(f => f.id === featureId);
if (feature && newValue.trim() !== '') {
feature.defaultValue = newValue.trim();
showToast('默认值已更新', 'success');
} else {
renderFeatures(); // 恢复原始值
}
}
// 添加新套餐
function addNewPackage() {
const newId = packages.length > 0 ? Math.max(...packages.map(p => p.id)) + 1 : 1;
const newPackage = {
id: newId,
name: "新套餐",
price: 0.00,
differences: {}, // 新套餐默认使用所有默认值,无差异
description: ""
};
packages.push(newPackage);
renderPackages();
// 自动展开新套餐的明细
setTimeout(() => {
togglePackageDetails(newId);
// 聚焦到套餐名称输入框
const nameInput = document.querySelector(`.package-card[data-id="${newId}"] .package-title input`);
if (nameInput) {
nameInput.focus();
}
}, 100);
showToast('新套餐已创建', 'success');
}
// 显示删除确认
function showDeleteConfirm(itemId, itemType) {
itemToDelete = { id: itemId, type: itemType };
const modalTitle = document.querySelector('.modal-content h3');
if (itemType === 'feature') {
modalTitle.textContent = '确定要删除这个明细项吗?';
} else if (itemType === 'package') {
modalTitle.textContent = '确定要删除这个套餐吗?';
}
document.getElementById('confirm-modal').classList.add('show');
}
// 删除明细项
function deleteFeature(featureId) {
const initialLength = features.length;
features = features.filter(f => f.id !== featureId);
// 同时从所有套餐的差异中移除该明细项
packages.forEach(pkg => {
if (pkg.differences && pkg.differences[featureId] !== undefined) {
delete pkg.differences[featureId];
}
});
if (features.length < initialLength) {
renderFeatures();
showToast('明细项已删除', 'success');
}
}
// 删除套餐
function deletePackage(packageId) {
const initialLength = packages.length;
packages = packages.filter(p => p.id !== packageId);
if (packages.length < initialLength) {
renderPackages();
showToast('套餐已删除', 'success');
}
}
// 更新套餐名称
function updatePackageName(packageId, newName) {
const pkg = packages.find(p => p.id === packageId);
if (pkg && newName.trim() !== '') {
pkg.name = newName.trim();
showToast('套餐名称已更新', 'success');
}
}
// 更新套餐价格
function updatePackagePrice(packageId, newPrice) {
const pkg = packages.find(p => p.id === packageId);
const price = parseFloat(newPrice);
if (pkg && !isNaN(price) && price >= 0) {
pkg.price = price;
showToast('套餐价格已更新', 'success');
} else {
showToast('请输入有效的价格', 'error');
renderPackages(); // 恢复原始价格
}
}
// 更新套餐明细项的值(记录差异)
function updatePackageFeature(packageId, featureId, newValue) {
const pkg = packages.find(p => p.id === packageId);
const feature = features.find(f => f.id === featureId);
if (pkg && feature) {
// 初始化差异对象
if (!pkg.differences) {
pkg.differences = {};
}
// 如果值与默认值不同,则记录差异;否则移除差异
if (newValue.trim() !== feature.defaultValue) {
pkg.differences[featureId] = newValue.trim();
} else {
delete pkg.differences[featureId];
}
// 更新显示以反映差异状态
renderPackages();
togglePackageDetails(packageId); // 保持展开状态
}
}
// 使用默认值(移除差异)
function useDefaultValue(packageId, featureId) {
const pkg = packages.find(p => p.id === packageId);
const feature = features.find(f => f.id === featureId);
if (pkg && feature && pkg.differences && pkg.differences[featureId] !== undefined) {
delete pkg.differences[featureId];
renderPackages();
togglePackageDetails(packageId); // 保持展开状态
showToast('已使用默认值', 'success');
}
}
// 更新套餐说明
function updatePackageDescription(packageId, newDescription) {
const pkg = packages.find(p => p.id === packageId);
if (pkg) {
pkg.description = newDescription.trim();
showToast('套餐说明已更新', 'success');
}
}
// 保存所有数据
function saveAllData() {
// 获取业务关联信息
const businessData = {
mei_id: document.getElementById('mei_id').value,
store_id: document.getElementById('store_id').value,
staff_id: document.getElementById('staff_id').value,
cyber_id: document.getElementById('cyber_id').value
};
// 构建完整数据对象
const allData = {
product: {
name: "智能办公系统 V3.0",
id: "PROD-2023-0589",
version: "企业级"
},
business: businessData,
features: features, // 共享的明细项
packages: packages, // 套餐数据(只包含差异)
updatedAt: new Date().toLocaleString()
};
// 模拟保存操作
const saveButton = document.getElementById('save-all-btn');
const originalContent = saveButton.innerHTML;
saveButton.innerHTML = '<div class="loading"></div>';
saveButton.disabled = true;
setTimeout(() => {
// 在实际应用中,这里会将数据发送到后端保存
console.log('保存所有数据:', allData);
// 恢复按钮状态
saveButton.innerHTML = originalContent;
saveButton.disabled = false;
showToast('所有数据已保存', 'success');
}, 1000);
return allData;
}
// 显示预览
function showPreview() {
// 获取最新的保存数据
const allData = saveAllData();
if (allData.packages.length === 0) {
showToast('请先添加套餐', 'error');
return;
}
// 更新预览时间
document.getElementById('preview-update-time').textContent = allData.updatedAt;
// 更新业务关联信息
document.getElementById('preview-mei-id').textContent = allData.business.mei_id || '-';
document.getElementById('preview-store-id').textContent = allData.business.store_id || '-';
document.getElementById('preview-staff-id').textContent = allData.business.staff_id || '-';
// 生成横向比对表格
const table = document.getElementById('comparison-table');
table.innerHTML = '';
// 设置CSS变量,用于计算列宽
table.style.setProperty('--package-count', allData.packages.length);
// 创建表头行
const headerRow = document.createElement('tr');
// 第一列是明细项名称
const headerFeatureCell = document.createElement('th');
headerFeatureCell.className = 'feature-column';
headerFeatureCell.textContent = '明细项目';
headerRow.appendChild(headerFeatureCell);
// 为每个套餐创建表头
allData.packages.forEach(pkg => {
const headerCell = document.createElement('th');
headerCell.className = 'package-column package-header';
headerCell.textContent = pkg.name;
headerRow.appendChild(headerCell);
});
table.appendChild(headerRow);
// 添加价格行
const priceRow = document.createElement('tr');
const priceLabelCell = document.createElement('td');
priceLabelCell.className = 'feature-column';
priceLabelCell.textContent = '套餐价格';
priceRow.appendChild(priceLabelCell);
allData.packages.forEach(pkg => {
const priceCell = document.createElement('td');
priceCell.className = 'price-cell';
priceCell.textContent = `¥${pkg.price.toFixed(2)}`;
priceRow.appendChild(priceCell);
});
table.appendChild(priceRow);
// 添加明细项行
allData.features.forEach(feature => {
const row = document.createElement('tr');
// 明细项名称
const featureCell = document.createElement('td');
featureCell.className = 'feature-column';
featureCell.textContent = feature.name;
row.appendChild(featureCell);
// 每个套餐的值
allData.packages.forEach(pkg => {
const valueCell = document.createElement('td');
// 检查是否有差异值,否则使用默认值
const hasDifference = pkg.differences && pkg.differences[feature.id] !== undefined;
const value = hasDifference ? pkg.differences[feature.id] : feature.defaultValue;
valueCell.textContent = value;
if (hasDifference) {
valueCell.classList.add('different-value');
}
row.appendChild(valueCell);
});
table.appendChild(row);
});
// 添加套餐说明行
const descriptionRow = document.createElement('tr');
const descriptionLabelCell = document.createElement('td');
descriptionLabelCell.className = 'feature-column';
descriptionLabelCell.textContent = '套餐说明';
descriptionRow.appendChild(descriptionLabelCell);
allData.packages.forEach(pkg => {
const descriptionCell = document.createElement('td');
descriptionCell.className = 'package-description-cell';
descriptionCell.textContent = pkg.description || '无';
descriptionRow.appendChild(descriptionCell);
});
table.appendChild(descriptionRow);
// 显示预览模态框
document.getElementById('preview-modal').classList.add('show');
}
// 显示提示信息
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = 'toast';
toast.classList.add(type, 'show');
// 3秒后自动隐藏
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
</script>
</body>
</html>
阿雪技术观
在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。
Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets, hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology.