最新案例动态,请查阅《基于华为开发者空间Astro低代码应用平台,构建业务用户登录页面前台开发》。小伙伴快来领取华为开发者空间,进入Astro低代码应用平台实操吧!
一、概述
1. 案例介绍
华为开发者空间是为全球开发者打造的专属云上成长空间,深度整合昇腾AI、鸿蒙、鲲鹏等华为根技术。开发者空间在HDC2025上迎来全面升级,新增AI原生应用引擎、AI Notebook、鸿蒙云手机、FunctionGraph云函数、Astro低代码等核心能力,并在算力、模型、平台、应用层实现全方位优化,助力开发者高效完成从编码到调测的全流程,打造智能AI应用开发新体验。
传统用户登录开发深陷 “高成本、低效率、弱安全” 困局,而华为Astro通过 “预制安全能力+可视化编排+云原生运维” 三位一体,实现:
- 安全零信任:内建金融级防护,杜绝密码泄露风险;
- 部署小时级:拖拽式开发释放IT资源;
- 业务可持续:权限热配置支撑敏捷迭代。
2. 案例流程

说明:
- 领取华为开发者空间,登录华为开发者空间-低代码应用开发平台;
- 新建低代码应用,进入Astro轻应用服务控制台主页,开发应用。
3. 资源总览
本案例预计花费0元。
| 资源名称 | 规格 | 单价(元) | 时长(分钟) |
|---|---|---|---|
| 华为开发者空间-低代码应用开发平台 | 系统标配 | 免费 | 90 |
二、华为开发者空间-低代码应用开发平台
1. 登录华为开发者空间-低代码应用开发平台
华为开发者空间-低代码应用开发平台是华为云推出的一款可视化应用开发平台,旨在通过"拖拽式"组件和模板化设计,降低开发门槛,提升企业数字化应用构建效率。平台主要特点包括:
- 可视化开发:通过图形化界面和预置组件,无需编写复杂代码即可快速搭建应用;
- 全场景支持:覆盖Web、移动端、大屏等多终端应用开发;
- 高效集成:内置连接器可快速对接华为云及其他主流企业系统;
- 智能辅助:提供AI辅助开发能力,如智能表单生成、流程自动化等;
- 企业级能力:具备权限管理、数据安全、高可用等企业所需特性。
Astro平台特别适合业务人员与开发者协同创新,能大幅缩短应用交付周期,典型适用于OA审批、数据看板、轻量级业务系统等场景。
登录华为开发者空间,在左侧菜单列表选择华为开发者空间 -> 开发平台 -> 低代码应用,进入华为开发者空间-低代码应用开发平台。

2. 创建低代码应用
- 华为开发者空间-低代码应用开发平台页面点击新建低代码应用,在弹出的新建低代码应用对话框中,选择标准应用,点击确定按钮。

- 在右侧弹出的新建空白应用配置页签中,配置应用名称和标签均为
userLoginPage。

- 点击右下角确认按钮,平台会自动打开一个新的页面:Astro轻应用服务控制台。

注:在点击确认后,在Astro轻应用管理页会同时新增一条刚才创建的名称为pro_man__userLoginPage的应用,点击编辑同样可以进入Astro轻应用服务控制台。

三、开发自定义组件
1. 修改环境变量
进入Astro轻应用服务控制台主页,点击左上角菜单图标,选择环境配置,进入环境配置页面。

在环境配置页面,顶部选择配置。左侧菜单列表选择系统设置 -> 系统参数。在系统参数页面选择内置系统参数,在其下方的内置系统参数列表中,参考下表修改bingo.permission.resource.default.switch、bingo.tenant.security.mode、bingo.security.sensitive.data几个参数的值。

2. 获取自定义登录组件模板
- 在环境配置页面,主菜单中选择维护。在左侧导航栏中,选择全局元素 > 页面资产管理 > 组件模板。

- 在组件模板列表中,单击
widgetVue3Template,进入模板详情页。

- 单击下载,设置组件的名称为
userLogin,单击保存,将模板下载到本地。

- 下载完成后,查看解压后的组件目录。

- userLogin.js:存放vue业务逻辑的代码。
- userLogin.ftl:存放html代码。
- userLogin.css:存放样式代码。
- userLogin.editor.js、packageinfo.json:配置文件。
- 在解压后的文件夹中,创建一个imgs文件夹,并放入一个登录页面的背景图片。(假设放入的背景图片名称为imagebg.png。)

