280 lines
8.5 KiB
TypeScript
280 lines
8.5 KiB
TypeScript
import { Method } from "alova";
|
||
import { alovaInstance } from "@/utils/alova/alova-iview";
|
||
import conf from "@/assets/config/host.config";
|
||
import { IObj } from "@/types/Basics";
|
||
import {
|
||
ArticleEditVo,
|
||
ArticlePageItem,
|
||
ArticlePageQuery,
|
||
ArticleSaveRequest,
|
||
AttachmentVo,
|
||
AuthUserInfo,
|
||
BannerVo,
|
||
CategoryTreeNode,
|
||
DeleteRequest,
|
||
LinkVo,
|
||
PageResult,
|
||
SchoolCard,
|
||
SchoolOption,
|
||
SiteVo,
|
||
ThemeVo,
|
||
} from "@/types/cms";
|
||
|
||
/**
|
||
* CMS 管理端 API(手写 class 风格,同 Notice front 约定)。
|
||
* baseURL = conf.serviceHost(dev 为空串走 vite 代理;生产 = 源 + /yschcms 上下文)。
|
||
*/
|
||
|
||
const AUTH = "/api/auth";
|
||
const BASE = "/api/base";
|
||
const SCHOOL = "/api/school";
|
||
const SITE = "/api/site";
|
||
const CATEGORY = "/api/category";
|
||
const ARTICLE = "/api/article";
|
||
const ATTACHMENT = "/api/attachment";
|
||
const BANNER = "/api/banner";
|
||
const LINK = "/api/link";
|
||
const THEME = "/api/theme";
|
||
|
||
/** GET(query 参数) */
|
||
function get<T>(url: string, params?: IObj): Method {
|
||
return alovaInstance.Get<T>(url, { params });
|
||
}
|
||
|
||
/** POST JSON(@RequestBody);params 为追加到 URL 的 query 参数(alova Post config 同 Get) */
|
||
function postJSON<T>(url: string, data?: unknown, params?: IObj): Method {
|
||
return alovaInstance.Post<T>(url, data, {
|
||
headers: { "Content-Type": "application/json;charset=UTF-8" },
|
||
params,
|
||
});
|
||
}
|
||
|
||
/** 上传地址(wangEditor customUpload / Upload 组件 action 直链,不经 alova) */
|
||
export function uploadUrl(): string {
|
||
return `${conf.isMock ? "" : conf.serviceHost}/api/attachment/upload`;
|
||
}
|
||
|
||
/** 门户预览基地址({serviceHost}/portal/{siteCode}) */
|
||
export function portalBase(siteCode: string): string {
|
||
return `${conf.isMock ? "" : conf.serviceHost}/portal/${siteCode}`;
|
||
}
|
||
|
||
/**
|
||
* 后端下发的站内资产路径补 serviceHost 前缀(如主题截图 /api/theme/{name}/asset/screenshot.png)。
|
||
* 生产 context-path=/yschcms,裸 /api 绝对路径会打到网关根上 404(破图);
|
||
* dev/mock 下 serviceHost 为空串,拼接后与原路径一致不受影响。
|
||
*/
|
||
export function assetUrl(url: string): string {
|
||
if (!url || /^(https?:|data:|blob:)/i.test(url)) return url;
|
||
return `${conf.isMock ? "" : conf.serviceHost}${url}`;
|
||
}
|
||
|
||
class AuthApi {
|
||
/** 登录态:用户 + 可管理站点 + 是否超管 */
|
||
getUser() {
|
||
return get<AuthUserInfo>(`${AUTH}/user`);
|
||
}
|
||
}
|
||
|
||
/** 平台代理域(Notice BaseApiController 同款契约) */
|
||
class BaseApi {
|
||
/** 当前登录用户信息(CAS 会话注入,头像/姓名用于 layout) */
|
||
getUserInfo() {
|
||
return get<any>(`${BASE}/GetUserInfo`);
|
||
}
|
||
|
||
/** 当前用户已授权的模块列表(左侧菜单树) */
|
||
getUserMod() {
|
||
return get<any[]>(`${BASE}/GetUserMod`);
|
||
}
|
||
}
|
||
|
||
class SchoolApi {
|
||
/** 学校工作台列表(学校卡片 = 顶层站点聚合,docs/09 §3.3) */
|
||
list() {
|
||
return get<SchoolCard[]>(`${SCHOOL}/list`);
|
||
}
|
||
|
||
/** UserBase 学校选项(添加学校绑定用,仅超管;平台不可达为空数组) */
|
||
options() {
|
||
return get<SchoolOption[]>(`${SCHOOL}/options`);
|
||
}
|
||
}
|
||
|
||
class SiteApi {
|
||
list() {
|
||
return get<SiteVo[]>(`${SITE}/list`);
|
||
}
|
||
|
||
/** 入参对齐 SiteSaveRequest(themeParams 服务端合并进 config_json.theme_params,方案 §5.3) */
|
||
save(data: Partial<SiteVo> & { themeParams?: Record<string, string> }) {
|
||
return postJSON<SiteVo>(`${SITE}/save`, data);
|
||
}
|
||
}
|
||
|
||
class CategoryApi {
|
||
tree(siteId: number) {
|
||
return get<CategoryTreeNode[]>(`${CATEGORY}/tree`, { siteId });
|
||
}
|
||
|
||
save(data: Partial<CategoryTreeNode>) {
|
||
return postJSON<CategoryTreeNode>(`${CATEGORY}/save`, data);
|
||
}
|
||
|
||
delete(data: DeleteRequest) {
|
||
return postJSON<void>(`${CATEGORY}/delete`, data);
|
||
}
|
||
|
||
/** 子栏目排序(UserBase SortChildren 同语义:按传入顺序落库 sortNo=1..n) */
|
||
sort(data: { siteId: number; parentId: number; ids: number[] }) {
|
||
return postJSON<void>(`${CATEGORY}/sort`, data);
|
||
}
|
||
}
|
||
|
||
class ArticleApi {
|
||
page(query: ArticlePageQuery) {
|
||
return get<PageResult<ArticlePageItem>>(`${ARTICLE}/page`, { ...query });
|
||
}
|
||
|
||
/** 编辑回显 */
|
||
get(siteId: number, id: number) {
|
||
return get<ArticleEditVo>(`${ARTICLE}/get`, { siteId, id });
|
||
}
|
||
|
||
/** 保存草稿(content 服务端 jsoup 清洗) */
|
||
save(data: ArticleSaveRequest) {
|
||
return postJSON<number>(`${ARTICLE}/save`, data);
|
||
}
|
||
|
||
/** 发布(publishTime 为未来时间即定时发布) */
|
||
publish(siteId: number, id: number) {
|
||
return postJSON<void>(`${ARTICLE}/publish/${id}`, undefined, { siteId });
|
||
}
|
||
|
||
offline(siteId: number, id: number) {
|
||
return postJSON<void>(`${ARTICLE}/offline/${id}`, undefined, { siteId });
|
||
}
|
||
|
||
topmost(siteId: number, id: number, enable: boolean) {
|
||
return postJSON<void>(`${ARTICLE}/topmost/${id}`, undefined, { siteId, enable });
|
||
}
|
||
|
||
delete(data: DeleteRequest) {
|
||
return postJSON<void>(`${ARTICLE}/delete`, data);
|
||
}
|
||
}
|
||
|
||
class AttachmentApi {
|
||
page(siteId: number, orphanOnly: boolean, page: number, pageSize: number) {
|
||
return get<PageResult<AttachmentVo> & { unusedCount: number }>(`${ATTACHMENT}/page`, {
|
||
siteId,
|
||
orphanOnly,
|
||
page,
|
||
pageSize,
|
||
});
|
||
}
|
||
|
||
delete(data: DeleteRequest) {
|
||
return postJSON<void>(`${ATTACHMENT}/delete`, data);
|
||
}
|
||
}
|
||
|
||
class BannerApi {
|
||
list(siteId: number, position?: string) {
|
||
return get<BannerVo[]>(`${BANNER}/list`, { siteId, position });
|
||
}
|
||
|
||
save(data: Partial<BannerVo>) {
|
||
return postJSON<BannerVo>(`${BANNER}/save`, data);
|
||
}
|
||
|
||
delete(data: DeleteRequest) {
|
||
return postJSON<void>(`${BANNER}/delete`, data);
|
||
}
|
||
}
|
||
|
||
class LinkApi {
|
||
list(siteId: number) {
|
||
return get<LinkVo[]>(`${LINK}/list`, { siteId });
|
||
}
|
||
|
||
save(data: Partial<LinkVo>) {
|
||
return postJSON<LinkVo>(`${LINK}/save`, data);
|
||
}
|
||
|
||
delete(data: DeleteRequest) {
|
||
return postJSON<void>(`${LINK}/delete`, data);
|
||
}
|
||
}
|
||
|
||
class ThemeApi {
|
||
/** 可用主题列表(含 params schema,建站向导/站点编辑共用,方案 §5.2) */
|
||
list() {
|
||
return get<ThemeVo[]>(`${THEME}/list`);
|
||
}
|
||
|
||
/** W1:上传合规 zip(multipart,超管;免重启立即注册) */
|
||
upload(file: File) {
|
||
const fd = new FormData();
|
||
fd.append("file", file);
|
||
return alovaInstance.Post<ThemeVo>(`${THEME}/upload`, fd);
|
||
}
|
||
|
||
/** W1:从现有包派生新皮肤(复制包体+改名) */
|
||
derive(source: string, name: string) {
|
||
return postJSON<ThemeVo>(`${THEME}/derive`, { source, name });
|
||
}
|
||
|
||
/** W1:编辑外部包 theme.json 的 title/参数 schema */
|
||
updateMeta(name: string, data: { title?: string; params?: unknown[] }) {
|
||
return alovaInstance.Put<ThemeVo>(`${THEME}/${name}/meta`, data, {
|
||
headers: { "Content-Type": "application/json;charset=UTF-8" },
|
||
});
|
||
}
|
||
|
||
/** W1:删除外部包(被用站点拒绝;内置不可删) */
|
||
remove(name: string) {
|
||
return alovaInstance.Delete<{ ok: boolean }>(`${THEME}/${name}`);
|
||
}
|
||
|
||
/** W1:热重载(运维直放目录后生效) */
|
||
reload() {
|
||
return postJSON<ThemeVo[]>(`${THEME}/reload`);
|
||
}
|
||
}
|
||
|
||
/** 主题预览 iframe URL(方案 §5.5:/api/theme/preview,CAS 会话同源,参数 p_ 前缀)。
|
||
* W4:内容源站点参数 sitePath(嵌套路径语义,后端 siteSnapshotByPath 解析)。 */
|
||
export function themePreviewUrl(sitePath: string, theme: string, themeParams: Record<string, string> = {}): string {
|
||
const params = new URLSearchParams({ sitePath, theme, page: "home" });
|
||
Object.keys(themeParams).forEach(k => params.set(`p_${k}`, themeParams[k]));
|
||
return `${conf.isMock ? "" : conf.serviceHost}/api/theme/preview?${params.toString()}`;
|
||
}
|
||
|
||
export const authApi = new AuthApi();
|
||
export const baseApi = new BaseApi();
|
||
export const schoolApi = new SchoolApi();
|
||
export const siteApi = new SiteApi();
|
||
export const categoryApi = new CategoryApi();
|
||
export const articleApi = new ArticleApi();
|
||
export const attachmentApi = new AttachmentApi();
|
||
export const bannerApi = new BannerApi();
|
||
export const linkApi = new LinkApi();
|
||
export const themeApi = new ThemeApi();
|
||
|
||
export default {
|
||
authApi,
|
||
baseApi,
|
||
schoolApi,
|
||
siteApi,
|
||
categoryApi,
|
||
articleApi,
|
||
attachmentApi,
|
||
bannerApi,
|
||
linkApi,
|
||
themeApi,
|
||
uploadUrl,
|
||
portalBase,
|
||
themePreviewUrl,
|
||
};
|