/** * 数据表管理:列表 / 可视化建表 / 查看结构 / 优化 / 删表 * * 与 database.js 同构:URL 只写动作名(当前页面目录即 /admin/datatable/), * 权限按钮用完整权限名,渲染后由 auth.filterDom() 移除无权限按钮。 */ layui.define(['table', 'layer', 'form', 'element', 'auth', 'http', 'jquery'], function (exports) { var table = layui.table, layer = layui.layer, form = layui.form, element = layui.element, auth = layui.auth, http = layui.http, $ = layui.jquery; var FUNC = { index: 'index', structure: 'structure', preview: 'preview', create: 'create', drop: 'drop', optimize: 'optimize', export: 'export' }; // 与后端 Datatable::TYPES 保持一致 var TYPES = [ 'tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'decimal', 'float', 'double', 'char', 'varchar', 'text', 'mediumtext', 'longtext', 'date', 'datetime', 'timestamp', 'time', 'year', 'json', 'blob', 'longblob' ]; function parse(res) { return { code: res.code, msg: res.message, count: res.count || 0, data: res.data || [] }; } function post(url, data, okTip, done) { var loading = layer.load(2); http.post(url, data).then(function (res) { layer.close(loading); if (res && res.code === 0) { layer.msg(okTip || res.message || '操作成功', {icon: 1}); if (done) { done(res.data); } } else { layer.msg((res && res.message) || '操作失败', {icon: 2}); } }).catch(function (err) { layer.close(loading); layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2}); }); } function checkedTables() { var rows = table.checkStatus('tableList').data || []; var names = []; for (var i = 0; i < rows.length; i++) { names.push(rows[i].name); } return names; } /* ====================== 建表弹窗 ====================== */ /** * 单行字段 */ function fieldRow(data) { data = data || {}; var opts = ''; for (var i = 0; i < TYPES.length; i++) { opts += ''; } return '' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + ' ' + '
'; } function addRow(data) { $('#fieldRows').append(fieldRow(data)); form.render(null, 'create-form'); } /** * 收集表单里的字段定义 */ function collect() { var base = form.val('create-form'); var fields = []; $('#fieldRows .field-row').each(function () { var r = $(this); fields.push({ name: $.trim(r.find('input[name=name]').val() || ''), type: r.find('select[name=type]').val(), length: $.trim(r.find('input[name=length]').val() || ''), default: $.trim(r.find('input[name=default]').val() || ''), comment: $.trim(r.find('input[name=comment]').val() || ''), unsigned: r.find('input[name=unsigned]').is(':checked') ? 1 : 0, nullable: r.find('input[name=nullable]').is(':checked') ? 1 : 0, autoinc: r.find('input[name=autoinc]').is(':checked') ? 1 : 0, pk: r.find('input[name=pk]').is(':checked') ? 1 : 0, index: r.find('select[name=index]').val() }); }); return { table: $.trim(base.table || ''), comment: $.trim(base.comment || ''), engine: base.engine, charset: base.charset, collation: '', fields: fields }; } function openCreate() { var idx = layer.open({ type: 1, title: '新建数据表', area: ['1080px', '640px'], content: $('#createTpl').html(), success: function () { form.render(null, 'create-form'); $('#fieldRows').html(''); addRow({name: 'id', type: 'bigint', unsigned: 1, autoinc: 1, pk: 1, comment: 'ID'}); addRow({name: 'name', type: 'varchar', length: '100', default: '', comment: '名称'}); addRow({name: 'create_at', type: 'int', length: '', default: '0', comment: '创建时间'}); element.render(); } }); $(document).off('click', '#btn-add-field').on('click', '#btn-add-field', function () { addRow({type: 'varchar', length: '255'}); }); $(document).off('click', '#btn-add-id').on('click', '#btn-add-id', function () { addRow({name: 'id', type: 'bigint', unsigned: 1, autoinc: 1, pk: 1, comment: 'ID'}); }); $(document).off('click', '[data-act=remove-field]').on('click', '[data-act=remove-field]', function () { if ($('#fieldRows .field-row').length <= 1) { return layer.msg('至少保留一个字段', {icon: 2}); } $(this).closest('.field-row').remove(); }); // 生成预览(不执行) $(document).off('click', '#btn-preview').on('click', '#btn-preview', function () { var payload = collect(); if (payload.table === '') { return layer.msg('请填写表名', {icon: 2}); } post(FUNC.preview, payload, '', function (d) { $('#sqlPreview').val(d.sql); layer.msg('已生成,请确认后执行', {icon: 1}); }); }); // 执行建表 $(document).off('click', '#btn-exec').on('click', '#btn-exec', function () { var payload = collect(); if (payload.table === '') { return layer.msg('请填写表名', {icon: 2}); } layer.confirm( '确定创建表 ' + payload.table + '?
建议先「生成 SQL 预览」确认无误', {title: '建表确认'}, function (i) { layer.close(i); post(FUNC.create, payload, '创建成功', function (d) { layer.close(idx); layer.alert( '表:' + d.table + '
下一步生成 CRUD:
' + d.command + '', {title: '建表完成', btn: ['知道了']} ); table.reload('tableList'); }); } ); }); } /* ====================== 结构弹窗 ====================== */ function openStructure(name) { http.get(FUNC.structure, {table: name}).then(function (res) { if (!res || res.code !== 0) { return layer.msg((res && res.message) || '读取失败', {icon: 2}); } var d = res.data; var html = ''; for (var i = 0; i < d.columns.length; i++) { var c = d.columns[i]; html += '' + c.field + '' + c.type + '' + c.null + '' + '' + (c.default === null ? 'NULL' : c.default) + '' + '' + (c.key || '-') + '' + (c.extra || '-') + '' + c.comment + ''; } layer.open({ type: 1, title: '表结构:' + name, area: ['900px', '580px'], content: $('#structureTpl').html(), success: function () { $('#columnBody').html(html || '无字段'); $('#createSqlText').val(d.sql); element.render(); } }); }).catch(function (err) { layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2}); }); } /* ====================== 导出安装 SQL ====================== */ /** * 取选中表的 CREATE TABLE(__PREFIX__ 形式),弹窗展示 + 复制 / 下载 */ function openExport(names) { if (!names.length) { return layer.msg('请先勾选要导出的表', {icon: 2}); } var loading = layer.load(2); http.post(FUNC.export, {tables: names}).then(function (res) { layer.close(loading); if (!res || res.code !== 0) { return layer.msg((res && res.message) || '导出失败', {icon: 2}); } var d = res.data; var rows = ''; for (var i = 0; i < d.items.length; i++) { var it = d.items[i]; var badge = it.registered ? '已归档' : '未归档'; rows += '' + it.short + '' + badge + '' + '' + (it.target ? '' + it.target + '' : '需人工指定') + ''; } layer.open({ type: 1, title: '导出安装 SQL(' + d.items.length + ' 张表)', area: ['860px', '640px'], content: $('#exportTpl').html(), success: function () { $('#exportBody').html(rows); $('#exportSqlText').val(d.text); $('#exportSummary').html( (d.unregistered && d.unregistered.length ? '其中未归档:' + d.unregistered.join('、') + ',请确认写入哪个 install.sql。' : '所选表均已登记,导出可用于核对结构是否漂移。') ); } }); $(document).off('click', '#btn-copy-sql').on('click', '#btn-copy-sql', function () { var el = document.getElementById('exportSqlText'); el.select(); try { document.execCommand('copy'); layer.msg('已复制', {icon: 1}); } catch (e) { layer.msg('复制失败,请手动选中复制', {icon: 2}); } }); $(document).off('click', '#btn-download-sql').on('click', '#btn-download-sql', function () { var blob = new Blob([d.text], {type: 'text/sql;charset=utf-8'}); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'install_' + new Date().getTime() + '.sql'; a.click(); setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000); }); }).catch(function (err) { layer.close(loading); layer.msg('请求失败:' + (err && err.message ? err.message : '网络异常'), {icon: 2}); }); } /* ====================== 删表 ====================== */ function dropTable(name) { layer.prompt({ title: '危险:删除表不可恢复,请输入表名确认', formType: 0, value: '' }, function (val, i) { layer.close(i); if (val !== name) { return layer.msg('输入的表名不一致,已取消', {icon: 2}); } post(FUNC.drop, {table: name, confirm: val}, '已删除', function () { table.reload('tableList'); }); }); } var obj = { renderTable: function () { table.render({ elem: '#tableList', url: FUNC.index, toolbar: '#tableBar', defaultToolbar: ['filter'], method: 'get', cols: [[ {type: 'checkbox', width: 50}, {field: 'name', title: '表名', minWidth: 240}, {field: 'comment', title: '备注', minWidth: 160}, {field: 'engine', title: '引擎', width: 100}, {field: 'registered', title: '归档', width: 110, templet: function (d) { return d.registered ? '已归档' : '未归档'; }}, {field: 'target', title: 'install.sql', minWidth: 170, templet: function (d) { return d.target ? '' + d.target + '' : '—'; }}, {field: 'rows', title: '行数', width: 110, sort: true}, {field: 'size_text', title: '大小', width: 110, sort: true}, {field: 'collation', title: '字符集', width: 160}, {title: '操作', width: 170, templet: '#tableAction', fixed: 'right'} ]], page: true, limit: 15, limits: [15, 30, 50, 100], response: {statusCode: 0}, parseData: parse, done: function () { var view = $('#tableList').next('.layui-table-view'); auth.filterDom(view); auth.filterDom('.layui-btn[data-perm]'); } }); }, events: function () { table.on('toolbar(tableList)', function (o) { if (o.event === 'create') { openCreate(); } else if (o.event === 'reload') { table.reload('tableList'); } else if (o.event === 'optimize') { var names = checkedTables(); if (!names.length) { return layer.msg('请先勾选要优化的表', {icon: 2}); } post(FUNC.optimize, {tables: names}, '优化完成', function () { table.reload('tableList'); }); } else if (o.event === 'export') { openExport(checkedTables()); } }); table.on('tool(tableList)', function (o) { if (o.event === 'structure') { openStructure(o.data.name); } else if (o.event === 'optimizeRow') { post(FUNC.optimize, {tables: [o.data.name]}, '优化完成', function () { table.reload('tableList'); }); } else if (o.event === 'exportRow') { openExport([o.data.name]); } else if (o.event === 'drop') { dropTable(o.data.name); } }); form.on('submit(data-search-btn)', function (o) { table.reload('tableList', { where: {keyword: o.field.keyword, unregistered: o.field.unregistered || '0'}, page: {curr: 1} }); return false; }); $('#btn-reset').on('click', function () { setTimeout(function () { table.reload('tableList', {where: {keyword: '', unregistered: '0'}, page: {curr: 1}}); }, 10); }); } }; form.render(null, 'search-form'); // 归档筛选下拉要 layui 渲染,否则是原生 select obj.renderTable(); obj.events(); exports('datatable', obj); });