1.优化渠道删除失败问题
This commit is contained in:
@@ -59,7 +59,8 @@ export default {
|
||||
delete(params: Record<string, any>) {
|
||||
return request.del<any>({
|
||||
url: '/core/dept/destroy',
|
||||
data: params
|
||||
data: params,
|
||||
showErrorMessage: false
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
<ElTable
|
||||
v-loading="loading"
|
||||
:data="bigwinRows"
|
||||
row-key="id"
|
||||
border
|
||||
size="default"
|
||||
class="config-table bigwin-table"
|
||||
@@ -262,14 +263,17 @@
|
||||
<template #default="{ row }">
|
||||
<div class="weight-cell">
|
||||
<ElSlider
|
||||
v-model="row.weight"
|
||||
:model-value="row.weight"
|
||||
:min="0"
|
||||
:max="10000"
|
||||
:step="100"
|
||||
:disabled="isBigwinWeightDisabled(row)"
|
||||
@update:model-value="
|
||||
(v: number | number[]) => setBigwinRowWeight(row, Array.isArray(v) ? v[0] : v)
|
||||
"
|
||||
/>
|
||||
<ElInputNumber
|
||||
v-model="row.weight"
|
||||
:model-value="row.weight"
|
||||
:min="0"
|
||||
:max="10000"
|
||||
:step="100"
|
||||
@@ -277,6 +281,7 @@
|
||||
controls-position="right"
|
||||
size="small"
|
||||
class="weight-input"
|
||||
@update:model-value="(v: number | undefined) => setBigwinRowWeight(row, v)"
|
||||
/>
|
||||
</div>
|
||||
<span v-if="isBigwinWeightDisabled(row)" class="weight-tip">{{
|
||||
@@ -672,7 +677,16 @@
|
||||
return n - 1
|
||||
}
|
||||
|
||||
/** BIGWIN 行不参与按 real_ev 推断 T1-T5,避免编辑时从大奖权重表消失 */
|
||||
function setBigwinRowWeight(row: IndexRow, v: number | number[] | undefined | null) {
|
||||
const n = Array.isArray(v) ? v[0] : v
|
||||
row.weight = toWeight(n)
|
||||
}
|
||||
|
||||
function handleRealEvChange(row: IndexRow) {
|
||||
if (row.tier === 'BIGWIN') {
|
||||
return
|
||||
}
|
||||
const n = rowRealEvNumber(row)
|
||||
syncRowTierFromRealEv(row)
|
||||
if (row.tier === 'T5') {
|
||||
@@ -1075,7 +1089,7 @@
|
||||
ui_text: r.ui_text,
|
||||
ui_text_en: r.ui_text_en,
|
||||
real_ev: r.real_ev,
|
||||
tier: r.tier,
|
||||
tier: 'BIGWIN',
|
||||
remark: r.remark
|
||||
}))
|
||||
await api.batchUpdate(batchPayload, { dept_id: filterDeptId.value })
|
||||
|
||||
@@ -106,8 +106,15 @@
|
||||
ElMessage.success('删除成功')
|
||||
emit('success')
|
||||
handleClose()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '删除失败')
|
||||
} catch (e: unknown) {
|
||||
let msg = '删除失败'
|
||||
if (e !== null && typeof e === 'object' && 'message' in e) {
|
||||
const m = Reflect.get(e, 'message')
|
||||
if (typeof m === 'string' && m !== '') {
|
||||
msg = m
|
||||
}
|
||||
}
|
||||
ElMessage.error(msg)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
@@ -54,6 +54,22 @@ class DiceChannelConfigService
|
||||
'dice_reward_config_record' => ['label' => '权重测试记录', 'group' => 'records'],
|
||||
];
|
||||
|
||||
/** 关联数据删除顺序:先删流水/记录,再删玩家,最后删配置 */
|
||||
private const DELETE_TABLE_ORDER = [
|
||||
'dice_play_record',
|
||||
'dice_play_record_test',
|
||||
'dice_player_wallet_record',
|
||||
'dice_player_ticket_record',
|
||||
'dice_reward_config_record',
|
||||
'dice_player',
|
||||
'dice_reward',
|
||||
'dice_reward_config',
|
||||
'dice_config',
|
||||
'dice_ante_config',
|
||||
'dice_lottery_pool_config',
|
||||
'dice_game',
|
||||
];
|
||||
|
||||
/**
|
||||
* 默认模板 dept_id 统一为 0,并为固定 id 的配置表建立 (dept_id, id) 唯一约束
|
||||
*/
|
||||
@@ -417,21 +433,54 @@ class DiceChannelConfigService
|
||||
if ($userCount > 0) {
|
||||
throw new ApiException('This channel has users, please delete or transfer them first');
|
||||
}
|
||||
$allowed = array_keys(self::RELATION_TABLES);
|
||||
foreach ($deleteTables as $table) {
|
||||
if (!in_array($table, $allowed, true)) {
|
||||
continue;
|
||||
}
|
||||
$tablesToDelete = $this->sortTablesForDelete($deleteTables);
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($tablesToDelete as $table) {
|
||||
if (!$this->tableHasColumn($table, 'dept_id')) {
|
||||
continue;
|
||||
}
|
||||
Db::table($table)->where('dept_id', $deptId)->delete();
|
||||
}
|
||||
Db::name('sa_system_role_dept')->where('dept_id', $deptId)->delete();
|
||||
SystemDept::destroy($deptId, true);
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
throw new ApiException('Channel delete failed: ' . $e->getMessage());
|
||||
}
|
||||
DiceRewardConfig::refreshCache($deptId);
|
||||
DiceReward::refreshCache($deptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按依赖顺序排列待删表(勾选顺序无关)
|
||||
*
|
||||
* @param array<int, string> $deleteTables
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function sortTablesForDelete(array $deleteTables): array
|
||||
{
|
||||
$allowed = array_keys(self::RELATION_TABLES);
|
||||
$picked = [];
|
||||
foreach ($deleteTables as $table) {
|
||||
if (is_string($table) && in_array($table, $allowed, true)) {
|
||||
$picked[$table] = true;
|
||||
}
|
||||
}
|
||||
$ordered = [];
|
||||
foreach (self::DELETE_TABLE_ORDER as $table) {
|
||||
if (isset($picked[$table])) {
|
||||
$ordered[] = $table;
|
||||
unset($picked[$table]);
|
||||
}
|
||||
}
|
||||
foreach (array_keys($picked) as $table) {
|
||||
$ordered[] = $table;
|
||||
}
|
||||
return $ordered;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
@@ -477,7 +526,9 @@ class DiceChannelConfigService
|
||||
if ($deptId === null || $deptId === AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) {
|
||||
$templateId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
|
||||
$query->where(function ($q) use ($templateId) {
|
||||
$q->where('dept_id', $templateId)->whereOr('dept_id', 'null');
|
||||
$q->where('dept_id', $templateId)->whereOr(function ($sub) {
|
||||
$sub->whereNull('dept_id');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$query->where('dept_id', $deptId);
|
||||
|
||||
52
server/db/debug_check_table_type.php
Normal file
52
server/db/debug_check_table_type.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 查询指定表/视图的类型(BASE TABLE / VIEW)。
|
||||
*
|
||||
* 用法(在 server 目录执行):
|
||||
* php db/debug_check_table_type.php bet_order_view
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (class_exists(\Dotenv\Dotenv::class) && is_file(dirname(__DIR__) . '/.env')) {
|
||||
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
|
||||
\Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
|
||||
} else {
|
||||
\Dotenv\Dotenv::createMutable(dirname(__DIR__))->load();
|
||||
}
|
||||
}
|
||||
|
||||
$table = $argv[1] ?? '';
|
||||
$table = trim((string) $table);
|
||||
if ($table === '') {
|
||||
fwrite(STDERR, "Missing table name.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$host = getenv('DB_HOST') ?: '127.0.0.1';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
$dbName = getenv('DB_NAME') ?: '';
|
||||
$user = getenv('DB_USER') ?: '';
|
||||
$pass = getenv('DB_PASSWORD') ?: '';
|
||||
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT TABLE_NAME, TABLE_TYPE
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :name"
|
||||
);
|
||||
$stmt->execute(['name' => $table]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) {
|
||||
echo "NOT_FOUND\t{$table}\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo $row['TABLE_NAME'] . "\t" . $row['TABLE_TYPE'] . "\n";
|
||||
|
||||
47
server/db/debug_list_view_backup_objects.php
Normal file
47
server/db/debug_list_view_backup_objects.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 列出当前数据库中所有 *__view_backup 对象,用于排查宝塔备份报错。
|
||||
*
|
||||
* 用法(在 server 目录执行):
|
||||
* php db/debug_list_view_backup_objects.php
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (class_exists(\Dotenv\Dotenv::class) && is_file(dirname(__DIR__) . '/.env')) {
|
||||
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
|
||||
\Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
|
||||
} else {
|
||||
\Dotenv\Dotenv::createMutable(dirname(__DIR__))->load();
|
||||
}
|
||||
}
|
||||
|
||||
$host = getenv('DB_HOST') ?: '127.0.0.1';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
$dbName = getenv('DB_NAME') ?: '';
|
||||
$user = getenv('DB_USER') ?: '';
|
||||
$pass = getenv('DB_PASSWORD') ?: '';
|
||||
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
$sql = "SELECT TABLE_NAME, TABLE_TYPE
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME LIKE '%\\_\\_view\\_backup'
|
||||
ORDER BY TABLE_NAME";
|
||||
|
||||
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (!$rows) {
|
||||
echo "No *__view_backup objects found.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
echo $row['TABLE_NAME'] . "\t" . $row['TABLE_TYPE'] . "\n";
|
||||
}
|
||||
|
||||
46
server/db/debug_list_views_and_definers.php
Normal file
46
server/db/debug_list_views_and_definers.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 列出当前数据库中的 VIEW 及其 DEFINER,用于排查宝塔备份“缺少表 xxx__view_backup”。
|
||||
*
|
||||
* 用法(在 server 目录执行):
|
||||
* php db/debug_list_views_and_definers.php
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (class_exists(\Dotenv\Dotenv::class) && is_file(dirname(__DIR__) . '/.env')) {
|
||||
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
|
||||
\Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
|
||||
} else {
|
||||
\Dotenv\Dotenv::createMutable(dirname(__DIR__))->load();
|
||||
}
|
||||
}
|
||||
|
||||
$host = getenv('DB_HOST') ?: '127.0.0.1';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
$dbName = getenv('DB_NAME') ?: '';
|
||||
$user = getenv('DB_USER') ?: '';
|
||||
$pass = getenv('DB_PASSWORD') ?: '';
|
||||
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
$sql = "SELECT TABLE_NAME, DEFINER, SECURITY_TYPE
|
||||
FROM information_schema.VIEWS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
ORDER BY TABLE_NAME";
|
||||
|
||||
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (!$rows) {
|
||||
echo "No views found.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
echo $row['TABLE_NAME'] . "\t" . ($row['DEFINER'] ?? '') . "\t" . ($row['SECURITY_TYPE'] ?? '') . "\n";
|
||||
}
|
||||
|
||||
33
server/db/dice_flowcharts_menu.sql
Normal file
33
server/db/dice_flowcharts_menu.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- 抽奖流程图:两个顶级外链菜单(type=4),点击新窗口打开 public/docs/flowcharts/*.html
|
||||
-- 挂载位置:与「后台操作指南」同级(parent_id=0),紧挨其下方(sort 略小,列表按 sort 降序)
|
||||
|
||||
SET @now = NOW();
|
||||
|
||||
-- 移除旧的「抽奖流程说明」内嵌页菜单(若已安装)
|
||||
SET @old_flow_menu_id = (
|
||||
SELECT `id` FROM `sa_system_menu`
|
||||
WHERE `path` = 'flowcharts' AND `component` = '/plugin/dice/flowcharts/index/index' AND `type` = 2
|
||||
ORDER BY `id` ASC LIMIT 1
|
||||
);
|
||||
|
||||
DELETE FROM `sa_system_role_menu` WHERE `menu_id` = @old_flow_menu_id;
|
||||
DELETE FROM `sa_system_menu` WHERE `parent_id` = @old_flow_menu_id AND `type` = 3;
|
||||
DELETE FROM `sa_system_menu` WHERE `id` = @old_flow_menu_id;
|
||||
|
||||
-- 1) 为何最终抽到该奖励
|
||||
INSERT INTO `sa_system_menu`
|
||||
(`parent_id`,`name`,`code`,`slug`,`type`,`path`,`component`,`method`,`icon`,`sort`,`link_url`,`is_iframe`,`is_keep_alive`,`is_hidden`,`is_fixed_tab`,`is_full_page`,`generate_id`,`generate_key`,`status`,`create_time`,`update_time`)
|
||||
SELECT 0, '为何最终抽到该奖励', 'DiceFlowWhyReward', NULL, 4, 'dice_flow_why_reward', '', NULL, 'ri:question-answer-line', 4, '/docs/flowcharts/dice-为何抽到该奖励.html', 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sa_system_menu`
|
||||
WHERE `type` = 4 AND `link_url` = '/docs/flowcharts/dice-为何抽到该奖励.html'
|
||||
);
|
||||
|
||||
-- 2) 后台如何配置中奖逻辑
|
||||
INSERT INTO `sa_system_menu`
|
||||
(`parent_id`,`name`,`code`,`slug`,`type`,`path`,`component`,`method`,`icon`,`sort`,`link_url`,`is_iframe`,`is_keep_alive`,`is_hidden`,`is_fixed_tab`,`is_full_page`,`generate_id`,`generate_key`,`status`,`create_time`,`update_time`)
|
||||
SELECT 0, '后台如何配置中奖逻辑', 'DiceFlowAdminConfig', NULL, 4, 'dice_flow_admin_config', '', NULL, 'ri:settings-3-line', 3, '/docs/flowcharts/dice-后台中奖逻辑配置.html', 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sa_system_menu`
|
||||
WHERE `type` = 4 AND `link_url` = '/docs/flowcharts/dice-后台中奖逻辑配置.html'
|
||||
);
|
||||
3
server/db/dice_play_record_add_remark.sql
Normal file
3
server/db/dice_play_record_add_remark.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- dice_play_record 新增备注(T4 惩罚余额不足等场景)
|
||||
ALTER TABLE `dice_play_record`
|
||||
ADD COLUMN `remark` varchar(255) DEFAULT NULL COMMENT '备注(如惩罚格余额不足)' AFTER `reward_tier`;
|
||||
23
server/db/dice_reward_config_tier_recommend_menu.sql
Normal file
23
server/db/dice_reward_config_tier_recommend_menu.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- 奖励配置:档位结算推荐配置(T1-T5 推荐金额、按规则生成)按钮权限
|
||||
-- 挂载在「奖励配置」菜单(type=2)下;slug 与 DiceRewardConfigController Permission 一致
|
||||
|
||||
SET @now = NOW();
|
||||
|
||||
SET @reward_menu_id = (
|
||||
SELECT `id` FROM `sa_system_menu`
|
||||
WHERE `type` = 2
|
||||
AND (
|
||||
`path` = 'reward_config'
|
||||
OR `component` LIKE '%reward_config%'
|
||||
)
|
||||
ORDER BY `id` ASC
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO `sa_system_menu`
|
||||
(`parent_id`,`name`,`code`,`slug`,`type`,`path`,`component`,`method`,`sort`,`is_iframe`,`is_keep_alive`,`is_hidden`,`is_fixed_tab`,`is_full_page`,`generate_id`,`generate_key`,`status`,`create_time`,`update_time`)
|
||||
SELECT @reward_menu_id, '档位结算推荐配置', '', 'dice:reward_config:index:tierRecommend', 3, '', '', '', 95, 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE @reward_menu_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sa_system_menu` WHERE `slug` = 'dice:reward_config:index:tierRecommend' AND `type` = 3
|
||||
);
|
||||
127
server/db/fix_bt_backup_view_tables.php
Normal file
127
server/db/fix_bt_backup_view_tables.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 解决宝塔面板备份偶发报错:
|
||||
* “备份文件中缺少表: xxx__view_backup”
|
||||
*
|
||||
* 原因通常是:数据库存在 VIEW,但宝塔的备份校验按“表”去匹配,
|
||||
* 或其内部会期望存在 xxx__view_backup 之类的“视图备份表”标记。
|
||||
*
|
||||
* 本脚本会:
|
||||
* - 扫描当前库所有 VIEW
|
||||
* - 为每个 VIEW 创建一个同名的备份表:<view_name>__view_backup
|
||||
* - 在该表写入 SHOW CREATE VIEW 的结果(若权限不足则写入空串)
|
||||
*
|
||||
* 用法(在 server 目录执行):
|
||||
* php db/fix_bt_backup_view_tables.php
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (class_exists(\Dotenv\Dotenv::class) && is_file(dirname(__DIR__) . '/.env')) {
|
||||
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
|
||||
\Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
|
||||
} else {
|
||||
\Dotenv\Dotenv::createMutable(dirname(__DIR__))->load();
|
||||
}
|
||||
}
|
||||
|
||||
$host = getenv('DB_HOST') ?: '127.0.0.1';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
$dbName = getenv('DB_NAME') ?: '';
|
||||
$user = getenv('DB_USER') ?: '';
|
||||
$pass = getenv('DB_PASSWORD') ?: '';
|
||||
|
||||
if ($dbName === '' || $user === '') {
|
||||
fwrite(STDERR, "Missing DB_NAME/DB_USER in .env\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
|
||||
$views = $pdo
|
||||
->query("SELECT TABLE_NAME FROM information_schema.VIEWS WHERE TABLE_SCHEMA = DATABASE() ORDER BY TABLE_NAME")
|
||||
->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (!$views) {
|
||||
echo "No views found; nothing to do.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$updated = 0;
|
||||
$failed = 0;
|
||||
|
||||
foreach ($views as $viewName) {
|
||||
$viewName = (string) $viewName;
|
||||
$backupTable = $viewName . '__view_backup';
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9_]+$/', $backupTable)) {
|
||||
$failed++;
|
||||
echo "[skip] invalid name: {$backupTable}\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->exec(
|
||||
"CREATE TABLE IF NOT EXISTS `{$backupTable}` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`view_name` VARCHAR(255) NOT NULL,
|
||||
`create_sql` LONGTEXT NOT NULL,
|
||||
`updated_at` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_view_name` (`view_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
||||
);
|
||||
$created++;
|
||||
} catch (Throwable $e) {
|
||||
$failed++;
|
||||
echo "[fail] create table {$backupTable}: " . $e->getMessage() . "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$createSql = '';
|
||||
try {
|
||||
$stmt = $pdo->query("SHOW CREATE VIEW `{$viewName}`");
|
||||
$row = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : false;
|
||||
if ($row) {
|
||||
// SHOW CREATE VIEW 返回字段名可能是 "Create View" 或类似
|
||||
foreach ($row as $k => $v) {
|
||||
if (is_string($k) && stripos($k, 'create') !== false && is_string($v)) {
|
||||
$createSql = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 权限不足也不阻断,只是留空,保证备份表存在
|
||||
$createSql = '';
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO `{$backupTable}` (`view_name`, `create_sql`, `updated_at`)
|
||||
VALUES (:view_name, :create_sql, :updated_at)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`create_sql` = VALUES(`create_sql`),
|
||||
`updated_at` = VALUES(`updated_at`)"
|
||||
);
|
||||
$stmt->execute([
|
||||
'view_name' => $viewName,
|
||||
'create_sql' => $createSql,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$updated++;
|
||||
echo "[ok] {$viewName} -> {$backupTable}\n";
|
||||
} catch (Throwable $e) {
|
||||
$failed++;
|
||||
echo "[fail] upsert {$backupTable}: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Done. created={$created}, updated={$updated}, failed={$failed}\n";
|
||||
|
||||
99
server/db/run_dice_flowcharts_menu.php
Normal file
99
server/db/run_dice_flowcharts_menu.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* 安装两个抽奖流程图外链菜单,并授权超级管理员
|
||||
* 用法(在 server 目录): php db/run_dice_flowcharts_menu.php
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../support/bootstrap.php';
|
||||
|
||||
use plugin\saiadmin\app\cache\UserMenuCache;
|
||||
use support\think\Db;
|
||||
|
||||
function runSqlFile(PDO $pdo, string $path, string $label): void
|
||||
{
|
||||
echo "\n=== {$label} ===\n";
|
||||
if (! is_file($path)) {
|
||||
echo "跳过:文件不存在 {$path}\n";
|
||||
return;
|
||||
}
|
||||
$sql = file_get_contents($path);
|
||||
$sql = preg_replace('/--.*$/m', '', $sql);
|
||||
$parts = array_filter(array_map('trim', explode(';', $sql)));
|
||||
$ok = 0;
|
||||
foreach ($parts as $statement) {
|
||||
if ($statement === '') {
|
||||
continue;
|
||||
}
|
||||
$pdo->exec($statement);
|
||||
$ok++;
|
||||
}
|
||||
echo "完成:执行 {$ok} 条语句\n";
|
||||
}
|
||||
|
||||
function cliPdo(): PDO
|
||||
{
|
||||
$host = getenv('DB_HOST') ?: '127.0.0.1';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
$db = getenv('DB_NAME') ?: '';
|
||||
$user = getenv('DB_USER') ?: '';
|
||||
$pass = getenv('DB_PASSWORD') ?: '';
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$db};charset=utf8mb4";
|
||||
return new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
]);
|
||||
}
|
||||
|
||||
echo "========== 抽奖流程图外链菜单安装 ==========\n";
|
||||
echo '数据库: ' . (getenv('DB_NAME') ?: '') . '@' . (getenv('DB_HOST') ?: '') . "\n";
|
||||
|
||||
$pdo = cliPdo();
|
||||
runSqlFile($pdo, __DIR__ . '/dice_flowcharts_menu.sql', '1. 外链菜单');
|
||||
|
||||
$menuIds = Db::name('sa_system_menu')
|
||||
->where('type', 4)
|
||||
->whereIn('link_url', [
|
||||
'/docs/flowcharts/dice-为何抽到该奖励.html',
|
||||
'/docs/flowcharts/dice-后台中奖逻辑配置.html',
|
||||
])
|
||||
->column('id');
|
||||
|
||||
if ($menuIds === [] || $menuIds === null) {
|
||||
echo "错误:未找到流程图菜单\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$menuIds = array_map('intval', $menuIds);
|
||||
echo "\n流程图菜单 ID: " . implode(', ', $menuIds) . "\n";
|
||||
|
||||
echo "\n=== 2. 授权超级管理员角色 ===\n";
|
||||
$adminRoleIds = Db::name('sa_system_role')
|
||||
->where('code', 'super_admin')
|
||||
->column('id');
|
||||
|
||||
if ($adminRoleIds === [] || $adminRoleIds === null) {
|
||||
$adminRoleIds = Db::name('sa_system_role')->where('id', 1)->column('id');
|
||||
}
|
||||
|
||||
foreach ($adminRoleIds as $roleId) {
|
||||
$roleId = (int) $roleId;
|
||||
foreach ($menuIds as $menuId) {
|
||||
$exists = Db::name('sa_system_role_menu')
|
||||
->where('role_id', $roleId)
|
||||
->where('menu_id', $menuId)
|
||||
->count();
|
||||
if ($exists > 0) {
|
||||
continue;
|
||||
}
|
||||
Db::name('sa_system_role_menu')->insert([
|
||||
'role_id' => $roleId,
|
||||
'menu_id' => $menuId,
|
||||
]);
|
||||
}
|
||||
echo "角色 {$roleId} 已关联流程图菜单\n";
|
||||
}
|
||||
|
||||
UserMenuCache::clearMenuCache();
|
||||
echo "\n已清理菜单缓存。请重新登录后台或刷新页面查看侧边栏。\n";
|
||||
echo "点击菜单将在新窗口打开 /docs/flowcharts/*.html\n";
|
||||
44
server/db/run_dice_play_record_add_remark.php
Normal file
44
server/db/run_dice_play_record_add_remark.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* 执行 dice_play_record 备注字段迁移
|
||||
* 用法:在 server 目录执行 php db/run_dice_play_record_add_remark.php
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
define('BASE_PATH', dirname(__DIR__));
|
||||
|
||||
require_once BASE_PATH . '/vendor/autoload.php';
|
||||
|
||||
if (class_exists(\Dotenv\Dotenv::class) && is_file(BASE_PATH . '/.env')) {
|
||||
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
|
||||
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
|
||||
} else {
|
||||
\Dotenv\Dotenv::createMutable(BASE_PATH)->load();
|
||||
}
|
||||
}
|
||||
|
||||
$host = getenv('DB_HOST') ?: '127.0.0.1';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
$db = getenv('DB_NAME') ?: '';
|
||||
$user = getenv('DB_USER') ?: '';
|
||||
$pass = getenv('DB_PASSWORD') ?: '';
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$db};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
|
||||
$sqlFile = __DIR__ . '/dice_play_record_add_remark.sql';
|
||||
$sql = file_get_contents($sqlFile);
|
||||
$sql = preg_replace('/--.*$/m', '', $sql);
|
||||
$parts = array_filter(array_map('trim', explode(';', $sql)));
|
||||
|
||||
echo "执行: dice_play_record_add_remark.sql\n";
|
||||
echo "数据库: {$db} @ {$host}\n\n";
|
||||
|
||||
foreach ($parts as $statement) {
|
||||
if ($statement === '') {
|
||||
continue;
|
||||
}
|
||||
$pdo->exec($statement);
|
||||
echo "OK\n";
|
||||
}
|
||||
|
||||
echo "\n完成。\n";
|
||||
60
server/db/run_dice_reward_config_tier_recommend_menu.php
Normal file
60
server/db/run_dice_reward_config_tier_recommend_menu.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* 执行档位结算推荐配置菜单权限 SQL
|
||||
* 用法:在 server 目录执行 php db/run_dice_reward_config_tier_recommend_menu.php
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
define('BASE_PATH', dirname(__DIR__));
|
||||
|
||||
require_once BASE_PATH . '/vendor/autoload.php';
|
||||
|
||||
if (class_exists(\Dotenv\Dotenv::class) && is_file(BASE_PATH . '/.env')) {
|
||||
if (method_exists(\Dotenv\Dotenv::class, 'createUnsafeMutable')) {
|
||||
\Dotenv\Dotenv::createUnsafeMutable(BASE_PATH)->load();
|
||||
} else {
|
||||
\Dotenv\Dotenv::createMutable(BASE_PATH)->load();
|
||||
}
|
||||
}
|
||||
|
||||
$host = getenv('DB_HOST') ?: '127.0.0.1';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
$db = getenv('DB_NAME') ?: '';
|
||||
$user = getenv('DB_USER') ?: '';
|
||||
$pass = getenv('DB_PASSWORD') ?: '';
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$db};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
|
||||
$sqlFile = __DIR__ . '/dice_reward_config_tier_recommend_menu.sql';
|
||||
$sql = file_get_contents($sqlFile);
|
||||
$sql = preg_replace('/--.*$/m', '', $sql);
|
||||
$parts = array_filter(array_map('trim', explode(';', $sql)));
|
||||
|
||||
echo "执行: dice_reward_config_tier_recommend_menu.sql\n";
|
||||
echo "数据库: {$db} @ {$host}\n\n";
|
||||
|
||||
foreach ($parts as $statement) {
|
||||
if ($statement === '') {
|
||||
continue;
|
||||
}
|
||||
$pdo->exec($statement);
|
||||
}
|
||||
|
||||
$row = $pdo->query(
|
||||
"SELECT id, parent_id, name, slug FROM sa_system_menu WHERE slug = 'dice:reward_config:index:tierRecommend' AND type = 3 LIMIT 1"
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
echo "成功:已写入菜单权限\n";
|
||||
echo " id={$row['id']} parent_id={$row['parent_id']} name={$row['name']} slug={$row['slug']}\n";
|
||||
} else {
|
||||
$parent = $pdo->query(
|
||||
"SELECT id, name, path FROM sa_system_menu WHERE type = 2 AND (path = 'reward_config' OR component LIKE '%reward_config%') ORDER BY id ASC LIMIT 1"
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
if ($parent === false) {
|
||||
echo "失败:未找到「奖励配置」父菜单 (type=2),请先创建 reward_config 菜单\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "警告:权限 slug 未查到(可能已存在但未插入)。父菜单: id={$parent['id']} path={$parent['path']}\n";
|
||||
exit(1);
|
||||
}
|
||||
@@ -107,11 +107,12 @@ class SystemDeptController extends BaseController
|
||||
#[Permission('渠道数据删除','core:dept:destroy')]
|
||||
public function destroy(Request $request) : Response
|
||||
{
|
||||
$ids = $request->post('ids', '');
|
||||
// DELETE + JSON body 须用 input();post() 仅表单 POST 有效(与 SystemUserController 一致)
|
||||
$ids = $request->input('ids', '');
|
||||
if (empty($ids)) {
|
||||
return $this->fail('please select data to delete');
|
||||
}
|
||||
$deleteTables = $request->post('delete_tables', []);
|
||||
$deleteTables = $request->input('delete_tables', []);
|
||||
if (!is_array($deleteTables)) {
|
||||
$deleteTables = [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user