用户组新增二手交易上架数量限制与保底价格字段

- land_api.php 六处 web_user_groups 建表加 shop_max_listings/shop_min_price 列
- 三处 migrations 加 ALTER,handleUpdateUserGroup INSERT/bind 带两字段
- sync.php 建表/迁移/INSERT 同步两字段(配合Java推送)
- admin.php 用户组新建/编辑表单与提交、列表详情显示两字段(-1=跟随全局)
This commit is contained in:
ypsdf1
2026-10-11 21:24:25 +08:00
parent fcc8f08ef7
commit 8a669b1e24
3 changed files with 56 additions and 4 deletions
+24
View File
@@ -3026,6 +3026,8 @@ async function loadUserGroups(el) {
html += `<span>💰 ${hasCustomPrice ? g.land_price_per_sqm+'债券/格²' : '默认'}</span>`;
html += `<span>🏗️ ${hasCustomMax ? '最多'+g.max_lands+'块' : '默认'}</span>`;
html += `<span>🔑 ${permCount}项默认权限</span>`;
html += `<span>📦 二手上架: ${(g.shop_max_listings ?? -1) >= 0 ? '最多'+g.shop_max_listings+'件' : '跟随全局'}</span>`;
html += `<span>🛡️ 保底价: ${(g.shop_min_price ?? -1) >= 0 ? g.shop_min_price+'债券' : '跟随全局'}</span>`;
html += `</div>`;
// 默认权限预览
if (permCount > 0) {
@@ -3120,6 +3122,16 @@ function showAddUserGroup() {
<input id="ugMaxEffectLevel" type="number" value="256" min="1" max="256" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
<div>
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">二手同时上架数上限(-1=跟随全局)</label>
<input id="ugShopMaxListings" type="number" value="-1" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
</div>
<div>
<label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">二手保底价格(债券, -1=跟随全局)</label>
<input id="ugShopMinPrice" type="number" value="-1" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)">
</div>
</div>
<div style="font-size:11px;color:var(--dim);margin-bottom:10px">普通玩家按各效果的生存可获取上限封顶(如抗性提升最高 2 级、海豚的恩惠最高 1 级),严禁超限。填 256 表示不额外限制。</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
<div>
@@ -3166,6 +3178,8 @@ async function doAddUserGroup() {
const renewPrice = document.getElementById('ugRenewPrice')?.value || '0';
const duration = document.getElementById('ugDuration')?.value || '0';
const autoRenew = document.getElementById('ugAutoRenew')?.checked ? '1' : '0';
const shopMaxListings = document.getElementById('ugShopMaxListings')?.value || '-1';
const shopMinPrice = document.getElementById('ugShopMinPrice')?.value || '-1';
if (!name || !/^[a-zA-Z0-9_]{2,20}$/.test(name)) {
glassAlert('组ID仅允许英文字母、数字和下划线,2-20位');
@@ -3178,6 +3192,7 @@ async function doAddUserGroup() {
priority, land_price_per_sqm: price, max_lands: maxLands, max_effects: maxEffects, max_effect_level: maxEffectLevel,
home_limit: homeLimit, join_price: joinPrice, renew_price: renewPrice,
duration_minutes: duration, auto_renew: autoRenew,
shop_max_listings: shopMaxListings, shop_min_price: shopMinPrice,
default_perms: '{}'
}, 'POST');
if (res.success) {
@@ -3240,6 +3255,12 @@ async function showEditUserGroup(groupName) {
<div><label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">续费价格(债券)</label>
<input id="eugRenewPrice" type="number" value="${g.renew_price || 0}" min="0" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)"></div>
</div>`;
html += `<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">
<div><label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">二手同时上架数上限(-1=跟随全局)</label>
<input id="eugShopMaxListings" type="number" value="${(g.shop_max_listings ?? -1)}" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)"></div>
<div><label style="display:block;font-size:12px;color:var(--dim);margin-bottom:4px">二手保底价格(债券, -1=跟随全局)</label>
<input id="eugShopMinPrice" type="number" value="${(g.shop_min_price ?? -1)}" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;color:var(--fg)"></div>
</div>`;
html += `<div style="margin-bottom:12px">
<label style="display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dim);cursor:pointer">
<input type="checkbox" id="eugAutoRenew" ${g.auto_renew == 1 ? 'checked' : ''} style="accent-color:#4caf50;width:14px;height:14px">
@@ -3279,6 +3300,8 @@ async function doEditUserGroup(groupName) {
const joinPrice = document.getElementById('eugJoinPrice')?.value || '0';
const renewPrice = document.getElementById('eugRenewPrice')?.value || '0';
const autoRenew = document.getElementById('eugAutoRenew')?.checked ? '1' : '0';
const shopMaxListings = document.getElementById('eugShopMaxListings')?.value || '-1';
const shopMinPrice = document.getElementById('eugShopMinPrice')?.value || '-1';
// 收集权限
const perms = {};
@@ -3292,6 +3315,7 @@ async function doEditUserGroup(groupName) {
priority, land_price_per_sqm: price, max_lands: maxLands, max_effects: maxEffects, max_effect_level: maxEffectLevel,
home_limit: homeLimit, duration_minutes: duration,
join_price: joinPrice, renew_price: renewPrice, auto_renew: autoRenew,
shop_max_listings: shopMaxListings, shop_min_price: shopMinPrice,
default_perms: JSON.stringify(perms)
}, 'POST');
if (res.success) {
+24 -2
View File
@@ -1709,6 +1709,8 @@ function getLandMaxEffects($db, $land) {
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
shop_max_listings INTEGER DEFAULT -1,
shop_min_price INTEGER DEFAULT -1,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2547,6 +2549,8 @@ function handleListUserGroups($db) {
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
shop_max_listings INTEGER DEFAULT -1,
shop_min_price INTEGER DEFAULT -1,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2569,6 +2573,8 @@ function handleListUserGroups($db) {
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
'shop_max_listings' => 'ALTER TABLE web_user_groups ADD COLUMN shop_max_listings INTEGER DEFAULT -1',
'shop_min_price' => 'ALTER TABLE web_user_groups ADD COLUMN shop_min_price INTEGER DEFAULT -1',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
@@ -2608,6 +2614,8 @@ function handleUpdateUserGroup($db, $data) {
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
shop_max_listings INTEGER DEFAULT -1,
shop_min_price INTEGER DEFAULT -1,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2633,12 +2641,14 @@ function handleUpdateUserGroup($db, $data) {
$durationMinutes = (int)($data['duration_minutes'] ?? 0);
$autoRenew = (int)($data['auto_renew'] ?? 0);
$defaultPerms = $data['default_perms'] ?? '{}';
$shopMaxListings = (int)($data['shop_max_listings'] ?? -1);
$shopMinPrice = (int)($data['shop_min_price'] ?? -1);
$stmt = $db->prepare("INSERT OR REPLACE INTO web_user_groups
(group_name, display_name, display_color, display_emoji, priority,
land_price_per_sqm, max_lands, max_effects, max_effect_level, home_limit, join_price, auto_renew, renew_price, duration_minutes, default_perms, synced_at)
land_price_per_sqm, max_lands, max_effects, max_effect_level, home_limit, join_price, auto_renew, renew_price, duration_minutes, default_perms, shop_max_listings, shop_min_price, synced_at)
VALUES (:name, :display, :color, :emoji, :priority,
:price, :maxlands, :maxEffects, :maxEffectLevel, :homeLimit, :joinPrice, :autoRenew, :renewPrice, :duration, :perms, :synced)");
:price, :maxlands, :maxEffects, :maxEffectLevel, :homeLimit, :joinPrice, :autoRenew, :renewPrice, :duration, :perms, :shopmax, :shopmin, :synced)");
$stmt->bindValue(':name', $name, SQLITE3_TEXT);
$stmt->bindValue(':display', $displayName, SQLITE3_TEXT);
$stmt->bindValue(':color', $displayColor, SQLITE3_TEXT);
@@ -2654,6 +2664,8 @@ function handleUpdateUserGroup($db, $data) {
$stmt->bindValue(':renewPrice', $renewPrice, SQLITE3_INTEGER);
$stmt->bindValue(':duration', $durationMinutes, SQLITE3_INTEGER);
$stmt->bindValue(':perms', $defaultPerms, SQLITE3_TEXT);
$stmt->bindValue(':shopmax', $shopMaxListings, SQLITE3_INTEGER);
$stmt->bindValue(':shopmin', $shopMinPrice, SQLITE3_INTEGER);
$stmt->bindValue(':synced', time(), SQLITE3_INTEGER);
$stmt->execute();
@@ -2837,6 +2849,8 @@ function handleGetPlayerGroups($db, $player) {
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
shop_max_listings INTEGER DEFAULT -1,
shop_min_price INTEGER DEFAULT -1,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2857,6 +2871,8 @@ function handleGetPlayerGroups($db, $player) {
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
'shop_max_listings' => 'ALTER TABLE web_user_groups ADD COLUMN shop_max_listings INTEGER DEFAULT -1',
'shop_min_price' => 'ALTER TABLE web_user_groups ADD COLUMN shop_min_price INTEGER DEFAULT -1',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
@@ -2882,6 +2898,8 @@ function handleGetPlayerGroups($db, $player) {
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
shop_max_listings INTEGER DEFAULT -1,
shop_min_price INTEGER DEFAULT -1,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2919,6 +2937,8 @@ function handleListAvailableGroups($db) {
max_lands INTEGER DEFAULT -1,
max_effects INTEGER DEFAULT 5,
max_effect_level INTEGER DEFAULT 256,
shop_max_listings INTEGER DEFAULT -1,
shop_min_price INTEGER DEFAULT -1,
home_limit INTEGER DEFAULT 0,
join_price INTEGER DEFAULT 0,
auto_renew INTEGER DEFAULT 0,
@@ -2937,6 +2957,8 @@ function handleListAvailableGroups($db) {
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
'shop_max_listings' => 'ALTER TABLE web_user_groups ADD COLUMN shop_max_listings INTEGER DEFAULT -1',
'shop_min_price' => 'ALTER TABLE web_user_groups ADD COLUMN shop_min_price INTEGER DEFAULT -1',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
+8 -2
View File
@@ -969,6 +969,8 @@ function syncUserGroups() {
renew_price INTEGER DEFAULT 0,
duration_minutes INTEGER DEFAULT 0,
default_perms TEXT DEFAULT '{}',
shop_max_listings INTEGER DEFAULT -1,
shop_min_price INTEGER DEFAULT -1,
synced_at INTEGER DEFAULT 0
)");
@@ -986,6 +988,8 @@ function syncUserGroups() {
'duration_minutes' => 'ALTER TABLE web_user_groups ADD COLUMN duration_minutes INTEGER DEFAULT 0',
'max_effects' => 'ALTER TABLE web_user_groups ADD COLUMN max_effects INTEGER DEFAULT 5',
'max_effect_level' => 'ALTER TABLE web_user_groups ADD COLUMN max_effect_level INTEGER DEFAULT 256',
'shop_max_listings' => 'ALTER TABLE web_user_groups ADD COLUMN shop_max_listings INTEGER DEFAULT -1',
'shop_min_price' => 'ALTER TABLE web_user_groups ADD COLUMN shop_min_price INTEGER DEFAULT -1',
];
foreach ($migrations as $col => $sql) {
if (!in_array($col, $columns)) {
@@ -997,10 +1001,10 @@ function syncUserGroups() {
$stmt = $db->prepare("INSERT OR REPLACE INTO web_user_groups
(group_name, display_name, display_color, display_emoji, priority,
land_price_per_sqm, max_lands, max_effects, max_effect_level, home_limit, join_price, auto_renew,
renew_price, duration_minutes, default_perms, synced_at)
renew_price, duration_minutes, default_perms, shop_max_listings, shop_min_price, synced_at)
VALUES (:name, :display, :color, :emoji, :priority,
:price, :maxlands, :maxeffects, :maxeffectlevel, :homelimit, :joinprice, :autorenew,
:renewprice, :duration, :perms, :synced)");
:renewprice, :duration, :perms, :shopmax, :shopmin, :synced)");
$count = 0;
foreach ($groups as $g) {
$stmt->bindValue(':name', $g['group_name'] ?? '', SQLITE3_TEXT);
@@ -1021,6 +1025,8 @@ function syncUserGroups() {
$stmt->bindValue(':renewprice', (int)($g['renew_price'] ?? 0), SQLITE3_INTEGER);
$stmt->bindValue(':duration', (int)($g['duration_minutes'] ?? 0), SQLITE3_INTEGER);
$stmt->bindValue(':perms', $g['default_perms'] ?? '{}', SQLITE3_TEXT);
$stmt->bindValue(':shopmax', (int)($g['shop_max_listings'] ?? -1), SQLITE3_INTEGER);
$stmt->bindValue(':shopmin', (int)($g['shop_min_price'] ?? -1), SQLITE3_INTEGER);
$stmt->bindValue(':synced', $now, SQLITE3_INTEGER);
$stmt->execute();
$count++;