|
|
@@ -1,134 +1,279 @@
|
|
|
<template>
|
|
|
- <el-main>
|
|
|
- <el-row class="movie-list">
|
|
|
- <el-col :md="20" style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
|
|
|
- <el-card class="box-card">
|
|
|
- <div slot="header" class="clearfix">
|
|
|
- <span>我的应用</span>
|
|
|
- <el-button style="float: right; padding: 3px 0" type="text" @click="addAppDialog">添加</el-button>
|
|
|
- </div>
|
|
|
- <el-table
|
|
|
- :data="dataList"
|
|
|
- style="width: 100%"
|
|
|
- >
|
|
|
- <el-table-column
|
|
|
- prop="clientId"
|
|
|
- label="客户端 ID"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="resourceIds"
|
|
|
- label="资源 ID"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="scope"
|
|
|
- label="作用域"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="authorizedGrantTypes"
|
|
|
- label="授权类型"
|
|
|
- />
|
|
|
- <el-table-column
|
|
|
- prop="autoapprove "
|
|
|
- label="自动授权"
|
|
|
+ <div class="api-key-container">
|
|
|
+ <el-card class="box-card">
|
|
|
+ <div slot="header" class="clearfix">
|
|
|
+ <span class="card-title">开发者中心 - API Key 管理</span>
|
|
|
+ <el-button
|
|
|
+ style="float: right;"
|
|
|
+ type="primary"
|
|
|
+ icon="el-icon-plus"
|
|
|
+ size="small"
|
|
|
+ @click="showCreateDialog"
|
|
|
+ >创建密钥</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-alert
|
|
|
+ title="安全提示:请妥善保管您的 Secret Access Key (SK)。开放平台接口调用涉及您的账户权限,切勿泄露给他人。"
|
|
|
+ type="warning"
|
|
|
+ show-icon
|
|
|
+ :closable="false"
|
|
|
+ style="margin-bottom: 20px;"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-table v-loading="loading" :data="apiKeyList" border style="width: 100%">
|
|
|
+ <el-table-column prop="appName" label="应用/脚本名称" min-width="150" />
|
|
|
+ <el-table-column prop="accessKey" label="Access Key ID (AK)" min-width="180" />
|
|
|
+ <el-table-column label="权限范围 (Scopes)" min-width="180">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag
|
|
|
+ v-for="item in splitScopes(scope.row.scopes)"
|
|
|
+ :key="item"
|
|
|
+ size="mini"
|
|
|
+ style="margin-right: 5px;"
|
|
|
>
|
|
|
- <template slot-scope="scope">
|
|
|
- <span v-if="scope.row.autoapprove">
|
|
|
- 是
|
|
|
- </span>
|
|
|
- <span v-else>
|
|
|
- 否
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- prop="webServerRedirectUri"
|
|
|
- label="回调地址"
|
|
|
- />
|
|
|
- <el-table-column label="操作">
|
|
|
- <template slot-scope="scope">
|
|
|
- <el-button
|
|
|
- size="mini"
|
|
|
- @click="handleDetail(scope.$index, scope.row)"
|
|
|
- >详情</el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ {{ item }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" width="100" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
|
|
|
+ {{ scope.row.status === 1 ? '已启用' : '已禁用' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createTime" label="创建时间" width="160" />
|
|
|
+ <el-table-column label="操作" width="160" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button
|
|
|
+ v-if="scope.row.status === 1"
|
|
|
+ size="mini"
|
|
|
+ type="text"
|
|
|
+ style="color: #E6A23C;"
|
|
|
+ @click="handleDisable(scope.row)"
|
|
|
+ >禁用</el-button>
|
|
|
+ <el-button
|
|
|
+ size="mini"
|
|
|
+ type="text"
|
|
|
+ style="color: #F56C6C;"
|
|
|
+ @click="handleDelete(scope.row)"
|
|
|
+ >删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-dialog title="创建开发者密钥" :visible.sync="createDialogVisible" width="500px">
|
|
|
+ <el-form ref="createForm" :model="createForm" :rules="rules" label-width="110px">
|
|
|
+ <el-form-item label="应用名称" prop="appName">
|
|
|
+ <el-input v-model="createForm.appName" placeholder="例如:Python自动上传脚本" />
|
|
|
+ <el-form-item label="权限范围" prop="scopes">
|
|
|
+ <el-checkbox-group v-model="createForm.scopes">
|
|
|
+ <el-checkbox label="video:write">发布视频 (video:write)</el-checkbox>
|
|
|
+ <el-checkbox label="video:read">读取视频数据 (video:read)</el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
+ <el-button size="small" @click="createDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" size="small" :loading="submitLoading" @click="submitCreate">确 定</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
|
|
|
<el-dialog
|
|
|
- title="OAuth 应用"
|
|
|
- append-to-body
|
|
|
- :visible.sync="oauthDialog"
|
|
|
- width="30%"
|
|
|
- center
|
|
|
+ title="🔑 密钥创建成功(请立即复制保存)"
|
|
|
+ :visible.sync="resultDialogVisible"
|
|
|
+ width="550px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ :show-close="false"
|
|
|
>
|
|
|
- <el-form ref="form" :model="appForm">
|
|
|
- <el-form-item label="应用名">
|
|
|
- <el-input v-model="appForm.name" style="margin-left: 5px" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="主页 URL">
|
|
|
- <el-input v-model="appForm.homeUrl" style="margin-left: 5px" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="应用描述">
|
|
|
- <el-input v-model="appForm.description" style="margin-left: 5px" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="回调 URL">
|
|
|
- <el-input v-model="appForm.callbackUrl" style="margin-left: 5px" />
|
|
|
+ <el-alert
|
|
|
+ title="以下 Secret Key 仅在创建时显示一次。如果您关闭此窗口或刷新页面,将永远无法再次查看该密钥!"
|
|
|
+ type="error"
|
|
|
+ :closable="false"
|
|
|
+ style="margin-bottom: 20px;"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-form label-position="top">
|
|
|
+ <el-form-item label="Access Key ID (AK)">
|
|
|
+ <el-input v-model="generatedResult.accessKey" readonly>
|
|
|
+ <el-button slot="append" icon="el-icon-document-copy" @click="copyText(generatedResult.accessKey)">复制</el-button>
|
|
|
+ </el-input>
|
|
|
</el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- @click="addApp"
|
|
|
- >确定</el-button>
|
|
|
+ <el-form-item label="Secret Access Key (SK)">
|
|
|
+ <el-input v-model="generatedResult.secretKey" readonly type="password" show-password>
|
|
|
+ <el-button slot="append" icon="el-icon-document-copy" @click="copyText(generatedResult.secretKey)">复制</el-button>
|
|
|
+ </el-input>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
+
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
+ <el-button type="danger" size="small" style="width: 100%;" @click="closeResultDialog">我已安全复制并保存密钥,关闭窗口</el-button>
|
|
|
+ </div>
|
|
|
</el-dialog>
|
|
|
- </el-main>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { createApp, getOAuth, getOAuthApps, getToken } from '@/api/account'
|
|
|
+import { getApiKeyList, createApiKey, disableApiKey, deleteApiKey } from '@/api/account'
|
|
|
|
|
|
export default {
|
|
|
- name: 'MyOAuth',
|
|
|
+ name: 'ApiKeyManager',
|
|
|
data() {
|
|
|
return {
|
|
|
- dataList: [],
|
|
|
- oauthDialog: false,
|
|
|
- appForm: {
|
|
|
- name: ''
|
|
|
+ loading: false,
|
|
|
+ submitLoading: false,
|
|
|
+ apiKeyList: [],
|
|
|
+
|
|
|
+ // 创建表单相关
|
|
|
+ createDialogVisible: false,
|
|
|
+ createForm: {
|
|
|
+ appName: '',
|
|
|
+ scopes: ['video:write'] // 默认勾选视频发布
|
|
|
+ },
|
|
|
+ rules: {
|
|
|
+ appName: [
|
|
|
+ { required: true, message: '请输入应用或脚本名称', trigger: 'blur' },
|
|
|
+ { min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ scopes: [
|
|
|
+ { type: 'array', required: true, message: '请至少选择一个权限范围', trigger: 'change' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+
|
|
|
+ // 结果展示相关
|
|
|
+ resultDialogVisible: false,
|
|
|
+ generatedResult: {
|
|
|
+ accessKey: '',
|
|
|
+ secretKey: ''
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
- document.title = '我的 OAuth 应用'
|
|
|
- getOAuthApps().then(resp => {
|
|
|
- if (resp.code === 0) {
|
|
|
- this.dataList = resp.data
|
|
|
- }
|
|
|
- })
|
|
|
+ this.fetchData()
|
|
|
},
|
|
|
methods: {
|
|
|
- addAppDialog() {
|
|
|
- this.oauthDialog = true
|
|
|
+ // 获取列表数据
|
|
|
+ async fetchData() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ const res = await getApiKeyList()
|
|
|
+ if (res.code === 0) {
|
|
|
+ this.apiKeyList = res.data
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('获取密钥列表失败')
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 打开创建弹窗
|
|
|
+ showCreateDialog() {
|
|
|
+ this.createForm.appName = ''
|
|
|
+ this.createForm.scopes = ['video:write']
|
|
|
+ this.createDialogVisible = true
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$refs.createForm) this.$refs.createForm.clearValidate()
|
|
|
+ })
|
|
|
},
|
|
|
- addApp() {
|
|
|
- createApp(this.appForm).then(resp => {
|
|
|
- if (resp.code !== 0) {
|
|
|
- this.$notify.warning({
|
|
|
- message: resp.msg,
|
|
|
- type: 'warning',
|
|
|
- duration: 3000
|
|
|
- })
|
|
|
+
|
|
|
+ // 提交创建
|
|
|
+ submitCreate() {
|
|
|
+ this.$refs.createForm.validate(async(valid) => {
|
|
|
+ if (!valid) return
|
|
|
+ this.submitLoading = true
|
|
|
+ try {
|
|
|
+ const res = await createApiKey(this.createForm)
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.createDialogVisible = false
|
|
|
+ // 将后端返回的明文 AK/SK 赋予结果变量
|
|
|
+ this.generatedResult.accessKey = res.data.accessKey
|
|
|
+ this.generatedResult.secretKey = res.data.secretKey
|
|
|
+ // 唤起不可盲退的“成果展示”弹窗
|
|
|
+ this.resultDialogVisible = true
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error('创建密钥失败')
|
|
|
+ } finally {
|
|
|
+ this.submitLoading = false
|
|
|
}
|
|
|
})
|
|
|
- this.oauthDialog = false
|
|
|
+ },
|
|
|
+
|
|
|
+ // 关闭结果弹窗,并刷新列表
|
|
|
+ closeResultDialog() {
|
|
|
+ this.resultDialogVisible = false
|
|
|
+ this.generatedResult.accessKey = ''
|
|
|
+ this.generatedResult.secretKey = ''
|
|
|
+ this.fetchData() // 重新加载列表(此时列表中不再有SK明文)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 禁用密钥
|
|
|
+ handleDisable(row) {
|
|
|
+ this.$confirm(`确定要禁用应用【${row.appName}】的 API Key 吗?禁用后相关脚本将立即失效且无法恢复。`, '安全警告', {
|
|
|
+ confirmButtonText: '确定禁用',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(async() => {
|
|
|
+ const res = await disableApiKey(row.accessKey)
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.$message.success('该密钥已成功禁用')
|
|
|
+ this.fetchData()
|
|
|
+ }
|
|
|
+ }).catch(() => {})
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除密钥
|
|
|
+ handleDelete(row) {
|
|
|
+ this.$confirm(`确定要彻底删除应用【${row.appName}】的 API Key 吗?该操作不可逆!`, '危险操作提示', {
|
|
|
+ confirmButtonText: '确定删除',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'error'
|
|
|
+ }).then(async() => {
|
|
|
+ const res = await deleteApiKey(row.accessKey)
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.$message.success('密钥已删除')
|
|
|
+ this.fetchData()
|
|
|
+ }
|
|
|
+ }).catch(() => {})
|
|
|
+ },
|
|
|
+
|
|
|
+ // 辅助工具:切分逗号分隔的权限范围字符串
|
|
|
+ splitScopes(scopesStr) {
|
|
|
+ return scopesStr ? scopesStr.split(',') : []
|
|
|
+ },
|
|
|
+
|
|
|
+ // 辅助工具:原生浏览器文本一键复制
|
|
|
+ copyText(text) {
|
|
|
+ const input = document.createElement('input')
|
|
|
+ input.value = text
|
|
|
+ document.body.appendChild(input)
|
|
|
+ input.select()
|
|
|
+ try {
|
|
|
+ document.execCommand('copy')
|
|
|
+ this.$message({ message: '已复制到剪贴板', type: 'success', duration: 1500 })
|
|
|
+ } catch (err) {
|
|
|
+ this.$message.error('复制失败,请手动选择复制')
|
|
|
+ }
|
|
|
+ document.body.removeChild(input)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+.api-key-container {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+.card-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+.dialog-footer {
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
</style>
|