3. 修改userLogin.editor.js文件
在本地编辑器中打开文件夹,把userLogin.editor.js文件中的propertiesConfig代码改为如下代码,用于配置桥接器。
propertiesConfig: [
{
headerTitle: 'Parameters',
accordion: true,
accordionState: 'open',
config: [
{
type: 'text',
name: 'loginAPI',
label: 'login API',
value: '',
},
],
},
{
config: [
{
type: 'connectorV2',
name: 'FlowConnector',
label: 'Flow Connector',
model: 'ViewModel',
},
{
type: 'connectorV2',
name: 'common.GetConnector',
label: 'View API Get Connector',
model: 'ViewModel',
},
{
type: 'connectorV2',
name: 'common.PostConnector',
label: 'View API Post Connector',
model: 'ViewModel',
},
{
type: 'connectorV2',
name: 'common.PutConnector',
label: 'View API Put Connector',
model: 'ViewModel',
},
{
type: 'connectorV2',
name: 'common.DeleteConnector',
label: 'View API Delete Connector',
model: 'ViewModel',
},
],
},
],
4. 修改packageinfo.json文件
把packageinfo.json文件中代码替换成如下内容。
{
"widgetApi": [
{
"name": "userLogin"
}
],
"widgetDescription": "",
"authorName": "",
"localFileBasePath": "",
"requires": [
{
"name": "global_Vue3",
"version": "3.3.4"
},
{
"name": "global_ElementPlus",
"version": "2.6.0"
},
{
"name": "global_Vue3I18n",
"version": "9.10.1"
},
{
"name": "global_Vue3Router",
"version": "4.3.0"
}
]
}
5. 修改userLogin.ftl文件
将userLogin.ftl文件中的内容,替换为如下代码。
<style>
[v-cloak] {
display: none
}
</style>
<div v-cloak id="userLogin" class="page-login" v-cloak>
<div :class="backgroundClass" class="bg-box">
<!-- <div class="title">
<span>设备管理系统</span>
</div> -->
<div class="login-box">
<div class="login-title">用户登录</div>
<input name="username" type="text" style="display: none" />
<input name="password" type="password" style="display: none" />
<div class="login-form">
<div v-show="errorDesc" class="error-line">
<!-- <img :src="BasePath + 'imgs/btn_errorInfo.png'" /> -->
<span class="error-text" v-html="errorDesc"></span>
</div>
<div class="login-item mg-top10">
<!-- :placeholder="isUserName ? getTransLang('ds.commerce.storefront.web.loginname') : getTransLang('ds.commerce.storefront.web.loginEmailOrPhone')" -->
<input
ref="accountInput"
v-model="account"
placeholder="请输入用户名"
@keyup.enter="validateBeforeSubmit"
autocomplete="off"
maxlength="32"
type="text"
/>
</div>
<div class="login-item mg-top10">
<!-- :placeholder="getTransLang('ds.commerce.storefront.web.password')" -->
<input
v-model="password"
@keyup.enter="validateBeforeSubmit"
placeholder="请输入密码"
autocomplete="off"
maxlength="32"
type="password"
/>
</div>
<div v-if="needVerify" class="login-item">
<!-- :placeholder="getTransLang('ds.commerce.storefront.web.verificationcode')" -->
<input
v-model="inputImgCode"
@keyup.enter="validateBeforeSubmit"
placeholder="请输入验证码"
autocomplete="off"
maxlength="10"
type="text"
/>
<div class="verify-code">
<img :src="imgCode" @click="getVerifyCode()" />
</div>
</div>
<div class="login-button" @click="validateBeforeSubmit">
登录
</div>
</div>
</div>
</div>
</div>
6. 修改userLogin.js文件
将userLogin.js文件内容,替换为如下代码。
userLogin = StudioWidgetWrapper.extend({
init: function () {
var thisObj = this
thisObj._super.apply(thisObj, arguments)
thisObj.render()
thisObj.initBusi()
if (typeof Studio != 'undefined' && Studio) {
Studio.registerEvents(thisObj, 'goHomepage', 'go Homepage', [])
}
},
render: function () {
var thisObj = this
let tenantId;
if (Studio.inReader) {
tenantId = STUDIO_DATA.catalogProperties["tenant-id"].value
} else {
tenantId = magno.pageService.getCatalogProperties()["tenant-id"].value
}
HttpUtils.setCookie("tenant-id", tenantId)
HttpUtils.setCookie("locale","zh_CN");
var elem = thisObj.getContainer()
if (elem) {
var containerDiv = $('.scfClientRenderedContainer', elem)
if (containerDiv.length) {
$(containerDiv).empty()
} else {
containerDiv = document.createElement('div')
containerDiv.className = 'scfClientRenderedContainer'
$(elem).append(containerDiv)
}
}
thisObj.sksBindItemEvent()
$(window).resize(function () {
thisObj.sksRefreshEvents()
})
},
initBusi: function () {
var thisObj = this
var widgetProperties = thisObj.getProperties()
var BasePath = thisObj.getWidgetBasePath() // 本地路径
var elem = thisObj.getContainer()
const app = Vue.createApp({
data(){
return {
BasePath: BasePath,
imgCode: '',
account: '',
password: '',
inputImgCode: '',
errorDesc: '',
needRead: false,
backgroundClass: 'backgroundClass',
needVerify: false,
accountName: '',
isMobile: false,
}
},
created() {
this.getVerifyCode()
this.checkIsLogin()
this.isMobile = /Android|webOS|iPhone|iPad|BlackBerry/i.test(
navigator.userAgent
)
},
watch: {
account(newVal, oldVal) {
if (newVal !== oldVal) {
this.password = ''
}
},
},
methods: {
checkIsLogin() {
const userInfo = JSON.parse(
window.sessionStorage.getItem('userInfo')
)
if (userInfo !== null && userInfo.username) {
setTimeout(() => {
thisObj.triggerEvent('goHomepage', {})
}, 1000)
}
},
shouldShowImgCode() {
connService(
thisObj,
`${ds_baseUrl}/identities/isNeedCaptcha`,
{
username: this.account,
},
'common.PostConnector'
)
.then((res) => {
const { data = [], resp = {} } = res
if (resp.code !== '0') {
this.$message.error(resp.message)
}
if (data && data.length) {
this.needVerify = data[0].needVerify
if (!this.needVerify) {
this.confirmLogin()
}
}
})
.catch((e) => {
this.$message.error(e.response.resMsg)
})
},
// 登录按钮
validateBeforeSubmit() {
if (!this.password) {
this.errorDesc = '请输入密码。'
this.needVerify = true
return false
}
if (!this.account) {
this.errorDesc = '请输入用户名。'
this.needVerify = true
return false
}
if (!this.inputImgCode && this.needVerify) {
this.errorDesc = '验证码错误。'
return false
}
if (this.needVerify && this.inputImgCode) {
this.confirmLogin()
return
}
this.confirmLogin()
},
// 调登录接口
confirmLogin() {
var request = {
username: this.account,
password: this.password,
captcha: this.inputImgCode,
}
//
this.callFlowConn1(
widgetProperties.loginAPI,
request,
this.callLogin,
this.loginFail,
'post'
)
},
// 登录接口成功函数
callLogin(response) {
if (response) {
this.errorDesc = ''
let userInfo = {
username: response.data[0].username,
userId: response.data[0].userId,
profile: response.data[0].profile,
}
HttpUtils.setCookie('isLogged', true)
window.sessionStorage.setItem(
'userInfo',
JSON.stringify(userInfo)
)
thisObj.triggerEvent('goHomepage', {})
}
},
// 登录失败resMsg
loginFail(data){
this.errorDesc = data.response.resMsg
this.getVerifyCode()
this.needVerify = true
},
// 获取验证码
getVerifyCode() {
this.imgCode =
'/u-route/baas/sys/v1.0/verificationcode?type=login&t=' +
Date.parse(new Date())
},
// 封装flow调用后台
callFlowConn1: function (service, param, callbackFunc,callbackfail, method) {
var thisView = this
let mMethod
switch (method) {
case 'get':
mMethod = 'common.GetConnector'
break
case 'post':
mMethod = 'common.PostConnector'
break
case 'put':
mMethod = 'common.PutConnector'
break
default:
mMethod = 'common.FlowConnector'
break
}
var connector = thisObj.getConnectorInstanceByName(mMethod)
if (connector) {
connector.setInputParams({
service: service,
needSchema: 'data',
async: false,
})
connector
.query(param) // 调用接口,以param为入参
.done(function (response) {
if (response.resp && response.resp.code) {
callbackFunc.call(thisView, response)
}
})
.fail(function (response) {
// 代表接口执行失败
callbackfail.call(thisView, response)
})
}
},
},
})
app.use(ElementPlus);
thisObj.vm = app.mount($("#userLogin", elem)[0]);
},
})
7. 修改userLogin.css文件
将userLogin.css文件内容,替换为如下代码。
注:background的url取值,需要和“2. 获取自定义登录组件模板 -> 步骤6. 创建的文件夹和图片”中名称保持一致(本案例中为imgs/imagebg.png)。
.page-login {
/* 使用webkit内核的浏览器 */
/* Firefox版本4-18 */
/* Firefox版本19+ */
}
.page-login * {
box-sizing: border-box;
}
.page-login .flex {
display: flex;
}
.page-login .login_tip {
font-size: 12px;
color: #167aeb;
}
.page-login .bg-box {
position: relative;
display: flex;
justify-content: center;
width: 100%;
height: 100%;
background: url('imgs/imagebg.png') no-repeat 50%;
background-size: cover;
}
.page-login .bg-box > img {
width: 100%;
}
@media (min-width: 767px) {
.page-login .bg-box .login-box {
position: absolute;
right: 18%;
top: 26%;
padding: 30px 30px 20px 30px;
width: 380px;
background: #fff;
border-radius: 10px;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.1);
}
.page-login .bg-box .login-box .login-title {
font-size: 24px;
color: #333;
text-align: center;
}
.page-login .bg-box .login-box .login-form {
padding-top: 20px;
}
.page-login .bg-box .login-box .login-form .error-line {
display: flex;
align-items: center;
background: #ffebeb;
color: #e4393c;
border: 1px solid #faccc6;
padding: 3px 10px 3px 10px;
height: auto;
text-align: left;
font-size: 12px;
}
.page-login .bg-box .login-box .login-form .error-line .error-text {
padding-left: 5px;
}
.page-login .bg-box .login-box .login-form .error-line .error-text .change_tab {
color: #167aeb;
cursor: pointer;
}
.page-login .bg-box .login-box .login-form .login-item {
display: flex;
align-items: center;
margin-top: 20px;
}
.page-login .bg-box .login-box .login-form .login-item .el-input__inner {
height: 36px;
}
.page-login .bg-box .login-box .login-form .login-item > input {
flex: 1;
padding: 5px 10px;
height: 36px;
border-radius: 3px;
border: 1px solid #ddd;
}
.page-login .bg-box .login-box .login-form .login-item .verify-code {
height: 36px;
margin-left: 5px;
}
.page-login .bg-box .login-box .login-form .login-item .verify-code img {
height: 100%;
}
.page-login .bg-box .login-box .login-form .login-item .el-checkbox {
margin-right: 10px;
}
.page-login .bg-box .login-box .login-form .login-item .read-text {
font-size: 12px;
color: #333;
}
.page-login .bg-box .login-box .login-form .login-item .read-text .clickable {
color: #167aeb;
cursor: pointer;
}
.page-login .bg-box .login-box .login-form .login-item .read-text .clickable:hover {
text-decoration: underline;
}
.page-login .bg-box .login-box .login-form .mg-top10 {
margin-top: 10px;
}
.page-login .bg-box .login-box .login-button {
margin-top: 20px;
height: 40px;
line-height: 40px;
border-radius: 3px;
background: #3d88ff;
text-align: center;
color: #fff;
font-size: 14px;
cursor: pointer;
}
.page-login .bg-box .login-box .login-button:hover {
opacity: 0.8;
}
.page-login .bg-box .login-box .divide-line {
margin-top: 20px;
height: 1px;
background: #eee;
opacity: 0.8;
}
.page-login .bg-box .login-box .login-bottom {
margin-top: 20px;
justify-content: center;
align-items: center;
}
.page-login .bg-box .login-box .login-bottom .bottom-text {
font-size: 12px;
color: #999;
}
.page-login .bg-box .login-box .login-bottom .type-item {
margin-left: 10px;
width: 30px;
height: 23px;
cursor: pointer;
}
.page-login .bg-box .login-box .login-bottom .type-item img {
width: 100%;
}
}
@media (max-width: 767px) {
.page-login .bg-box .login-box {
position: absolute;
right: auto;
top: 26%;
padding: 30px 30px 20px 30px;
width: 380px;
background: #fff;
border-radius: 10px;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.1);
}
.page-login .bg-box .login-box .login-title {
font-size: 24px;
color: #333;
text-align: center;
}
.page-login .bg-box .login-box .login-form {
padding-top: 20px;
}
.page-login .bg-box .login-box .login-form .error-line {
display: flex;
align-items: center;
background: #ffebeb;
color: #e4393c;
border: 1px solid #faccc6;
padding: 3px 10px 3px 10px;
height: auto;
text-align: left;
font-size: 12px;
}
.page-login .bg-box .login-box .login-form .error-line .error-text {
padding-left: 5px;
}
.page-login .bg-box .login-box .login-form .error-line .error-text .change_tab {
color: #167aeb;
cursor: pointer;
}
.page-login .bg-box .login-box .login-form .login-item {
display: flex;
align-items: center;
margin-top: 20px;
}
.page-login .bg-box .login-box .login-form .login-item .el-input__inner {
height: 36px;
}
.page-login .bg-box .login-box .login-form .login-item > input {
flex: 1;
padding: 5px 10px;
height: 36px;
border-radius: 3px;
border: 1px solid #ddd;
}
.page-login .bg-box .login-box .login-form .login-item .verify-code {
height: 36px;
margin-left: 5px;
}
.page-login .bg-box .login-box .login-form .login-item .verify-code img {
height: 100%;
}
.page-login .bg-box .login-box .login-form .login-item .el-checkbox {
margin-right: 10px;
}
.page-login .bg-box .login-box .login-form .login-item .read-text {
font-size: 12px;
color: #333;
}
.page-login .bg-box .login-box .login-form .login-item .read-text .clickable {
color: #167aeb;
cursor: pointer;
}
.page-login .bg-box .login-box .login-form .login-item .read-text .clickable:hover {
text-decoration: underline;
}
.page-login .bg-box .login-box .login-form .mg-top10 {
margin-top: 10px;
}
.page-login .bg-box .login-box .login-button {
margin-top: 20px;
height: 40px;
line-height: 40px;
border-radius: 3px;
background: #3d88ff;
text-align: center;
color: #fff;
font-size: 14px;
cursor: pointer;
}
.page-login .bg-box .login-box .login-button:hover {
opacity: 0.8;
}
.page-login .bg-box .login-box .divide-line {
margin-top: 20px;
height: 1px;
background: #eee;
opacity: 0.8;
}
.page-login .bg-box .login-box .login-bottom {
margin-top: 20px;
justify-content: center;
align-items: center;
}
.page-login .bg-box .login-box .login-bottom .bottom-text {
font-size: 12px;
color: #999;
}
.page-login .bg-box .login-box .login-bottom .type-item {
margin-left: 10px;
width: 30px;
height: 23px;
cursor: pointer;
}
.page-login .bg-box .login-box .login-bottom .type-item img {
width: 100%;
}
}
.page-login ::-webkit-input-placeholder {
font-size: 12px;
color: #aaa;
}
.page-login :-moz-placeholder {
font-size: 12px;
color: #aaa;
}
.page-login ::-moz-placeholder {
font-size: 12px;
color: #aaa;
}
.page-login :-ms-input-placeholder {
font-size: 12px;
color: #aaa;
}
.page-login .logining-text {
margin-top: 120px;
text-align: center;
font-size: 30px;
color: #333;
}
8. 上传自定义组件
- 自定义组件打包
将修改后的配置文件和自定义开发的文件,压缩成一个zip包,包名为userLogin.zip。

