权限管理与测试
This commit is contained in:
@@ -0,0 +1,910 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>数据权限规则管理</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
|
||||
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f5f7fb;
|
||||
--card: rgba(255, 255, 255, .92);
|
||||
--line: #e5e7eb;
|
||||
--text: #111827;
|
||||
--muted: #6b7280;
|
||||
--primary: #3b82f6;
|
||||
--primary-weak: #eff6ff;
|
||||
--success: #16a34a;
|
||||
--danger: #dc2626;
|
||||
--warning: #d97706;
|
||||
--radius: 18px;
|
||||
}
|
||||
|
||||
body.pear-container {
|
||||
background: radial-gradient(circle at top left, #eef4ff 0%, #f7f9fc 42%, #f5f7fb 100%);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.page-wrap {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: linear-gradient(135deg, rgba(59,130,246,.12), rgba(99,102,241,.08));
|
||||
border: 1px solid rgba(59,130,246,.15);
|
||||
border-radius: 20px;
|
||||
padding: 18px 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 8px 30px rgba(15, 23, 42, .05);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.meta-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,.8);
|
||||
border: 1px solid rgba(148,163,184,.25);
|
||||
font-size: 13px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.layui-card {
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid rgba(229,231,235,.9);
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, .04);
|
||||
}
|
||||
|
||||
.layui-card-header {
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
border-bottom: 1px solid rgba(229,231,235,.85);
|
||||
background: rgba(255,255,255,.8);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 10px;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.field-explain {
|
||||
background: linear-gradient(180deg, rgba(239,246,255,.9), rgba(255,255,255,.95));
|
||||
border: 1px solid rgba(191,219,254,.8);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.field-explain-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.field-explain-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.field-key {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--primary-weak);
|
||||
color: #1d4ed8;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
min-width: 76px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.field-text {
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hint-box {
|
||||
margin-top: 12px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, #f8fbff, #ffffff);
|
||||
border: 1px dashed #c7d2fe;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.hint-code {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
background: #eef2ff;
|
||||
color: #4338ca;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.map-flow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
padding: 14px;
|
||||
border-radius: 14px;
|
||||
background: #f0fdf4;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
.flow-step {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #dcfce7;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
color: #14532d;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
color: #16a34a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.toolbar-wrap {
|
||||
padding: 14px 14px 0;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 220px 160px auto auto auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.search-row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-soft {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
.layui-table-view {
|
||||
border: none !important;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.layui-table-tool {
|
||||
border-top: 1px solid rgba(229,231,235,.8);
|
||||
border-bottom: 1px solid rgba(229,231,235,.8);
|
||||
background: linear-gradient(180deg, #ffffff, #fafbff);
|
||||
}
|
||||
|
||||
.layui-table-header {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.layui-table thead tr th {
|
||||
color: #334155;
|
||||
font-weight: 700;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.layui-table tbody tr:hover {
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.table-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 9px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag-blue { background: #eff6ff; color: #1d4ed8; }
|
||||
.tag-green { background: #ecfdf5; color: #047857; }
|
||||
.tag-orange { background: #fff7ed; color: #c2410c; }
|
||||
.tag-gray { background: #f3f4f6; color: #4b5563; }
|
||||
.tag-red { background: #fef2f2; color: #b91c1c; }
|
||||
|
||||
.ellipsis {
|
||||
display: inline-block;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sql-preview-wrap {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sql-preview-head {
|
||||
display: grid;
|
||||
grid-template-columns: 1.3fr auto auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sql-box-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.sql-box-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.sql-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sql-box-hd {
|
||||
padding: 10px 12px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.sql-box-bd {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
background: #0f172a;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.sql-pre {
|
||||
margin: 0;
|
||||
font-family: Consolas, Menlo, Monaco, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.preview-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-meta .meta-pill {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.small-help {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.rule-note {
|
||||
margin-top: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: #fffbeb;
|
||||
border: 1px solid #fde68a;
|
||||
color: #92400e;
|
||||
line-height: 1.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="pear-container">
|
||||
<div class="page-wrap">
|
||||
|
||||
<div class="hero">
|
||||
<h1 class="hero-title">数据权限规则管理</h1>
|
||||
<p class="hero-sub">
|
||||
这里负责定义“谁能看哪张表、哪些数据”。<br>
|
||||
一条规则可以理解成:当前登录人的某个属性,去匹配目标表的某个字段,从而自动限制可见范围。
|
||||
</p>
|
||||
<div class="hero-meta">
|
||||
<span class="meta-pill">规则作用:自动加 WHERE 条件</span>
|
||||
<span class="meta-pill">支持:in / not in / = / like / null 判断</span>
|
||||
<span class="meta-pill">支持:跨表映射 / join 预览</span>
|
||||
<span class="meta-pill">支持:SQL 预览</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">规则列表</div>
|
||||
<div class="toolbar-wrap">
|
||||
<form class="layui-form" lay-filter="search-form">
|
||||
<div class="search-row">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="text" name="table" placeholder="按表名搜索,如 opm_mw_info_data" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-input-wrap">
|
||||
<input type="text" name="admin_attr" placeholder="按Admin属性搜索,如 hospitals" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-input-wrap">
|
||||
<select name="status">
|
||||
<option value="">全部状态</option>
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="search-actions">
|
||||
<button type="button" class="pear-btn pear-btn-primary btn-soft" lay-submit lay-filter="search-btn">
|
||||
<i class="layui-icon layui-icon-search"></i> 搜索
|
||||
</button>
|
||||
<button type="reset" class="pear-btn btn-soft">
|
||||
<i class="layui-icon layui-icon-refresh-1"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="pear-btn pear-btn-warning btn-soft" id="btn-preview-global">
|
||||
<i class="layui-icon layui-icon-code-circle"></i> SQL预览
|
||||
</button>
|
||||
<button type="button" class="pear-btn pear-btn-primary btn-soft" id="btn-add">
|
||||
<i class="layui-icon layui-icon-add-1"></i> 新增规则
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="layui-card-body" style="padding-top: 0;">
|
||||
<table id="data-table" lay-filter="data-table"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">通俗解释</div>
|
||||
<div class="layui-card-body">
|
||||
<div class="field-explain">
|
||||
<div class="field-explain-item">
|
||||
<span class="field-key">table</span>
|
||||
<span class="field-text">目标表。你要给哪张表加限制,比如 <strong>opm_mw_info_data</strong>。</span>
|
||||
</div>
|
||||
<div class="field-explain-item">
|
||||
<span class="field-key">field</span>
|
||||
<span class="field-text">目标字段。就是拿哪一列来做过滤,比如 <strong>organ_id</strong>。</span>
|
||||
</div>
|
||||
<div class="field-explain-item">
|
||||
<span class="field-key">admin_attr</span>
|
||||
<span class="field-text">Admin属性。当前登录人身上保存权限值的字段,比如 <strong>hospitals</strong>、<strong>departments</strong>。</span>
|
||||
</div>
|
||||
<div class="field-explain-item">
|
||||
<span class="field-key">admin_attr_map</span>
|
||||
<span class="field-text">映射规则。适合“用户拿到的是 ID,但最终要拿另一个字段去匹配”的场景。</span>
|
||||
</div>
|
||||
<div class="field-explain-item">
|
||||
<span class="field-key">action</span>
|
||||
<span class="field-text">比较方式。比如 <strong>in</strong>、<strong>=</strong>、<strong>like</strong>、<strong>is null</strong>。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hint-box">
|
||||
<div class="section-title">一句话理解 admin_attr_map</div>
|
||||
<div>
|
||||
它就是一条“翻译路线”:
|
||||
<span class="hint-code">先从Admin属性拿值</span>
|
||||
→ <span class="hint-code">去某张表找对应记录</span>
|
||||
→ <span class="hint-code">取出你真正想匹配的字段</span>
|
||||
→ <span class="hint-code">再去限制目标表</span>
|
||||
</div>
|
||||
|
||||
<div class="map-flow">
|
||||
<span class="flow-step">1. 取 admin_attr</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">2. 查映射表 / join 关联</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">3. 取目标字段</span>
|
||||
<span class="flow-arrow">→</span>
|
||||
<span class="flow-step">4. 自动拼 WHERE 条件</span>
|
||||
</div>
|
||||
|
||||
<div class="rule-note">
|
||||
格式:<strong>源表.源字段...目标表.目标字段</strong><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">配置示例</div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-table" lay-skin="line">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">场景</th>
|
||||
<th>table</th>
|
||||
<th>field</th>
|
||||
<th>admin_attr</th>
|
||||
<th>admin_attr_map</th>
|
||||
<th>action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>只看自己医院的数据</td>
|
||||
<td><span class="table-tag tag-blue">opm_mw_info_data</span></td>
|
||||
<td><span class="table-tag tag-green">organ_id</span></td>
|
||||
<td><span class="table-tag tag-orange">hospitals</span></td>
|
||||
<td><span class="table-tag tag-gray">-</span></td>
|
||||
<td><span class="table-tag tag-blue">in</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>科室 ID 转科室名称过滤</td>
|
||||
<td><span class="table-tag tag-blue">opm_mw_info_data</span></td>
|
||||
<td><span class="table-tag tag-green">dept_name</span></td>
|
||||
<td><span class="table-tag tag-orange">departments</span></td>
|
||||
<td><span class="table-tag tag-gray">opm_mw_department.id...opm_mw_department.name</span></td>
|
||||
<td><span class="table-tag tag-blue">in</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>跨表 join 取医院名称</td>
|
||||
<td><span class="table-tag tag-blue">opm_mw_info_data</span></td>
|
||||
<td><span class="table-tag tag-green">hospital_name</span></td>
|
||||
<td><span class="table-tag tag-orange">hospitals</span></td>
|
||||
<td><span class="table-tag tag-gray">opm_mw_department.id:organ_id...opm_mw_hospital.name:id</span></td>
|
||||
<td><span class="table-tag tag-blue">in</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>医院表自身权限</td>
|
||||
<td><span class="table-tag tag-blue">opm_mw_hospital</span></td>
|
||||
<td><span class="table-tag tag-green">id</span></td>
|
||||
<td><span class="table-tag tag-orange">hospitals</span></td>
|
||||
<td><span class="table-tag tag-gray">-</span></td>
|
||||
<td><span class="table-tag tag-blue">in</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/html" id="table-toolbar">
|
||||
<button class="pear-btn pear-btn-warning pear-btn-md" lay-event="sqlPreview">
|
||||
<i class="layui-icon layui-icon-code-circle"></i> SQL预览
|
||||
</button>
|
||||
<button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove">
|
||||
<i class="layui-icon layui-icon-delete"></i> 批量删除
|
||||
</button>
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="table-bar">
|
||||
<button class="pear-btn pear-btn-xs pear-btn-warning" lay-event="preview">预览</button>
|
||||
<button class="pear-btn pear-btn-xs" lay-event="edit">编辑</button>
|
||||
<button class="pear-btn pear-btn-xs pear-btn-danger" lay-event="remove">删除</button>
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="sql-preview-tpl">
|
||||
<div class="sql-preview-wrap">
|
||||
<div class="sql-preview-head">
|
||||
<div class="layui-input-wrap">
|
||||
<select name="preview_table" lay-search>
|
||||
<option value="">请选择要预览的表</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="pear-btn pear-btn-primary" id="btn-run-preview">
|
||||
<i class="layui-icon layui-icon-play"></i> 运行预览
|
||||
</button>
|
||||
<button type="button" class="pear-btn" id="btn-copy-preview">
|
||||
<i class="layui-icon layui-icon-template-1"></i> 复制 SQL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="preview-meta" id="preview-meta"></div>
|
||||
|
||||
<div class="sql-box-grid">
|
||||
<div class="sql-box">
|
||||
<div class="sql-box-hd">原始 SQL</div>
|
||||
<div class="sql-box-bd"><pre class="sql-pre" id="preview-original">请选择表后点击“运行预览”。</pre></div>
|
||||
</div>
|
||||
<div class="sql-box">
|
||||
<div class="sql-box-hd">应用权限后的 SQL</div>
|
||||
<div class="sql-box-bd"><pre class="sql-pre" id="preview-permission">请选择表后点击“运行预览”。</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="small-help">
|
||||
说明:这里会用当前登录人的权限信息模拟一次真实查询,方便你检查规则是否写对。
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/app/admin/component/layui/layui.js?v=2.8.12"></script>
|
||||
<script src="/app/admin/component/pear/pear.js"></script>
|
||||
<script src="/app/admin/admin/js/permission.js"></script>
|
||||
<script src="/app/admin/admin/js/common.js"></script>
|
||||
|
||||
<script>
|
||||
layui.use(["table", "form", "common", "popup", "util", "jquery", "layer"], function() {
|
||||
let table = layui.table;
|
||||
let form = layui.form;
|
||||
let $ = layui.$;
|
||||
let common = layui.common;
|
||||
let layer = layui.layer;
|
||||
|
||||
const PRIMARY_KEY = "id";
|
||||
const SELECT_API = "/app/admin/opm-mw-permission-rule/select";
|
||||
const UPDATE_API = "/app/admin/opm-mw-permission-rule/update";
|
||||
const DELETE_API = "/app/admin/opm-mw-permission-rule/delete";
|
||||
const INSERT_URL = "/app/admin/opm-mw-permission-rule/insert";
|
||||
const UPDATE_URL = "/app/admin/opm-mw-permission-rule/update";
|
||||
const PREVIEW_API = "/app/admin/opm-mw-permission-rule/preview-sql";
|
||||
const TABLES_API = "/app/admin/opm-mw-permission-rule/get-tables";
|
||||
|
||||
let tableIns = null;
|
||||
let previewSqlCache = "";
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function tag(text, cls) {
|
||||
return '<span class="table-tag ' + cls + '">' + escapeHtml(text) + '</span>';
|
||||
}
|
||||
|
||||
function renderStatus(status) {
|
||||
return status == 1 ? tag("启用", "tag-green") : tag("禁用", "tag-red");
|
||||
}
|
||||
|
||||
function renderAction(action) {
|
||||
const map = {
|
||||
"in": "tag-blue",
|
||||
"not in": "tag-orange",
|
||||
"=": "tag-green",
|
||||
"like": "tag-orange",
|
||||
"is null": "tag-gray",
|
||||
"is not null": "tag-gray"
|
||||
};
|
||||
return tag(action || "-", map[String(action || "").toLowerCase()] || "tag-gray");
|
||||
}
|
||||
|
||||
function loadPreviewTables(selectedTable) {
|
||||
$.ajax({
|
||||
url: TABLES_API,
|
||||
dataType: "json",
|
||||
type: "get",
|
||||
success: function(res) {
|
||||
if (res.code !== 0) return;
|
||||
|
||||
const tables = res.data || [];
|
||||
const select = $('select[name="preview_table"]');
|
||||
select.empty();
|
||||
select.append('<option value="">请选择要预览的表</option>');
|
||||
|
||||
tables.forEach(function(t) {
|
||||
let selected = (t === selectedTable) ? 'selected' : '';
|
||||
select.append('<option value="' + escapeHtml(t) + '" ' + selected + '>' + escapeHtml(t) + '</option>');
|
||||
});
|
||||
|
||||
form.render("select");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openSqlPreview(defaultTable) {
|
||||
const content = $("#sql-preview-tpl").html();
|
||||
previewSqlCache = "";
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: "SQL 预览",
|
||||
shade: 0.18,
|
||||
maxmin: true,
|
||||
area: [common.isModile() ? "100%" : "1100px", common.isModile() ? "100%" : "82%"],
|
||||
content: content,
|
||||
success: function(layero) {
|
||||
layero.addClass("card-soft");
|
||||
loadPreviewTables(defaultTable || "");
|
||||
|
||||
const runPreview = function() {
|
||||
const tableName = $(layero).find('select[name="preview_table"]').val();
|
||||
if (!tableName) {
|
||||
layui.popup.warning("请选择要预览的表");
|
||||
return;
|
||||
}
|
||||
|
||||
const loading = layer.load(1);
|
||||
$.ajax({
|
||||
url: PREVIEW_API,
|
||||
dataType: "json",
|
||||
type: "get",
|
||||
data: { table: tableName },
|
||||
success: function(res) {
|
||||
layer.close(loading);
|
||||
if (res.code !== 0) {
|
||||
layui.popup.failure(res.msg || "预览失败");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = res.data || {};
|
||||
const original = data.original || "";
|
||||
const permission = data.permission || "";
|
||||
previewSqlCache = original + "\n\n/* ===== 权限 SQL ===== */\n\n" + permission;
|
||||
|
||||
$(layero).find("#preview-original").text(original || "未返回原始 SQL");
|
||||
$(layero).find("#preview-permission").text(permission || "未返回权限 SQL");
|
||||
|
||||
const metaHtml = [
|
||||
tag("表:" + (data.table || tableName), "tag-blue"),
|
||||
tag("原始 SQL:已生成", "tag-gray"),
|
||||
tag("权限 SQL:已生成", "tag-green")
|
||||
].join(" ");
|
||||
$(layero).find("#preview-meta").html(metaHtml);
|
||||
|
||||
const adminAttr = data.admin_attr || {};
|
||||
if (adminAttr && Object.keys(adminAttr).length) {
|
||||
let attrText = Object.keys(adminAttr).map(function(k) {
|
||||
return '<span class="meta-pill"><strong>' + escapeHtml(k) + '</strong>:' + escapeHtml(adminAttr[k]) + '</span>';
|
||||
}).join("");
|
||||
$(layero).find("#preview-meta").append(attrText);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
layer.close(loading);
|
||||
layui.popup.failure("预览请求失败");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$(layero).on("click", "#btn-run-preview", runPreview);
|
||||
$(layero).on("click", "#btn-copy-preview", function() {
|
||||
if (!previewSqlCache) {
|
||||
layui.popup.warning("请先运行预览");
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(previewSqlCache).then(function() {
|
||||
layui.popup.success("已复制 SQL");
|
||||
}).catch(function() {
|
||||
layui.popup.warning("复制失败,请手动复制");
|
||||
});
|
||||
} else {
|
||||
layui.popup.warning("当前浏览器不支持自动复制");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let cols = [
|
||||
{ type: "checkbox", align: "center", fixed: "left" },
|
||||
{ title: "ID", field: "id", width: 76, align: "center", fixed: "left" },
|
||||
{
|
||||
title: "表名",
|
||||
field: "table",
|
||||
minWidth: 180,
|
||||
templet: d => tag(d.table || "-", "tag-blue")
|
||||
},
|
||||
{
|
||||
title: "字段名",
|
||||
field: "field",
|
||||
minWidth: 140,
|
||||
templet: d => tag(d.field || "-", "tag-green")
|
||||
},
|
||||
{
|
||||
title: "Admin属性",
|
||||
field: "admin_attr",
|
||||
minWidth: 140,
|
||||
templet: d => tag(d.admin_attr || "-", "tag-orange")
|
||||
},
|
||||
{
|
||||
title: "属性映射",
|
||||
field: "admin_attr_map",
|
||||
minWidth: 260,
|
||||
templet: function(d) {
|
||||
if (!d.admin_attr_map) return '<span style="color:#9ca3af">-</span>';
|
||||
return '<span class="ellipsis" title="' + escapeHtml(d.admin_attr_map) + '">' + escapeHtml(d.admin_attr_map) + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "运算符",
|
||||
field: "action",
|
||||
width: 110,
|
||||
align: "center",
|
||||
templet: d => renderAction(d.action)
|
||||
},
|
||||
{ title: "排序", field: "sort", width: 80, align: "center" },
|
||||
{
|
||||
title: "状态",
|
||||
field: "status",
|
||||
width: 90,
|
||||
align: "center",
|
||||
templet: d => renderStatus(d.status)
|
||||
},
|
||||
{
|
||||
title: "备注",
|
||||
field: "remark",
|
||||
minWidth: 180,
|
||||
templet: function(d) {
|
||||
return d.remark ? '<span class="ellipsis" title="' + escapeHtml(d.remark) + '">' + escapeHtml(d.remark) + '</span>' : '<span style="color:#9ca3af">-</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
toolbar: "#table-bar",
|
||||
align: "center",
|
||||
fixed: "right",
|
||||
width: 240
|
||||
}
|
||||
];
|
||||
|
||||
function reloadTable() {
|
||||
const formData = form.val("search-form") || {};
|
||||
tableIns.reload({
|
||||
where: {
|
||||
table: formData.table || "",
|
||||
admin_attr: formData.admin_attr || "",
|
||||
status: formData.status || ""
|
||||
},
|
||||
page: { curr: 1 },
|
||||
scrollPos: "fixed"
|
||||
});
|
||||
}
|
||||
|
||||
form.render();
|
||||
|
||||
tableIns = table.render({
|
||||
elem: "#data-table",
|
||||
url: SELECT_API,
|
||||
page: true,
|
||||
cols: [cols],
|
||||
skin: "line",
|
||||
size: "lg",
|
||||
toolbar: "#table-toolbar",
|
||||
defaultToolbar: ["refresh", "filter", "exports"],
|
||||
height: "full",
|
||||
text: {
|
||||
none: "暂无规则,点击右上角“新增规则”开始配置"
|
||||
}
|
||||
});
|
||||
|
||||
table.on("tool(data-table)", function(obj) {
|
||||
if (obj.event === "remove") remove(obj);
|
||||
else if (obj.event === "edit") edit(obj);
|
||||
else if (obj.event === "preview") openSqlPreview(obj.data.table);
|
||||
});
|
||||
|
||||
table.on("toolbar(data-table)", function(obj) {
|
||||
if (obj.event === "batchRemove") batchRemove(obj);
|
||||
else if (obj.event === "sqlPreview") openSqlPreview("");
|
||||
});
|
||||
|
||||
form.on("submit(search-btn)", function() {
|
||||
reloadTable();
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#btn-add").on("click", function() {
|
||||
add();
|
||||
});
|
||||
|
||||
$("#btn-preview-global").on("click", function() {
|
||||
openSqlPreview("");
|
||||
});
|
||||
|
||||
let add = function() {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: "新增规则",
|
||||
shade: 0.12,
|
||||
maxmin: true,
|
||||
area: [common.isModile() ? "100%" : "820px", common.isModile() ? "100%" : "640px"],
|
||||
content: INSERT_URL
|
||||
});
|
||||
};
|
||||
|
||||
let edit = function(obj) {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: "修改规则",
|
||||
shade: 0.12,
|
||||
maxmin: true,
|
||||
area: [common.isModile() ? "100%" : "820px", common.isModile() ? "100%" : "640px"],
|
||||
content: UPDATE_URL + "?" + PRIMARY_KEY + "=" + obj.data[PRIMARY_KEY]
|
||||
});
|
||||
};
|
||||
|
||||
let remove = function(obj) {
|
||||
doRemove(obj.data[PRIMARY_KEY]);
|
||||
};
|
||||
|
||||
let batchRemove = function(obj) {
|
||||
let checkIds = common.checkField(obj, PRIMARY_KEY);
|
||||
if (!checkIds) {
|
||||
layui.popup.warning("未选中数据");
|
||||
return;
|
||||
}
|
||||
doRemove(checkIds.split(","));
|
||||
};
|
||||
|
||||
let doRemove = function(ids) {
|
||||
let data = {};
|
||||
data[PRIMARY_KEY] = ids;
|
||||
|
||||
layer.confirm("确定删除选中的规则吗?", { icon: 3, title: "提示" }, function(index) {
|
||||
layer.close(index);
|
||||
let loading = layer.load(1);
|
||||
$.ajax({
|
||||
url: DELETE_API,
|
||||
data: data,
|
||||
dataType: "json",
|
||||
type: "post",
|
||||
success: function(res) {
|
||||
layer.close(loading);
|
||||
if (res.code) return layui.popup.failure(res.msg);
|
||||
layui.popup.success("操作成功", reloadTable);
|
||||
},
|
||||
error: function() {
|
||||
layer.close(loading);
|
||||
layui.popup.failure("删除失败");
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.refreshTable = function() {
|
||||
reloadTable();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user