feat(oidc): 接入统一认证登录与身份自助绑定

- 登录页接入统一认证入口并处理认证回调
- 个人中心新增认证身份区块,支持自助绑定与解绑
- 头像菜单与应用中心新增认证身份入口
- 应用中心删除经营管理分组,业务系统卡片指向统一认证授权地址
- 用户管理页移除认证身份代绑入口与弹窗
- 身份关联接口从用户域移到认证域
This commit is contained in:
Ray.Hao
2026-10-28 08:21:27 +08:00
parent 9cddb95fb7
commit b5bacd2cbf
205 changed files with 6705 additions and 7079 deletions

View File

@@ -220,7 +220,6 @@ const sortFlag = ref<Sortable | null>(null);
const fieldConfigs = computed(() => formData.value?.fieldConfigs || []);
// 只看 AI 改动过的字段
const onlyAiChanged = ref(false);
// AI 改动过的字段数
@@ -334,6 +333,10 @@ function destroySort() {
/**
* 暴露字段排序、显隐切换与定位能力
*/
/**
* 暴露给父组件
*/
defineExpose({ initSort, destroySort, showOnlyAiChanged, locateField });
onBeforeUnmount(() => {

View File

@@ -198,6 +198,9 @@ import { useGenConfig } from "../composables/useGenConfig";
import { useCodePreview } from "../composables/useCodePreview";
import { useLocalWrite } from "../composables/useLocalWrite";
import { useAiFillDiff, type AiChangeRow } from "../composables/useAiFillDiff";
import BasicConfigStep from "./BasicConfigStep.vue";
import FieldConfigStep from "./FieldConfigStep.vue";
import PreviewStep from "./PreviewStep.vue";
const STEP = { BASIC_CONFIG: 0, FIELD_CONFIG: 1, PREVIEW: 2 } as const;
const STEPS = [
@@ -244,9 +247,9 @@ const loading = ref(false);
const aiLoading = ref(false);
const loadingText = ref("loading...");
const basicConfigRef = ref();
const fieldConfigRef = ref();
const previewRef = ref();
const basicConfigRef = ref<InstanceType<typeof BasicConfigStep>>();
const fieldConfigRef = ref<InstanceType<typeof FieldConfigStep>>();
const previewRef = ref<InstanceType<typeof PreviewStep>>();
const {
genConfigFormData,
@@ -482,7 +485,7 @@ async function handleNext() {
if (currentStep.value === STEP.PREVIEW) {
const pageType = genConfigFormData.value.pageType || "classic";
GeneratorAPI.download(currentTableName.value, pageType as "classic" | "crud", "ts");
await GeneratorAPI.download(currentTableName.value, pageType as "classic" | "crud", "ts");
}
}

View File

@@ -146,6 +146,8 @@ import "codemirror/mode/javascript/javascript.js";
import Codemirror from "codemirror-editor-vue3";
import type { CmComponentRef } from "codemirror-editor-vue3";
import type { EditorConfiguration } from "codemirror";
import type { TreeInstance } from "element-plus";
import type { TreeNode } from "../utils/tree-builder";
import GeneratorAPI from "@/api/codegen";
import type { GenConfigForm } from "@/api/codegen";
import { getFileIcon } from "../utils/tree-builder";
@@ -158,7 +160,7 @@ const props = defineProps<{
previewScope: "all" | "frontend" | "backend";
previewTypes: string[];
previewTypeOptions: string[];
filteredTreeData: any[];
filteredTreeData: TreeNode[];
code: string;
currentFileKey: string;
tableName: string;
@@ -170,13 +172,13 @@ const props = defineProps<{
const emit = defineEmits<{
(_e: "update:previewScope", _val: "all" | "frontend" | "backend"): void;
(_e: "update:previewTypes", _val: string[]): void;
(_e: "file-click", _data: any): void;
(_e: "file-click", _data: TreeNode): void;
(_e: "copy"): void;
}>();
const cmRef = ref<CmComponentRef>();
const cmOptions: EditorConfiguration = { mode: "text/javascript" };
const fileTreeRef = ref();
const fileTreeRef = ref<TreeInstance>();
const fileTreeWidth = ref(280);
const currentFilePath = computed(() => {
@@ -189,15 +191,16 @@ const currentFilePath = computed(() => {
/**
* 切换预览范围
*/
function onScopeChange(val: any) {
emit("update:previewScope", val as "all" | "frontend" | "backend");
function onScopeChange(val: unknown) {
if (val === "all" || val === "frontend" || val === "backend") emit("update:previewScope", val);
}
/**
* 切换预览文件类型
*/
function onTypesChange(val: any) {
emit("update:previewTypes", val as string[]);
function onTypesChange(val: unknown) {
if (Array.isArray(val) && val.every((item) => typeof item === "string"))
emit("update:previewTypes", val);
}
const currentLanguage = computed(() => {
@@ -211,7 +214,7 @@ const fileCount = computed(() => {
/**
* 递归统计文件数量
*/
function walk(nodes: any[]) {
function walk(nodes: TreeNode[]) {
nodes.forEach((n) => {
if (!n.children || !n.children.length) count++;
else walk(n.children);
@@ -226,48 +229,52 @@ const fileCount = computed(() => {
*/
function handleDownload() {
const pageType = props.genConfigFormData.pageType || "classic";
GeneratorAPI.download(props.tableName, pageType as "classic" | "crud", "ts");
return GeneratorAPI.download(props.tableName, pageType as "classic" | "crud", "ts");
}
/**
* 刷新代码编辑器布局
*/
function refreshEditor() {
const inst = cmRef.value as any;
inst?.cminstance?.refresh?.();
inst?.cm?.refresh?.();
inst?.editor?.refresh?.();
cmRef.value?.refresh();
}
// 拖拽结束、再次开始或组件卸载时,共用同一份监听清理函数。
let stopResize: (() => void) | undefined;
/**
* 开始拖动文件树宽度
*/
function startResize(e: MouseEvent) {
stopResize?.();
const startX = e.clientX;
const startWidth = fileTreeWidth.value;
/**
* 拖动中更新文件树宽度
* 按鼠标位移调整预览区域宽度。
*/
const onMove = (ev: MouseEvent) => {
fileTreeWidth.value = Math.max(200, Math.min(500, startWidth + ev.clientX - startX));
};
/**
* 结束拖动并解绑监听
* 结束拖动并移除鼠标监听。
*/
const onUp = () => {
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
stopResize = undefined;
};
stopResize = onUp;
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
}
/**
* 暴露编辑器刷新与文件树引用
* 父组件切换到预览步骤后刷新编辑器布局。
*/
defineExpose({ refreshEditor, fileTreeRef });
defineExpose({ refreshEditor });
onBeforeUnmount(() => {
stopResize?.();
cmRef.value?.destroy();
});
</script>

View File

@@ -80,7 +80,7 @@ import { MagicStick, Refresh, RefreshLeft, Search } from "@element-plus/icons-vu
import { ElMessage, ElMessageBox, type FormInstance } from "element-plus";
import GeneratorAPI from "@/api/codegen";
import type { TableItem, TableQueryParams } from "@/api/codegen";
import type { TableItem, TableQuery } from "@/api/codegen";
import { usePageTable } from "@/composables";
// 表已配置代码生成(1:是;0:否)
@@ -100,7 +100,7 @@ const queryFormRef = ref<FormInstance>();
// 分页表格数据管理
const { loading, list, total, params, fetchData, handleQuery, handleResetQuery } = usePageTable<
TableItem,
TableQueryParams
TableQuery
>({
initialParams: {
pageNum: 1,

View File

@@ -25,7 +25,6 @@ export function useCodePreview(genConfigFormData: Ref<GenConfigForm>) {
const code = ref("");
const currentFileKey = ref("");
const fileTreeRef = ref();
const { copy, copied } = useClipboard();
@@ -53,21 +52,15 @@ export function useCodePreview(genConfigFormData: Ref<GenConfigForm>) {
previewTypeOptions.value = typeOptions;
previewTypes.value = [...typeOptions];
/**
* 构建树
*/
// 构建树
const tree = buildFileTree(previewList);
treeData.value = tree?.children ? [...tree.children] : [];
/**
* 选中第一个叶子节点
*/
// 选中第一个叶子节点
const firstLeaf = findFirstLeaf(tree);
if (firstLeaf) {
code.value = firstLeaf.content || "";
currentFileKey.value = firstLeaf.key || "";
await nextTick();
fileTreeRef.value?.setCurrentKey?.(currentFileKey.value);
}
return previewList;
@@ -93,7 +86,7 @@ export function useCodePreview(genConfigFormData: Ref<GenConfigForm>) {
// 过滤条件变了之后,如果当前选中的文件还在就继续高亮,否则选第一个
watch(
() => filteredTreeData.value,
async (nodes) => {
(nodes) => {
if (!nodes.length) {
currentFileKey.value = "";
code.value = "";
@@ -102,8 +95,6 @@ export function useCodePreview(genConfigFormData: Ref<GenConfigForm>) {
if (currentFileKey.value) {
const leaf = findLeafByKey(nodes, currentFileKey.value);
if (leaf) {
await nextTick();
fileTreeRef.value?.setCurrentKey?.(currentFileKey.value);
return;
}
}
@@ -111,8 +102,6 @@ export function useCodePreview(genConfigFormData: Ref<GenConfigForm>) {
if (first) {
code.value = first.content || "";
currentFileKey.value = first.key || "";
await nextTick();
fileTreeRef.value?.setCurrentKey?.(currentFileKey.value);
}
},
{ immediate: true }
@@ -126,7 +115,6 @@ export function useCodePreview(genConfigFormData: Ref<GenConfigForm>) {
previewTypeOptions,
code,
currentFileKey,
fileTreeRef,
handlePreview,
handleFileTreeNodeClick,
handleCopyCode,

View File

@@ -75,8 +75,6 @@ export function useLocalWrite(genConfigFormData: Ref<GenConfigForm>) {
writeDialog.visible = false;
}
// ---- 内部工具函数 ----
/**
* 判断生成项写入前端还是后端根目录
*/