实现折扣价格功能:数据库表、后端API、前端界面完整支持折扣价格字段
This commit is contained in:
@@ -3540,6 +3540,7 @@ async function loadShopConfig(el) {
|
||||
<div class="form-row"><label>临时优惠</label><input id="scfOffer" placeholder="如:限时8折"></div>
|
||||
<div class="form-row"><label>优惠有效期</label><input id="scfExpire" placeholder="yyyy-MM-dd 或 2026年7月16日"></div>
|
||||
<div class="form-row"><label>售价(元)</label><input id="scfPrice" type="number" min="0.01" step="0.01" placeholder="0.01"></div>
|
||||
<div class="form-row"><label>优惠后价格</label><input id="scfDiscountPrice" type="text" inputmode="decimal" placeholder="留空=无优惠, 0.9=9折, 1.5=直接价格"></div>
|
||||
<div class="form-row"><label>到账债券</label><input id="scfBond" placeholder="如:1-10 或 100"></div>
|
||||
<div class="form-row"><label>上下架</label><select id="scfActive"><option value="1">上架</option><option value="0">下架</option></select></div>
|
||||
<div style="margin-top:14px;text-align:right">
|
||||
@@ -3593,6 +3594,18 @@ function showShopConfigForm(config) {
|
||||
document.getElementById('scfOffer').value = config ? (config.temporary_offer||'') : '';
|
||||
document.getElementById('scfExpire').value = config ? (config.offer_expire||'') : '';
|
||||
document.getElementById('scfPrice').value = config ? config.price : '';
|
||||
// 优惠后价格:如果原价<1认为是折扣,否则是直接价格
|
||||
if (config && config.discount_price > 0) {
|
||||
const ratio = config.discount_price / (config.price || 1);
|
||||
// 如果是整数倍(如原价10,优惠价5),显示折扣形式
|
||||
if (ratio < 1 && ratio > 0) {
|
||||
document.getElementById('scfDiscountPrice').value = ratio;
|
||||
} else {
|
||||
document.getElementById('scfDiscountPrice').value = config.discount_price;
|
||||
}
|
||||
} else {
|
||||
document.getElementById('scfDiscountPrice').value = '';
|
||||
}
|
||||
document.getElementById('scfBond').value = config ? config.bond_reward : '1-1';
|
||||
document.getElementById('scfActive').value = config ? config.is_active : 1;
|
||||
document.getElementById('shopConfigFormOverlay').classList.add('show');
|
||||
@@ -3630,6 +3643,7 @@ async function saveShopConfig() {
|
||||
const offer = document.getElementById('scfOffer').value.trim();
|
||||
const expire = document.getElementById('scfExpire').value.trim();
|
||||
const price = parseFloat(document.getElementById('scfPrice').value) || 0;
|
||||
const discountPriceRaw = document.getElementById('scfDiscountPrice').value.trim();
|
||||
const bond = document.getElementById('scfBond').value.trim();
|
||||
const active = parseInt(document.getElementById('scfActive').value) || 1;
|
||||
|
||||
@@ -3651,6 +3665,7 @@ async function saveShopConfig() {
|
||||
temporary_offer: offer,
|
||||
offer_expire: expire,
|
||||
price: price,
|
||||
discount_price: discountPriceRaw,
|
||||
bond_reward: bond,
|
||||
is_active: active
|
||||
});
|
||||
|
||||
+26
-2
@@ -315,7 +315,12 @@ function createOrder($token, $productId = 0) {
|
||||
error('商品已售罄');
|
||||
}
|
||||
|
||||
$money = number_format($product['price'], 2, '.', '');
|
||||
// 使用折扣价格(如果有)
|
||||
$actualPrice = $product['price'];
|
||||
if (isset($product['discount_price']) && $product['discount_price'] > 0) {
|
||||
$actualPrice = $product['discount_price'];
|
||||
}
|
||||
$money = number_format($actualPrice, 2, '.', '');
|
||||
$name = $product['item_name'];
|
||||
|
||||
// 解析债券范围
|
||||
@@ -952,10 +957,21 @@ function getShopProducts() {
|
||||
temporary_offer TEXT NOT NULL DEFAULT '',
|
||||
offer_expire TEXT NOT NULL DEFAULT '',
|
||||
price REAL NOT NULL DEFAULT 0,
|
||||
discount_price REAL NOT NULL DEFAULT 0,
|
||||
bond_reward TEXT NOT NULL DEFAULT '1-1',
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at INTEGER NOT NULL DEFAULT 0
|
||||
created_at INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL DEFAULT 0
|
||||
)");
|
||||
// 兼容旧表:检查并添加discount_price字段
|
||||
$hasCol = false;
|
||||
$cols = $db->query("PRAGMA table_info(shop_configs)");
|
||||
while ($col = $cols->fetchArray(SQLITE3_ASSOC)) {
|
||||
if ($col['name'] === 'discount_price') { $hasCol = true; break; }
|
||||
}
|
||||
if (!$hasCol) {
|
||||
$db->exec("ALTER TABLE shop_configs ADD COLUMN discount_price REAL NOT NULL DEFAULT 0");
|
||||
}
|
||||
|
||||
// 查询上架商品(is_active=1)
|
||||
$stmt = $db->query("SELECT * FROM shop_configs WHERE is_active = 1 ORDER BY id ASC");
|
||||
@@ -976,6 +992,12 @@ function getShopProducts() {
|
||||
// 解析有效期
|
||||
$expireInfo = parseExpireDate($row['offer_expire']);
|
||||
|
||||
// 计算实际显示价格(如果有折扣价格且折扣价格>0)
|
||||
$displayPrice = $row['price'];
|
||||
if (isset($row['discount_price']) && $row['discount_price'] > 0) {
|
||||
$displayPrice = $row['discount_price'];
|
||||
}
|
||||
|
||||
$products[] = [
|
||||
'id' => $row['id'],
|
||||
'item_name' => $row['item_name'],
|
||||
@@ -985,6 +1007,8 @@ function getShopProducts() {
|
||||
'expire_valid' => $expireInfo['valid'],
|
||||
'expire_display' => $expireInfo['display'],
|
||||
'price' => $row['price'],
|
||||
'discount_price' => $row['discount_price'] ?? 0,
|
||||
'display_price' => $displayPrice,
|
||||
'bond_reward' => $bondReward,
|
||||
'bond_min' => $bondMin,
|
||||
'bond_max' => $bondMax,
|
||||
|
||||
+48
-14
@@ -45,11 +45,21 @@ function ensureShopConfigsTable($db) {
|
||||
temporary_offer TEXT NOT NULL DEFAULT '',
|
||||
offer_expire TEXT NOT NULL DEFAULT '',
|
||||
price REAL NOT NULL DEFAULT 0,
|
||||
discount_price REAL NOT NULL DEFAULT 0,
|
||||
bond_reward TEXT NOT NULL DEFAULT '1-1',
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL DEFAULT 0
|
||||
)");
|
||||
// 兼容旧表:检查并添加discount_price字段
|
||||
$hasCol = false;
|
||||
$cols = $db->query("PRAGMA table_info(shop_configs)");
|
||||
while ($col = $cols->fetchArray(SQLITE3_ASSOC)) {
|
||||
if ($col['name'] === 'discount_price') { $hasCol = true; break; }
|
||||
}
|
||||
if (!$hasCol) {
|
||||
$db->exec("ALTER TABLE shop_configs ADD COLUMN discount_price REAL NOT NULL DEFAULT 0");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查管理员登录
|
||||
@@ -463,40 +473,63 @@ function handleShopConfigSave($db) {
|
||||
$temporaryOffer = getParam('temporary_offer', '');
|
||||
$offerExpire = getParam('offer_expire', '');
|
||||
$price = floatval(getParam('price', 0));
|
||||
$discountPriceRaw = getParam('discount_price', '0');
|
||||
$bondReward = getParam('bond_reward', '1-1');
|
||||
$isActive = intval(getParam('is_active', 1));
|
||||
|
||||
|
||||
// 验证库存
|
||||
if ($stock < -1) {
|
||||
error('库存不能小于-1');
|
||||
}
|
||||
|
||||
|
||||
// 验证售价
|
||||
if ($price <= 0) {
|
||||
error('售价必须大于0');
|
||||
}
|
||||
|
||||
|
||||
// 解析优惠后价格
|
||||
// 支持两种格式:
|
||||
// 1. 直接价格(如 1、2、1.5)→ 直接使用
|
||||
// 2. 折扣(如 0.9、0.8)→ 计算 原价 × 折扣
|
||||
$discountPrice = 0;
|
||||
$discountPriceRaw = trim($discountPriceRaw);
|
||||
if ($discountPriceRaw !== '' && $discountPriceRaw !== '0') {
|
||||
$discountValue = floatval($discountPriceRaw);
|
||||
if ($discountValue <= 0) {
|
||||
error('优惠后价格必须大于0');
|
||||
}
|
||||
// 判断是直接价格还是折扣(<1的数认为是折扣)
|
||||
if ($discountValue < 1) {
|
||||
// 折扣:原价 × 折扣
|
||||
$discountPrice = round($price * $discountValue, 2);
|
||||
} else {
|
||||
// 直接价格
|
||||
$discountPrice = round($discountValue, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证债券范围格式(支持盲盒范围,如"1-10")
|
||||
if (!preg_match('/^\d+(-\d+)?$/', $bondReward)) {
|
||||
error('债券范围格式错误,应为数字或范围如"1-10"');
|
||||
}
|
||||
|
||||
|
||||
// 解析并验证有效期
|
||||
$expireInfo = parseExpireDate($offerExpire);
|
||||
if ($offerExpire && !$expireInfo['valid']) {
|
||||
error('有效期格式错误,请使用yyyy-MM-dd或中文格式如"2026年7月16日"');
|
||||
}
|
||||
|
||||
|
||||
$now = time();
|
||||
|
||||
|
||||
if ($id > 0) {
|
||||
// 编辑
|
||||
$stmt = $db->prepare("UPDATE shop_configs SET
|
||||
$stmt = $db->prepare("UPDATE shop_configs SET
|
||||
item_name = :name,
|
||||
stock = :stock,
|
||||
temporary_offer = :offer,
|
||||
offer_expire = :expire,
|
||||
price = :price,
|
||||
discount_price = :discount_price,
|
||||
bond_reward = :bond,
|
||||
is_active = :active,
|
||||
updated_at = :updated
|
||||
@@ -504,25 +537,26 @@ function handleShopConfigSave($db) {
|
||||
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
|
||||
} else {
|
||||
// 添加
|
||||
$stmt = $db->prepare("INSERT INTO shop_configs
|
||||
(item_name, stock, temporary_offer, offer_expire, price, bond_reward, is_active, created_at, updated_at)
|
||||
VALUES (:name, :stock, :offer, :expire, :price, :bond, :active, :created, :updated)");
|
||||
$stmt = $db->prepare("INSERT INTO shop_configs
|
||||
(item_name, stock, temporary_offer, offer_expire, price, discount_price, bond_reward, is_active, created_at, updated_at)
|
||||
VALUES (:name, :stock, :offer, :expire, :price, :discount_price, :bond, :active, :created, :updated)");
|
||||
$stmt->bindValue(':created', $now, SQLITE3_INTEGER);
|
||||
}
|
||||
|
||||
|
||||
$stmt->bindValue(':name', $itemName, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':stock', $stock, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':offer', $temporaryOffer, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':expire', $offerExpire, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':price', $price, SQLITE3_FLOAT);
|
||||
$stmt->bindValue(':discount_price', $discountPrice, SQLITE3_FLOAT);
|
||||
$stmt->bindValue(':bond', $bondReward, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':active', $isActive, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':updated', $now, SQLITE3_INTEGER);
|
||||
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
|
||||
$savedId = $id > 0 ? $id : $db->lastInsertRowID();
|
||||
success(['id' => $savedId], $id > 0 ? '配置已更新' : '配置已添加');
|
||||
success(['id' => $savedId, 'discount_price' => $discountPrice], $id > 0 ? '配置已更新' : '配置已添加');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
-2
@@ -1218,14 +1218,22 @@ if ($currentVersion !== $BUILD_VERSION) {
|
||||
// ★ 盲盒随机语录
|
||||
const quoteText = isBlindBox ? `<div style="font-size:12px;color:var(--accent);margin-top:6px;font-style:italic">${blindBoxQuotes[Math.floor(Math.random() * blindBoxQuotes.length)]}</div>` : '';
|
||||
|
||||
// 计算显示价格(折扣价格优先)
|
||||
const displayPrice = product.discount_price > 0 ? product.discount_price : product.price;
|
||||
const hasDiscount = product.discount_price > 0 && product.discount_price < product.price;
|
||||
|
||||
html += `
|
||||
<div class="shop-product-card" data-product-id="${product.id}" data-price="${product.price}" data-bond-min="${product.bond_min}" data-bond-max="${product.bond_max}" data-stock="${product.stock}" onclick="selectShopProduct(this)" style="border:1px solid ${isSelected ? 'var(--accent)' : 'var(--border)'};border-radius:14px;padding:16px;margin-bottom:12px;background:${isSelected ? 'linear-gradient(135deg,rgba(63,185,80,0.08),rgba(88,166,255,0.06))' : 'transparent'};cursor:pointer;transition:all 0.2s ease;${isSoldOut ? 'opacity:0.72;' : ''}">
|
||||
<div class="shop-product-card" data-product-id="${product.id}" data-price="${displayPrice}" data-bond-min="${product.bond_min}" data-bond-max="${product.bond_max}" data-stock="${product.stock}" onclick="selectShopProduct(this)" style="border:1px solid ${isSelected ? 'var(--accent)' : 'var(--border)'};border-radius:14px;padding:16px;margin-bottom:12px;background:${isSelected ? 'linear-gradient(135deg,rgba(63,185,80,0.08),rgba(88,166,255,0.06))' : 'transparent'};cursor:pointer;transition:all 0.2s ease;${isSoldOut ? 'opacity:0.72;' : ''}">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
||||
<span style="font-size:14px;color:var(--fg)">${product.item_name}</span>
|
||||
<span style="font-size:12px;color:${stockColor};border:1px solid ${stockColor};border-radius:999px;padding:2px 10px">${stockText}</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;align-items:baseline">
|
||||
<div><div style="font-size:28px;font-weight:800;color:var(--accent)">¥${product.price}</div><div style="font-size:12px;color:var(--dim)">支付金额</div></div>
|
||||
<div>
|
||||
<div style="font-size:28px;font-weight:800;color:var(--accent)">¥${displayPrice}</div>
|
||||
${hasDiscount ? `<div style="font-size:12px;color:var(--dim);text-decoration:line-through">原价 ¥${product.price}</div>` : ''}
|
||||
<div style="font-size:12px;color:var(--dim)">支付金额</div>
|
||||
</div>
|
||||
<div style="font-size:22px;color:var(--green)">→ ${bondDisplay}</div>
|
||||
</div>
|
||||
${offerText}
|
||||
|
||||
Reference in New Issue
Block a user