注:在打包zip包时,要注意打包目录层级,确保压缩包内不会有多层目录。
- 上传自定义组件
自定义组件开发完成后,需要上传到华为云Astro轻应用组件库中,供高级页面使用。
在Astro轻应用环境配置页面,单击主菜单中的维护。在左侧导航栏中,选择全局元素 > 页面资产管理 > 组件。单击提交新组件,进入提交新组件页面。

- 单击上传,上传自定义组件包
userLogin.zip。

- 参数设置完成后,单击提交,完成组件的上传。

参数说明:
| 参数 | 说明 | 示例 |
|---|---|---|
| 名字 | 新提交组件的名称,系统会根据组件包名称自动填充。 | userLogin |
| 上传源文件 | 组件的源文件包。 | 选择2. 开发自定义组件中的创建的自定义组件登录包 |
| 场景 | 选择组件包的应用场景,可同时勾选多项,勾选后,在相应类型页面开发中,才可使用该组件。 | 高级页面 |
| 发行说明 | 组件的描述信息,需要配置不同语种下的描述信息。此处配置的信息,将会在组件详情页的“概况”页签中进行展示。 | 自定义组件 |
注:此时在Astro轻应用环境配置页面 > 维护 > 全局元素 > 页面资产管理 > 组件中新增了我们刚才提交的userLogin组件

