1.新增菜单后台操作指南,方便管理员查看使用
26
saiadmin-artd/src/api/system/admin_guide.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import request from '@/utils/http'
|
||||
|
||||
/**
|
||||
* 后台操作指南 API
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* 读取 Markdown 内容
|
||||
*/
|
||||
read() {
|
||||
return request.get<Api.Common.ApiData>({
|
||||
url: '/core/adminGuide/read'
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存 Markdown 内容
|
||||
*/
|
||||
save(params: { content: string }) {
|
||||
return request.post<Api.Common.ApiData>({
|
||||
url: '/core/adminGuide/save',
|
||||
data: params,
|
||||
showSuccessMessage: true
|
||||
})
|
||||
}
|
||||
}
|
||||
20
saiadmin-artd/src/locales/langs/en/system/admin_guide.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "Admin Guide",
|
||||
"toolbar": {
|
||||
"edit": "Edit",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"meta": {
|
||||
"filePath": "File Path",
|
||||
"updateTime": "Updated At"
|
||||
},
|
||||
"message": {
|
||||
"loadFailed": "Failed to load admin guide",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"saveFailed": "Failed to save",
|
||||
"cancelConfirm": "You have unsaved changes. Cancel editing?",
|
||||
"editRequired": "Please click Edit before saving"
|
||||
}
|
||||
}
|
||||
20
saiadmin-artd/src/locales/langs/zh/system/admin_guide.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "后台操作指南",
|
||||
"toolbar": {
|
||||
"edit": "编辑",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"refresh": "刷新"
|
||||
},
|
||||
"meta": {
|
||||
"filePath": "文档路径",
|
||||
"updateTime": "更新时间"
|
||||
},
|
||||
"message": {
|
||||
"loadFailed": "加载操作指南失败",
|
||||
"saveSuccess": "保存成功",
|
||||
"saveFailed": "保存失败",
|
||||
"cancelConfirm": "当前有未保存的修改,确定取消编辑吗?",
|
||||
"editRequired": "请先点击编辑后再保存"
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@ const NO_CHANNEL_LAYOUT_PATHS = [
|
||||
'/safeguard/database',
|
||||
'/safeguard/server',
|
||||
'/safeguard/cache',
|
||||
'/safeguard/email-log'
|
||||
'/safeguard/email-log',
|
||||
'/admin_guide'
|
||||
]
|
||||
|
||||
/** 日志页:左侧首项为「全部」,dept_id=0 表示不按渠道过滤 */
|
||||
|
||||
211
saiadmin-artd/src/views/system/admin_guide/index.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="art-full-height admin-guide-page">
|
||||
<ElCard class="art-card-xs flex flex-col h-full mt-0" shadow="never">
|
||||
<template #header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<b>{{ $t('page.title') }}</b>
|
||||
<div v-if="meta.filePath" class="mt-1 text-xs text-g-500">
|
||||
{{ $t('page.meta.filePath') }}:{{ meta.filePath }}
|
||||
<span v-if="meta.updateTime" class="ml-3">
|
||||
{{ $t('page.meta.updateTime') }}:{{ meta.updateTime }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ElSpace wrap>
|
||||
<ElButton
|
||||
v-permission="'system:admin_guide:index:read'"
|
||||
:loading="loading"
|
||||
@click="loadContent"
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:refresh-line" />
|
||||
</template>
|
||||
{{ $t('page.toolbar.refresh') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="!isEditing"
|
||||
v-permission="'system:admin_guide:index:edit'"
|
||||
type="primary"
|
||||
@click="startEdit"
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:pencil-line" />
|
||||
</template>
|
||||
{{ $t('page.toolbar.edit') }}
|
||||
</ElButton>
|
||||
<template v-if="isEditing">
|
||||
<ElButton @click="handleCancel">
|
||||
{{ $t('page.toolbar.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="'system:admin_guide:index:save'"
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
@click="handleSave"
|
||||
>
|
||||
<template #icon>
|
||||
<ArtSvgIcon icon="ri:save-line" />
|
||||
</template>
|
||||
{{ $t('page.toolbar.save') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElSpace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-loading="loading" class="admin-guide-body flex-1 min-h-0 overflow-auto">
|
||||
<SaMdEditor
|
||||
v-if="isEditing"
|
||||
v-model="editContent"
|
||||
height="calc(100vh - 220px)"
|
||||
min-height="480px"
|
||||
/>
|
||||
<MdPreview
|
||||
v-else
|
||||
:model-value="previewContent"
|
||||
:theme="previewTheme"
|
||||
preview-theme="github"
|
||||
class="admin-guide-preview"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { MdPreview } from 'md-editor-v3'
|
||||
import 'md-editor-v3/lib/preview.css'
|
||||
import SaMdEditor from '@/components/sai/sa-md-editor/index.vue'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
import api from '@/api/system/admin_guide'
|
||||
|
||||
defineOptions({ name: 'SystemAdminGuide' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const previewContent = ref('')
|
||||
const editContent = ref('')
|
||||
const originalContent = ref('')
|
||||
const meta = ref<{ filePath?: string; updateTime?: string }>({})
|
||||
|
||||
const previewTheme = computed(() => (settingStore.isDark ? 'dark' : 'light'))
|
||||
|
||||
/** 静态资源根地址(public 目录,开发环境走代理地址) */
|
||||
const getStaticFileBase = (): string => {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || ''
|
||||
if (apiUrl.startsWith('http')) {
|
||||
return apiUrl.replace(/\/$/, '')
|
||||
}
|
||||
const proxyUrl = import.meta.env.VITE_API_PROXY_URL || ''
|
||||
if (proxyUrl) {
|
||||
return String(proxyUrl).replace(/\/$/, '')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const resolveGuideImages = (content: string): string => {
|
||||
const staticBase = getStaticFileBase()
|
||||
if (!staticBase) {
|
||||
return content
|
||||
}
|
||||
return content.replace(/!\[([^\]]*)\]\((\/docs\/picture\/[^)]+)\)/g, (_match, alt, path) => {
|
||||
return ``
|
||||
})
|
||||
}
|
||||
|
||||
const loadContent = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.read()
|
||||
const data = res as { content?: string; file_path?: string; update_time?: string }
|
||||
const rawContent = data.content ?? ''
|
||||
previewContent.value = resolveGuideImages(rawContent)
|
||||
editContent.value = rawContent
|
||||
originalContent.value = rawContent
|
||||
meta.value = {
|
||||
filePath: data.file_path,
|
||||
updateTime: data.update_time
|
||||
}
|
||||
} catch {
|
||||
// 错误由 http 工具统一处理
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const startEdit = () => {
|
||||
editContent.value = previewContent.value
|
||||
originalContent.value = previewContent.value
|
||||
isEditing.value = true
|
||||
}
|
||||
|
||||
const handleCancel = async () => {
|
||||
if (editContent.value !== originalContent.value) {
|
||||
try {
|
||||
await ElMessageBox.confirm(t('page.message.cancelConfirm'), {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
editContent.value = originalContent.value
|
||||
isEditing.value = false
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!isEditing.value) {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await api.save({ content: editContent.value })
|
||||
const data = res as { content?: string; file_path?: string; update_time?: string }
|
||||
const savedContent = data.content ?? editContent.value
|
||||
editContent.value = savedContent
|
||||
originalContent.value = savedContent
|
||||
previewContent.value = resolveGuideImages(savedContent)
|
||||
meta.value = {
|
||||
filePath: data.file_path ?? meta.value.filePath,
|
||||
updateTime: data.update_time ?? meta.value.updateTime
|
||||
}
|
||||
isEditing.value = false
|
||||
} catch {
|
||||
// 错误由 http 工具统一处理
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadContent()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-guide-page {
|
||||
:deep(.el-card__body) {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-guide-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-guide-preview {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
</style>
|
||||
107
server/db/run_system_admin_guide_menu.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* 安装「后台操作指南」菜单与权限,并授权给超级管理员角色
|
||||
* 用法(在 server 目录): php db/run_system_admin_guide_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__ . '/system_admin_guide_menu.sql', '1. 菜单与按钮权限');
|
||||
|
||||
$menuId = (int) Db::name('sa_system_menu')
|
||||
->where('path', 'admin_guide')
|
||||
->where('component', '/system/admin_guide/index')
|
||||
->where('type', 2)
|
||||
->value('id');
|
||||
|
||||
if ($menuId <= 0) {
|
||||
echo "错误:未找到后台操作指南菜单\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$buttonIds = Db::name('sa_system_menu')
|
||||
->where('parent_id', $menuId)
|
||||
->where('type', 3)
|
||||
->column('id');
|
||||
|
||||
$allMenuIds = array_values(array_unique(array_merge([$menuId], array_map('intval', $buttonIds ?: []))));
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
$inserted = 0;
|
||||
foreach ($adminRoleIds as $roleId) {
|
||||
$roleId = (int) $roleId;
|
||||
foreach ($allMenuIds as $mid) {
|
||||
$exists = Db::name('sa_system_role_menu')
|
||||
->where('role_id', $roleId)
|
||||
->where('menu_id', $mid)
|
||||
->count();
|
||||
if ($exists > 0) {
|
||||
continue;
|
||||
}
|
||||
Db::name('sa_system_role_menu')->insert([
|
||||
'role_id' => $roleId,
|
||||
'menu_id' => $mid,
|
||||
]);
|
||||
$inserted++;
|
||||
}
|
||||
echo " 角色 {$roleId}:新增授权 {$inserted} 条\n";
|
||||
}
|
||||
|
||||
UserMenuCache::clearMenuCache();
|
||||
\plugin\saiadmin\app\cache\UserAuthCache::clear();
|
||||
|
||||
echo "\n菜单 ID: {$menuId}\n";
|
||||
echo "按钮权限: " . implode(',', $allMenuIds) . "\n";
|
||||
echo "已清除菜单缓存,请重新登录后台查看。\n";
|
||||
echo "========== 安装完成 ==========\n";
|
||||
39
server/db/system_admin_guide_menu.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
-- 后台操作指南顶级菜单与权限
|
||||
-- 说明:挂载到顶级菜单(parent_id=0),内容来源 server/docs/ADMIN_GUIDE.md
|
||||
|
||||
SET @now = NOW();
|
||||
|
||||
-- 1) 创建后台操作指南顶级菜单(type=2,parent_id=0)
|
||||
INSERT INTO `sa_system_menu`
|
||||
(`parent_id`,`name`,`code`,`slug`,`type`,`path`,`component`,`method`,`icon`,`sort`,`is_iframe`,`is_keep_alive`,`is_hidden`,`is_fixed_tab`,`is_full_page`,`generate_id`,`generate_key`,`status`,`create_time`,`update_time`)
|
||||
SELECT 0, '后台操作指南', 'AdminGuide', NULL, 2, 'admin_guide', '/system/admin_guide/index', NULL, 'ri:book-read-line', 5, 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sa_system_menu` WHERE `path` = 'admin_guide' AND `component` = '/system/admin_guide/index' AND `type` = 2
|
||||
);
|
||||
|
||||
SET @admin_guide_menu_id = (
|
||||
SELECT `id` FROM `sa_system_menu`
|
||||
WHERE `path` = 'admin_guide' AND `component` = '/system/admin_guide/index' AND `type` = 2
|
||||
ORDER BY `id` ASC LIMIT 1
|
||||
);
|
||||
|
||||
-- 2) 创建按钮权限
|
||||
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 @admin_guide_menu_id, '数据列表', '', 'system:admin_guide:index:index', 3, '', '', '', 100, 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sa_system_menu` WHERE `slug` = 'system:admin_guide:index:index' AND `type` = 3);
|
||||
|
||||
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 @admin_guide_menu_id, '读取', '', 'system:admin_guide:index:read', 3, '', '', '', 100, 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sa_system_menu` WHERE `slug` = 'system:admin_guide:index:read' AND `type` = 3);
|
||||
|
||||
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 @admin_guide_menu_id, '编辑', '', 'system:admin_guide:index:edit', 3, '', '', '', 100, 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sa_system_menu` WHERE `slug` = 'system:admin_guide:index:edit' AND `type` = 3);
|
||||
|
||||
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 @admin_guide_menu_id, '保存', '', 'system:admin_guide:index:save', 3, '', '', '', 100, 2, 2, 2, 2, 2, 0, NULL, 1, @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sa_system_menu` WHERE `slug` = 'system:admin_guide:index:save' AND `type` = 3);
|
||||
128
server/docs/ADMIN_GUIDE.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# 大富翁-使用说明指南
|
||||
|
||||
## 菜单简单介绍
|
||||
|
||||
## 工作台/统计页面:统计数据
|
||||
|
||||

|
||||
|
||||
## 角色管理:对角色的菜单权限设置
|
||||
|
||||
按等级设定,等级越低权限越少(不要出现上级角色没有的权限,子角色有)
|
||||
避免方式:使用子角色创建下级角色,可以避免下级角色比上级角色操作权限更多的问题
|
||||
|
||||

|
||||
|
||||
这里设置角色的菜单以及按钮权限
|
||||
|
||||

|
||||
|
||||
## 彩金池配置:监听彩金池实时变化
|
||||
|
||||
可以实时监听彩金池累积金额的变化
|
||||
|
||||

|
||||
|
||||
## 游戏配置:游戏规则和平台币转化比
|
||||
|
||||
游戏配置
|
||||
|
||||

|
||||
|
||||
其中游戏玩法为进入游戏的弹窗,和规则介绍(无特殊需求不需要大改)
|
||||
|
||||

|
||||
|
||||
游戏平台币兑换币:为进入平台时平台比转化比,比如,当前设置的为1:1,如果从jk8平台转入100,那么获取的游戏币为100,如果设置1:2则获取的平台币为200
|
||||
|
||||
## 底注配置:方便玩家快速调整压注倍率
|
||||
|
||||
底注配置
|
||||
|
||||

|
||||
|
||||
对应游戏中的,其中每次游玩对局基础消耗为1游戏币(无法修改),底注的设置只是方便玩家快速修改压注金额
|
||||
|
||||

|
||||
|
||||
# 抽奖逻辑
|
||||
|
||||
## 判断抽奖档位
|
||||
|
||||
当前的抽奖逻辑时,按照抽奖档位(T1-T5)进行抽奖,在【玩家管理】菜单中的设置玩家具体的档位权重
|
||||
|
||||

|
||||
|
||||
也可以在【彩金池配置】菜单中设置玩家正常的抽奖档位权重
|
||||
|
||||
- 其中正常的档位权重为注册玩家默认绑定的档位权重,并且只有在修改完后,创建的玩家才能绑定最新的正常档位权重
|
||||
|
||||

|
||||
|
||||
- 其中free为杀分权重为如果当前彩金池(平台)盈利超过设置的安全线则制动走杀分的权重
|
||||
|
||||

|
||||
|
||||
- 剩余的两个权重可以方便快速切换用户的档位抽奖权重
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 根据档位抽取中奖号码
|
||||
|
||||
### 设置中奖号码地图
|
||||
|
||||
在后台设置地图缩影
|
||||
|
||||

|
||||
|
||||
地图的索引参看如下
|
||||
|
||||

|
||||
|
||||
其中地图的索引可以按照需求点击图中的按规则生成
|
||||
|
||||
并且规则尽可能符合:结算金额 < 0 → T4;0 < 结算金额 < 100 → T3;100 < 结算金额 < 200 → T2;200 < 结算金额 → T1;T5「再来一次」结算金额=0
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 创建完地图索引后创建相应的奖励对照表
|
||||
|
||||
创建奖励对照表的原因是由于有每个号码的权重不一样,豹子号10,15,20,25有多重组合方式,所以需要设置奖励对照表中的权重配比
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
根据抽到的奖励档位,抽取号码(主要用于设置抽取豹子号的5,10,15,20,25,30的权重)
|
||||
|
||||

|
||||
|
||||
比如上图中如果不设置,色子点数5抽到的概率和其他点数的概率是一样的,可能抽7次T1奖励,就有1次中豹子号5的可能
|
||||
|
||||
由于抽到色子点数和为10,15,20,25的色子点数组合有多种,所以在抽该这四个点数时还需要单独配置相应的中大奖概率(其中豹子号5和30只有一种组合【1,1,1,1,1】和【6,6,6,6,6】,所以不需要配置),其中权重拉到最大10000,那么中奖概率为100%(只要摇到了相应的色子点数和则中奖概率为100%)
|
||||
|
||||

|
||||
|
||||
### 扩展
|
||||
|
||||
- 测试设置的中奖概率,根据如下设置可以测试当前设置权重的中奖概率,该测试数据不记录到真实数据系统中
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
能够准确的反馈抽中点数的统计
|
||||
|
||||

|
||||
|
||||
如果当前中奖概率符合预期,或则测试多组数据选取一组符合预期的导入到当前的配置中
|
||||
|
||||

|
||||
|
||||
这里可以详情查询到指定测试记录的详情,
|
||||
|
||||

|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | saiadmin [ saiadmin快速开发框架 ]
|
||||
// +----------------------------------------------------------------------
|
||||
namespace plugin\saiadmin\app\controller\system;
|
||||
|
||||
use plugin\saiadmin\app\logic\system\SystemAdminGuideLogic;
|
||||
use plugin\saiadmin\basic\BaseController;
|
||||
use plugin\saiadmin\service\Permission;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 后台操作指南控制器
|
||||
*/
|
||||
class SystemAdminGuideController extends BaseController
|
||||
{
|
||||
private SystemAdminGuideLogic $guideLogic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->guideLogic = new SystemAdminGuideLogic();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取后台操作指南 Markdown 内容
|
||||
*/
|
||||
#[Permission('后台操作指南读取', 'system:admin_guide:index:read')]
|
||||
public function read(Request $request): Response
|
||||
{
|
||||
$data = $this->guideLogic->read();
|
||||
return $this->success($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存后台操作指南 Markdown 内容
|
||||
*/
|
||||
#[Permission('后台操作指南保存', 'system:admin_guide:index:save')]
|
||||
public function save(Request $request): Response
|
||||
{
|
||||
$content = $request->post('content', '');
|
||||
if (! is_string($content)) {
|
||||
return $this->fail('invalid content');
|
||||
}
|
||||
$data = $this->guideLogic->save($content);
|
||||
return $this->success($data, 'save success');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | saiadmin [ saiadmin快速开发框架 ]
|
||||
// +----------------------------------------------------------------------
|
||||
namespace plugin\saiadmin\app\logic\system;
|
||||
|
||||
use plugin\saiadmin\exception\ApiException;
|
||||
|
||||
/**
|
||||
* 后台操作指南逻辑(读写 server/docs/ADMIN_GUIDE.md)
|
||||
*/
|
||||
class SystemAdminGuideLogic
|
||||
{
|
||||
private const GUIDE_FILENAME = 'ADMIN_GUIDE.md';
|
||||
|
||||
/**
|
||||
* 获取指南 Markdown 文件绝对路径
|
||||
*/
|
||||
public function getFilePath(): string
|
||||
{
|
||||
return base_path() . DIRECTORY_SEPARATOR . 'docs' . DIRECTORY_SEPARATOR . self::GUIDE_FILENAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指南内容
|
||||
* @return array{content: string, file_path: string, update_time: string|null}
|
||||
*/
|
||||
public function read(): array
|
||||
{
|
||||
$filePath = $this->getFilePath();
|
||||
if (! is_file($filePath)) {
|
||||
throw new ApiException('admin guide file not found');
|
||||
}
|
||||
$content = file_get_contents($filePath);
|
||||
if ($content === false) {
|
||||
throw new ApiException('failed to read admin guide file');
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $content,
|
||||
'file_path' => 'docs/' . self::GUIDE_FILENAME,
|
||||
'update_time' => date('Y-m-d H:i:s', filemtime($filePath)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存指南内容到 Markdown 文件
|
||||
* @param string $content
|
||||
* @return array{content: string, file_path: string, update_time: string}
|
||||
*/
|
||||
public function save(string $content): array
|
||||
{
|
||||
$filePath = $this->getFilePath();
|
||||
$dir = dirname($filePath);
|
||||
if (! is_dir($dir) && ! mkdir($dir, 0755, true) && ! is_dir($dir)) {
|
||||
throw new ApiException('failed to create docs directory');
|
||||
}
|
||||
|
||||
$result = file_put_contents($filePath, $content, LOCK_EX);
|
||||
if ($result === false) {
|
||||
throw new ApiException('failed to save admin guide file');
|
||||
}
|
||||
|
||||
clearstatcache(true, $filePath);
|
||||
|
||||
return [
|
||||
'content' => $content,
|
||||
'file_path' => 'docs/' . self::GUIDE_FILENAME,
|
||||
'update_time' => date('Y-m-d H:i:s', filemtime($filePath)),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,10 @@ Route::group('/core', function () {
|
||||
Route::delete("/logs/deleteOperLog", [\plugin\saiadmin\app\controller\system\SystemLogController::class, 'deleteOperLog']);
|
||||
fastRoute("email", \plugin\saiadmin\app\controller\system\SystemMailController::class);
|
||||
|
||||
// 后台操作指南
|
||||
Route::get("/adminGuide/read", [\plugin\saiadmin\app\controller\system\SystemAdminGuideController::class, 'read']);
|
||||
Route::post("/adminGuide/save", [\plugin\saiadmin\app\controller\system\SystemAdminGuideController::class, 'save']);
|
||||
|
||||
// 服务管理
|
||||
Route::get("/server/monitor", [\plugin\saiadmin\app\controller\system\SystemServerController::class, 'monitor']);
|
||||
Route::get("/server/cache", [\plugin\saiadmin\app\controller\system\SystemServerController::class, 'cache']);
|
||||
|
||||
BIN
server/public/docs/picture/guide_01.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
server/public/docs/picture/guide_02.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
server/public/docs/picture/guide_03.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
server/public/docs/picture/guide_04.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
server/public/docs/picture/guide_05.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
server/public/docs/picture/guide_06.png
Normal file
|
After Width: | Height: | Size: 671 KiB |
BIN
server/public/docs/picture/guide_07.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
server/public/docs/picture/guide_08.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
server/public/docs/picture/guide_09.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
server/public/docs/picture/guide_10.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
server/public/docs/picture/guide_11.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
server/public/docs/picture/guide_12.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
server/public/docs/picture/guide_13.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
server/public/docs/picture/guide_14.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
server/public/docs/picture/guide_15.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
server/public/docs/picture/guide_16.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
server/public/docs/picture/guide_17.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
server/public/docs/picture/guide_18.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
server/public/docs/picture/guide_19.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
server/public/docs/picture/guide_20.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
server/public/docs/picture/guide_21.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
server/public/docs/picture/guide_22.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
server/public/docs/picture/guide_23.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
server/public/docs/picture/guide_24.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
server/public/docs/picture/guide_25.png
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
server/public/docs/picture/guide_26.png
Normal file
|
After Width: | Height: | Size: 113 KiB |