910 lines
32 KiB
HTML
910 lines
32 KiB
HTML
<!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> |