四、开发高级页面,发布应用
1. 创建高级页面
“业务用户登录”页面是一个高级页面,主要是通过引用上传的自定义登录组件,再配置相关参数,来实现登录功能。
在组装业务用户登录页面时,需要配置从登录页面登录成功后跳转的页面,所以需要提前创建一个高级页面。
在实际开发过程中,可以根据业务需要,选择跳转到需要的页面,或者执行自定义的业务逻辑。
-
在Astro轻应用服务控制台左侧导航栏中选择界面,点击高级页面后的“+”。
-
在弹出的添加高级页面弹窗中设置标签和名称为
Login,并选择绝对布局,单击添加。

2. 开发登录页面
- 在高级页面
Login中,单击左上角的组件展开图标,选择全部,在自定义页签,拖拽userLogin组件到页面编辑区。

- 设置自定义组件“userLogin”的位置属性。
-
将
userLogin组件拖拽到页面编辑区后,会在右侧显示该组件的属性配置面板。 -
在位置中,设置距离左端、距离顶端为
0,宽度为1920,高度为1080。 -
组件位置、样式等属性修改完成后,单击页面上方的保存图标,保存页面修改。

- 在界面 > 高级页面中,将鼠标放在Login上,单击,选择设置。

- 在页面设置中,选中拉伸属性,单击保存。

