v0.2.0: 解耦 shrimp-server,内置 IShrimpType 契约副本
- 拷 shrimp-server/src/IShrimpType.ts 到 src/IShrimpType.ts,头改 @syncedFrom shrimp-server contract 1.0.0 - 全部内部 import 'shrimp-server/types' → 本地 './IShrimpType'(各文件按相对路径) - src/index.ts 加上类型契约的 re-export(ShrimpCode/ShrimpError/枚举/全部 interface),消费侧从 'shrimp-admin' 一处拿到 - 删 dependencies.shrimp-server,前端 bundle 不再牵 mongoose/express 为什么不直接 import 'shrimp-server': shrimp-server v0.6.0 收单入口后,入口含 mongoose/express,前端会被 vite 牵进 bundle。 契约副本 + 版本号对照是低频改动场景的更干净选择。
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* Shrimp 类型契约 — 前后端 wire protocol 定义(前端副本)
|
||||
*
|
||||
* @syncedFrom shrimp-server contract 1.0.0
|
||||
*
|
||||
* 这是 shrimp-server/src/IShrimpType.ts 的逐字副本。
|
||||
* 不要在这里改 wire protocol,先改 shrimp-server 那份升版本,再 copy 过来更新 @syncedFrom。
|
||||
*
|
||||
* 故意保留副本而不 import 'shrimp-server' 的原因:
|
||||
* - shrimp-server 入口会把 mongoose / express 牵进前端 bundle
|
||||
* - 拆 './types' 子入口会绕开 vite optimize、可读性也差
|
||||
* - 类型契约低频改动,版本号对照即可
|
||||
*/
|
||||
|
||||
/** 业务错误码 */
|
||||
export enum ShrimpCode {
|
||||
OK = 0,
|
||||
FAIL = 1,
|
||||
UNAUTHORIZED = 401,
|
||||
FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
SERVER_ERROR = 500,
|
||||
}
|
||||
|
||||
/** 业务错误 - tip 展示给用户 */
|
||||
export class ShrimpError extends Error {
|
||||
code: ShrimpCode
|
||||
tip: ILabel
|
||||
constructor(tip: ILabel, code: ShrimpCode = ShrimpCode.FAIL) {
|
||||
super(extractILabelText(tip))
|
||||
this.code = code
|
||||
this.tip = tip
|
||||
this.name = 'ShrimpError'
|
||||
}
|
||||
}
|
||||
|
||||
/** 抽取 ILabel 的字符串形态用于 Error.message / 日志(非展示) */
|
||||
function extractILabelText(v: ILabel): string {
|
||||
if (typeof v === 'string') return v
|
||||
if (v && typeof v === 'object') {
|
||||
const o = v as any
|
||||
if ('t' in o) return o.t
|
||||
if ('tc' in o) return o.tc
|
||||
if ('d' in o) return String(o.d)
|
||||
if ('n' in o) return String(o.n)
|
||||
}
|
||||
return '[ILabel]'
|
||||
}
|
||||
|
||||
/** 统一响应格式 */
|
||||
export interface IShrimpResponse<T = any> {
|
||||
code: ShrimpCode
|
||||
data?: T
|
||||
tip?: ILabel // 给用户看的提示
|
||||
log?: string // 给开发者看的日志
|
||||
}
|
||||
|
||||
/** 模块信息(结构元数据,扁平表示:子模块与主模块同列,通过 parent 关联) */
|
||||
export interface IModuleInfo {
|
||||
moduleKey: string
|
||||
events: string[]
|
||||
label: ILabel
|
||||
fields: IFieldConfig[]
|
||||
icon?: string // 子模块跳转按钮图标(element-plus icon 名称)
|
||||
parent?: string // 父模块 moduleKey;存在即为子模块(不进菜单、路径嵌套)
|
||||
}
|
||||
|
||||
/** 登录结果(仅 token) */
|
||||
export interface ILoginResult {
|
||||
token: string
|
||||
}
|
||||
|
||||
/** 当前用户信息 */
|
||||
export interface IMeResult {
|
||||
nickname?: string
|
||||
moduleInfo: IModuleInfo[]
|
||||
menuTree: IMenuItem[]
|
||||
options?: Record<string, any>
|
||||
}
|
||||
|
||||
// ============== 通用类型 ==============
|
||||
|
||||
/**
|
||||
* 通用国际化标签 - 所有前端可见文本字段统一类型
|
||||
*
|
||||
* 形态:
|
||||
* - string 字面量,前端原样显示
|
||||
* - { t: 'i18n.key', params?: ... } 翻译(vue-i18n t)
|
||||
* - { tc: 'i18n.key', count, params?: ... } 复数(vue-i18n tc)
|
||||
* - { d: Date|number, format? } 日期格式化(vue-i18n d)
|
||||
* - { n: number, format? } 数字/货币格式化(vue-i18n n)
|
||||
* - { zh: '提交', en: 'Submit' } 内联多语言
|
||||
*
|
||||
* params 值本身可以是 ILabel,前端递归解析
|
||||
*/
|
||||
export type ILabel =
|
||||
| string
|
||||
| { t: string; params?: Record<string, ILabel | any> }
|
||||
| { tc: string; count: number; params?: Record<string, ILabel | any> }
|
||||
| { d: number | Date; format?: string }
|
||||
| { n: number; format?: string }
|
||||
| Record<string, string>
|
||||
|
||||
/** 菜单项 */
|
||||
export interface IMenuItem {
|
||||
name: string
|
||||
icon?: string
|
||||
label: ILabel // 菜单显示名称
|
||||
path?: string
|
||||
moduleKey?: string
|
||||
children?: IMenuItem[]
|
||||
}
|
||||
|
||||
// ============== Proto 协议定义 ==============
|
||||
|
||||
/** 标准 Table CRUD 协议(前端可见) */
|
||||
export interface ITableProto<T> {
|
||||
list(query?: IPageQuery & any): Promise<IPageResult<T>>
|
||||
get(id: string): Promise<T | null>
|
||||
add(item: Omit<T, '_id'>, ownerId?: string): Promise<T>
|
||||
edit(id: string, item: Partial<T>): Promise<T | null>
|
||||
delete(id: string): Promise<boolean>
|
||||
}
|
||||
|
||||
/** 完整 Table 协议(后端可用,含管理操作) */
|
||||
export interface ITableCrudProto<T> extends ITableProto<T> {
|
||||
clear(query?: IPageQuery & any): Promise<boolean>
|
||||
export(query?: IPageQuery & any): Promise<T[]>
|
||||
import(data?: T[], ownerId?: string): Promise<void>
|
||||
}
|
||||
|
||||
// ============== 预制数据模型 ==============
|
||||
|
||||
/** 用户 */
|
||||
export interface IUser {
|
||||
username: string
|
||||
password: string
|
||||
nickname: string
|
||||
roleId: string[]
|
||||
active: boolean
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
/** 角色 */
|
||||
export interface IRole {
|
||||
name: string
|
||||
description: string
|
||||
isSuperAdmin: boolean
|
||||
powers: string[]
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
/** 操作日志 */
|
||||
export interface IOperationLog {
|
||||
operatorId: string | null // 操作者 userId,登录失败等场景为 null
|
||||
ip: string // 来源 IP
|
||||
method: string // RPC 路径,如 "user.edit"、"login"
|
||||
args: string // 参数(已脱敏 + JSON.stringify + 截断)
|
||||
success: boolean
|
||||
errorMsg: string // success=false 时填错误信息
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
/** 为数据模型注入 _id 的泛型工具类型 */
|
||||
export type WithId<T> = T & { _id: string }
|
||||
|
||||
/** 操作者契约 - 框架层通用操作者上下文,不依赖具体用户模型 */
|
||||
export interface IShrimpOperator {
|
||||
userId: string
|
||||
roleId: string[]
|
||||
isSuperAdmin: boolean
|
||||
}
|
||||
|
||||
/** 权限 */
|
||||
export interface IShrimpPowerData {
|
||||
_id: string
|
||||
powerKey: string
|
||||
}
|
||||
|
||||
// ============== 前端客户端接口 ==============
|
||||
|
||||
/** 底层请求函数类型 */
|
||||
export type ShrimpRequestFn = (path: string[], ...args: any[]) => Promise<IShrimpResponse>
|
||||
|
||||
/** Shrimp 客户端接口(前端实现) */
|
||||
export interface IShrimpClient {
|
||||
shrimpRequest: ShrimpRequestFn
|
||||
hasEvent(moduleKey: string, eventName: string): boolean
|
||||
}
|
||||
|
||||
// ============== 字段配置 ==============
|
||||
//
|
||||
// 【功能说明 - 给前端】
|
||||
//
|
||||
// IFieldConfig 是 Shrimp 框架的动态字段配置系统。
|
||||
// 后端模块定义 fields 后,前端通过 GET /me 获取 moduleInfo[].fields,
|
||||
// 即可自动生成表格列、搜索栏、新增/编辑表单,无需硬编码页面结构。
|
||||
//
|
||||
// 注意: _id 由框架自动注入(tableHide=true), 不需要在 fields 中配置, 数据返回但表格不显示。
|
||||
//
|
||||
// 使用流程:
|
||||
// 1. 调用 GET /me → 拿到 moduleInfo 数组
|
||||
// 2. 每个 moduleInfo 包含: moduleKey(模块名), events(可调用接口), fields(字段配置)
|
||||
// 3. 根据 fields 渲染:
|
||||
// - 表格列: 非 hidden 且非 tableHide 的字段 → 列标题=label, 数据=key
|
||||
// - 搜索栏: searchable=true 的字段 → 渲染对应类型的搜索输入
|
||||
// - 表单: 非 hidden 且 editable≠"readonly" 的字段, 根据 editable 决定:
|
||||
// editable="always" 或缺省 → 新增/编辑都可编辑
|
||||
// editable="readonly" → 新增/编辑都不可编辑(只读,如createdAt)
|
||||
// editable="addOnly" → 新增可编辑, 编辑时不可编辑(如username)
|
||||
// required=true 的字段标记必填, defaultValue 填入默认值
|
||||
// - 排序: sortable=true 的字段 → 表头可点击排序
|
||||
//
|
||||
// type 与前端控件映射建议:
|
||||
// string → Input 文本框
|
||||
// number → InputNumber 数字框
|
||||
// boolean → Switch 开关
|
||||
// date → DatePicker 日期选择器
|
||||
// select → Select 下拉框, 选项由 options 提供
|
||||
// textarea → Input.TextArea 多行文本
|
||||
// password → Input.Password 密码框
|
||||
// relation → Select 下拉框, 选项从 moduleKey 指定的模块 list 接口获取,
|
||||
// 显示字段为 labelKey, 值为 _id
|
||||
//
|
||||
|
||||
/** 字段类型(string 基础类型,前端可自由扩展自定义类型) */
|
||||
export type ShrimpFieldType = string
|
||||
|
||||
/** 框架内置字段类型常量(前端可直接使用或在此基础上扩展) */
|
||||
export const ShrimpFieldTypes = {
|
||||
String: "string" as ShrimpFieldType,
|
||||
Number: "number" as ShrimpFieldType,
|
||||
Boolean: "boolean" as ShrimpFieldType,
|
||||
Date: "date" as ShrimpFieldType,
|
||||
Select: "select" as ShrimpFieldType,
|
||||
Textarea: "textarea" as ShrimpFieldType,
|
||||
Password: "password" as ShrimpFieldType,
|
||||
Relation: "relation" as ShrimpFieldType,
|
||||
Json: "json" as ShrimpFieldType,
|
||||
Tab: "tab" as ShrimpFieldType,
|
||||
}
|
||||
|
||||
/** 字段可编辑状态 */
|
||||
export type FieldEditable = "always" | "readonly" | "addOnly"
|
||||
|
||||
/** 字段类型处理器:定义每种 type 在请求/响应时的转换、验证、默认值 */
|
||||
export interface IFieldTypeHandler {
|
||||
/** 请求过滤:前端提交 → 保存前转换(如密码哈希、trim) */
|
||||
reqFilter?: (value: any, field: IFieldConfig, item: Record<string, any>) => any | Promise<any>
|
||||
/** 响应过滤:数据库取出 → 返回前端前转换(如密码脱敏为 ***、日期格式化) */
|
||||
resFilter?: (value: any, field: IFieldConfig, item: Record<string, any>) => any
|
||||
/** 验证:保存前校验,返回错误 ILabel 或 null(可直接返回字面字符串、{t}、{tc} 等) */
|
||||
validate?: (value: any, field: IFieldConfig, item: Record<string, any>) => ILabel | null | Promise<ILabel | null>
|
||||
/** 新增时的默认值(静态值或工厂函数) */
|
||||
defaultValue?: any | (() => any)
|
||||
}
|
||||
|
||||
/** 字段配置 */
|
||||
export interface IFieldConfig {
|
||||
key: string // 字段名(对应数据对象的属性名)
|
||||
label: ILabel // 显示名称(表格列标题、表单标签)
|
||||
type: ShrimpFieldType // 字段类型(决定前端渲染哪种控件)
|
||||
required?: boolean // 是否必填(表单校验)
|
||||
hidden?: boolean // 数据不返回给前端
|
||||
tableHide?: boolean // 表格中不显示此列,但数据仍返回(如 _id)
|
||||
editable?: FieldEditable // 可编辑状态: always(默认,始终可编辑) | readonly(始终只读,如createdAt) | addOnly(仅添加可编辑,编辑时只读,如username)
|
||||
searchable?: boolean // 是否可搜索(出现在搜索栏)
|
||||
sortable?: boolean // 是否可排序(表头可点击排序)
|
||||
filterable?: boolean // 是否可筛选(出现在筛选栏,用于 filter 精确匹配)
|
||||
options?: { label: ILabel; value: any }[] | string // select 类型的可选项,或全局 options key
|
||||
relation?: { // relation 类型的关联配置
|
||||
moduleKey: string // 关联模块名(从该模块的 list 接口获取选项)
|
||||
labelKey: string // 关联模块中用作显示的字段名
|
||||
}
|
||||
defaultValue?: any // 新增时的默认值(优先级高于 typeHandler.defaultValue)
|
||||
multiple?: boolean // 是否支持多选(relation、select 等)
|
||||
rules?: IFieldRule[] // 通用验证规则(前后端共用)
|
||||
/** tab 类型专用:每个 type 对应的子字段配置 */
|
||||
tabs?: { key: any; label: ILabel; fields: IFieldConfig[] }[]
|
||||
}
|
||||
|
||||
/** 通用验证规则(前后端共用) */
|
||||
export interface IFieldRule {
|
||||
required?: boolean
|
||||
min?: number
|
||||
max?: number
|
||||
pattern?: string
|
||||
message?: ILabel
|
||||
}
|
||||
|
||||
// ============== 预制协议 ==============
|
||||
|
||||
/** 分页查询参数 */
|
||||
export interface IPageQuery {
|
||||
page?: number // 页码,从 1 开始
|
||||
pageSize?: number // 每页数量,默认 ShrimpConfig.defaultPageSize
|
||||
keyword?: string // 关键词搜索,按 searchable 字段模糊匹配
|
||||
sortBy?: string // 排序字段(对应 field.key)
|
||||
sortOrder?: 'asc' | 'desc' // 排序方向
|
||||
filter?: Record<string, any> // 字段精确匹配条件(AND 关系)
|
||||
ownerId?: string // 子表场景: 父记录 _id。后端按模块 ownerKey 配置翻译为 filter 注入(用户无法绕过)
|
||||
}
|
||||
|
||||
/** Shrimp 全局配置 */
|
||||
export class ShrimpConfig {
|
||||
static defaultPageSize: number = 100000
|
||||
}
|
||||
|
||||
/** 分页结果 */
|
||||
export interface IPageResult<T> {
|
||||
list: T[] // 当前页数据
|
||||
total: number // 总记录数
|
||||
page: number // 当前页码
|
||||
pageSize: number // 每页数量
|
||||
totalPages: number // 总页数
|
||||
}
|
||||
|
||||
// ============== 数据源查询协议 ==============
|
||||
//
|
||||
// IShrimpQuery 是数据源无关的查询协议。
|
||||
// ShrimpTableModule 负责将前端参数(IPageQuery)翻译为 IShrimpQuery,
|
||||
// 各 ShrimpCrud 实现(MemoryCrud/MongoShrimpCrud)负责执行。
|
||||
//
|
||||
// 查询语义:
|
||||
// WHERE (where 全部满足) AND (orWhere 至少一组满足)
|
||||
// ORDER BY order
|
||||
// LIMIT limit OFFSET offset
|
||||
//
|
||||
|
||||
/** 查询条件运算符 */
|
||||
export interface IShrimpCondition {
|
||||
eq?: any // 精确匹配 (=)
|
||||
in?: any[] // IN 查询
|
||||
like?: string // 模糊匹配 (不区分大小写)
|
||||
gte?: any // 大于等于 (>=)
|
||||
lte?: any // 小于等于 (<=)
|
||||
}
|
||||
|
||||
/** 判断值是否为 IShrimpCondition 对象 */
|
||||
export function isShrimpCondition(val: any): val is IShrimpCondition {
|
||||
return val != null && typeof val === 'object' && !Array.isArray(val)
|
||||
&& ('eq' in val || 'in' in val || 'like' in val || 'gte' in val || 'lte' in val)
|
||||
}
|
||||
|
||||
/** 关联填充指令 */
|
||||
export interface IShrimpPopulate {
|
||||
key: string // 当前模块的字段名 (如 roleId)
|
||||
moduleKey: string // 关联模块名 (如 role)
|
||||
labelKey: string // 关联模块中用作显示的字段 (如 name)
|
||||
}
|
||||
|
||||
/** 通用查询协议 (数据源无关) */
|
||||
export interface IShrimpQuery {
|
||||
where?: Record<string, IShrimpCondition | any> // AND 条件 (原始值=eq简写)
|
||||
orWhere?: Record<string, IShrimpCondition>[] // OR 条件组 (用于 keyword)
|
||||
order?: [string, 'asc' | 'desc'][] // 排序
|
||||
offset?: number // 偏移量
|
||||
limit?: number // 限制数量
|
||||
populate?: IShrimpPopulate[] // 关联填充 (输出 {key}_label)
|
||||
}
|
||||
|
||||
/** 权限项 */
|
||||
export interface IPowerItem {
|
||||
key: string
|
||||
label: ILabel
|
||||
}
|
||||
|
||||
/** 用户模块协议 */
|
||||
export interface IUserProto extends ITableCrudProto<WithId<IUser>> { }
|
||||
|
||||
/** 角色模块协议 */
|
||||
export interface IRoleProto extends ITableCrudProto<WithId<IRole>> {
|
||||
allPower(): Promise<IPowerItem[]>
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
* ShrimpClient — Shrimp 框架前端基类
|
||||
* 框架无关层,包含协议通信、模块管理、菜单树操作
|
||||
*/
|
||||
import { ShrimpCode, ShrimpRequestFn, IShrimpResponse, IModuleInfo, IMenuItem, ILoginResult, IMeResult, IShrimpClient, ILabel } from 'shrimp-server/types'
|
||||
import { ShrimpCode, ShrimpRequestFn, IShrimpResponse, IModuleInfo, IMenuItem, ILoginResult, IMeResult, IShrimpClient, ILabel } from './IShrimpType'
|
||||
|
||||
/** tip 展示接口(由框架层实现,如 Element Plus)。msg 是 ILabel,实现层负责翻译后展示 */
|
||||
export interface IShrimpTipper {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { createRouter, createWebHashHistory, Router } from 'vue-router'
|
||||
import zh_cn from './locales/zh-cn.json'
|
||||
import en from './locales/en.json'
|
||||
import { createI18n, I18n } from 'vue-i18n'
|
||||
import { IShrimpResponse, ILabel } from 'shrimp-server/types'
|
||||
import { IShrimpResponse, ILabel } from './IShrimpType'
|
||||
|
||||
import { ShrimpClient, handleShrimpResponse, type IShrimpTipper, type IRouteInfo } from './ShrimpClient'
|
||||
import FieldString from './components/fields/FieldString.vue'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { vueAdmin } from '../VueAdmin'
|
||||
import type { IMenuItem, ILabel } from 'shrimp-server/types'
|
||||
import type { IMenuItem, ILabel } from '../IShrimpType'
|
||||
|
||||
interface IPowerItem {
|
||||
key: string
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { provide, computed, ref } from 'vue'
|
||||
import { IFieldConfig, IFieldRule } from 'shrimp-server/types'
|
||||
import { IFieldConfig, IFieldRule } from '../IShrimpType'
|
||||
import { vueAdmin } from '../VueAdmin'
|
||||
import type { FormInstance, FormItemRule } from 'element-plus'
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { IFieldConfig } from 'shrimp-server/types'
|
||||
import { IFieldConfig } from '../IShrimpType'
|
||||
import { vueAdmin } from '../VueAdmin'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, useSlots, onMounted, type Component } from 'vue'
|
||||
import { ITableCrudProto, IFieldConfig, IModuleInfo, IPageQuery } from 'shrimp-server/types'
|
||||
import { ITableCrudProto, IFieldConfig, IModuleInfo, IPageQuery } from '../IShrimpType'
|
||||
import { vueAdmin } from '../VueAdmin'
|
||||
import { Plus, Delete as DeleteIcon, Download, Upload, Edit, VideoPlay, VideoPause, Operation } from '@element-plus/icons-vue'
|
||||
import * as ElIcons from '@element-plus/icons-vue'
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { vueAdmin } from '../../VueAdmin'
|
||||
import { ITableCrudProto } from 'shrimp-server/types'
|
||||
import { ITableCrudProto } from '../../IShrimpType'
|
||||
|
||||
const props = defineProps<{
|
||||
field: any
|
||||
|
||||
@@ -2,6 +2,47 @@
|
||||
//
|
||||
// 消费侧用法见 README.md。本文件只做 re-export。
|
||||
|
||||
// ===== 类型契约(IShrimpType 本地副本,@syncedFrom shrimp-server contract 1.0.0)=====
|
||||
export {
|
||||
ShrimpCode,
|
||||
ShrimpError,
|
||||
ShrimpFieldTypes,
|
||||
ShrimpConfig,
|
||||
isShrimpCondition,
|
||||
} from './IShrimpType'
|
||||
|
||||
export type {
|
||||
ILabel,
|
||||
IShrimpResponse,
|
||||
IModuleInfo,
|
||||
ILoginResult,
|
||||
IMeResult,
|
||||
IMenuItem,
|
||||
ITableProto,
|
||||
ITableCrudProto,
|
||||
IUser,
|
||||
IRole,
|
||||
IOperationLog,
|
||||
WithId,
|
||||
IShrimpOperator,
|
||||
IShrimpPowerData,
|
||||
ShrimpRequestFn,
|
||||
IShrimpClient,
|
||||
ShrimpFieldType,
|
||||
FieldEditable,
|
||||
IFieldTypeHandler,
|
||||
IFieldConfig,
|
||||
IFieldRule,
|
||||
IPageQuery,
|
||||
IPageResult,
|
||||
IShrimpCondition,
|
||||
IShrimpPopulate,
|
||||
IShrimpQuery,
|
||||
IPowerItem,
|
||||
IUserProto,
|
||||
IRoleProto,
|
||||
} from './IShrimpType'
|
||||
|
||||
export {
|
||||
VueAdmin,
|
||||
vueAdmin,
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { vueAdmin } from '../VueAdmin'
|
||||
import { IRole, IRoleProto, WithId, ILabel } from 'shrimp-server/types'
|
||||
import { IRole, IRoleProto, WithId, ILabel } from '../IShrimpType'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import PowerTree from '../components/PowerTree.vue'
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ import navMenu from "./navMenu.vue";
|
||||
import Topbar from "./topbar.vue";
|
||||
import Userbar from "./userbar.vue";
|
||||
import { vueAdmin } from "../VueAdmin";
|
||||
import { type IMenuItem } from "shrimp-server/types";
|
||||
import { type IMenuItem } from "../IShrimpType";
|
||||
import Logo from "./logo.vue";
|
||||
|
||||
// 初始化选中菜单
|
||||
|
||||
Reference in New Issue
Block a user