mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-28 15:09:57 +00:00
feat: 新增AI模型配置功能,优化前端组件与代码规范
1. 新增Redis AI_MODEL_CONFIG枚举与后端AI模型配置CRUD接口 2. 升级vue-img-cutter到3.1.1版本,更新前端依赖 3. 重构前端多处ElMessage提示逻辑,统一由拦截器处理 4. 替换ElDrawer为FaDrawer组件,统一弹窗组件库 5. 重构文章详情、评论组件,新增租户切换器与AI配置面板 6. 优化代码生成模板、菜单树表格逻辑与代码高亮样式 7. 修复前端路由与组件命名问题,更新快速入口配置
This commit is contained in:
@@ -295,7 +295,6 @@ import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue"
|
||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||
import type FaForm from "@/components/forms/fa-form/index.vue";
|
||||
import FaStatusTag from "@/components/others/fa-status-tag/index.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
defineOptions({ name: "Email" });
|
||||
|
||||
@@ -1004,7 +1003,7 @@ async function handleSubmitSend() {
|
||||
sendSubmitting.value = true;
|
||||
try {
|
||||
await EmailAPI.sendEmail(sendFormData.value);
|
||||
ElMessage.success("发送成功");
|
||||
// 成功 / 失败提示由 axios 拦截器统一处理
|
||||
sendVisible.value = false;
|
||||
} catch {
|
||||
/* ignore */
|
||||
@@ -1057,7 +1056,7 @@ async function handleSubmitTest() {
|
||||
config_id: testFormData.value.config_id,
|
||||
to_email: testFormData.value.to_email,
|
||||
});
|
||||
ElMessage.success("测试邮件已发送");
|
||||
// 成功 / 失败提示由 axios 拦截器统一处理
|
||||
testVisible.value = false;
|
||||
} catch {
|
||||
/* ignore */
|
||||
|
||||
@@ -619,14 +619,13 @@ async function deletePkgRow(id: number) {
|
||||
|
||||
async function togglePkgStatus(row: PackageTable) {
|
||||
const newStatus = row.status === 0 ? 1 : 0;
|
||||
const label = newStatus === 0 ? "启用" : "禁用";
|
||||
try {
|
||||
await confirmToggleStatus(newStatus);
|
||||
await PackageAPI.batchPackageStatus({ ids: [row.id!], status: Number(newStatus) });
|
||||
ElMessage.success(`${label}成功`);
|
||||
// 成功提示由 axios 拦截器统一处理
|
||||
await refreshData();
|
||||
} catch {
|
||||
// 用户取消
|
||||
// 用户取消 / 接口错误(已由拦截器提示)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,7 +685,6 @@ async function handleSaveMenus() {
|
||||
menuSaveLoading.value = true;
|
||||
try {
|
||||
await PackageAPI.setPackageMenus(currentMenuPkgId.value, checkedIds);
|
||||
ElMessage.success("菜单权限保存成功");
|
||||
menuDialogVisible.value = false;
|
||||
} catch {
|
||||
// 错误由全局拦截处理
|
||||
|
||||
@@ -259,15 +259,7 @@ import PluginAPI, { type PluginForm, type PluginTable } from "@/api/module_platf
|
||||
import { useAuth } from "@/hooks/core/useAuth";
|
||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||
import {
|
||||
ElTag,
|
||||
ElMessage,
|
||||
ElButton,
|
||||
ElIcon,
|
||||
ElDropdown,
|
||||
ElDropdownMenu,
|
||||
ElDropdownItem,
|
||||
} from "element-plus";
|
||||
import { ElTag, ElButton, ElIcon, ElDropdown, ElDropdownMenu, ElDropdownItem } from "element-plus";
|
||||
import { Plus, Edit, Delete, MoreFilled } from "@element-plus/icons-vue";
|
||||
|
||||
defineOptions({
|
||||
@@ -421,10 +413,10 @@ async function doInstall(row: PluginTable) {
|
||||
if (!row.id) return;
|
||||
try {
|
||||
await PluginAPI.install(row.id);
|
||||
ElMessage.success("安装成功");
|
||||
// 成功 / 失败提示由 axios 拦截器统一处理
|
||||
row.installed = true;
|
||||
} catch {
|
||||
ElMessage.error("安装失败");
|
||||
/* 接口错误已由拦截器提示 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,10 +424,10 @@ async function doUninstall(row: PluginTable) {
|
||||
if (!row.id) return;
|
||||
try {
|
||||
await PluginAPI.uninstall(row.id);
|
||||
ElMessage.success("卸载成功");
|
||||
// 成功 / 失败提示由 axios 拦截器统一处理
|
||||
row.installed = false;
|
||||
} catch {
|
||||
ElMessage.error("卸载失败");
|
||||
/* 接口错误已由拦截器提示 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,10 +435,10 @@ async function doToggle(row: PluginTable) {
|
||||
if (!row.id) return;
|
||||
try {
|
||||
await PluginAPI.toggle(row.id);
|
||||
ElMessage.success(row.status === 0 ? "已禁用" : "已启用");
|
||||
// 成功 / 失败提示由 axios 拦截器统一处理
|
||||
row.status = row.status === 0 ? 1 : 0;
|
||||
} catch {
|
||||
ElMessage.error("操作失败");
|
||||
/* 接口错误已由拦截器提示 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,9 @@
|
||||
:show-tip="true"
|
||||
:enable-preview="true"
|
||||
:enable-crop="true"
|
||||
v-bind="brandCropBind('tenant_logo')"
|
||||
crop-dialog-title="裁剪站点 Logo"
|
||||
crop-inner-title="调整 Logo"
|
||||
crop-preview-title="预览"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
@@ -152,7 +154,9 @@
|
||||
:show-tip="true"
|
||||
:enable-preview="true"
|
||||
:enable-crop="true"
|
||||
v-bind="brandCropBind('tenant_favicon')"
|
||||
crop-dialog-title="裁剪网站图标"
|
||||
crop-inner-title="调整图标"
|
||||
crop-preview-title="预览"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
@@ -166,7 +170,9 @@
|
||||
:show-tip="true"
|
||||
:enable-preview="true"
|
||||
:enable-crop="true"
|
||||
v-bind="brandCropBind('tenant_login_bg')"
|
||||
crop-dialog-title="裁剪登录背景"
|
||||
crop-inner-title="调整背景图"
|
||||
crop-preview-title="预览"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
@@ -214,6 +220,7 @@ import TenantAPI, {
|
||||
type TenantTable,
|
||||
type TenantUpdateForm,
|
||||
} from "@/api/module_platform/tenant";
|
||||
import PackageAPI from "@/api/module_platform/package";
|
||||
import { useAuth } from "@/hooks/core/useAuth";
|
||||
import { renderTableOperationCell, type TableOperationAction, resolveStatusColumns } from "@utils";
|
||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||
@@ -221,7 +228,7 @@ import type FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||
import type FaForm from "@/components/forms/fa-form/index.vue";
|
||||
import { ElMessage, ElTabs, ElTabPane, ElForm, ElFormItem, ElRow, ElCol } from "element-plus";
|
||||
import { h, ref } from "vue";
|
||||
import { h, ref, computed, onMounted } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "Tenant",
|
||||
@@ -365,7 +372,7 @@ async function toggleTenantStatus(id: number) {
|
||||
}
|
||||
await refreshData();
|
||||
} catch {
|
||||
ElMessage.error("状态切换失败");
|
||||
/* 接口错误已由拦截器提示 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,44 +558,6 @@ const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||
const submitLoading = ref(false);
|
||||
const tenantFormRenderKey = ref(0);
|
||||
|
||||
function brandCropBind(key: string) {
|
||||
switch (key) {
|
||||
case "tenant_favicon":
|
||||
return {
|
||||
cropCutWidth: 64,
|
||||
cropCutHeight: 64,
|
||||
cropBoxWidth: 380,
|
||||
cropBoxHeight: 320,
|
||||
cropDialogTitle: "裁剪网站图标",
|
||||
cropInnerTitle: "调整图标",
|
||||
cropPreviewTitle: "预览",
|
||||
};
|
||||
case "tenant_logo":
|
||||
return {
|
||||
cropCutWidth: 320,
|
||||
cropCutHeight: 96,
|
||||
cropBoxWidth: 520,
|
||||
cropBoxHeight: 360,
|
||||
cropDialogTitle: "裁剪站点 Logo",
|
||||
cropInnerTitle: "调整 Logo",
|
||||
cropPreviewTitle: "预览",
|
||||
};
|
||||
case "tenant_login_bg":
|
||||
return {
|
||||
cropCutWidth: 960,
|
||||
cropCutHeight: 540,
|
||||
cropBoxWidth: 560,
|
||||
cropBoxHeight: 380,
|
||||
cropDialogTitle: "裁剪登录背景",
|
||||
cropInnerTitle: "调整背景图",
|
||||
cropPreviewTitle: "预览",
|
||||
cropFileType: "jpeg" as const,
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdd() {
|
||||
createLoading.value = true;
|
||||
try {
|
||||
@@ -628,7 +597,27 @@ async function handleCloseDialog() {
|
||||
|
||||
const activeTab = ref("basic");
|
||||
|
||||
const basicFormItems: FormItem[] = [
|
||||
const packageOptions = ref<{ label: string; value: number }[]>([]);
|
||||
const packageLoading = ref(false);
|
||||
|
||||
async function fetchPackageOptions() {
|
||||
packageLoading.value = true;
|
||||
try {
|
||||
const res = await PackageAPI.listPackage({ page_no: 1, page_size: 100 });
|
||||
const list = (res.data?.data?.items ?? res.data?.data ?? []) as { id: number; name: string }[];
|
||||
packageOptions.value = list.map((p) => ({ label: p.name, value: p.id }));
|
||||
} catch {
|
||||
packageOptions.value = [];
|
||||
} finally {
|
||||
packageLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchPackageOptions();
|
||||
});
|
||||
|
||||
const basicFormItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
label: "租户名称",
|
||||
key: "name",
|
||||
@@ -659,10 +648,16 @@ const basicFormItems: FormItem[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "关联套餐ID",
|
||||
label: "关联套餐",
|
||||
key: "package_id",
|
||||
type: "number",
|
||||
props: { placeholder: "选填", min: 1, style: { width: "100%" } },
|
||||
type: "select",
|
||||
props: {
|
||||
placeholder: "请选择套餐",
|
||||
options: packageOptions.value,
|
||||
loading: packageLoading.value,
|
||||
clearable: true,
|
||||
style: { width: "100%" },
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
@@ -726,7 +721,7 @@ const basicFormItems: FormItem[] = [
|
||||
valueFormat: "YYYY-MM-DD HH:mm:ss",
|
||||
},
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
const websiteFormItems: FormItem[] = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user