Fastadmin列表页点击生成二维码:完整实现与踩坑总结

Fastadmin列表页点击生成二维码:完整实现与踩坑总结 1. 需求拆解与实现方案选型先说结论Fastadmin后台列表页点击生成二维码本质上是在表格的操作列里挂一个自定义按钮点击后弹出一个二维码图片让访客或者线下人员扫码就能访问对应页面、识别设备信息或者完成签到核销。这个需求在实际项目里出现频率极高尤其是做设备巡检、商品溯源、活动报名、门店核销这一类系统时几乎每个Details列表都要来一个“生成二维码”的入口。我刚接到类似需求时第一反应是这玩意儿不是很简单吗直接在列表的按钮回调里写一层弹窗把当前行的某个字段喂给二维码生成库就完事了。但真正落地的时候发现Fastadmin的表格渲染机制、按钮传参、弹窗刷新时机到处是坑。你想在操作列塞一个按钮先要搞懂Fastadmin的表格列配置规则然后还得在require.js的模块机制里找到正确的挂载方式最后还要解决二维码内容包含中文、URL带参数、弹窗关闭再打开不刷新等一系列问题。这次我以Fastadmin 1.2.0版本ThinkPHP 5.1内核为例完整走一遍从按钮定义、前端弹出、二维码生成到后端数据下发的实现流程同时把我在实际项目中踩过的几个坑一并整理出来。如果你正好用Fastadmin做后台管理系统想在列表页实现“点击生成二维码”这类高频交互这篇文章可以直接当作业抄。在方案选型上目前主流的做法分两类一类是纯前端生成也就是在浏览器里用JS库把内容变成二维码图片服务端只负责把数据吐给前端另一类是后端生成由PHP调用二维码库生成图片再以图片URL的形式返回给前端展示。两种方案没有绝对的好坏全看你的业务场景。我这次选择的是前端生成 后端提供数据源的混合方案主要原因有三个第一纯前端方案不需要在服务器装额外的图像库也不占用PHP进程去处理图片流二维码内容在浏览器本地完成编码响应速度更快第二Fastadmin的表格数据本来就是通过接口从后端拉取的列表行里已经带了ID、标题、URL等关键字段前端直接取这些字段拼出二维码内容逻辑上最顺第三如果二维码内容不是简单的URL而是需要后端临时生成一串带签名的token或者短链后端还是得参与所以我们把后端做成“数据供给方”前端做成“展示方”各自职责清晰后续要改二维码内容格式前端改一行代码就行。当然如果你的二维码内容是敏感数据比如包含了内部路径、密钥信息、手机号等我建议改用后端生成方案避免把敏感字段透传到浏览器端。这个决策点在动手之前一定要想清楚否则后期改造成本很高。2. Fastadmin列表页按钮定制的底层逻辑Fastadmin的表格是基于Bootstrap Table封装的每一个列都可以通过formatter回调函数自定义渲染内容。操作列里的“编辑”“删除”按钮本质上都是通过operate列的formatter去输出HTML标签的。你想要加一个“生成二维码”按钮最干净的做法是重写operate列的formatter在原有按钮后面追加一个自定义按钮。先看Fastadmin默认的列表控制器里表格是怎么构建的。通常index()方法长这样public function index() { //设置过滤方法 $this-request-filter([strip_tags, trim]); if (false $this-request-isAjax()) { return $this-view-fetch(); } //如果发送的来源是Selectpage则转发到Selectpage if ($this-request-request(keyField)) { return $this-selectpage(); } list($where, $sort, $order, $offset, $limit) $this-buildparams(); $list $this-model -where($where) -order($sort, $order) -limit($offset) -limit($limit) -select(); foreach ($list as $row) { $row-visible([id, title, url, status, createtime]); } $result array(total $this-model-total, rows $list); return json($result); }对应的前端index.html里表格初始化代码一般是这样的table.bootstrapTable({ url: $.fn.bootstrapTable.defaults.extend.index_url, pk: id, sortName: id, columns: [ {checkbox: true}, {field: id, title: __(Id)}, {field: title, title: __(Title)}, {field: url, title: __(Url)}, {field: operate, title: __(Operate), align: center, formatter: Table.api.formatter.operate} ] });这里最关键的就是最后一列field: operate它的formatter指向Table.api.formatter.operate这是一个由Fastadmin框架预先定义好的通用操作列渲染函数。它会判断当前行是否有编辑、删除权限然后自动输出对应的按钮HTML。要追加自定义按钮有两条路可走路线A全局替换formatter。不给operate列指定formatter改成自己写一个函数完全控制按钮的输出。好处是灵活想输出什么就输出什么坏处是你要自己处理编辑、删除、拖拽排序这些默认按钮的HTML并且要考虑权限控制代码量会翻倍。路线B保留默认formatter用buttons参数扩展。Fastadmin的Table.api.formatter.operate内部有一个机制它读取表格初始化时传入的buttons配置把自定义按钮的HTML追加到默认按钮后面。这个是最省事的方案我强烈推荐。具体做法是这样的在表格初始化前给按钮组追加一个自定义项Table.api.init({ extend: { index_url: activity/index, table: activity }, buttons: { qrcode: { name: qrcode, title: 生成二维码, icon: fa fa-qrcode, classname: btn btn-xs btn-success btn-magic, visible: function (row) { // 返回true表示显示row为当前行的数据 return true; }, click: function (data) { // 这里处理点击事件data是当前行的完整数据 var row data.row; // 生成二维码并弹出展示 showQrcode(row); } } } });使用这种方案按钮会自动出现在操作列里并且默认自带了权限节点的校验如果后台没有给当前管理员分配activity/qrcode权限按钮不会显示。这一点在多人协作的后台项目里特别重要不然任何管理员都能操作所有按钮权限就形同虚设了。在前端代码的require([table, layer, qrcode], function (Table, Layer, qrcode) {...})回调里把showQrcode函数定义好然后封装成一个独立的方法这样后续其他列表页也能复用。3. 前端二维码生成与弹窗展示的实现前端二维码生成目前最成熟的库是qrcodejs2或者它的前身qrcode.js纯JavaScript实现不依赖任何框架支持canvas和table两种渲染方式。Fastadmin项目里我们可以通过Composer安装前端资源包也可以直接在视图模板里引入CDN但我建议还是把它内置到项目的public/assets/libs/qrcode/目录下避免每次加载都走外网。我在实际操作中的做法是用npm或者直接下载源码包放到本地。先看看require.js怎么加载这个库require([table, layer, qrcode], function (Table, Layer, qrcode) { // qrcode 是全局变量 });如果放到public/assets/libs/qrcode/需要在require.js的配置里加一条paths映射或者在HTML里直接用script src/assets/libs/qrcode/qrcode.min.js/script然后require的时候把它当作全局变量用。这里要注意qrcodejs2这个库在AMD规范下会自动检测define是否存在如果检测到就会走define([jquery], factory)这个分支这会导致你在require的回调里拿不到返回值因为它在工厂函数里没有返回jQuery对象。我踩过一次这个坑最后解决的办法是不用require加载直接在模板底部用原生script标签引进来让QRCode变成全局对象然后在require的回调里直接用全局变量。这个方法最稳也最省心。相关的弹窗展示逻辑我来贴一份核心代码基于Fastadmin自带的Layer弹出层组件var showQrcode function (row) { // 二维码内容这里根据实际业务拼接比如访问详情页的链接 var content row.url || (/index.php/index/detail?id row.id); var dialog Layer.open({ type: 1, title: 二维码预览, area: [320px, 380px], content: div stylepadding:20px;text-align:center; div idqrcode-box stylemargin:0 auto;/div p stylemargin-top:15px;color:#999;扫码即可查看对应内容/p /div, success: function (layero, index) { // 这里必须等弹窗内容完全渲染后再初始化二维码否则找不到DOM节点 var qr new QRCode(document.getElementById(qrcode-box), { text: content, width: 240, height: 240, colorDark: #000000, colorLight: #ffffff, correctLevel: QRCode.CorrectLevel.H }); } }); };这段代码里有两个细节值得注意。第一个Layer.open的success回调是弹窗DOM渲染完成之后才触发的必须把二维码初始化放在这个回调里。如果你在Layer.open之后立刻执行new QRCode()大概率会报getElementById找不到节点的错误。第二个area的宽度要比二维码宽一点留出padding的余量不然二维码会被遮住一半。我一般设为340px左右内容区域padding给20px二维码本身240px加起来刚好。二维码的容错级别这里我也多说一句。correctLevel: QRCode.CorrectLevel.H表示最高容错率二维码左上、右上、左下三个角就算有一定程度的污损或遮挡也能正常识别。如果二维码内容本身不长用H级别完全没问题扫出来的识别速度也不会慢。但如果你要做的是二维码串码内容特别紧凑可以适当降级到M级别图片颗粒会细腻一点。还有一点弹窗关闭再打开新的二维码时#qrcode-box这个div里的内容必须清空。因为QRCode这个库在同一个DOM节点上重复初始化会把之前画的canvas或table一直追加进去导致二维码重叠显示扫码识别率直线下降。我惯用的清理方式是$(#qrcode-box).html(); // 清空容器避免重复生成时二维码重叠 var dom document.getElementById(qrcode-box); var qr new QRCode(dom, { text: content, width: 240, height: 240 });如果你使用的是qrcodejs2的canvas渲染模式还需要额外考虑canvas高DPI屏幕下模糊的问题。一般做法是把width和height设大然后通过CSS把展示尺寸缩小比如canvas实际画480pxCSS里限制为240px这样在Retina屏幕上才会清晰。不过后台管理系统大部分使用场景是内网这个问题可以暂缓处理。4. 后端数据接口与权限节点的侧重点前端把二维码内容拼好、弹窗渲染出来看起来已经“能用了”但放在真实项目里还不够严谨。至少有两个问题需要后端参与才能处理干净。第一个问题是权限控制。Fastadmin的按钮机制会读取当前登录管理员的权限节点但如果你只是在前端buttons里定义了qrcode按钮而后台菜单规则里没有对应的“生成二维码”权限节点按钮默认就不会显示。很多人加了半天按钮发现列表页没变化十有八九是这个原因。解决办法很简单在后台的“权限管理-菜单规则”里新增一条规则比如名称生成二维码 类型按钮 标识activity/qrcode新增后给对应的管理员分组勾选这个权限。这样按钮出现的同时后端接口的安全校验也有了依据。第二个问题是数据脱敏与签名生成。如果你的二维码内容不是单纯的一条URL而是需要携带一次性token、时间戳、UUID等参数的动态链接那前端自己去拼接就不合适了。因为这类签名一般要配合服务端校验生成逻辑放置在后端更安全。我这次项目的实际场景是列表页每一条数据代表一个设备点位点击“生成二维码”后需要生成一个包含设备ID和过期时间的签到二维码访客用手机扫码后会进入一个临时的签到页面签到链接里带的token只能使用一次。这种情况下前端的URL是后端动态生成的前端只需要把后端返回的URL塞给二维码生成器。后端对应的接口我用Fastadmin的资源路由风格实现public function qrcode() { $id $this-request-post(id); if (!$id) { $this-error(参数错误); } // 从数据库获取设备信息 $device DeviceModel::get($id); if (!$device) { $this-error(设备不存在); } // 生成一次性签名有效期10分钟 $token md5(uniqid(mt_rand(), true)); $expire time() 600; // 将token存储到缓存或数据库表扫码时校验 cache(qr_token_ . $token, [device_id $id, expire $expire], 600); // 拼接完整二维码内容 $url url(index/device/scan, [token $token], true, true); $this-success(生成成功, null, [url $url, expire $expire]); }接口返回的JSON结构是Fastadmin标准的{code:1,msg:生成成功,data:{url:...,expire:1650000000}}格式。前端拿到url字段后再初始化二维码。这里有个特别容易被忽略的坑使用php的url()函数生成带域名的完整URL时第四个参数必须传true这样才会拼接绝对地址否则默认输出的是相对路径比如/index/device/scan?tokenxxx手机扫码之后无法正确解析域名。我排查这个问题时花了整整一下午最后发现是url()的$domain参数没开。后端生成一次token后还要在扫码页做一次校验。校验逻辑很简单从请求参数里取token查缓存判断是否过期、是否为第一次使用校验通过后删除缓存中的token完成一次性校验闭环。这块逻辑属于业务层不同的项目差异较大这里就不展开写完整代码了但思路是通用的扫码页的逻辑一定不能用“只看URL是否可访问”来判断有效性必须走一次后端状态校验。5. 完整实操步骤从零实现列表页点击生成二维码前面把原理和关键环节都讲得差不多了这一章我直接把整个过程串成一个标准的操作清单方便你照着做。我以设备管理列表为例完整跑一遍。5.1 准备二维码前端库把qrcode.min.js下载下来放到public/assets/libs/qrcode/qrcode.min.js。如果不想手动下载可以直接用npm在项目根目录拉取npm install qrcodejs2然后把node_modules/qrcodejs2/qrcode.min.js复制到public/assets/libs/qrcode/目录。注意检查文件是否存在AMD模块化的头如果是下面这个样式(function (f) { if (typeof exports object typeof module ! undefined) { module.exports f() } else if (typeof define function define.amd) { define([], f) } else { var g; if (typeof window ! undefined) { g window } else if (typeof global ! undefined) { g global } else if (typeof self ! undefined) { g self } else { g this } g.QRCode f() } })(function () { ... });这种带define.amd检测的在Fastadmin的require.js环境里加载时会尝试注册成AMD模块。但因为二维码库本身并不依赖jQuery注册后你在require([qrcode], ...)回调里拿到的还是undefined这是一个比较典型的AMD包装器冲突。为了少踩坑我建议在index.html的{__SCRIPT__}之前先用原生script标签引入script src/assets/libs/qrcode/qrcode.min.js/script这样window.QRCode就全局有值了后面require的回调里直接访问window.QRCode即可。5.2 添加表格按钮配置在public/assets/js/backend/device.js里修改表格初始化代码增加buttons配置define([jquery, bootstrap, backend, table, form, layer], function ($, undefined, Backend, Table, Form, Layer) { var Controller { index: function () { // 初始化表格参数配置 Table.api.init({ extend: { index_url: device/index, table: device } }); var table $(#table); // 核心追加自定义按钮 Table.api.init({ extend: { index_url: device/index, table: device }, buttons: { qrcode: { name: qrcode, title: 生成二维码, icon: fa fa-qrcode, classname: btn btn-xs btn-success btn-magic, visible: function (row) { return row.status 1; // 只有启用状态的设备才显示按钮 }, click: function (data) { var row data.row; // 打开二维码弹窗 showQrcode(row); } } } }); // 初始化表格 table.bootstrapTable({ url: $.fn.bootstrapTable.defaults.extend.index_url, pk: id, sortName: id, columns: [ {checkbox: true}, {field: id, title: __(Id)}, {field: title, title: __(设备名称)}, {field: device_no, title: __(设备编号)}, {field: status, title: __(状态), searchList: {0: __(停用), 1: __(启用)}, formatter: Table.api.formatter.status}, {field: createtime, title: __(创建时间), formatter: Table.api.formatter.datetime}, {field: operate, title: __(操作), align: center, formatter: Table.api.formatter.operate} ] }); // 为表格绑定事件 Table.api.bindevent(table); // 二维码展示函数 function showQrcode(row) { var content /index.php/index/device/detail?id row.id; Layer.open({ type: 1, title: 设备二维码, area: [340px, 420px], content: div stylepadding:20px;text-align:center; div idqrcode-box/div p stylemargin-top:15px;color:#999;font-size:13px;扫码查看设备详情/p /div, success: function (layero, index) { $(#qrcode-box).html(); var dom document.getElementById(qrcode-box); var qr new QRCode(dom, { text: content, width: 260, height: 260, colorDark: #000000, colorLight: #ffffff, correctLevel: QRCode.CorrectLevel.H }); } }); } }, add: function () { // 新增逻辑 }, edit: function () { // 编辑逻辑 }, del: function () { // 删除逻辑 } }; return Controller; });5.3 新增菜单规则绑定权限节点在Fastadmin后台进入“权限管理 - 菜单规则”添加一条按钮规则父级菜单选择设备管理对应的控制器比如device名称生成二维码类型按钮标识device/qrcode排序随意建议在原操作按钮后面图标fa fa-qrcode保存后到“权限管理 - 管理员分组”里给需要操作该功能的管理员组勾选新加的权限。这里提醒一下如果管理员是超级管理员默认拥有所有权限不用单独勾选。但普通管理员组的用户必须勾选这个节点才能看到按钮也才能调用对应的后端接口。5.4 后端提供二维码数据接口在Device.php控制器里加入qrcode方法提供签名数据或者拼接好的二维码内容。为了保持接口幂等这里我使用POST方式接收参数public function qrcode() { $id $this-request-post(id); if (!$id) { $this-error(缺少参数); } $device DeviceModel::get($id); if (!$device) { $this-error(设备不存在); } // 签名信息 $sign md5($device-id . $device-device_no . time()); $url url(index/device/detail, [id $device-id, sign $sign], true, true); $this-success(生成成功, null, [url $url]); }前端在click回调里如果要走后端接口生成URL需要先发一个AJAX请求拿到data.url后再弹二维码。代码调整如下click: function (data) { var row data.row; $.ajax({ url: device/qrcode, type: POST, data: {id: row.id}, dataType: json, success: function (res) { if (res.code 1) { showQrcode(res.data.url); } else { Layer.msg(res.msg, {icon: 2}); } } }); }注意Fastadmin默认的AJAX请求头里带有X-Requested-With: XMLHttpRequestPOST请求还会自动携带token所以直接用$.ajax即可不需要额外处理CSRF。5.5 顺手优化按钮的显示与容错实际项目中操作列不止一行两行如果每一行都生成一个二维码弹窗用户操作起来会很烦。我做过的项目里比较舒服的交互方式是二维码弹窗右上角放一个“下载”按钮让用户可以把二维码图片保存下来线下打印贴到设备上。这个可以后续扩展原理是用canvas把二维码转成base64图片再触发浏览器下载。另外visible回调里除了判断status字段还可以判断当前行是否已经有二维码链接、是否已经过期等场景。总之把按钮的“可见性”和业务状态绑定能少很多无效操作。6. 常见问题与排查技巧实录这一章整理我在开发过程中遇到的高频问题一个一个说清楚原因和解法。6.1 列表页没有出现“生成二维码”按钮这恐怕是遇到最多的情况。排查方向有三个第一确认buttons配置是否真的写进了Table.api.init里。不少人会在表格初始化之后重新调用Table.api.init导致前面的配置被覆盖按钮自然出不来。我习惯把按钮配置放在table.bootstrapTable({...})之前确保一次性完成参数合并。第二确认权限节点是否已经添加并分配到当前管理员组。operate列的默认formatter会读取后台菜单规则如果device/qrcode这个标识没有在后台定义按钮会被框架过滤掉。这是最隐蔽的问题你只在前端写代码是看不到按钮的。第三确认visible回调返回值是否为true。如果里面写了类似row.status 1的判断而当前行的status字段没有在控制器index方法里的visible(...)白名单中出现该字段就不会输出到前端导致判断永远不成立。解决办法就是在后端的$row-visible([id, title, device_no, status, createtime, ...])里把status加进去或者干脆用$row-hidden([])放开所有字段。6.2 弹窗打开后二维码显示空白这个问题我排查过很多次绝大部分原因是#qrcode-box节点在二维码初始化时还不存在于DOM中。Layer.open是异步弹窗success回调虽然表示DOM渲染完成但如果你使用的Layer版本较老可能会出现节点已经渲染但尺寸为0的情况导致二维码画在一个0×0的容器里视觉上就是空白。解决办法不要急着初始化QRCode用一个setTimeout延迟执行比如延迟100mssetTimeout(function () { $(#qrcode-box).html(); var qr new QRCode(document.getElementById(qrcode-box), {...}); }, 100);这个技巧虽然有点土但确实很多场景下能解决“弹窗节点宽度计算异常”的问题。如果延迟后还是空白检查浏览器控制台是否有JS报错重点看QRCode是不是undefined。6.3 重复打开弹窗时二维码叠在一起这个原因很明确同名的#qrcode-box节点在多次弹窗中一直保留着旧二维码的canvas元素。因为Layer.open默认弹出的是同一个DOM结构如果上一次弹窗关闭时只是隐藏了弹窗节点而没有销毁内容下次打开时会继续往同一个节点里追加新的canvas。在success回调里先执行$(#qrcode-box).html()把容器清空再初始化新二维码就能解决。如果用的是canvas渲染模式还需要额外调用一次clear方法但我实际测试中直接清空DOM是最省事的。6.4 扫描二维码后提示内容格式错误二维码本质上是把一串文本编码成图形扫码器识别后会把文本原样解码。如果内容里包含了中文、特殊符号、压缩的URL扫码器可能识别成纯文本或乱码。解决思路是二维码内容尽量使用ASCII字符集比如URL链接、纯数字、英文缩写避免直接放中文文本。比如你直接在二维码里存“设备-001”少数扫码器能识别但很多老式的扫码枪会输出乱码或者直接提示“无法识别的二维码格式”。我在项目中统一把设备名称编码进短链参数里比如https://example.com/device/detail?id1name%E8%AE%BE%E5%A4%87-001URL编码后虽然很长但都是ASCII字符兼容性大幅提升。还有一点二维码内容越长图案越密识别越慢。所以如果URL特别长比如超过200个字符建议后端做短链映射把长链接转成短码让二维码图案更稀疏识别率更高。6.5 表格翻页后按钮点击报id undefinedFastadmin的operate列按钮回调中data.row是当前行的数据对象但如果你用的是自定义的formatter而不是默认按钮机制row的取值方式和绑定方式会不一样。常见的一个坑是visible回调里看到的row是undefined因为你配置按钮时没有正确绑定row的上下文。排查方法在click回调里先console.log(data)看数据结构确认data.row确实存在。如果data.row里没有id检查后端的index()方法返回的字段是否把id落在了visible()之外导致字段被隐藏。这种情况我一开始说过很多人改前端半天其实问题出在后端字段隐藏。6.6 无权限访问qrcode接口提示“无权限访问”这种情况出现在你没有新增菜单规则但前端按钮还是显示出来了比如你绕过权限系统直接写死了按钮然后点击时后端qrcode方法提示无权限。Fastadmin的权限拦截是基于控制器的$noNeedRight属性或者菜单规则的标识匹配。如果这个接口确实允许所有登录管理员访问可以在控制器顶部声明protected $noNeedRight [*];但生产环境不建议这么做这是图省事的写法会失去权限分层控制。更好的做法是在后台完整添加device/qrcode菜单规则并为对应管理员组勾选权限然后前端按钮和接口就都统一走权限校验了。7. 从列表页二维码扩展到更多业务场景列表页点击生成二维码这个功能做好之后你会发现它可以复用到很多地方。我目前已经把它用在了三个不同模块里都是同一个思路稍微改一下数据流即可。第一个是设备巡检。巡检人员到现场后扫设备上的二维码就能在手机上看到这台设备的巡检记录、上次保养时间、关联责任人。这个场景里二维码内容是设备的固定链接不涉及动态签名前端直接把设备详情页URL塞给QRCode就行。后端只需要放行详情页的访问就行。相当于点击生成二维码变成了一个打印入口把二维码图片下载下来贴到设备表面。第二个是活动核销。活动报名人员的列表页里操作列加一个“生成核销二维码”点击后生成一个带token的短期链接参会者到了现场工作人员用扫码枪或者手机扫码后台校验token有效性和是否已核销核销完成后mark状态防止重复使用。这个场景里我用了刚才提到的后端签名方案核心是token的一次性效验和过期时间控制。第三个是内容分享。在一个CMS文章管理列表里给每篇文章生成一个二维码扫码后直接打开文章的移动端H5页面。这里需要注意的是文章URL里可能带有id和category_id等参数直接用Fastadmin默认的url()函数会生成后台URL而不是前端H5的URL。处理方式是在后端单独配置一个shareUrl方法返回前台的绝对地址。所以说列表页生成二维码核心是“把操作列按钮、二维码库、动态数据拼接”这三个点打通之后所有需要“扫码”的业务场景都能套用这套模式。8. 我在实际项目中的几点体会功能虽然不大但做完之后回过头看有几个决策点很值得沉淀。第一能用前端生成就别让后端出图。很多开发者习惯用后端库生成二维码图片然后以文件URL形式返回给前端。这种做法的好处是图片可以被缓存、下载、打印坏处是每点击一次都会在服务器上生成一张图片文件垃圾文件会越来越多。如果你只是临时弹窗展示不要求长期保存图片文件前端生成完全够用省事又干净。第二二维码内容的构成越短越好。我发现很多人在二维码里放冗长的JSON字符串或者超长的查询参数导致二维码图案密集到扫描困难。二维码适合存短链接、纯文本、电话号码、短ID标识不适合存JSON。遇到需要携带多参数的情况后端先做一次短链映射让二维码内容保持简短用户体验会好很多。第三别忽略权限节点。Fastadmin这套框架的权限体系非常完备操作按钮、数据接口、菜单显示是三层联动。如果你只是在前端加按钮后端写接口而不去后台添加对应的权限节点等运维接手或者有其他管理员登录时会发现按钮时有时无快速定位问题特别费劲。我的习惯是所有新增按钮都在开发阶段同步把菜单规则配好测试时直接验证完整链路省得后期补。第四二维码弹窗最好支持下载。实际业务中生成的二维码往往需要被打印出来贴到设备上、海报里、工牌上。如果一个弹窗只支持实时扫码不支持下载图片使用效果会打折扣。实现下载的方式很简单canvas模式下用canvas.toDataURL()导出base64图片然后创建一个a标签触发下载就行。如果你用的是table渲染模式则需要把table里的模块element字符串重新拼成真正的图片这一步比较麻烦所以我一直推荐用canvas模式。第五注意扫码后的落地页适配。二维码一扫通常是手机打开页面。如果你的H5页面没有做成响应式或者没有适配移动端用户扫码后看到的可能是错乱的PC端页面这体验就很糟了。我的建议是二维码指向的落地页至少要做好viewport适配、基础按钮点击区域放大、浏览器兼容这三个基本点。最后再分享一个小技巧。如果你不想引入任何二维码前端库Fastadmin自带了一个基于phpqrcode的后端方案Composer包名叫endroid/qr-code装好之后在控制器里直接一行代码生成base64数据流输出到模板use Endroid\QrCode\QrCode; $qrCode new QrCode($url); header(Content-Type: . $qrCode-getContentType()); echo $qrCode-writeString(); exit;这种方案的优势是后端可以直接把二维码图片保存到OSS服务器、打印、加水印劣势是会占用PHP进程时间。两种方案各有适用场景我这次选择的是前端库方案因为它能更好地融入Fastadmin的前后端分离思维。你在项目落地时可以根据团队的技术偏好和服务器环境灵活变通。