import type { Plugin } from 'vite';
import { relative, extname } from 'path';
import { parse, walk } from 'vue-eslint-parser';
import { generate } from 'escodegen';
export default function autoPermissionPlugin({ srcDir = 'src' }: { srcDir?: string } = {}): Plugin {
const filter = (id: string) => /\.vue$/.test(id);
return {
name: 'tty-auto-permission',
transform(code, id) {
if (!filter(id)) return;
try {
const ast = parse(code, {
ecmaVersion: 2020,
sourceType: 'module',
loc: true,
});
// 获取相对于 src 的路径
const filePath = relative(process.cwd(), id).replace(extname(id), '');
// 按钮文案映射表
const butTextMap: Record<string, string> = {
新增: 'create',
编辑: 'edit',
删除: 'delete',
查看: 'view',
导出: 'export',
};
// 查找模板中的按钮并注入权限指令
const templateAST = ast.templateBody;
if (templateAST) {
walk(templateAST, {
enter(node) {
if (node.type === 'VElement' && ['button', 'a-button', 'el-button'].includes(node.name)) {
let suffix: string | undefined = undefined;
// 从按钮文字推断后缀
const buttonText = node.children?.find((c) => c.type === 'VText')?.value.trim();
if (buttonText && butTextMap[buttonText]) {
suffix = butTextMap[buttonText];
}
// 从 @click 方法名推断
const clickHandler = node.attributes.find((attr) => attr.key.name === '@click');
if (clickHandler?.value?.expression?.callee?.name) {
const fnName = clickHandler.value.expression.callee.name;
if (fnName.startsWith('handle')) {
suffix = fnName.charAt(6).toLowerCase() + fnName.slice(7);
}
}
//如果有后缀
if (suffix) {
const permCode = `${filePath}_${suffix}`;
// 是否已有权限判断指令
const hasPermissionDirective = node.startTag.attributes.some(
(attr) =>
attr.type === 'VDirective' &&
attr.key.name.name === 'if' &&
attr.value?.value?.includes('hasPerm'),
);
if (hasPermissionDirective) {
// 已有权限指令,跳过插入判断
return;
}
// AST 注入权限指令
node.startTag.attributes.push({
type: 'VDirective',
key: {
name: { name: 'if' },
argument: null,
modifiers: [],
},
value: {
type: 'VLiteral',
value: `permissionStore.hasPerm('${permCode}')`,
},
});
}
}
},
});
}
// 是否已导入存储仓储
const hasImportStore = code.includes(
"import { butPermissionStore } from '@/stores/butPermission'",
);
const warehouseCode = `
<script setup>
import { butPermissionStore } from '@/stores/butPermission'
const permissionStore = butPermissionStore()
</script>
`.trim();
// 如没有 <script> 标签,插入新的 <script setup>
if (!code.includes('<script')) {
ast.body.unshift(parse(warehouseCode).body[0]);
} else {
// 否则查找第一个 <script> 或 <script setup> 并在其后插入 store
walk(ast, {
enter(node) {
if (
node.type === 'VElement' &&
node.name === 'script' &&
node.startTag.attributes.some((attr) => attr.key.name === 'setup')
) {
if (!hasImportStore) {
// 在 <script setup> 中注入 import 语句
const importNode = parse(warehouseCode).body[0];
ast.body.splice(ast.body.indexOf(node) + 1, 0, importNode);
}
this.skip(); // 跳过后续遍历
}
},
});
// 如果没找到 <script setup>,则在第一个 <script> 后插入
if (
!ast.body.some(
(n) =>
n.type === 'VElement' &&
n.name === 'script' &&
n.startTag.attributes.some((a) => a.key.name === 'setup'),
)
) {
for (let i = 0; i < ast.body.length; i++) {
const node = ast.body;
if (node.type === 'VElement' && node.name === 'script') {
if (!hasImportStore) {
const importNode = parse(warehouseCode).body[0];
ast.body.splice(i + 1, 0, importNode);
}
break;
}
}
}
}
const newCode = generate(ast);
return {
code: newCode,
map: null,
};
} catch (e) {
console.error(`权限注入失败: ${id}`, e);
return { code, map: null };
}
},
};
}