fix: align portal queries and super auth
This commit is contained in:
@@ -25,6 +25,15 @@
|
||||
- Go tests: `go test ./...` (some service tests exist under `backend/app/services/*_test.go`).
|
||||
- Frontend: build + lint are the main checks (`npm -C frontend/superadmin run build && npm -C frontend/superadmin run lint`).
|
||||
|
||||
## Planning Requirements
|
||||
|
||||
- Before any non-trivial development work, first produce a complete plan document (tasks, sequence, dependencies, and acceptance criteria) and get confirmation to proceed.
|
||||
- Plan format MUST follow spec-kit `plan-template.md` structure (Summary, Technical Context, Constitution Check, Project Structure, Plan Phases, Tasks, Dependencies, Acceptance Criteria, Risks). `docs/plan.md` MUST include a task breakdown list.
|
||||
- Use the local template `docs/templates/plan-template.md` as the source of truth (no web fetch required).
|
||||
- Use `docs/plan.md` as the active plan for the current phase.
|
||||
- When the phase completes, move `docs/plan.md` to `docs/plans/<date>.md` for archival.
|
||||
- After archiving, clear `docs/plan.md` to await the next plan.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Commits generally follow a simple convention like `feat: ...` / `fix: ...` / `chore: ...` (keep subject short and imperative).
|
||||
|
||||
@@ -5,5 +5,7 @@ func (r *Routes) Path() string {
|
||||
}
|
||||
|
||||
func (r *Routes) Middlewares() []any {
|
||||
return []any{}
|
||||
return []any{
|
||||
r.middlewares.SuperAuth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ type Content struct{}
|
||||
// @Produce json
|
||||
// @Param keyword query string false "Search keyword"
|
||||
// @Param genre query string false "Genre"
|
||||
// @Param tenant_id query int64 false "Filter by creator"
|
||||
// @Param tenant_id query int64 false "Filter by tenant"
|
||||
// @Param author_id query int64 false "Filter by author"
|
||||
// @Param sort query string false "Sort order" Enums(latest, hot, price_asc)
|
||||
// @Param page query int false "Page number"
|
||||
// @Success 200 {object} requests.Pager{items=[]dto.ContentItem}
|
||||
|
||||
@@ -496,13 +496,13 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
router.Post("/t/:tenantCode/v1/me/favorites"[len(r.Path()):], Func2(
|
||||
r.user.AddFavorite,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
QueryParam[int64]("contentId"),
|
||||
QueryParam[int64]("content_id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/me/likes -> user.AddLike")
|
||||
router.Post("/t/:tenantCode/v1/me/likes"[len(r.Path()):], Func2(
|
||||
r.user.AddLike,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
QueryParam[int64]("contentId"),
|
||||
QueryParam[int64]("content_id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/me/notifications/:id<int>/read -> user.MarkNotificationRead")
|
||||
router.Post("/t/:tenantCode/v1/me/notifications/:id<int>/read"[len(r.Path()):], Func2(
|
||||
|
||||
@@ -78,7 +78,7 @@ func (t *Tenant) Get(ctx fiber.Ctx, user *models.User, id int64) (*dto.TenantPro
|
||||
if tenantID > 0 && id != tenantID {
|
||||
return nil, errorx.ErrForbidden.WithMsg("租户不匹配")
|
||||
}
|
||||
return services.Tenant.GetPublicProfile(ctx, tenantID, uid)
|
||||
return services.Tenant.GetPublicProfile(ctx, id, uid)
|
||||
}
|
||||
|
||||
// Follow a tenant
|
||||
|
||||
@@ -165,10 +165,10 @@ func (u *User) Favorites(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, e
|
||||
// @Tags UserCenter
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param contentId query int64 true "Content ID"
|
||||
// @Param content_id query int64 true "Content ID"
|
||||
// @Success 200 {string} string "Added"
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind contentId query
|
||||
// @Bind contentId query key(content_id)
|
||||
func (u *User) AddFavorite(ctx fiber.Ctx, user *models.User, contentId int64) error {
|
||||
tenantID := getTenantID(ctx)
|
||||
return services.Content.AddFavorite(ctx, tenantID, user.ID, contentId)
|
||||
@@ -214,10 +214,10 @@ func (u *User) Likes(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, error
|
||||
// @Tags UserCenter
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param contentId query int64 true "Content ID"
|
||||
// @Param content_id query int64 true "Content ID"
|
||||
// @Success 200 {string} string "Liked"
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind contentId query
|
||||
// @Bind contentId query key(content_id)
|
||||
func (u *User) AddLike(ctx fiber.Ctx, user *models.User, contentId int64) error {
|
||||
tenantID := getTenantID(ctx)
|
||||
return services.Content.AddLike(ctx, tenantID, user.ID, contentId)
|
||||
|
||||
@@ -3902,10 +3902,17 @@ const docTemplate = `{
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Filter by creator",
|
||||
"description": "Filter by tenant",
|
||||
"name": "tenant_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Filter by author",
|
||||
"name": "author_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"latest",
|
||||
@@ -5576,7 +5583,7 @@ const docTemplate = `{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Content ID",
|
||||
"name": "contentId",
|
||||
"name": "content_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
@@ -5718,7 +5725,7 @@ const docTemplate = `{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Content ID",
|
||||
"name": "contentId",
|
||||
"name": "content_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
|
||||
@@ -3896,10 +3896,17 @@
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Filter by creator",
|
||||
"description": "Filter by tenant",
|
||||
"name": "tenant_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Filter by author",
|
||||
"name": "author_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"latest",
|
||||
@@ -5570,7 +5577,7 @@
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Content ID",
|
||||
"name": "contentId",
|
||||
"name": "content_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
@@ -5712,7 +5719,7 @@
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Content ID",
|
||||
"name": "contentId",
|
||||
"name": "content_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
|
||||
@@ -6171,11 +6171,16 @@ paths:
|
||||
in: query
|
||||
name: genre
|
||||
type: string
|
||||
- description: Filter by creator
|
||||
- description: Filter by tenant
|
||||
format: int64
|
||||
in: query
|
||||
name: tenant_id
|
||||
type: integer
|
||||
- description: Filter by author
|
||||
format: int64
|
||||
in: query
|
||||
name: author_id
|
||||
type: integer
|
||||
- description: Sort order
|
||||
enum:
|
||||
- latest
|
||||
@@ -7273,7 +7278,7 @@ paths:
|
||||
- description: Content ID
|
||||
format: int64
|
||||
in: query
|
||||
name: contentId
|
||||
name: content_id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
@@ -7367,7 +7372,7 @@ paths:
|
||||
- description: Content ID
|
||||
format: int64
|
||||
in: query
|
||||
name: contentId
|
||||
name: content_id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
|
||||
157
docs/plan.md
Normal file
157
docs/plan.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Implementation Plan: 多租户隔离优先 + 契约对齐
|
||||
|
||||
**Branch**: `N/A` | **Date**: 2026-01-23 | **Spec**: `docs/review_report.md`
|
||||
**Input**: 生产评估与当前系统缺口确认
|
||||
|
||||
**Note**: 本计划遵循 `docs/templates/plan-template.md`。
|
||||
|
||||
## Summary
|
||||
|
||||
本阶段聚焦多租户强隔离、鉴权与权限完善、超管接口补齐,并同步前后端契约(路由前缀、参数命名、ID 类型)以形成最小可运行闭环。
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Go 1.22, Node 20+ (Vite), Vue 3
|
||||
**Primary Dependencies**: Fiber, GORM-Gen, PrimeVue
|
||||
**Storage**: PostgreSQL + Redis
|
||||
**Testing**: `go test ./...`, `npm -C frontend/superadmin run build && npm -C frontend/superadmin run lint`
|
||||
**Target Platform**: Linux server (Docker ready)
|
||||
**Project Type**: Web application (backend + frontend)
|
||||
**Performance Goals**: N/A (遵循现有服务要求)
|
||||
**Constraints**: 多租户隔离、鉴权强制、生成文件不可手改
|
||||
**Scale/Scope**: 现有核心业务与超管链路可跑通
|
||||
|
||||
## Constitution Check
|
||||
|
||||
- 必须遵循 `backend/llm.txt`:DTO 注释、路由规范、服务层约束、生成流程。
|
||||
- `*.gen.go`、`backend/docs/docs.go` 禁止手改,必须通过 `atomctl` 生成。
|
||||
- Controller 仅做绑定与调用,`tenantID/userID` 从 Controller 传入 Service。
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
docs/
|
||||
├── plan.md
|
||||
└── templates/
|
||||
└── plan-template.md
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
backend/
|
||||
├── app/
|
||||
│ ├── http/
|
||||
│ ├── middlewares/
|
||||
│ └── services/
|
||||
├── database/
|
||||
└── docs/
|
||||
|
||||
frontend/
|
||||
├── portal/
|
||||
└── superadmin/
|
||||
```
|
||||
|
||||
**Structure Decision**: 采用现有 `backend/` + `frontend/` 双端结构。
|
||||
|
||||
## Plan Phases
|
||||
|
||||
### Phase 1: 影响面确认与现有能力盘点
|
||||
- 明确 `/t/:tenantCode/v1` 作为统一路由前缀。
|
||||
- 盘点必须补齐的超管接口与核心业务链路。
|
||||
|
||||
### Phase 2: 多租户强隔离(后端)
|
||||
- 新增 tenant 解析中间件,注入 `tenantID` 到上下文。
|
||||
- 所有业务路由切换为 `/t/:tenantCode/v1` 前缀。
|
||||
- Service 层所有查询显式带 `tenantID` 条件。
|
||||
|
||||
### Phase 3: 鉴权与权限(后端)
|
||||
- 拆分 `AuthOptional` / `AuthRequired`。
|
||||
- 超管路由增加 `super_admin` 角色校验。
|
||||
- 完成 `Super.Login` / `CheckToken`。
|
||||
|
||||
### Phase 4: 超管接口实现(后端)
|
||||
- 补齐超管统计、列表、详情接口实现与 DTO 映射。
|
||||
- 分页接口统一使用 `requests.Pager`。
|
||||
|
||||
### Phase 5: 契约对齐(前后端)
|
||||
- API 前缀与路由基座统一。
|
||||
- 统一参数命名与 ID 类型。
|
||||
- 更新前端调用与路由配置。
|
||||
|
||||
### Phase 6: 生成与回归
|
||||
- `atomctl gen route` / `atomctl gen provider` / `atomctl swag init`。
|
||||
- 关键流程自测与最小回归。
|
||||
|
||||
## Tasks
|
||||
|
||||
**Format**: `[ID] [P?] [Story] Description`
|
||||
|
||||
### Phase 1: Foundational
|
||||
- [ ] T001 [US0] 盘点现有路由与接口空实现(`backend/app/http/*`, `backend/app/services/*`)
|
||||
- [ ] T002 [US0] 确认多租户前缀与 tenant 解析策略(`/t/:tenantCode/v1`)
|
||||
|
||||
### Phase 2: User Story 1 - 多租户强隔离 (P1)
|
||||
**Goal**: 路由前缀与服务查询强制 tenant 隔离。
|
||||
|
||||
- [ ] T010 [US1] 新增 tenant 解析中间件(`backend/app/middlewares/tenant_resolver.go`)
|
||||
- [ ] T011 [US1] 调整 HTTP 模块路由前缀与注解(`backend/app/http/**/routes.manual.go` + controller 注解)
|
||||
- [ ] T012 [US1] Controller 中提取 `tenantID` 并显式传入 Service(`backend/app/http/**`)
|
||||
- [ ] T013 [US1] Service 查询统一加 `tenantID` 条件(`backend/app/services/*`)
|
||||
|
||||
**Checkpoint**: 所有业务路由在 `/t/:tenantCode/v1` 下可访问,查询带租户隔离。
|
||||
|
||||
### Phase 3: User Story 2 - 鉴权与权限 (P1)
|
||||
**Goal**: 受保护接口强制鉴权,超管角色校验。
|
||||
|
||||
- [ ] T020 [US2] 拆分并实现 `AuthOptional` / `AuthRequired`(`backend/app/middlewares/*`)
|
||||
- [ ] T021 [US2] 超管路由注入角色校验中间件(`backend/app/http/super/v1/routes.manual.go`)
|
||||
- [ ] T022 [US2] 完成 `Super.Login` / `CheckToken`(`backend/app/services/super.go`)
|
||||
|
||||
**Checkpoint**: 受保护接口必须授权;超管接口角色有效。
|
||||
|
||||
### Phase 4: User Story 3 - 超管接口补齐 (P1)
|
||||
**Goal**: 超管核心页面可用,不再空实现。
|
||||
|
||||
- [ ] T030 [US3] 统计/列表/详情接口实现与 DTO 映射(`backend/app/services/super.go`)
|
||||
- [ ] T031 [US3] 统一分页返回 `requests.Pager`(`backend/app/http/super/v1/*`)
|
||||
|
||||
**Checkpoint**: 超管关键接口返回有效数据。
|
||||
|
||||
### Phase 5: User Story 4 - 契约对齐 (P1)
|
||||
**Goal**: 前后端路由、参数、ID 类型一致。
|
||||
|
||||
- [ ] T040 [US4] 统一后端路由前缀与 `@Router` 参数(`backend/app/http/**`)
|
||||
- [ ] T041 [US4] 统一 API 参数命名(后端 `@Bind` 与前端调用匹配)
|
||||
- [ ] T042 [US4] ID 类型调整与前端适配(`backend/app/http/**`, `frontend/portal/*`, `frontend/superadmin/*`)
|
||||
|
||||
**Checkpoint**: 前后端关键链路可跑通。
|
||||
|
||||
### Phase 6: Generation & Regression
|
||||
- [ ] T050 [US5] 生成路由与 provider(`atomctl gen route`, `atomctl gen provider`)
|
||||
- [ ] T051 [US5] 生成 swagger(`atomctl swag init`)
|
||||
- [ ] T052 [US5] 回归测试(`go test ./...`,必要时前端 build/lint)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 → Phase 6。
|
||||
- Phase 2 完成后,Phase 3/4 可并行推进,但路由前缀需先统一。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- 多租户路由与解析生效,查询强制带 `tenantID`。
|
||||
- 受保护接口必须鉴权通过;超管接口具备角色校验。
|
||||
- 超管核心页面可调用对应接口获取数据。
|
||||
- 前后端接口路径与参数命名一致。
|
||||
- 生成文件已更新且未手改。
|
||||
|
||||
## Risks
|
||||
|
||||
- 路由前缀变更影响全部前端调用。
|
||||
- ID 类型调整涉及 DTO/前端展示与校验,需要逐模块推进。
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
无。
|
||||
124
docs/templates/plan-template.md
vendored
Normal file
124
docs/templates/plan-template.md
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# Implementation Plan: [FEATURE]
|
||||
|
||||
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
[Extract from feature spec: primary requirement + technical approach from research]
|
||||
|
||||
## Technical Context
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the content in this section with the technical details
|
||||
for the project. The structure here is presented in advisory capacity to guide
|
||||
the iteration process.
|
||||
-->
|
||||
|
||||
**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]
|
||||
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
|
||||
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
|
||||
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
|
||||
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
|
||||
**Project Type**: [single/web/mobile - determines source structure]
|
||||
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
|
||||
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
|
||||
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
[Gates determined based on constitution file]
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/[###-feature]/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
|
||||
for this feature. Delete unused options and expand the chosen structure with
|
||||
real paths (e.g., apps/admin, packages/something). The delivered plan must
|
||||
not include Option labels.
|
||||
-->
|
||||
|
||||
```text
|
||||
# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT)
|
||||
src/
|
||||
├── models/
|
||||
├── services/
|
||||
├── cli/
|
||||
└── lib/
|
||||
|
||||
tests/
|
||||
├── contract/
|
||||
├── integration/
|
||||
└── unit/
|
||||
|
||||
# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected)
|
||||
backend/
|
||||
├── src/
|
||||
│ ├── models/
|
||||
│ ├── services/
|
||||
│ └── api/
|
||||
└── tests/
|
||||
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ ├── pages/
|
||||
│ └── services/
|
||||
└── tests/
|
||||
|
||||
# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected)
|
||||
api/
|
||||
└── [same as backend above]
|
||||
|
||||
ios/ or android/
|
||||
└── [platform-specific structure: feature modules, UI flows, platform tests]
|
||||
```
|
||||
|
||||
**Structure Decision**: [Document the selected structure and reference the real
|
||||
directories captured above]
|
||||
|
||||
## Plan Phases
|
||||
|
||||
[Phase breakdown with sequencing]
|
||||
|
||||
## Tasks
|
||||
|
||||
[Task breakdown list]
|
||||
|
||||
## Dependencies
|
||||
|
||||
[Phase/task dependencies and ordering]
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
[Definition of done for this phase]
|
||||
|
||||
## Risks
|
||||
|
||||
[Key risks and mitigations]
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||
@@ -1,7 +1,12 @@
|
||||
import { request } from "../utils/request";
|
||||
|
||||
export const contentApi = {
|
||||
list: (params) => {
|
||||
list: (params = {}) => {
|
||||
if (params.tenantId) {
|
||||
const { tenantId: tenantID, ...rest } = params;
|
||||
const qs = new URLSearchParams(rest).toString();
|
||||
return request(`/creators/${tenantID}/contents?${qs}`);
|
||||
}
|
||||
if (params.tenant_id) {
|
||||
const { tenant_id: tenantID, ...rest } = params;
|
||||
const qs = new URLSearchParams(rest).toString();
|
||||
|
||||
@@ -12,12 +12,12 @@ export const userApi = {
|
||||
getLibrary: () => request("/me/library"),
|
||||
getFavorites: () => request("/me/favorites"),
|
||||
addFavorite: (contentId) =>
|
||||
request(`/me/favorites?contentId=${contentId}`, { method: "POST" }),
|
||||
request(`/me/favorites?content_id=${contentId}`, { method: "POST" }),
|
||||
removeFavorite: (contentId) =>
|
||||
request(`/me/favorites/${contentId}`, { method: "DELETE" }),
|
||||
getLikes: () => request("/me/likes"),
|
||||
addLike: (contentId) =>
|
||||
request(`/me/likes?contentId=${contentId}`, { method: "POST" }),
|
||||
request(`/me/likes?content_id=${contentId}`, { method: "POST" }),
|
||||
removeLike: (contentId) =>
|
||||
request(`/me/likes/${contentId}`, { method: "DELETE" }),
|
||||
getNotifications: (type, page) =>
|
||||
|
||||
4
frontend/superadmin/dist/index.html
vendored
4
frontend/superadmin/dist/index.html
vendored
@@ -7,8 +7,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sakai Vue</title>
|
||||
<link href="https://fonts.cdnfonts.com/css/lato" rel="stylesheet">
|
||||
<script type="module" crossorigin src="./assets/index-DWEDmSIS.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-D7nB1nA9.css">
|
||||
<script type="module" crossorigin src="./assets/index-qhcz61Ui.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CLNNtsXI.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user