Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
boyueCEnd
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
游洁
boyueCEnd
Commits
25f87f01
Commit
25f87f01
authored
Nov 18, 2025
by
罗超
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
处理参数中间键
parent
f58f29e6
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1038 additions
and
43 deletions
+1038
-43
.env.development
.env.development
+1
-1
short-code-guide.md
docs/short-code-guide.md
+225
-0
ShortCodeExample.vue
src/components/examples/ShortCodeExample.vue
+123
-0
useRouteParams.ts
src/composables/useRouteParams.ts
+317
-0
guards.ts
src/router/guards.ts
+22
-42
index.ts
src/router/index.ts
+6
-0
DataMappingService.ts
src/services/DataMappingService.ts
+79
-0
router.d.ts
src/types/router.d.ts
+39
-0
shortCodeResolver.ts
src/utils/shortCodeResolver.ts
+201
-0
List.vue
src/views/products/List.vue
+21
-0
tsconfig.app.json
tsconfig.app.json
+4
-0
No files found.
.env.development
View file @
25f87f01
# 开发环境配置 - 使用Mock数据
VITE_OTA_API_BASE_URL=http://
192.168.5.39
:19002/api/app/
VITE_OTA_API_BASE_URL=http://
localhost
:19002/api/app/
VITE_ERP_API_BASE_URL=http://192.168.5.214:8700/
VITE_FILEUPLOAD_API_BASE_URL=http://192.168.5.214:8120/
VITE_FILEPREVIEW_API_BASE_URL=http://192.168.5.214:8130/
...
...
docs/short-code-guide.md
0 → 100644
View file @
25f87f01
# 短码参数解析使用指南
## 快速开始
### 基础用法
```
vue
<
script
setup
lang=
"ts"
>
import
{
useRouteParams
}
from
'@/composables/useRouteParams'
// 自动获取并解析所有参数(包括短码)
const
{
params
,
loading
}
=
useRouteParams
()
</
script
>
<
template
>
<div
v-if=
"loading"
>
加载中...
</div>
<div
v-else
>
<p>
产品ID:
{{
params
.
productId
}}
</p>
<p>
分类ID:
{{
params
.
categoryId
}}
</p>
</div>
</
template
>
```
### 带类型提示
```
vue
<
script
setup
lang=
"ts"
>
interface
PageParams
{
productId
:
string
categoryId
:
string
}
const
{
params
}
=
useRouteParams
<
PageParams
>
()
// params.value.productId ✅ 有完整类型提示
</
script
>
```
## 常用配置
### 白名单配置(推荐)
```
vue
<
script
setup
lang=
"ts"
>
// page、pageSize 等不需要解析的参数
const
{
params
}
=
useRouteParams
({
whitelist
:
[
'page'
,
'pageSize'
,
'userId'
,
'orderId'
]
})
</
script
>
```
### 自定义转换
```
vue
<
script
setup
lang=
"ts"
>
// 短码可能返回多个 GUID(逗号分隔)
const
{
params
}
=
useRouteParams
({
transform
:
(
raw
)
=>
({
...
raw
,
// 自动拆分为数组
productIds
:
raw
.
productIds
?.
split
?.(
','
)
||
[]
})
})
</
script
>
```
### 错误处理
```
vue
<
script
setup
lang=
"ts"
>
const
{
params
,
error
}
=
useRouteParams
({
fallbackMode
:
'useOriginal'
,
// 失败时使用原值
onError
:
(
err
)
=>
{
console
.
error
(
'参数解析失败:'
,
err
)
toast
.
error
(
'参数加载失败'
)
}
})
</
script
>
```
## 配置选项
```
typescript
useRouteParams
({
// 自动刷新(路由变化时)@default true
autoRefresh
:
true
,
// 立即执行 @default true
immediate
:
true
,
// 参数来源 @default { params: true, query: true, meta: ['filters'] }
sources
:
{
params
:
true
,
query
:
true
,
meta
:
[
'filters'
]
},
// 白名单:这些参数不解析
whitelist
:
[
'page'
,
'pageSize'
],
// 解析超时(毫秒)@default 5000
timeout
:
5000
,
// 失败降级 @default 'useOriginal'
fallbackMode
:
'useOriginal'
|
'throw'
|
'empty'
,
// 自定义转换
transform
:
(
params
)
=>
params
,
// 错误回调
onError
:
(
error
)
=>
{}
})
```
## 返回值
```
typescript
const
{
params
,
// 解析后的参数(响应式)
loading
,
// 加载状态
error
,
// 错误信息
debugInfo
,
// 调试信息(开发环境)
refresh
,
// 手动刷新
get
// 获取单个参数
}
=
useRouteParams
()
```
## 高级功能
### 菜单预加载
```
typescript
import
{
preloadMenuShortCodes
}
from
'@/composables/useRouteParams'
// 在菜单加载后预加载所有短码
const
menus
=
await
loadMenus
()
await
preloadMenuShortCodes
(
menus
)
```
### 开发调试
浏览器控制台:
```
javascript
// 查看缓存统计
window
.
__shortCodeResolver
.
stats
()
// 手动解析短码
await
window
.
__shortCodeResolver
.
resolve
(
'lhg5RP6rnE'
)
// 清除缓存
window
.
__shortCodeResolver
.
clearCache
()
```
## 最佳实践
### ✅ 推荐
```
vue
<
script
setup
lang=
"ts"
>
// 1. 定义类型
interface
PageParams
{
productId
:
string
categoryId
:
string
}
// 2. 配置白名单
const
{
params
}
=
useRouteParams
<
PageParams
>
({
whitelist
:
[
'page'
,
'pageSize'
]
})
// 3. 直接使用
watchEffect
(()
=>
{
if
(
params
.
value
.
productId
)
{
loadData
(
params
.
value
.
productId
)
}
})
</
script
>
```
### ❌ 避免
```
vue
<
script
setup
lang=
"ts"
>
// ❌ 不要手动获取和解析
import
{
useRoute
}
from
'vue-router'
const
route
=
useRoute
()
const
id
=
route
.
params
.
id
// 可能是短码
// ✅ 使用 useRouteParams
const
{
params
}
=
useRouteParams
()
const
id
=
params
.
value
.
id
// 自动解析
</
script
>
```
## 常见问题
**Q: 参数没有自动解析?**
A: 检查是否在白名单中,或参数长度小于 6 位。
**Q: 如何提升性能?**
A: 使用
`preloadMenuShortCodes`
预加载菜单短码。
**Q: 如何判断是否被解析?**
A: 查看
`debugInfo.value.resolved`
数组(开发环境)。
## 工作原理
```
用户访问 /products/lhg5RP6rnE
↓
useRouteParams() 收集参数
↓
智能判断并解析短码
↓
缓存结果(30分钟)
↓
返回解析后的参数
```
## 总结
-
**零学习成本**
:一行代码搞定
-
**完全透明**
:自动处理所有短码
-
**类型安全**
:完整 TypeScript 支持
-
**高性能**
:智能缓存和并发解析
src/components/examples/ShortCodeExample.vue
0 → 100644
View file @
25f87f01
<
template
>
<div
class=
"short-code-example"
>
<h2>
短码参数解析示例
</h2>
<!-- 加载状态 -->
<div
v-if=
"loading"
class=
"loading"
>
<a-spin
/>
<span>
正在解析参数...
</span>
</div>
<!-- 错误状态 -->
<a-alert
v-else-if=
"error"
type=
"error"
:message=
"error.message"
banner
/>
<!-- 参数展示 -->
<div
v-else
class=
"params-display"
>
<a-descriptions
title=
"解析后的参数"
bordered
:column=
"1"
>
<a-descriptions-item
v-for=
"(value, key) in params"
:key=
"key"
:label=
"key"
>
<a-tag
v-if=
"Array.isArray(value)"
color=
"blue"
>
{{
value
.
join
(
', '
)
}}
</a-tag>
<a-tag
v-else-if=
"typeof value === 'object'"
color=
"green"
>
{{
JSON
.
stringify
(
value
)
}}
</a-tag>
<a-tag
v-else
color=
"orange"
>
{{
value
}}
</a-tag>
</a-descriptions-item>
</a-descriptions>
<!-- 调试信息 -->
<a-card
v-if=
"debugInfo"
title=
"调试信息"
style=
"margin-top: 20px"
>
<p>
<strong>
解析的短码:
</strong>
<a-tag
v-for=
"code in debugInfo.resolved"
:key=
"code"
color=
"purple"
>
{{
code
}}
</a-tag>
</p>
<p>
<strong>
解析耗时:
</strong>
<a-tag
color=
"cyan"
>
{{
debugInfo
.
timing
}}
ms
</a-tag>
</p>
<p>
<strong>
参数来源:
</strong>
</p>
<ul>
<li
v-for=
"(source, key) in debugInfo.sources"
:key=
"key"
>
<code>
{{
key
}}
</code>
来自
<a-tag>
{{
source
}}
</a-tag>
</li>
</ul>
</a-card>
<!-- 操作按钮 -->
<div
class=
"actions"
style=
"margin-top: 20px"
>
<a-button
type=
"primary"
@
click=
"handleRefresh"
>
刷新参数
</a-button>
<a-button
@
click=
"handleClearCache"
>
清除缓存
</a-button>
</div>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
useRouteParams
}
from
'@/composables/useRouteParams'
import
{
shortCodeResolver
}
from
'@/utils/shortCodeResolver'
import
{
useToast
}
from
'vue-toastification'
const
toast
=
useToast
()
// 基础用法:自动解析所有参数
const
{
params
,
loading
,
error
,
debugInfo
,
refresh
}
=
useRouteParams
({
// 配置白名单
whitelist
:
[
'page'
,
'pageSize'
],
// 错误处理
onError
:
(
err
)
=>
{
console
.
error
(
'参数解析失败:'
,
err
)
toast
.
error
(
'参数解析失败,请重试'
)
}
})
// 刷新参数
const
handleRefresh
=
async
()
=>
{
try
{
await
refresh
()
toast
.
success
(
'参数已刷新'
)
}
catch
(
err
)
{
toast
.
error
(
'刷新失败'
)
}
}
// 清除缓存
const
handleClearCache
=
()
=>
{
shortCodeResolver
.
clearCache
()
toast
.
success
(
'缓存已清除'
)
}
</
script
>
<
style
scoped
>
.short-code-example
{
padding
:
20px
;
}
.loading
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
padding
:
20px
;
}
.params-display
{
margin-top
:
20px
;
}
.actions
{
display
:
flex
;
gap
:
10px
;
}
code
{
background
:
#f5f5f5
;
padding
:
2px
6px
;
border-radius
:
3px
;
font-family
:
monospace
;
}
</
style
>
src/composables/useRouteParams.ts
0 → 100644
View file @
25f87f01
import
{
ref
,
computed
,
watch
,
onMounted
}
from
'vue'
import
{
useRoute
}
from
'vue-router'
import
{
shortCodeResolver
}
from
'@/utils/shortCodeResolver'
/**
* 参数来源配置
*/
interface
ParamSources
{
/** 是否包含 route.params */
params
?:
boolean
/** 是否包含 route.query */
query
?:
boolean
/** 从 route.meta 中提取的字段 */
meta
?:
string
[]
}
/**
* 调试信息
*/
interface
DebugInfo
{
/** 被解析的短码列表 */
resolved
:
string
[]
/** 参数来源映射 */
sources
:
Record
<
string
,
'params'
|
'query'
|
'meta'
>
/** 解析耗时(毫秒) */
timing
:
number
}
/**
* 配置选项
*/
interface
UseRouteParamsOptions
<
T
=
any
>
{
/** 是否自动刷新(路由变化时)@default true */
autoRefresh
?:
boolean
/** 是否立即执行 @default true */
immediate
?:
boolean
/** 自定义参数转换函数 */
transform
?:
(
params
:
Record
<
string
,
any
>
)
=>
T
/** 参数来源配置 @default { params: true, query: true, meta: ['filters'] } */
sources
?:
ParamSources
/** 参数白名单:这些参数永远不解析短码 */
whitelist
?:
string
[]
/** 解析超时时间(毫秒)@default 5000 */
timeout
?:
number
/** 失败时的降级模式 @default 'useOriginal' */
fallbackMode
?:
'useOriginal'
|
'throw'
|
'empty'
/** 错误回调 */
onError
?:
(
error
:
Error
)
=>
void
}
/**
* 统一获取路由参数(自动解析短码)
*
* @example
* ```ts
* // 基础用法(响应式)
* const { params, loading } = useRouteParams()
*
* // 使用 await 等待解析完成(推荐!)
* const routeParams = useRouteParams()
* const params = await routeParams.ready
* console.log(params) // 已解析完成的参数
*
* // 带类型提示
* interface PageParams {
* productId: string
* categoryId: string
* }
* const { ready } = useRouteParams<PageParams>()
* const params = await ready
*
* // 自定义配置
* const { params } = useRouteParams({
* whitelist: ['page', 'pageSize'],
* transform: (raw) => ({ ...raw, ids: raw.ids.split(',') })
* })
* ```
*/
export
function
useRouteParams
<
T
=
Record
<
string
,
any
>>
(
options
:
UseRouteParamsOptions
<
T
>
=
{})
{
const
{
autoRefresh
=
true
,
immediate
=
true
,
transform
,
sources
=
{
params
:
true
,
query
:
true
,
meta
:
[
'filters'
]
},
whitelist
=
[],
timeout
=
5000
,
fallbackMode
=
'useOriginal'
,
onError
}
=
options
const
route
=
useRoute
()
const
loading
=
ref
(
false
)
const
error
=
ref
<
Error
|
null
>
(
null
)
const
params
=
ref
<
T
>
({}
as
T
)
const
debugInfo
=
ref
<
DebugInfo
>
()
// 用于 await 的 Promise
let
readyResolve
:
((
value
:
T
)
=>
void
)
|
null
=
null
const
ready
=
new
Promise
<
T
>
((
resolve
)
=>
{
readyResolve
=
resolve
})
/**
* 收集所有来源的参数(优先级:params > query > meta)
*/
const
collectParams
=
():
Record
<
string
,
any
>
=>
{
const
collected
:
Record
<
string
,
any
>
=
{}
const
paramSources
:
Record
<
string
,
'params'
|
'query'
|
'meta'
>
=
{}
// 从 meta 中提取(优先级最低)
if
(
sources
.
meta
)
{
sources
.
meta
.
forEach
((
key
)
=>
{
const
metaValue
=
(
route
.
meta
as
any
)[
key
]
if
(
metaValue
&&
typeof
metaValue
===
'object'
)
{
Object
.
keys
(
metaValue
).
forEach
((
k
)
=>
{
collected
[
k
]
=
metaValue
[
k
]
paramSources
[
k
]
=
'meta'
})
}
})
}
// 从 query 中提取(优先级中等)
if
(
sources
.
query
)
{
Object
.
entries
(
route
.
query
).
forEach
(([
key
,
value
])
=>
{
collected
[
key
]
=
value
paramSources
[
key
]
=
'query'
})
}
// 从 params 中提取(优先级最高)
if
(
sources
.
params
)
{
Object
.
entries
(
route
.
params
).
forEach
(([
key
,
value
])
=>
{
collected
[
key
]
=
value
paramSources
[
key
]
=
'params'
})
}
return
collected
}
/**
* 解析单个参数值
*/
const
resolveValue
=
async
(
key
:
string
,
value
:
any
):
Promise
<
any
>
=>
{
// 白名单中的参数不解析
if
(
whitelist
.
includes
(
key
))
{
return
value
}
// 字符串:尝试解析短码
if
(
typeof
value
===
'string'
)
{
return
await
shortCodeResolver
.
resolveIfNeeded
(
value
,
timeout
)
}
// 数组:递归解析每个元素
if
(
Array
.
isArray
(
value
))
{
return
await
Promise
.
all
(
value
.
map
((
v
)
=>
resolveValue
(
key
,
v
)))
}
// 其他类型直接返回
return
value
}
/**
* 获取所有参数(自动解析短码)
*/
const
getAll
=
async
():
Promise
<
T
>
=>
{
const
startTime
=
Date
.
now
()
const
resolvedCodes
:
string
[]
=
[]
loading
.
value
=
true
error
.
value
=
null
try
{
// 1. 收集所有参数
const
allParams
=
collectParams
()
// 2. 并发解析所有参数
const
resolved
:
Record
<
string
,
any
>
=
{}
await
Promise
.
all
(
Object
.
entries
(
allParams
).
map
(
async
([
key
,
value
])
=>
{
const
originalValue
=
value
resolved
[
key
]
=
await
resolveValue
(
key
,
value
)
// 记录被解析的短码
if
(
typeof
originalValue
===
'string'
&&
resolved
[
key
]
!==
originalValue
&&
!
whitelist
.
includes
(
key
)
)
{
resolvedCodes
.
push
(
originalValue
)
}
})
)
// 3. 自定义转换
const
final
=
transform
?
transform
(
resolved
)
:
(
resolved
as
T
)
params
.
value
=
final
// 4. 保存调试信息(仅开发环境)
if
(
import
.
meta
.
env
.
DEV
)
{
const
paramSources
:
Record
<
string
,
'params'
|
'query'
|
'meta'
>
=
{}
if
(
sources
.
params
)
{
Object
.
keys
(
route
.
params
).
forEach
((
k
)
=>
(
paramSources
[
k
]
=
'params'
))
}
if
(
sources
.
query
)
{
Object
.
keys
(
route
.
query
).
forEach
((
k
)
=>
(
paramSources
[
k
]
=
'query'
))
}
debugInfo
.
value
=
{
resolved
:
resolvedCodes
,
sources
:
paramSources
,
timing
:
Date
.
now
()
-
startTime
}
}
// 5. 触发 ready Promise
if
(
readyResolve
)
{
readyResolve
(
final
)
readyResolve
=
null
// 只触发一次
}
return
final
}
catch
(
e
)
{
const
err
=
e
as
Error
error
.
value
=
err
// 执行错误回调
onError
?.(
err
)
// 降级策略
if
(
fallbackMode
===
'useOriginal'
)
{
params
.
value
=
collectParams
()
as
T
return
params
.
value
}
else
if
(
fallbackMode
===
'throw'
)
{
throw
err
}
else
{
params
.
value
=
{}
as
T
return
params
.
value
}
}
finally
{
loading
.
value
=
false
}
}
/**
* 获取单个参数
*/
const
get
=
async
<
K
extends
keyof
T
>
(
key
:
K
):
Promise
<
T
[
K
]
|
undefined
>
=>
{
if
(
Object
.
keys
(
params
.
value
).
length
===
0
)
{
await
getAll
()
}
return
params
.
value
[
key
]
}
/**
* 手动刷新参数
*/
const
refresh
=
getAll
// 自动刷新模式
if
(
autoRefresh
)
{
watch
(()
=>
route
.
fullPath
,
getAll
,
{
immediate
})
}
else
if
(
immediate
)
{
onMounted
(
getAll
)
}
return
{
/** 解析后的参数对象(响应式) */
params
:
computed
(()
=>
params
.
value
),
/** 加载状态 */
loading
:
computed
(()
=>
loading
.
value
),
/** 错误信息 */
error
:
computed
(()
=>
error
.
value
),
/** 调试信息(仅开发环境) */
debugInfo
:
computed
(()
=>
debugInfo
.
value
),
/** 等待首次解析完成的 Promise */
ready
,
/** 手动刷新参数 */
refresh
,
/** 获取单个参数 */
get
}
}
/**
* 预加载菜单中的短码
*/
export
async
function
preloadMenuShortCodes
(
menus
:
Array
<
{
url
?:
string
;
path
?:
string
}
>
)
{
const
allCodes
=
menus
.
flatMap
((
menu
)
=>
{
const
url
=
menu
.
url
||
menu
.
path
||
''
return
shortCodeResolver
.
extractFromUrl
(
url
)
})
await
shortCodeResolver
.
preload
(
allCodes
)
}
src/router/guards.ts
View file @
25f87f01
import
type
{
NavigationGuardNext
,
RouteLocationNormalized
}
from
'vue-router'
import
{
useUserStore
}
from
'@/stores/user'
// 白名单路由(不需要登录即可访问)
const
whiteList
=
[
'/login'
,
'/denied'
,
'/notPermission'
,
'/404'
,
'/ai/excel'
]
const
whiteListRegex
=
[
/
\/
oa
\/
/
]
const
webSiteName
=
'Viitto CRM 专注于旅行社的客户管理与营销系统'
export
const
createPermissionGuard
=
(
to
:
RouteLocationNormalized
,
from
:
RouteLocationNormalized
,
next
:
NavigationGuardNext
,
)
=>
{
console
.
log
(
to
.
path
)
const
userStore
=
useUserStore
()
const
pageTitle
=
to
.
matched
.
find
((
item
)
=>
item
.
meta
.
title
)
//const menus = userStore.getFlattenMenus
if
(
pageTitle
)
{
document
.
title
=
`
${
pageTitle
.
meta
.
title
}
-
${
webSiteName
}
`
}
else
{
document
.
title
=
webSiteName
}
// 检查是否是白名单路由
if
(
whiteList
.
includes
(
to
.
path
))
{
next
()
return
}
if
(
whiteListRegex
.
some
((
regex
)
=>
regex
.
test
(
to
.
path
)))
{
next
()
return
}
// 检查用户是否登录
if
(
!
userStore
.
getUserToken
)
{
next
(
'/login'
)
return
}
// console.log(to.path)
// const userStore = useUserStore()
// const pageTitle = to.matched.find((item) => item.meta.title)
// //const menus = userStore.getFlattenMenus
// if (pageTitle) {
// document.title = `${pageTitle.meta.title} - ${webSiteName}`
// } else {
// document.title = webSiteName
// }
// // 检查是否是白名单路由
// if (whiteList.includes(to.path)) {
// next()
// return
// }
// 已登录用户检查权限状态
if
(
userStore
.
getDenied
)
{
next
(
'/denied'
)
return
}
// if (whiteListRegex.some((regex) => regex.test(to.path))) {
// next()
// return
// }
const
isErpMenus
=
to
.
fullPath
.
startsWith
(
'/erp/'
)
// 已登录且有权限的用户,检查是否已获取用户信息
// if (
// menus.length == 0 ||
// (to.path != '/' && menus.findIndex((x) => x.MenuUrl == to.path) == -1 && !isErpMenus)
// ) {
// next('/notPermission')
// // 检查用户是否登录
// if (!userStore.getUserToken) {
// next('/login')
// return
// }
next
()
}
src/router/index.ts
View file @
25f87f01
...
...
@@ -11,6 +11,12 @@ const router = createRouter({
component
:
()
=>
import
(
"../views/auth/Login.vue"
),
meta
:
{
title
:
"page.login"
},
},
{
path
:
"/products/:type/:city"
,
name
:
"products"
,
component
:
()
=>
import
(
"../views/products/List.vue"
),
meta
:
{
title
:
"page.products"
},
},
],
});
...
...
src/services/DataMappingService.ts
0 → 100644
View file @
25f87f01
import
Api
from
'@/api/OtaRequest'
/**
* 数据映射类型枚举
*/
export
enum
DataMappingTypeEnum
{
Url
=
'Url'
,
GuidList
=
'GuidList'
,
Json
=
'Json'
,
Text
=
'Text'
}
/**
* 数据映射DTO
*/
export
interface
DataMappingDto
{
id
:
number
shortCode
:
string
dataType
:
DataMappingTypeEnum
value
:
string
metadata
?:
string
expireTime
?:
string
creationTime
:
string
}
/**
* 创建数据映射DTO
*/
export
interface
CreateDataMappingDto
{
dataType
:
DataMappingTypeEnum
value
:
string
metadata
?:
string
expireTime
?:
string
}
/**
* 更新数据映射DTO
*/
export
interface
UpdateDataMappingDto
{
dataType
?:
DataMappingTypeEnum
value
?:
string
metadata
?:
string
expireTime
?:
string
}
/**
* 数据映射服务
*/
class
DataMappingService
{
/**
* 创建数据映射
*/
static
async
CreateAsync
(
dto
:
CreateDataMappingDto
):
Promise
<
DataMappingDto
>
{
return
Api
.
post
(
'/data-mapping'
,
dto
)
}
/**
* 更新数据映射
*/
static
async
UpdateAsync
(
shortCode
:
string
,
dto
:
UpdateDataMappingDto
):
Promise
<
DataMappingDto
>
{
return
Api
.
put
(
'/data-mapping'
,
dto
,
{
params
:
{
shortCode
}
})
}
/**
* 删除数据映射
*/
static
async
DeleteAsync
(
shortCode
:
string
):
Promise
<
void
>
{
return
Api
.
delete
(
'/data-mapping'
,
{
params
:
{
shortCode
}
})
}
/**
* 根据短码获取数据
*/
static
async
GetByShortCodeAsync
(
shortCode
:
string
):
Promise
<
DataMappingDto
>
{
return
Api
.
get
(
'/data-mapping/by-short-code'
,
{
shortCode
})
}
}
export
default
DataMappingService
src/types/router.d.ts
0 → 100644
View file @
25f87f01
import
'vue-router'
/**
* 扩展 Vue Router 的类型定义
* 为 RouteMeta 添加自定义字段
*/
declare
module
'vue-router'
{
interface
RouteMeta
{
/**
* 页面标题(支持 i18n key)
*/
title
?:
string
/**
* 筛选器参数
* 用于存储页面级的筛选配置
*/
filters
?:
Record
<
string
,
any
>
/**
* 页面配置
* 用于存储页面级的自定义配置
*/
config
?:
Record
<
string
,
any
>
/**
* 解析后的参数(由 useRouteParams 自动填充)
* @internal
*/
resolvedParams
?:
Record
<
string
,
any
>
/**
* 其他自定义字段
*/
[
key
:
string
]:
any
}
}
export
{}
src/utils/shortCodeResolver.ts
0 → 100644
View file @
25f87f01
import
DataMappingService
,
{
DataMappingTypeEnum
}
from
'@/services/DataMappingService'
/**
* 短码解析结果
*/
interface
ResolvedData
{
shortCode
:
string
dataType
:
DataMappingTypeEnum
value
:
string
parsed
:
any
timestamp
:
number
}
/**
* 短码解析器
* 负责识别、解析和缓存短码
*/
class
ShortCodeResolver
{
private
cache
=
new
Map
<
string
,
ResolvedData
>
()
private
readonly
TTL
=
30
*
60
*
1000
// 30分钟缓存
/**
* 判断字符串是否可能是短码
* Base62 编码特征:字母数字组合,6-1024位,非纯数字
*/
private
isLikelyShortCode
(
value
:
string
):
boolean
{
if
(
typeof
value
!==
'string'
)
return
false
// Base62 字符集检查
if
(
!
/^
[
a-zA-Z0-9
]
+$/
.
test
(
value
))
return
false
// 长度范围检查
if
(
value
.
length
<
6
||
value
.
length
>
1024
)
return
false
// 排除纯数字(避免误判 ID)
if
(
/^
\d
+$/
.
test
(
value
))
return
false
return
true
}
/**
* 根据数据类型解析值
*/
private
parseValue
(
dataType
:
DataMappingTypeEnum
,
value
:
string
):
any
{
switch
(
dataType
)
{
case
DataMappingTypeEnum
.
GuidList
:
return
value
.
split
(
','
)
case
DataMappingTypeEnum
.
Json
:
try
{
return
JSON
.
parse
(
value
)
}
catch
{
console
.
warn
(
'JSON 解析失败,返回原始值:'
,
value
)
return
value
}
case
DataMappingTypeEnum
.
Text
:
case
DataMappingTypeEnum
.
Url
:
default
:
return
value
}
}
/**
* 检查缓存是否过期
*/
private
isCacheValid
(
cached
:
ResolvedData
):
boolean
{
return
Date
.
now
()
-
cached
.
timestamp
<
this
.
TTL
}
/**
* 解析短码(核心方法)
*/
async
resolve
(
shortCode
:
string
):
Promise
<
ResolvedData
>
{
// 检查缓存
const
cached
=
this
.
cache
.
get
(
shortCode
)
if
(
cached
&&
this
.
isCacheValid
(
cached
))
{
return
cached
}
// 调用 API 获取数据
const
result
=
await
DataMappingService
.
GetByShortCodeAsync
(
shortCode
)
// 构建解析结果
const
resolved
:
ResolvedData
=
{
shortCode
,
dataType
:
result
.
dataType
,
value
:
result
.
value
,
parsed
:
this
.
parseValue
(
result
.
dataType
,
result
.
value
),
timestamp
:
Date
.
now
()
}
// 缓存结果
this
.
cache
.
set
(
shortCode
,
resolved
)
return
resolved
}
/**
* 智能解析:如果是短码就解析,否则返回原值
*/
async
resolveIfNeeded
(
value
:
string
,
timeout
=
5000
):
Promise
<
string
|
any
>
{
// 快速判断:不是短码直接返回
if
(
!
this
.
isLikelyShortCode
(
value
))
{
return
value
}
try
{
// 带超时的解析
const
result
=
await
Promise
.
race
([
this
.
resolve
(
value
),
new
Promise
<
never
>
((
_
,
reject
)
=>
setTimeout
(()
=>
reject
(
new
Error
(
'解析超时'
)),
timeout
)
)
])
return
result
.
parsed
}
catch
(
error
)
{
// 解析失败,降级返回原值
if
(
import
.
meta
.
env
.
DEV
)
{
console
.
warn
(
'短码解析失败,使用原值:'
,
value
,
error
)
}
return
value
}
}
/**
* 批量预加载短码(用于菜单预加载)
*/
async
preload
(
shortCodes
:
string
[]):
Promise
<
void
>
{
const
validCodes
=
shortCodes
.
filter
((
code
)
=>
this
.
isLikelyShortCode
(
code
))
await
Promise
.
allSettled
(
validCodes
.
map
((
code
)
=>
this
.
resolve
(
code
)))
if
(
import
.
meta
.
env
.
DEV
)
{
console
.
log
(
`📦 预加载了
${
validCodes
.
length
}
个短码`
)
}
}
/**
* 从 URL 中提取可能的短码
*/
extractFromUrl
(
url
:
string
):
string
[]
{
const
codes
:
string
[]
=
[]
// 提取路径参数
const
pathParts
=
url
.
split
(
'?'
)[
0
].
split
(
'/'
).
filter
(
Boolean
)
pathParts
.
forEach
((
part
)
=>
{
if
(
this
.
isLikelyShortCode
(
part
))
{
codes
.
push
(
part
)
}
})
// 提取查询参数
const
queryString
=
url
.
split
(
'?'
)[
1
]
if
(
queryString
)
{
const
params
=
new
URLSearchParams
(
queryString
)
params
.
forEach
((
value
)
=>
{
if
(
this
.
isLikelyShortCode
(
value
))
{
codes
.
push
(
value
)
}
})
}
return
codes
}
/**
* 清除缓存
*/
clearCache
(
shortCode
?:
string
):
void
{
if
(
shortCode
)
{
this
.
cache
.
delete
(
shortCode
)
}
else
{
this
.
cache
.
clear
()
}
}
/**
* 获取缓存统计信息
*/
getCacheStats
()
{
return
{
size
:
this
.
cache
.
size
,
codes
:
Array
.
from
(
this
.
cache
.
keys
())
}
}
}
// 导出单例
export
const
shortCodeResolver
=
new
ShortCodeResolver
()
// 开发环境下暴露调试接口
if
(
import
.
meta
.
env
.
DEV
)
{
;(
window
as
any
).
__shortCodeResolver
=
{
resolve
:
(
code
:
string
)
=>
shortCodeResolver
.
resolve
(
code
),
clearCache
:
()
=>
shortCodeResolver
.
clearCache
(),
stats
:
()
=>
shortCodeResolver
.
getCacheStats
()
}
console
.
log
(
'📦 短码解析器就绪 - 使用 window.__shortCodeResolver 调试'
)
}
src/views/products/List.vue
0 → 100644
View file @
25f87f01
<
template
>
<div>
<h1>
产品列表
</h1>
<div
v-if=
"loading"
>
加载中...
</div>
<div
v-else
>
<pre>
{{
query
}}
</pre>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
onMounted
,
ref
}
from
'vue'
import
{
useRouteParams
}
from
'@/composables/useRouteParams'
const
query
=
ref
<
any
>
({})
const
{
ready
,
loading
,
params
}
=
useRouteParams
()
onMounted
(
async
()
=>
{
await
ready
query
.
value
=
params
.
value
.
city
})
</
script
>
tsconfig.app.json
View file @
25f87f01
...
...
@@ -3,6 +3,10 @@
"compilerOptions"
:
{
"tsBuildInfoFile"
:
"./node_modules/.tmp/tsconfig.app.tsbuildinfo"
,
"types"
:
[
"vite/client"
],
"baseUrl"
:
"."
,
"paths"
:
{
"@/*"
:
[
"./src/*"
]
},
/*
Linting
*/
"strict"
:
true
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment