Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
client
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
75a5a491
authored
Jul 14, 2026
by
renyizhao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
添加api应用使用说明
parent
36bd014c
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
396 additions
and
3 deletions
+396
-3
src/views/index.vue
+4
-3
src/views/industryApplications/detail.vue
+90
-0
src/views/marketplace/AIMarketplaceDetail.vue
+9
-0
src/views/marketplace/components/UsageGuide.vue
+293
-0
No files found.
src/views/index.vue
View file @
75a5a491
...
@@ -65,9 +65,10 @@
...
@@ -65,9 +65,10 @@
:style=
"
{ background: 'url(' + i.homeImage + ')',backgroundSize: 'cover' }">
:style=
"
{ background: 'url(' + i.homeImage + ')',backgroundSize: 'cover' }">
<div
class=
"item"
><h3
class=
"title"
>
{{
i
.
title
}}
</h3>
<div
class=
"item"
><h3
class=
"title"
>
{{
i
.
title
}}
</h3>
<p
class=
"desc"
v-html=
"i.description"
></p>
<p
class=
"desc"
v-html=
"i.description"
></p>
<p
class=
"info flex-justify-end"
@
click=
"openAssembly(i)"
><span
class=
"btn"
>
{{
<p
class=
"info flex-justify-end"
style=
"height: 52px"
@
click=
"openAssembly(i)"
>
i
.
information
<span
class=
"btn"
>
{{
i
.
information
}}
</span>
}}
</span></p></div>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
src/views/industryApplications/detail.vue
View file @
75a5a491
...
@@ -112,19 +112,109 @@ getDetail()
...
@@ -112,19 +112,109 @@ getDetail()
color
:
#303233
;
color
:
#303233
;
font-size
:
16px
;
font-size
:
16px
;
line-height
:
1.8
;
line-height
:
1.8
;
word-break
:
break-word
;
//
段落
p
{
p
{
margin
:
0
0
12px
;
margin
:
0
0
12px
;
}
}
//
标题(
wangEditor
工具栏的
"标题1~6"
输出真实
h1
~
h6
)
h1
,
h2
,
h3
,
h4
,
h5
,
h6
{
font-weight
:
bold
;
color
:
#303233
;
line-height
:
1.4
;
margin
:
24px
0
12px
;
}
h1
{
font-size
:
28px
;
}
h2
{
font-size
:
24px
;
}
h3
{
font-size
:
20px
;
}
h4
{
font-size
:
18px
;
}
h5
{
font-size
:
16px
;
}
h6
{
font-size
:
14px
;
}
//
强调
strong
{
font-weight
:
bold
;
}
em
{
font-style
:
italic
;
}
u
{
text-decoration
:
underline
;
}
s
,
del
{
text-decoration
:
line-through
;
}
//
列表
ul
,
ol
{
margin
:
0
0
12px
;
padding-left
:
24px
;
li
{
margin-bottom
:
4px
;
}
}
ul
li
{
list-style
:
disc
;
}
ol
li
{
list-style
:
decimal
;
}
//
引用
blockquote
{
margin
:
12px
0
;
padding
:
8px
16px
;
border-left
:
4px
solid
#2E77E3
;
background-color
:
#f5f7fa
;
color
:
#626566
;
}
//
代码块
/
行内代码
pre
{
margin
:
12px
0
;
padding
:
12px
16px
;
background-color
:
#f5f7fa
;
border-radius
:
4px
;
font-family
:
Consolas
,
Monaco
,
"Courier New"
,
monospace
;
font-size
:
14px
;
overflow-x
:
auto
;
code
{
background
:
transparent
;
padding
:
0
;
font-size
:
inherit
;
}
}
code
{
background-color
:
#f5f7fa
;
padding
:
2px
6px
;
border-radius
:
3px
;
font-family
:
Consolas
,
Monaco
,
"Courier New"
,
monospace
;
font-size
:
14px
;
color
:
#c7254e
;
}
//
链接
a
{
color
:
#2E77E3
;
text-decoration
:
none
;
&:hover
{
text-decoration
:
underline
;
}
}
//
图片
img
{
img
{
height
:
auto
;
height
:
auto
;
max-width
:
100%
;
max-width
:
100%
;
}
}
//
表格
table
{
table
{
width
:
100%
;
width
:
100%
;
border-collapse
:
collapse
;
border-collapse
:
collapse
;
margin
:
12px
0
;
th,
td
{
border
:
1px
solid
#dcdfe6
;
padding
:
8px
12px
;
text-align
:
left
;
}
th
{
background-color
:
#f5f7fa
;
font-weight
:
bold
;
}
}
//
分割线
hr
{
border
:
none
;
border-top
:
1px
solid
#dcdfe6
;
margin
:
20px
0
;
}
}
}
}
...
...
src/views/marketplace/AIMarketplaceDetail.vue
View file @
75a5a491
...
@@ -107,6 +107,7 @@
...
@@ -107,6 +107,7 @@
<el-tabs
v-model=
"mainTab"
class=
"main-tabs"
>
<el-tabs
v-model=
"mainTab"
class=
"main-tabs"
>
<el-tab-pane
label=
"应用说明"
name=
"detail"
></el-tab-pane>
<el-tab-pane
label=
"应用说明"
name=
"detail"
></el-tab-pane>
<el-tab-pane
label=
"接口文档"
name=
"doc"
></el-tab-pane>
<el-tab-pane
label=
"接口文档"
name=
"doc"
></el-tab-pane>
<el-tab-pane
label=
"使用说明"
name=
"usage"
></el-tab-pane>
</el-tabs>
</el-tabs>
<div
class=
"tab-content-area"
>
<div
class=
"tab-content-area"
>
...
@@ -150,6 +151,13 @@
...
@@ -150,6 +151,13 @@
<!-- 接口文档 HTML -->
<!-- 接口文档 HTML -->
<div
class=
"doc-content"
v-html=
"productData.doc"
></div>
<div
class=
"doc-content"
v-html=
"productData.doc"
></div>
</div>
</div>
<!-- 使用说明 tab (通用) -->
<UsageGuide
v-else-if=
"mainTab === 'usage'"
:appName=
"productData.name"
:apiEndPoints=
"productData.apiEndPoints"
/>
</div>
</div>
</div>
</div>
...
@@ -266,6 +274,7 @@ import { OfficeBuilding, Phone } from '@element-plus/icons-vue'
...
@@ -266,6 +274,7 @@ import { OfficeBuilding, Phone } from '@element-plus/icons-vue'
import
QRCode
from
"qrcode"
;
import
QRCode
from
"qrcode"
;
import
{
PayOrderStatusEnum
}
from
"@/utils/constants.js"
;
import
{
PayOrderStatusEnum
}
from
"@/utils/constants.js"
;
import
request
from
"@/utils/request.js"
;
import
request
from
"@/utils/request.js"
;
import
UsageGuide
from
"./components/UsageGuide.vue"
;
const
router
=
useRouter
();
const
router
=
useRouter
();
const
route
=
useRoute
();
const
route
=
useRoute
();
...
...
src/views/marketplace/components/UsageGuide.vue
0 → 100644
View file @
75a5a491
<
template
>
<div
class=
"usage-guide main-content"
>
<!-- 顶部概览 -->
<section
class=
"section"
>
<h2
class=
"section-title"
>
一、概述
</h2>
<p
class=
"paragraph"
>
本使用说明适用于普惠平台「API 服务市场」中
<span
class=
"hl-bold"
>
所有
</span>
应用,介绍通用的鉴权、签名与调用流程。文档中出现的接口地址、请求 / 响应字段以本应用「
<span
class=
"hl-bold"
>
接口文档
</span>
」Tab 中公布的内容为准,本说明只描述与具体应用无关的
<span
class=
"hl-bold"
>
通用规则
</span>
。
</p>
</section>
<!-- 当前应用调用地址 -->
<section
v-if=
"apiEndPoints && apiEndPoints.length"
class=
"section"
>
<h2
class=
"section-title"
>
二、本应用接口地址
</h2>
<p
class=
"paragraph"
>
本应用对外开放以下接口,具体参数详见「
<span
class=
"hl-bold"
>
接口文档
</span>
」Tab:
</p>
<el-table
:data=
"apiEndPoints"
border
stripe
size=
"default"
class=
"endpoint-table"
>
<el-table-column
prop=
"name"
label=
"接口名称"
min-width=
"180"
/>
<el-table-column
label=
"请求方法"
width=
"100"
align=
"center"
>
<template
#
default=
"scope"
>
<el-tag
size=
"small"
:type=
"methodTagType(scope.row.method)"
>
{{
scope
.
row
.
method
}}
</el-tag>
</
template
>
</el-table-column>
<el-table-column
prop=
"path"
label=
"请求路径"
min-width=
"360"
>
<
template
#
default=
"scope"
>
<code
class=
"path-code"
>
{{
scope
.
row
.
path
}}
</code>
</
template
>
</el-table-column>
</el-table>
<p
class=
"paragraph tip"
>
实际请求时需要在路径前拼接网关前缀
<code>
{{ gatewayPrefix }}
</code>
,完整地址形如:
</p>
<p
class=
"paragraph tip"
><code
class=
"full-url"
>
{{ gatewayPrefix }}{{ apiEndPoints[0].path }}
</code></p>
</section>
<!-- 准备工作 -->
<section
class=
"section"
>
<h2
class=
"section-title"
>
{{ hasEndpoint ? '三' : '二' }}、准备工作
</h2>
<ol
class=
"ordered"
>
<li><span
class=
"hl-bold"
>
注册并登录普惠平台
</span>
:在客户端完成账号注册、登录;部分应用要求完成企业认证后才能购买。
</li>
<li><span
class=
"hl-bold"
>
购买资源包
</span>
:在「API 服务市场」找到
<span
v-if=
"appName"
class=
"hl"
>
{{ appName }}
</span><span
v-else
>
目标应用
</span>
,进入详情页选择合适的资源包规格,完成支付。资源包购买后立即生效,有效期内可调用,过期后未使用次数作废。
</li>
<li>
<span
class=
"hl-bold"
>
获取调用凭据
</span>
:购买完成后,前往「控制台 → 应用凭据」获取本应用对应的
<code>
appId
</code>
与
<code>
appSecret
</code>
。
<ul
class=
"unordered"
>
<li><code>
appId
</code>
:应用唯一标识,公开传输。
</li>
<li><code>
appSecret
</code>
:调用签名密钥,
<span
class=
"hl-bold"
>
仅在服务端保存
</span>
,严禁写入前端代码、客户端仓库或公开示例。
</li>
<li>
忘记凭据或怀疑泄漏时,请在凭据管理页重置
<code>
appSecret
</code>
,重置后旧密钥立即失效。
</li>
</ul>
</li>
</ol>
</section>
<!-- 签名机制 -->
<section
class=
"section"
>
<h2
class=
"section-title"
>
{{ hasEndpoint ? '四' : '三' }}、通用签名机制
</h2>
<p
class=
"paragraph"
>
平台采用
<code>
appId / timestamp / nonce / sign
</code>
四 Header 签名。
</p>
<h3
class=
"sub-title"
>
1. Header 说明
</h3>
<el-table
:data=
"headerRows"
border
size=
"default"
class=
"kv-table"
>
<el-table-column
prop=
"key"
label=
"Header"
width=
"180"
/>
<el-table-column
prop=
"required"
label=
"必填"
width=
"80"
align=
"center"
/>
<el-table-column
prop=
"desc"
label=
"说明"
min-width=
"360"
/>
</el-table>
<h3
class=
"sub-title"
>
2. 拼串 + SHA256 规则
</h3>
<p
class=
"paragraph"
>
将以下三个
<span
class=
"hl-bold"
>
Header 中已存在的明文值
</span>
+
<span
class=
"hl-bold"
>
本地的
<code>
appSecret
</code></span>
,按
<span
class=
"hl-bold"
>
键名字典升序
</span>
拼成查询串:
</p>
<pre
class=
"code-block"
>
appId=
<
appId
>&
nonce=
<
nonce
>&
timestamp=
<
timestamp
>&
appSecret=
<
appSecret
>
</pre>
<p
class=
"paragraph"
>
对该字符串做 UTF-8 编码后计算 SHA256,结果取 32 位小写十六进制,即为
<code>
sign
</code>
Header 的值。
</p>
<ul
class=
"unordered"
>
<li>
顺序必须严格按字母升序
<code>
appId → nonce → timestamp
</code>
(不要按写入 Header 的顺序)。
</li>
<li>
中间用
<code>
&
</code>
分隔,
<code>
=
</code>
直接连接键值,
<span
class=
"hl-bold"
>
不要对值做 URLEncode
</span>
。
</li>
<li>
服务端的
<code>
appSecret
</code>
仅参与哈希比对,客户端的
<code>
appSecret
</code>
也不出现在 Header / Body / URL 上。
</li>
</ul>
<h3
class=
"sub-title"
>
3. ⚠️ 重要:appSecret 不出网
</h3>
<p
class=
"paragraph warning"
>
真正在 Header 里传输的只有 4 个字段:
<code>
appId / timestamp / nonce / sign
</code>
。
<code>
appSecret
</code>
只在你本地参与 SHA256 计算,服务端拿到
<code>
appId
</code>
后会从凭据库里查出对应的
<code>
appSecret
</code>
,用同样的拼串规则算一次哈希,只有两个哈希值完全一致才放行。抓包、网络嗅探、网关日志都
<span
class=
"hl-bold"
>
不会泄露 appSecret
</span>
。
</p>
</section>
<!-- 通用调用流程 -->
<section
class=
"section"
>
<h2
class=
"section-title"
>
{{ hasEndpoint ? '五' : '四' }}、通用调用流程
</h2>
<ol
class=
"ordered"
>
<li>
从控制台拿到
<code>
appId
</code>
和
<code>
appSecret
</code>
。
</li>
<li>
生成
<code>
timestamp
</code>
(Unix 毫秒)与
<code>
nonce
</code>
(12 位以上随机数,2 分钟内不可重复)。
</li>
<li>
按
<code>
appId
&
nonce
&
timestamp
&
appSecret
</code>
顺序拼串 → SHA256 → 得到
<code>
sign
</code>
。
</li>
<li>
把
<code>
appId / timestamp / nonce / sign
</code>
四个值分别放入对应 Header,按业务要求发送请求(
<code>
POST
</code>
/
<code>
GET
</code>
/
<code>
multipart
</code>
等)。
</li>
<li>
解析响应:
<code>
code = 0
</code>
表示成功,资源包扣 1 次;非 0 表示失败,不扣费。
</li>
</ol>
</section>
<!-- 通用错误码 -->
<section
class=
"section"
>
<h2
class=
"section-title"
>
{{ hasEndpoint ? '六' : '五' }}、通用错误码
</h2>
<el-table
:data=
"errorCodeRows"
border
size=
"default"
class=
"kv-table"
>
<el-table-column
prop=
"code"
label=
"错误码"
width=
"160"
/>
<el-table-column
prop=
"msg"
label=
"含义"
min-width=
"200"
/>
<el-table-column
prop=
"action"
label=
"处理建议"
min-width=
"320"
/>
</el-table>
</section>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
'vue'
interface
ApiEndpoint
{
id
?:
number
|
string
name
?:
string
method
?:
string
path
?:
string
industryApplications
?:
any
[]
}
const
props
=
defineProps
<
{
appName
?:
string
apiEndPoints
?:
ApiEndpoint
[]
gatewayPrefix
?:
string
}
>
()
const
gatewayPrefix
=
computed
(()
=>
props
.
gatewayPrefix
||
'/open-api/external'
)
const
hasEndpoint
=
computed
(()
=>
Array
.
isArray
(
props
.
apiEndPoints
)
&&
props
.
apiEndPoints
.
length
>
0
)
const
headerRows
=
[
{
key
:
'appId'
,
required
:
'是'
,
desc
:
'资源包对应的应用 ID,明文传输'
},
{
key
:
'timestamp'
,
required
:
'是'
,
desc
:
'请求时刻的 Unix 毫秒时间戳,服务端允许 ±60 秒偏差'
},
{
key
:
'nonce'
,
required
:
'是'
,
desc
:
'随机字符串,长度 ≥ 10 位(建议 12 位),单次请求唯一,2 分钟内不可重复'
},
{
key
:
'sign'
,
required
:
'是'
,
desc
:
'32 位小写 SHA256 签名(hex),由 appId / nonce / timestamp / appSecret 拼串后哈希得到'
},
{
key
:
'appSecret'
,
required
:
'—'
,
desc
:
'不出现在 Header / Body / URL,仅在本地参与 SHA256 运算'
},
]
const
errorCodeRows
=
[
{
code
:
'0'
,
msg
:
'成功'
,
action
:
'—'
},
{
code
:
'1010001021'
,
msg
:
'Header 异常'
,
action
:
'检查四个签名头是否齐全,nonce 长度是否 ≥ 10,sign 是否与本地重算一致'
},
{
code
:
'1010001024'
,
msg
:
'timestamp 异常'
,
action
:
'校准本机时间(建议 NTP 同步),重新生成 timestamp 和 sign'
},
{
code
:
'1010001025'
,
msg
:
'无效的 appId'
,
action
:
'请确认 appId'
},
{
code
:
'1010001017'
,
msg
:
'接口不存在'
,
action
:
'检查请求地址,确认路径与方法是否拼写正确'
},
{
code
:
'1010001022'
,
msg
:
'未订阅该接口'
,
action
:
'在 API 服务市场购买对应的资源包'
},
{
code
:
'1010001023'
,
msg
:
'接口调用额度不足或已过期'
,
action
:
'资源包次数用完或已过有效期,需重新购买'
},
{
code
:
'1010001019'
,
msg
:
'用户密钥信息不存在'
,
action
:
'凭据可能已被重置,前往控制台重新获取 appSecret'
},
]
function
methodTagType
(
method
?:
string
)
{
switch
((
method
||
''
).
toUpperCase
())
{
case
'GET'
:
return
'success'
case
'POST'
:
return
'primary'
case
'PUT'
:
return
'warning'
case
'DELETE'
:
return
'danger'
default
:
return
'info'
}
}
</
script
>
<
style
scoped
lang=
"scss"
>
.usage-guide
{
font-size
:
14px
;
line-height
:
1.8
;
color
:
#303233
;
//
显式声明,避免被父级
flex
布局影响行内元素的换行点
white-space
:
normal
;
//
CJK
文本
keep-all
:
禁止在任意两个汉字之间断行
,
只允许在空格
/
标点处断行
//
配合
overflow-wrap
处理超长
URL
/
英文单词
word-break
:
keep-all
;
overflow-wrap
:
break-word
;
}
.usage-guide
:deep
(
strong
),
.usage-guide
strong
,
.hl-bold
{
display
:
inline
;
font-weight
:
700
;
font-style
:
normal
;
color
:
var
(
--el-color-primary
);
}
//
局部覆盖全局
.app-wrapper
:after
残留规则
//
src
/
assets
/
styles
/
index-CMeKdScn
.scss
中有
:
//
.app-wrapper
:after
{
content
:
""
;
display
:
table
;
clear
:
both
;
}
//
该选择器有空格
=
匹配
.app-wrapper
任意后代
,
会把
span
.hl-bold
等
//
行内元素的
::after
强制变成
display
:
table
,
触发意外换行
//
在
.usage-guide
作用域内重置
,
不影响其它页面
.usage-guide
:deep
(*)
::after
{
content
:
none
!important
;
display
:
inline
!important
;
clear
:
none
!important
;
}
.section
{
margin-bottom
:
28px
;
}
.section-title
{
font-size
:
18px
;
font-weight
:
600
;
color
:
#1f2329
;
margin
:
0
0
12px
;
padding-left
:
10px
;
border-left
:
4px
solid
var
(
--el-color-primary
);
}
.sub-title
{
font-size
:
15px
;
font-weight
:
600
;
color
:
#303233
;
margin
:
18px
0
8px
;
}
.paragraph
{
margin
:
6px
0
;
color
:
#4a4f57
;
white-space
:
normal
;
//
关键:CJK
不在汉字之间断行,strong
/
code
不会变成"断行点"
word-break
:
keep-all
;
overflow-wrap
:
break-word
;
}
.paragraph.warning
{
background
:
#fff7e6
;
border
:
1px
solid
#ffd591
;
color
:
#874d00
;
padding
:
10px
14px
;
border-radius
:
6px
;
}
.paragraph.tip
{
margin-top
:
12px
;
color
:
#5a6573
;
font-size
:
13px
;
}
.ordered
,
.unordered
{
margin
:
6px
0
6px
20px
;
padding
:
0
;
}
.ordered
li
,
.unordered
li
{
margin-bottom
:
4px
;
color
:
#4a4f57
;
}
.unordered
{
list-style
:
disc
;
}
.hl
{
color
:
var
(
--el-color-primary
);
font-weight
:
600
;
}
code
{
background
:
#f3f4f6
;
padding
:
1px
6px
;
border-radius
:
4px
;
font-size
:
13px
;
color
:
#c7254e
;
font-family
:
Consolas
,
Menlo
,
monospace
;
}
.code-block
{
background
:
#1e1e1e
;
color
:
#d4d4d4
;
padding
:
12px
16px
;
border-radius
:
6px
;
font-family
:
Consolas
,
Menlo
,
monospace
;
font-size
:
13px
;
line-height
:
1.6
;
margin
:
8px
0
;
overflow-x
:
auto
;
white-space
:
pre-wrap
;
word-break
:
break-all
;
}
.endpoint-table
,
.kv-table
{
margin
:
8px
0
4px
;
}
.path-code
{
background
:
#f3f4f6
;
padding
:
2px
8px
;
border-radius
:
4px
;
color
:
#303233
;
font-size
:
13px
;
font-family
:
Consolas
,
Menlo
,
monospace
;
}
.full-url
{
display
:
inline-block
;
margin-top
:
4px
;
background
:
#ecf5ff
;
color
:
#256dff
;
border
:
1px
solid
#d6e8ff
;
padding
:
4px
10px
;
border-radius
:
4px
;
font-family
:
Consolas
,
Menlo
,
monospace
;
font-size
:
13px
;
}
</
style
>
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