- 设置自定义组件的桥接器。
- 选中
userLogin组件,在“属性 > Parameters”中,设置“login API”为“/命名空间__A/1.0.0/Flow_login”。
注:
-
login API为登录接口的URL后半段,命名空间请根据实际情况配置,A为应用名。 -
本案例登录接口是在《基于华为开发者空间Astro低代码应用平台,构建业务用户登录后台开发》案例中创建的。详细操作步骤,请点击案例超链接,查看案例。

- 在数据页签,单击View API Get Connector,设置桥接器实例为通用AstroZero API数据桥接器,数据类型为动态数据,请求方法为get,如下图所示。

- 参考上一步,分别设置下图中框选的View API Post Connector、View API put Connector、View API Delete Connector桥接器实例。 (请求方法分别对应修改为post、put、delete。)

- 设置自定义组件
userLogin的事件,使自定义组件与其他页面关联。

单击go Homepage后的齿轮图标,进入事件编辑页面。
单击新建动作,选择默认 > 页面跳转,进入跳转编辑页面。

在页面跳转编辑页面,页面类型选择外部页面;外部页面地址这里假设填https://developer.huaweicloud.com/space/home;页面打开方式可以选则当前窗口或新窗口,本案例中选用新窗口。点击右下角确定按钮,完成页面跳转配置。
注:用户也可以根据自己实际业务需求,选择页面类型及其配置。

- 在上述步骤完成后,单击Login页面上方的保存图标,保存页面。
3. 发布应用,登录页面
-
单击Login页面上方的发布图标,发布页面。
-
在弹出的页面成功发布提示框中,点击或复制发布链接,也可以点击Login页面上方的预览图标,预览发布的登录页面。

- 在发布的登录页中,输入业务用户账号及密码,单击登录。
注:这里的账号密码是在是在《基于华为开发者空间Astro低代码应用平台,构建业务用户登录后台开发》案例中步骤“三、创建用户注册脚本”创建的。

页面成功跳转,案例结束。

关注“华为云开发者联盟”,了解更多技术动态。 更多开发者空间技术干货请关注: 开发者空间案例中心

480

被折叠的 条评论
为什么被折叠?



