1.新增菜单后台操作指南,方便管理员查看使用

This commit is contained in:
2026-05-30 17:48:55 +08:00
parent a4c8f623be
commit 90abab14a3
37 changed files with 678 additions and 1 deletions

View 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
})
}
}

View 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"
}
}

View File

@@ -0,0 +1,20 @@
{
"title": "后台操作指南",
"toolbar": {
"edit": "编辑",
"save": "保存",
"cancel": "取消",
"refresh": "刷新"
},
"meta": {
"filePath": "文档路径",
"updateTime": "更新时间"
},
"message": {
"loadFailed": "加载操作指南失败",
"saveSuccess": "保存成功",
"saveFailed": "保存失败",
"cancelConfirm": "当前有未保存的修改,确定取消编辑吗?",
"editRequired": "请先点击编辑后再保存"
}
}

View File

@@ -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 表示不按渠道过滤 */

View 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 `![${alt}](${staticBase}${path})`
})
}
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>

View 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";

View File

@@ -0,0 +1,39 @@
-- 后台操作指南顶级菜单与权限
-- 说明挂载到顶级菜单parent_id=0内容来源 server/docs/ADMIN_GUIDE.md
SET @now = NOW();
-- 1) 创建后台操作指南顶级菜单type=2parent_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
View File

@@ -0,0 +1,128 @@
# 大富翁-使用说明指南
## 菜单简单介绍
## 工作台/统计页面:统计数据
![image.png](/docs/picture/guide_01.png)
## 角色管理:对角色的菜单权限设置
按等级设定,等级越低权限越少(不要出现上级角色没有的权限,子角色有)
避免方式:使用子角色创建下级角色,可以避免下级角色比上级角色操作权限更多的问题
![image.png](/docs/picture/guide_02.png)
这里设置角色的菜单以及按钮权限
![image.png](/docs/picture/guide_03.png)
## 彩金池配置:监听彩金池实时变化
可以实时监听彩金池累积金额的变化
![image.png](/docs/picture/guide_04.png)
## 游戏配置:游戏规则和平台币转化比
游戏配置
![image.png](/docs/picture/guide_05.png)
其中游戏玩法为进入游戏的弹窗,和规则介绍(无特殊需求不需要大改)
![image.png](/docs/picture/guide_06.png)
游戏平台币兑换币为进入平台时平台比转化比比如当前设置的为11如果从jk8平台转入100那么获取的游戏币为100如果设置12则获取的平台币为200
## 底注配置:方便玩家快速调整压注倍率
底注配置
![image.png](/docs/picture/guide_07.png)
对应游戏中的其中每次游玩对局基础消耗为1游戏币无法修改底注的设置只是方便玩家快速修改压注金额
![image.png](/docs/picture/guide_08.png)
# 抽奖逻辑
## 判断抽奖档位
当前的抽奖逻辑时按照抽奖档位T1-T5进行抽奖在【玩家管理】菜单中的设置玩家具体的档位权重
![image.png](/docs/picture/guide_09.png)
也可以在【彩金池配置】菜单中设置玩家正常的抽奖档位权重
- 其中正常的档位权重为注册玩家默认绑定的档位权重,并且只有在修改完后,创建的玩家才能绑定最新的正常档位权重
![image.png](/docs/picture/guide_10.png)
- 其中free为杀分权重为如果当前彩金池平台盈利超过设置的安全线则制动走杀分的权重
![image.png](/docs/picture/guide_11.png)
- 剩余的两个权重可以方便快速切换用户的档位抽奖权重
![image.png](/docs/picture/guide_12.png)
![image.png](/docs/picture/guide_13.png)
## 根据档位抽取中奖号码
### 设置中奖号码地图
在后台设置地图缩影
![image.png](/docs/picture/guide_14.png)
地图的索引参看如下
![image.png](/docs/picture/guide_15.png)
其中地图的索引可以按照需求点击图中的按规则生成
并且规则尽可能符合:结算金额 < 0 → T40 < 结算金额 < 100 → T3100 < 结算金额 < 200 → T2200 < 结算金额 → T1T5「再来一次」结算金额=0
![image.png](/docs/picture/guide_16.png)
![image.png](/docs/picture/guide_17.png)
### 创建完地图索引后创建相应的奖励对照表
创建奖励对照表的原因是由于有每个号码的权重不一样豹子号10152025有多重组合方式所以需要设置奖励对照表中的权重配比
![image.png](/docs/picture/guide_18.png)
![image.png](/docs/picture/guide_19.png)
根据抽到的奖励档位抽取号码主要用于设置抽取豹子号的51015202530的权重
![image.png](/docs/picture/guide_20.png)
比如上图中如果不设置色子点数5抽到的概率和其他点数的概率是一样的可能抽7次T1奖励就有1次中豹子号5的可能
由于抽到色子点数和为10152025的色子点数组合有多种所以在抽该这四个点数时还需要单独配置相应的中大奖概率其中豹子号5和30只有一种组合【11111】和【66666】所以不需要配置其中权重拉到最大10000那么中奖概率为100%只要摇到了相应的色子点数和则中奖概率为100%
![image.png](/docs/picture/guide_21.png)
### 扩展
- 测试设置的中奖概率,根据如下设置可以测试当前设置权重的中奖概率,该测试数据不记录到真实数据系统中
![image.png](/docs/picture/guide_22.png)
![image.png](/docs/picture/guide_23.png)
能够准确的反馈抽中点数的统计
![image.png](/docs/picture/guide_24.png)
如果当前中奖概率符合预期,或则测试多组数据选取一组符合预期的导入到当前的配置中
![image.png](/docs/picture/guide_25.png)
这里可以详情查询到指定测试记录的详情,
![image.png](/docs/picture/guide_26.png)

View File

@@ -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');
}
}

View File

@@ -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)),
];
}
}

View File

@@ -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']);

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB