mirror of
https://github.com/certd/certd.git
synced 2026-04-23 19:57:27 +08:00
refactor: rename ui
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<a-config-provider :locale="locale">
|
||||
|
||||
<a-layout class="page-layout">
|
||||
<a-layout-header>Cert-D</a-layout-header>
|
||||
<a-layout style="flex:1">
|
||||
<router-view/>
|
||||
</a-layout>
|
||||
<a-layout-footer>
|
||||
by greper
|
||||
</a-layout-footer>
|
||||
|
||||
</a-layout>
|
||||
|
||||
</a-config-provider>
|
||||
</template>
|
||||
<script>
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
locale: zhCN
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` returning
|
||||
return { t } // return render context that included `t`
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.page-layout{
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
.ant-layout-header{
|
||||
color:#fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { request } from './service'
|
||||
|
||||
export default {
|
||||
list () {
|
||||
return request({
|
||||
url: '/plugins/list'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { request } from './service'
|
||||
|
||||
export default {
|
||||
list () {
|
||||
return request({
|
||||
url: '/providers/list'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { assign, map } from 'lodash'
|
||||
import { service, request } from './service'
|
||||
|
||||
const files = require.context('./modules', false, /\.js$/)
|
||||
const generators = files.keys().map(key => files(key).default)
|
||||
|
||||
export default assign({}, ...map(generators, generator => generator({
|
||||
service,
|
||||
request
|
||||
})))
|
||||
@@ -0,0 +1,95 @@
|
||||
import axios from 'axios'
|
||||
import { get } from 'lodash-es'
|
||||
import { errorLog, errorCreate } from './tools'
|
||||
|
||||
/**
|
||||
* @description 创建请求实例
|
||||
*/
|
||||
function createService () {
|
||||
// 创建一个 axios 实例
|
||||
const service = axios.create()
|
||||
// 请求拦截
|
||||
service.interceptors.request.use(
|
||||
config => config,
|
||||
error => {
|
||||
// 发送失败
|
||||
console.log(error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
// 响应拦截
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
// dataAxios 是 axios 返回数据中的 data
|
||||
const dataAxios = response.data
|
||||
// 这个状态码是和后端约定的
|
||||
const { code } = dataAxios
|
||||
// 根据 code 进行判断
|
||||
if (code === undefined) {
|
||||
// 如果没有 code 代表这不是项目后端开发的接口 比如可能是 D2Admin 请求最新版本
|
||||
if (response.config.unpack) {
|
||||
return dataAxios
|
||||
}
|
||||
return dataAxios.data
|
||||
} else {
|
||||
// 有 code 代表这是一个后端接口 可以进行进一步的判断
|
||||
switch (code) {
|
||||
case 0:
|
||||
// [ 示例 ] code === 0 代表没有错误
|
||||
// TODO 可能结果还需要code和msg进行后续处理,所以返回全部结果
|
||||
return dataAxios.data
|
||||
case 'xxx':
|
||||
// [ 示例 ] 其它和后台约定的 code
|
||||
errorCreate(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`)
|
||||
break
|
||||
default:
|
||||
// 不是正确的 code
|
||||
errorCreate(`${dataAxios.msg}: ${response.config.url}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
error => {
|
||||
const status = get(error, 'response.status')
|
||||
switch (status) {
|
||||
case 400: error.message = '请求错误'; break
|
||||
case 401: error.message = '未授权,请登录'; break
|
||||
case 403: error.message = '拒绝访问'; break
|
||||
case 404: error.message = `请求地址出错: ${error.response.config.url}`; break
|
||||
case 408: error.message = '请求超时'; break
|
||||
case 500: error.message = '服务器内部错误'; break
|
||||
case 501: error.message = '服务未实现'; break
|
||||
case 502: error.message = '网关错误'; break
|
||||
case 503: error.message = '服务不可用'; break
|
||||
case 504: error.message = '网关超时'; break
|
||||
case 505: error.message = 'HTTP版本不受支持'; break
|
||||
default: break
|
||||
}
|
||||
errorLog(error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
return service
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建请求方法
|
||||
* @param {Object} service axios 实例
|
||||
*/
|
||||
function createRequestFunction (service) {
|
||||
return function (config) {
|
||||
const configDefault = {
|
||||
headers: {
|
||||
'Content-Type': get(config, 'headers.Content-Type', 'application/json')
|
||||
},
|
||||
timeout: 5000,
|
||||
baseURL: process.env.VUE_APP_API,
|
||||
data: {}
|
||||
}
|
||||
return service(Object.assign(configDefault, config))
|
||||
}
|
||||
}
|
||||
|
||||
// 用于真实网络请求的实例和请求方法
|
||||
export const service = createService()
|
||||
export const request = createRequestFunction(service)
|
||||
@@ -0,0 +1,73 @@
|
||||
import { notification } from 'ant-design-vue'
|
||||
|
||||
/**
|
||||
* @description 安全地解析 json 字符串
|
||||
* @param {String} jsonString 需要解析的 json 字符串
|
||||
* @param {String} defaultValue 默认值
|
||||
*/
|
||||
export function parse (jsonString = '{}', defaultValue = {}) {
|
||||
let result = defaultValue
|
||||
try {
|
||||
result = JSON.parse(jsonString)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 接口请求返回
|
||||
* @param {Any} data 返回值
|
||||
* @param {String} msg 状态信息
|
||||
* @param {Number} code 状态码
|
||||
*/
|
||||
export function response (data = {}, msg = '', code = 0) {
|
||||
return [
|
||||
200,
|
||||
{ code, msg, data }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 接口请求返回 正确返回
|
||||
* @param {Any} data 返回值
|
||||
* @param {String} msg 状态信息
|
||||
*/
|
||||
export function responseSuccess (data = {}, msg = '成功') {
|
||||
return response(data, msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 接口请求返回 错误返回
|
||||
* @param {Any} data 返回值
|
||||
* @param {String} msg 状态信息
|
||||
* @param {Number} code 状态码
|
||||
*/
|
||||
export function responseError (data = {}, msg = '请求失败', code = 500) {
|
||||
return response(data, msg, code)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 记录和显示错误
|
||||
* @param {Error} error 错误对象
|
||||
*/
|
||||
export function errorLog (error) {
|
||||
// 打印到控制台
|
||||
console.log(error)
|
||||
// 显示提示
|
||||
notification({
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建一个错误
|
||||
* @param {String} msg 错误信息
|
||||
*/
|
||||
export function errorCreate (msg) {
|
||||
const error = new Error(msg)
|
||||
errorLog(error)
|
||||
throw error
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,32 @@
|
||||
<script>
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import _ from 'lodash-es'
|
||||
export default {
|
||||
name: 'component-render',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: 'a-input'
|
||||
},
|
||||
children: {
|
||||
type: Array
|
||||
},
|
||||
on: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup (props, context) {
|
||||
const attrs = {
|
||||
...context.$attrs
|
||||
}
|
||||
_.forEach(props.on, (value, key) => {
|
||||
attrs[key] = value
|
||||
if (typeof value === 'string') {
|
||||
// eslint-disable-next-line no-eval
|
||||
attrs[key] = eval(value)
|
||||
}
|
||||
})
|
||||
return () => h(resolveComponent(props.name), context.$attrs, props.children)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="d-container">
|
||||
<div class="box">
|
||||
<div class="inner">
|
||||
<div class="header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div class="body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'd-container'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.d-container{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.box {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
.inner{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.header{
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.body{
|
||||
overflow-y: auto;
|
||||
flex:1
|
||||
}
|
||||
.footer{
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,16 @@
|
||||
import DContainer from './d-container'
|
||||
import ComponentRender from './component-render'
|
||||
import ProviderSelector from './provider-selector/provider-selector'
|
||||
|
||||
const list = [
|
||||
DContainer,
|
||||
ComponentRender,
|
||||
ProviderSelector
|
||||
]
|
||||
export default {
|
||||
install (app) {
|
||||
for (const item of list) {
|
||||
app.component(item.name, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
title="授权管理"
|
||||
placement="right"
|
||||
:closable="true"
|
||||
width="500px"
|
||||
v-model:visible="visible"
|
||||
:after-visible-change="onAfterVisibleChange"
|
||||
>
|
||||
<div class="d-container provider-manager">
|
||||
<a-button @click="add">
|
||||
添加授权
|
||||
</a-button>
|
||||
<a-list
|
||||
class="list"
|
||||
item-layout="horizontal"
|
||||
:data-source="getProviders()"
|
||||
>
|
||||
<template #renderItem="{ item ,index }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a-button type="primary" @click="openEdit(item,index)"><template #icon><EditOutlined /></template></a-button>
|
||||
<a-button type="danger" @click="remove(item,index)"><template #icon ><DeleteOutlined /></template></a-button>
|
||||
</template>
|
||||
|
||||
<a-radio :disabled="isDisabled(item)" :checked="item.key===selectedKey" @update:checked="selectedKey = item.key" >
|
||||
{{ item.name }} ({{item.type}})
|
||||
</a-radio>
|
||||
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
|
||||
<div>
|
||||
<a-button @click="onProviderSelectSubmit">确定</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
||||
<a-modal v-model:visible="editVisible" dialogClass="d-dialog" width="700px" title="编辑授权" @ok="onSubmit">
|
||||
|
||||
<a-alert v-if="currentProvider?.desc" :message="currentProvider.desc" type="success" />
|
||||
|
||||
<a-form ref="formRef" class="domain-form" :model="formData" labelWidth="150px" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<a-form-item label="类型" name="type" :rules="rules.type">
|
||||
<a-radio-group :disabled="editIndex!=null" v-model:value="formData.type" @change="onTypeChanged" >
|
||||
<a-radio-button v-for="(option) of providerDefineList" :disabled="isDisabled(option,'name')" :key="option.name" :value="option.name">
|
||||
{{option.label}}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="formData.type && currentProvider">
|
||||
<a-form-item label="名称" name="name" :rules="rules.name">
|
||||
<a-input v-model:value="formData.name"/>
|
||||
</a-form-item>
|
||||
<a-form-item v-for="(item,key,index) in currentProvider.input"
|
||||
:key="index"
|
||||
v-bind="item.component||{}"
|
||||
:label="item.label || key"
|
||||
:name="key">
|
||||
<component-render v-model:value="formData[key]" v-bind="item.component || {}"></component-render>
|
||||
<template #extra >
|
||||
<div v-if="item.desc" class="helper">{{item.desc}}</div>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
</a-form>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, nextTick, watch, inject } from 'vue'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { useForm } from '@ant-design-vue/use'
|
||||
import _ from 'lodash-es'
|
||||
import providerApi from '@/api/api.providers'
|
||||
function useEdit (props, context, onEditSave) {
|
||||
const formData = reactive({
|
||||
key: '',
|
||||
name: '',
|
||||
type: undefined
|
||||
})
|
||||
|
||||
const rules = ref({
|
||||
type: [{
|
||||
type: 'string',
|
||||
required: true,
|
||||
message: '请选择类型'
|
||||
}],
|
||||
name: [{
|
||||
type: 'string',
|
||||
required: true,
|
||||
message: '请输入名称'
|
||||
}]
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// const { resetFields, validate, validateInfos } = useForm(formData, rules)
|
||||
const onSubmit = async e => {
|
||||
e.preventDefault()
|
||||
const res = await formRef.value.validate()
|
||||
console.log('validation:', res)
|
||||
const newProvider = _.cloneDeep(formData)
|
||||
onEditSave(newProvider, editIndex.value)
|
||||
closeEdit()
|
||||
}
|
||||
|
||||
const editVisible = ref(false)
|
||||
const editIndex = ref(null)
|
||||
const openEdit = (item, index) => {
|
||||
if (item) {
|
||||
editIndex.value = index
|
||||
_.forEach(formData, (value, key) => {
|
||||
formData[key] = null
|
||||
})
|
||||
_.merge(formData, item)
|
||||
changeType(item.type)
|
||||
} else {
|
||||
editIndex.value = null
|
||||
formData.type = null
|
||||
}
|
||||
editVisible.value = true
|
||||
}
|
||||
const add = () => {
|
||||
openEdit()
|
||||
}
|
||||
const closeEdit = () => {
|
||||
editVisible.value = false
|
||||
}
|
||||
const providerDefineList = ref([])
|
||||
const onCreated = async () => {
|
||||
providerDefineList.value = await providerApi.list()
|
||||
}
|
||||
onCreated()
|
||||
const currentProvider = ref(null)
|
||||
const onTypeChanged = (e) => {
|
||||
const value = e.target.value
|
||||
changeType(value)
|
||||
// 遍历input 设置到form rules
|
||||
}
|
||||
const changeType = (type) => {
|
||||
if (providerDefineList.value == null) {
|
||||
return
|
||||
}
|
||||
for (const item of providerDefineList.value) {
|
||||
if (item.name === type) {
|
||||
currentProvider.value = item
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (editIndex.value == null) {
|
||||
formData.key = currentProvider.value.name
|
||||
formData.name = currentProvider.value.label || currentProvider.value.name
|
||||
}
|
||||
}
|
||||
|
||||
const isDisabled = (item, keyName = 'type') => {
|
||||
if (!props.filter) {
|
||||
return false
|
||||
}
|
||||
return item[keyName
|
||||
] !== props.filter
|
||||
}
|
||||
return {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 16 },
|
||||
formData,
|
||||
onSubmit,
|
||||
rules,
|
||||
editVisible,
|
||||
formRef,
|
||||
currentProvider,
|
||||
providerDefineList,
|
||||
editIndex,
|
||||
openEdit,
|
||||
onTypeChanged,
|
||||
add,
|
||||
isDisabled
|
||||
}
|
||||
}
|
||||
|
||||
let index = 0
|
||||
const keyPrefix = 'provider_'
|
||||
function generateNewKey (list) {
|
||||
index++
|
||||
let exists = false
|
||||
for (const item of list) {
|
||||
if (item.key === keyPrefix + index) {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (exists) {
|
||||
return generateNewKey(list)
|
||||
}
|
||||
return keyPrefix + index
|
||||
}
|
||||
export default {
|
||||
name: 'provider-manager',
|
||||
props: {
|
||||
value: {},
|
||||
filter: {}
|
||||
},
|
||||
emits: ['update:value'],
|
||||
setup (props, context) {
|
||||
const visible = ref(false)
|
||||
|
||||
const close = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
const onAfterVisibleChange = () => {
|
||||
|
||||
}
|
||||
|
||||
const getProviders = inject('get:accessProviders')
|
||||
// const providerList = ref([])
|
||||
const selectedKey = ref(null)
|
||||
|
||||
watch(() => props.value, () => {
|
||||
selectedKey.value = props.value
|
||||
}, { immediate: true })
|
||||
|
||||
const onEditSave = (newProvider, editIndex) => {
|
||||
const providerList = getProviders()
|
||||
if (editIndex == null) {
|
||||
// add 生成一个key
|
||||
newProvider.key = generateNewKey(providerList)
|
||||
providerList.push(newProvider)
|
||||
} else {
|
||||
_.merge(providerList[editIndex], newProvider)
|
||||
}
|
||||
}
|
||||
|
||||
const editModule = useEdit(props, context, onEditSave)
|
||||
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
const providerList = getProviders()
|
||||
if (providerList.length === 0) {
|
||||
nextTick(() => {
|
||||
editModule.add()
|
||||
})
|
||||
}
|
||||
}
|
||||
const remove = (item, index) => {
|
||||
const providerList = getProviders()
|
||||
providerList.splice(index, 1)
|
||||
}
|
||||
|
||||
const updateProviders = inject('update:accessProviders')
|
||||
|
||||
// watch(() => providers, () => {
|
||||
// providerList.value = _.cloneDeep(props.providers || [])
|
||||
// }, { immediate: true })
|
||||
|
||||
const onProviderSelectSubmit = () => {
|
||||
const providerList = getProviders()
|
||||
updateProviders(providerList)
|
||||
context.emit('update:value', selectedKey.value)
|
||||
close()
|
||||
}
|
||||
return {
|
||||
visible,
|
||||
open,
|
||||
close,
|
||||
onAfterVisibleChange,
|
||||
remove,
|
||||
selectedKey,
|
||||
onProviderSelectSubmit,
|
||||
getProviders,
|
||||
...editModule
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.provider-manager{
|
||||
padding:10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="provider-selector">
|
||||
<a-select
|
||||
:value="value"
|
||||
@update:value="valueUpdate"
|
||||
>
|
||||
<a-select-option v-for="item of getProviders()" :key="item.key" :value="item.key" :disabled="isDisabled(item)">
|
||||
{{ item.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button class="suffix" @click="providerManagerOpen">
|
||||
管理授权
|
||||
</a-button>
|
||||
</div>
|
||||
<provider-manager ref="providerManagerRef"
|
||||
:value="value"
|
||||
:filter="filter"
|
||||
@update:value="valueUpdate"
|
||||
></provider-manager>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import { ref, inject } from 'vue'
|
||||
import ProviderManager from './provider-manager'
|
||||
|
||||
export default {
|
||||
name: 'provider-selector',
|
||||
components: { ProviderManager },
|
||||
emits: ['update:providers', 'update:value'],
|
||||
// 属性定义
|
||||
props: {
|
||||
value: {
|
||||
type: String
|
||||
},
|
||||
filter: {}
|
||||
},
|
||||
setup (props, context) {
|
||||
const providerManagerRef = ref(null)
|
||||
const providerManagerOpen = () => {
|
||||
console.log('providerManagerRef', providerManagerRef)
|
||||
if (providerManagerRef.value) {
|
||||
providerManagerRef.value.open()
|
||||
}
|
||||
}
|
||||
const providersUpdate = (val) => {
|
||||
console.log('accessUpdate', val)
|
||||
context.emit('update:providers', val)
|
||||
}
|
||||
const valueUpdate = (val) => {
|
||||
context.emit('update:value', val)
|
||||
}
|
||||
|
||||
const isDisabled = (item) => {
|
||||
if (!props.filter) {
|
||||
return false
|
||||
}
|
||||
return item.type !== props.filter
|
||||
}
|
||||
|
||||
const getProviders = inject('get:accessProviders')
|
||||
|
||||
return {
|
||||
providersUpdate,
|
||||
valueUpdate,
|
||||
providerManagerOpen,
|
||||
providerManagerRef,
|
||||
isDisabled,
|
||||
getProviders
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.provider-selector{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.ant-select{
|
||||
flex:1;
|
||||
}
|
||||
.suffix{
|
||||
flex-shrink: 0;
|
||||
margin-left:5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import zh from '@/locales/zh.json'
|
||||
import en from '@/locales/en.json'
|
||||
export const i18n = createI18n({
|
||||
// something vue-i18n options here ...
|
||||
locale: 'zh', // set current locale
|
||||
messages: {
|
||||
en,
|
||||
zh
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import _ from 'lodash'
|
||||
import {
|
||||
PlusCircleOutlined,
|
||||
PlusOutlined,
|
||||
CheckOutlined, EditOutlined,
|
||||
ArrowRightOutlined,
|
||||
NodeIndexOutlined,
|
||||
ThunderboltOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const icons = {
|
||||
PlusCircleOutlined,
|
||||
PlusOutlined,
|
||||
CheckOutlined,
|
||||
EditOutlined,
|
||||
ArrowRightOutlined,
|
||||
NodeIndexOutlined,
|
||||
ThunderboltOutlined,
|
||||
DeleteOutlined
|
||||
}
|
||||
export default function (app) {
|
||||
_.forEach(icons, item => {
|
||||
app.component(item.name, item)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"hello": "你好",
|
||||
"domain": "域名",
|
||||
"next": "下一步",
|
||||
"submit": "提交",
|
||||
"reset": "重置",
|
||||
"please.input.domain": "请输入域名",
|
||||
"email": "邮箱"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createApp } from 'vue'
|
||||
import router from './router'
|
||||
import App from './App.vue'
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/antd.css'
|
||||
import '@/style/common.less'
|
||||
import { i18n } from '@/i18n'
|
||||
import icons from './icons'
|
||||
import components from './components'
|
||||
const app = createApp(App)
|
||||
app.config.productionTip = false
|
||||
app.use(i18n)
|
||||
app.use(Antd)
|
||||
icons(app)
|
||||
app.use(components)
|
||||
app.use(router).mount('#app')
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import Home from '../views/Home.vue'
|
||||
import Detail from '../views/detail/index.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: '/detail',
|
||||
name: 'detail',
|
||||
component: Detail
|
||||
// component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,97 @@
|
||||
div#app {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ml-10{
|
||||
margin-left:10px;
|
||||
}
|
||||
.mt-10{
|
||||
margin-top:10px;
|
||||
}
|
||||
.mr-10{
|
||||
margin-right:10px;
|
||||
}
|
||||
.mb-10{
|
||||
margin-bottom:10px;
|
||||
}
|
||||
.ant-layout {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.ant-form{
|
||||
.ant-form-item-children {
|
||||
&>{
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-explain, .ant-form-extra{
|
||||
font-size:12px;
|
||||
line-height: 1.4;
|
||||
min-height: 20px;
|
||||
margin:0px;
|
||||
padding:0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ant-drawer-body{
|
||||
padding:0px;
|
||||
}
|
||||
.ant-drawer-content {
|
||||
|
||||
.ant-drawer-wrapper-body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.ant-drawer-body {
|
||||
position: relative;
|
||||
flex:1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.d-dialog{
|
||||
.ant-modal-body{
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media(min-height:600px) and (max-height:700px){
|
||||
.ant-modal-body {
|
||||
max-height: 50vh
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height:600px) {
|
||||
.ant-modal-body {
|
||||
max-height: 40vh
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="page-index flex-center">
|
||||
<H2 class="title">CERT-D</H2>
|
||||
<div class="page-body">
|
||||
|
||||
<a-tabs @change="callback">
|
||||
<a-tab-pane key="1" tab="创建新证书">
|
||||
<div class="create-from-domains">
|
||||
<div class="input-row flex-row">
|
||||
<a-select
|
||||
size="large"
|
||||
mode="tags"
|
||||
:placeholder="$t('please.input.domain')"
|
||||
v-model:value="formData.cert.domains"
|
||||
:open="false"
|
||||
></a-select>
|
||||
<div class="row-append">
|
||||
<a-button size="large" type="primary" @click="createFromDomain">创建新证书</a-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="helper">
|
||||
支持泛域名(例如*.test.yourdomain.com)<br/>
|
||||
支持多个域名打包到一张证书(输入一个域名后回车,再输下一个)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="从配置导入" force-render>
|
||||
<a-textarea class="textarea" type="textarea" :auto-size="autoSize" allow-clear></a-textarea>
|
||||
<a-button class="mt-10" type="primary" >导入</a-button>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { reactive, toRaw } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
export default {
|
||||
setup () {
|
||||
const formData = reactive({
|
||||
cert: {
|
||||
domains: ['*.docmirror.cn'],
|
||||
email: 'xiaojunnuo@qq.com',
|
||||
dnsProvider: 'aliyun'
|
||||
}
|
||||
})
|
||||
const router = useRouter()
|
||||
const createFromDomain = () => {
|
||||
router.push({ name: 'detail', params: { options: JSON.stringify(formData) } })
|
||||
}
|
||||
|
||||
const autoSize = reactive({ minRows: 8, maxRows: 10 })
|
||||
return {
|
||||
createFromDomain,
|
||||
formData,
|
||||
autoSize
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.page-index{
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
&.flex-center{
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.title{
|
||||
margin:50px;
|
||||
}
|
||||
.page-body{
|
||||
min-width: 700px;width: 60%
|
||||
}
|
||||
.create-from-domains{
|
||||
width:100%;
|
||||
.input-row{
|
||||
width:100%;
|
||||
.ant-select{
|
||||
flex:1;
|
||||
}
|
||||
.row-append{
|
||||
padding-left:10px
|
||||
}
|
||||
}
|
||||
}
|
||||
.helper{
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
.ant-tabs-bar {
|
||||
margin: 0 0 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
title="证书申请配置"
|
||||
placement="right"
|
||||
:closable="true"
|
||||
width="500px"
|
||||
v-model:visible="visible"
|
||||
:after-visible-change="afterVisibleChange"
|
||||
>
|
||||
|
||||
<d-container>
|
||||
<a-form class="domain-form" :scrollToFirstError="true" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
|
||||
<h3>域名信息</h3>
|
||||
<a-form-item :label="$t('domain')" v-bind="validateInfos.domains">
|
||||
<a-select
|
||||
mode="tags"
|
||||
:placeholder="$t('please.input.domain')"
|
||||
v-model:value="formData.domains"
|
||||
:open="false"
|
||||
></a-select>
|
||||
<template #extra >
|
||||
例如:*.yourdomain.com,输入完成后回车,支持多个
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('email')" v-bind="validateInfos.email">
|
||||
<a-input v-model:value="formData.email"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="dns验证" v-bind="validateInfos.dnsProvider">
|
||||
<provider-selector v-model:value="formData.dnsProvider"></provider-selector>
|
||||
</a-form-item>
|
||||
<a-form-item label="CA" v-bind="validateInfos.ca">
|
||||
<a-radio-group v-model:value="formData.ca" >
|
||||
<a-radio value="LetEncrypt">
|
||||
LetEncrypt
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<h3>CSR <span>必须全英文</span></h3>
|
||||
<a-form-item label="国家" v-bind="validateInfos['csr.country']">
|
||||
<a-input v-model:value="formData.csr.country"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="省份" v-bind="validateInfos['csr.state']">
|
||||
<a-input v-model:value="formData.csr.state"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="市区" v-bind="validateInfos['csr.locality']">
|
||||
<a-input v-model:value="formData.csr.locality"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="组织" v-bind="validateInfos['csr.organization']">
|
||||
<a-input v-model:value="formData.csr.organization"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="部门" v-bind="validateInfos['csr.organizationUnit']">
|
||||
<a-input v-model:value="formData.csr.organizationUnit"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="联系人邮箱">
|
||||
<a-input v-model:value="formData.csr.emailAddress"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="onSubmit">
|
||||
确定
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
</d-container>
|
||||
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { reactive, toRaw, ref, watch } from 'vue'
|
||||
import { useForm } from '@ant-design-vue/use'
|
||||
import _ from 'lodash-es'
|
||||
|
||||
function useDrawer () {
|
||||
const visible = ref(false)
|
||||
const afterVisibleChange = (val) => {
|
||||
console.log('visible', val)
|
||||
}
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
}
|
||||
const close = () => {
|
||||
visible.value = false
|
||||
}
|
||||
return {
|
||||
afterVisibleChange,
|
||||
open,
|
||||
close,
|
||||
visible
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'cert-form',
|
||||
emits: ['update:accessProviders', 'update:cert'],
|
||||
// 属性定义
|
||||
props: {
|
||||
cert: {
|
||||
type: Object
|
||||
},
|
||||
accessProviders: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup (props, context) {
|
||||
const drawer = useDrawer()
|
||||
|
||||
const certFormData = {
|
||||
domains: [],
|
||||
email: undefined,
|
||||
dnsProvider: '',
|
||||
ca: 'LetEncrypt',
|
||||
csr: {
|
||||
country: '',
|
||||
state: 'GuangDong',
|
||||
locality: 'ShengZhen',
|
||||
organization: 'CertD Org.',
|
||||
organizationUnit: 'IT Department',
|
||||
emailAddress: undefined
|
||||
}
|
||||
}
|
||||
const formData = reactive(certFormData)
|
||||
watch(props.cert, () => {
|
||||
console.log('cert props')
|
||||
_.merge(formData, props.cert)
|
||||
}, { immediate: true })
|
||||
|
||||
const rules = reactive({
|
||||
domains: [{
|
||||
type: 'array',
|
||||
required: true,
|
||||
message: '请输入域名'
|
||||
}],
|
||||
email: [{
|
||||
type: 'email',
|
||||
required: true,
|
||||
message: '请输入正确的邮箱'
|
||||
}],
|
||||
dnsProvider: [{
|
||||
required: true,
|
||||
message: '请选择dns授权提供者'
|
||||
}],
|
||||
'csr.country': [{ required: true, message: '请输入国家代码' }],
|
||||
'csr.state': [{ required: true, message: '请输入省份' }],
|
||||
'csr.locality': [{ required: true, message: '请输入市区' }],
|
||||
'csr.organization': [{ required: false, message: '请输入组织名称' }],
|
||||
'csr.organizationUnit': [{ required: false, message: '请输入部门名称' }],
|
||||
'csr.emailAddress': [{ required: false, message: '请输入邮箱' }]
|
||||
})
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { resetFields, validate, validateInfos } = useForm(formData, rules)
|
||||
|
||||
const onSubmit = async e => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
const res = await validate()
|
||||
console.log('validation', res, toRaw(formData))
|
||||
|
||||
context.emit('update:cert', formData)
|
||||
console.log('1111')
|
||||
drawer.close()
|
||||
} catch (err) {
|
||||
console.error('表单校验错误', err)
|
||||
}
|
||||
}
|
||||
const reset = () => {
|
||||
resetFields()
|
||||
}
|
||||
|
||||
const providerManagerRef = ref(null)
|
||||
const providerManagerOpen = () => {
|
||||
console.log('providerManagerRef', providerManagerRef)
|
||||
if (providerManagerRef.value) {
|
||||
providerManagerRef.value.open()
|
||||
}
|
||||
}
|
||||
const accessProvidersUpdate = (val) => {
|
||||
console.log('accessUpdate', val)
|
||||
context.emit('update:accessProviders', val)
|
||||
}
|
||||
return {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 18 },
|
||||
formData,
|
||||
onSubmit,
|
||||
reset,
|
||||
validateInfos,
|
||||
providerManagerRef,
|
||||
providerManagerOpen,
|
||||
accessProvidersUpdate,
|
||||
...drawer
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.ant-form.domain-form {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 10px 24px;
|
||||
|
||||
h3 {
|
||||
span {
|
||||
font-weight: 200;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
title="编辑任务"
|
||||
placement="right"
|
||||
:closable="true"
|
||||
width="600px"
|
||||
v-model:visible="taskDrawerVisible"
|
||||
:after-visible-change="taskDrawerOnAfterVisibleChange"
|
||||
>
|
||||
|
||||
<template v-if="currentTask">
|
||||
<d-container v-if="currentTask._isAdd" class="task-edit-form">
|
||||
<a-row :gutter="10">
|
||||
<a-col v-for="(item,index) of taskPluginDefineList" :key="index" class="task-plugin" :span="12">
|
||||
<a-card hoverable :class="{'current':item.name === currentTask.type}"
|
||||
@click="taskTypeSelected(item)" @dblclick="taskTypeSelected(item);taskTypeSave()">
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<a-avatar :src="item.icon||'/images/plugin.png'"/>
|
||||
<span class="title">{{ item.label }}</span>
|
||||
</template>
|
||||
<template #description>
|
||||
<span :title="item.desc">{{ item.desc }}</span>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" @click="taskTypeSave">
|
||||
确定
|
||||
</a-button>
|
||||
</d-container>
|
||||
<d-container v-else class="d-container" >
|
||||
<a-form ref="taskFormRef" class="task-form" :model="currentTask" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<a-form-item label="任务名称" name="taskName" :rules="rules.name">
|
||||
<a-input
|
||||
placeholder="请输入任务名称"
|
||||
v-model:value="currentTask.taskName"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-for="(item,key) in currentPlugin.input" v-bind="item.component || {}" :key="key" :label="item.label" :name="key">
|
||||
<component-render v-model:value="currentTask[key]" v-bind="item.component || {}"></component-render>
|
||||
<template #extra v-if="item.desc" >
|
||||
{{item.desc}}
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="taskSave">
|
||||
确定
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</d-container>
|
||||
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { message } from 'ant-design-vue'
|
||||
import pluginsApi from '@/api/api.plugins'
|
||||
import { ref } from 'vue'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import _ from 'lodash-es'
|
||||
/**
|
||||
* task drawer
|
||||
* @returns
|
||||
*/
|
||||
function useTaskForm (context) {
|
||||
const taskPluginDefineList = ref([])
|
||||
const onCreated = async () => {
|
||||
const plugins = await pluginsApi.list()
|
||||
taskPluginDefineList.value = plugins
|
||||
}
|
||||
|
||||
onCreated()
|
||||
|
||||
const currentTask = ref({ taskName: undefined })
|
||||
const currentTaskIndex = ref()
|
||||
const currentDeploy = ref()
|
||||
const currentPlugin = ref(null)
|
||||
const taskFormRef = ref(null)
|
||||
const taskDrawerVisible = ref(false)
|
||||
const rules = ref({
|
||||
name: [{
|
||||
type: 'string',
|
||||
required: true,
|
||||
message: '请输入名称'
|
||||
}]
|
||||
})
|
||||
const taskAdd = (deploy) => {
|
||||
const task = { taskName: '新任务', type: undefined, _isAdd: true }
|
||||
currentDeploy.value = deploy
|
||||
currentDeploy.value.tasks.push(task)
|
||||
currentTask.value = deploy.tasks[deploy.tasks.length - 1]
|
||||
taskDrawerShow()
|
||||
}
|
||||
|
||||
const taskTypeSelected = (item) => {
|
||||
currentTask.value.type = item.name
|
||||
currentTask.value.taskName = item.label
|
||||
}
|
||||
|
||||
const taskTypeSave = () => {
|
||||
currentTask.value._isAdd = false
|
||||
if (currentTask.value.type == null) {
|
||||
message.warn('请先选择类型')
|
||||
return
|
||||
}
|
||||
changeCurrentPlugin(currentTask.value)
|
||||
}
|
||||
|
||||
const taskDrawerShow = () => {
|
||||
taskDrawerVisible.value = true
|
||||
}
|
||||
const taskDrawerClose = () => {
|
||||
taskDrawerVisible.value = false
|
||||
}
|
||||
|
||||
const taskDrawerOnAfterVisibleChange = (val) => {
|
||||
console.log('taskDrawerOnAfterVisibleChange', val)
|
||||
}
|
||||
|
||||
const taskEdit = (deploy, task, index) => {
|
||||
if (task) {
|
||||
currentTask.value = task
|
||||
currentTaskIndex.value = index
|
||||
}
|
||||
currentDeploy.value = deploy
|
||||
changeCurrentPlugin(currentTask.value)
|
||||
|
||||
taskDrawerShow()
|
||||
}
|
||||
|
||||
const changeCurrentPlugin = (task) => {
|
||||
const taskType = task.type
|
||||
const currentPlugins = taskPluginDefineList.value.filter(p => {
|
||||
return p.name === taskType
|
||||
})
|
||||
if (currentPlugins.length <= 0) {
|
||||
task.type = undefined
|
||||
task._isAdd = true
|
||||
throw new Error('未知插件:' + taskType)
|
||||
}
|
||||
currentPlugin.value = currentPlugins[0]
|
||||
}
|
||||
|
||||
const taskSave = async (e) => {
|
||||
console.log('currentTask', currentTask)
|
||||
|
||||
e.preventDefault()
|
||||
await taskFormRef.value.validate()
|
||||
// context.emit('update', currentTask.value)
|
||||
taskDrawerClose()
|
||||
}
|
||||
|
||||
return {
|
||||
taskTypeSelected,
|
||||
taskTypeSave,
|
||||
taskPluginDefineList,
|
||||
taskFormRef,
|
||||
taskAdd,
|
||||
taskEdit,
|
||||
taskDrawerShow,
|
||||
taskDrawerVisible,
|
||||
taskDrawerOnAfterVisibleChange,
|
||||
currentTask,
|
||||
currentTaskIndex,
|
||||
currentPlugin,
|
||||
taskSave,
|
||||
rules
|
||||
}
|
||||
}
|
||||
|
||||
function useProviderManager () {
|
||||
const providerManager = ref(null)
|
||||
const providerManagerOpen = () => {
|
||||
providerManager.value.open()
|
||||
}
|
||||
return { providerManager, providerManagerOpen }
|
||||
}
|
||||
export default {
|
||||
name: 'task-form',
|
||||
emits: ['update'],
|
||||
props: {
|
||||
options: {}
|
||||
},
|
||||
setup (props, context) {
|
||||
return {
|
||||
...useTaskForm(context),
|
||||
...useProviderManager(),
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 16 }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.task-edit-form{
|
||||
.body{
|
||||
padding:10px;
|
||||
.ant-card {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.current {
|
||||
border-color: #00B7FF;
|
||||
}
|
||||
|
||||
.ant-card-meta-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ant-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
padding: 14px;
|
||||
height: 100px;
|
||||
|
||||
overflow-y: hidden;
|
||||
|
||||
.ant-card-meta-description {
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,428 @@
|
||||
<template>
|
||||
|
||||
<div class="page-detail">
|
||||
|
||||
<div class="flow">
|
||||
<div class="flow-group flow-cert">
|
||||
<h3 class="group-head">
|
||||
证书申请
|
||||
</h3>
|
||||
<a-divider></a-divider>
|
||||
<div class="cert-display">
|
||||
<a-button class="cert-edit-btn" type="link" @click="certFormOpen">
|
||||
编辑
|
||||
</a-button>
|
||||
|
||||
<div class="label-list">
|
||||
<div class="title">证书</div>
|
||||
<div class="label-item">
|
||||
<label>域名:</label>
|
||||
<div class="value">
|
||||
<a-tag type="primary" v-for="item of options.cert.domains " :key="item">
|
||||
{{ item }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label-item">
|
||||
<label>邮箱:</label>
|
||||
<div>
|
||||
{{ options.cert.email }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>CA:</label>
|
||||
<div>
|
||||
{{ options.cert.ca }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="title">
|
||||
CSR
|
||||
<span>必须全英文</span>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>国家:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.country }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>省份:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.state }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>市区:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.locality }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>组织:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.organization }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>部门:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.organizationUnit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>邮箱:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.emailAddress }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flow-group flow-deploy">
|
||||
<h3 class="group-head">
|
||||
部署流程
|
||||
<PlusCircleOutlined title="添加部署流程" class="add-icon" @click="deployAdd"/>
|
||||
</h3>
|
||||
<a-divider></a-divider>
|
||||
|
||||
<div class="deploy-list">
|
||||
|
||||
<a-card class="deploy-item" v-for="(deploy,index) of options.deploy" :key="index">
|
||||
<template #title>
|
||||
<div class="deploy-name">
|
||||
<template v-if="deploy._isEdit">
|
||||
<a-input v-model:value="deploy.deployName"
|
||||
:validateStatus="deploy.deployName?'':'error'"
|
||||
placeholder="请输入流程名称"
|
||||
@keyup.enter="deployCloseEditMode(deploy)"
|
||||
>
|
||||
<template #suffix>
|
||||
<CheckOutlined @click="deployCloseEditMode(deploy)" style="color: rgba(0,0,0,.45)"/>
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span @click="deployNameEdit"> <NodeIndexOutlined/> {{ deploy.deployName }}</span>
|
||||
<EditOutlined class="ml-10 edit-icon" @click="deployOpenEditMode(deploy)"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<div class="task-list">
|
||||
<div class="task-item-wrapper" v-for="(task,iindex) of deploy.tasks" :key="iindex">
|
||||
<a-button class="task-item" shape="round" @click="taskEdit(deploy,task,index)">
|
||||
<ThunderboltOutlined/>
|
||||
{{ task.taskName }}
|
||||
</a-button>
|
||||
<ArrowRightOutlined class="task-next-icon"/>
|
||||
</div>
|
||||
<div class="task-item-wrapper">
|
||||
<a-button type="primary" class="task-item" shape="round" @click="taskAdd(deploy)">
|
||||
<PlusOutlined/>
|
||||
添加新任务
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<cert-form ref="certFormRef" v-model:cert="options.cert" v-model:access-providers="options.accessProviders"></cert-form>
|
||||
|
||||
<task-form ref="taskFormRef" ></task-form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { message } from 'ant-design-vue'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { reactive, ref, toRef, provide, readonly } from 'vue'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { useRoute } from 'vue-router'
|
||||
import CertForm from '@/views/detail/components/cert-form'
|
||||
import TaskForm from './components/task-form'
|
||||
import _ from 'lodash-es'
|
||||
|
||||
function useDeploy (options) {
|
||||
const deployAdd = () => {
|
||||
options.deploy.push({
|
||||
deployName: `D${options.deploy.length + 1}-新部署流程`,
|
||||
_isEdit: false,
|
||||
tasks: []
|
||||
})
|
||||
}
|
||||
|
||||
const deployCloseEditMode = (deploy) => {
|
||||
if (!deploy.deployName) {
|
||||
message.error('请输入流程名称')
|
||||
return
|
||||
}
|
||||
deploy._isEdit = false
|
||||
console.log('options', options)
|
||||
}
|
||||
|
||||
const deployOpenEditMode = (deploy) => {
|
||||
deploy._isEdit = true
|
||||
}
|
||||
return {
|
||||
deployAdd, deployCloseEditMode, deployOpenEditMode
|
||||
}
|
||||
}
|
||||
|
||||
function useProvideAccessProviders (options) {
|
||||
provide('get:accessProviders', () => {
|
||||
return options.accessProviders
|
||||
})
|
||||
provide('update:accessProviders', (providers) => {
|
||||
options.accessProviders = providers
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { CertForm, TaskForm },
|
||||
setup () {
|
||||
const route = useRoute()
|
||||
console.log('route', route)
|
||||
const optionParams = route.params.options ? JSON.parse(route.params.options) : {}
|
||||
const optionsDefault = {
|
||||
cert: {
|
||||
csr: {
|
||||
country: 'CN',
|
||||
state: 'GuangDong',
|
||||
locality: 'ShengZhen',
|
||||
organization: 'CertD Org.',
|
||||
organizationUnit: 'IT Department'
|
||||
}
|
||||
},
|
||||
accessProviders: [{ key: 'aliyun', type: 'aliyun', name: 'aliyun' }],
|
||||
deploy: []
|
||||
}
|
||||
_.merge(optionsDefault, optionParams)
|
||||
// optionsDefault.accessProviders = reactive(optionsDefault.accessProviders)
|
||||
const options = reactive(optionsDefault)
|
||||
|
||||
const certFormChanged = (value) => {
|
||||
console.log('certFormChanged', value)
|
||||
options.cert = value
|
||||
}
|
||||
|
||||
const certFormRef = ref(null)
|
||||
const certFormOpen = () => {
|
||||
certFormRef.value.open()
|
||||
}
|
||||
|
||||
const taskFormRef = ref(null)
|
||||
const taskAdd = (deploy) => {
|
||||
taskFormRef.value.taskAdd(deploy)
|
||||
}
|
||||
const taskEdit = (deploy, task, index) => {
|
||||
taskFormRef.value.taskEdit(deploy, task, index)
|
||||
}
|
||||
|
||||
useProvideAccessProviders(options)
|
||||
|
||||
return {
|
||||
options,
|
||||
certFormChanged,
|
||||
certFormRef,
|
||||
certFormOpen,
|
||||
...useDeploy(options),
|
||||
taskFormRef,
|
||||
taskAdd,
|
||||
taskEdit
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.page-detail {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
|
||||
.label-list {
|
||||
.title{
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color:#555;
|
||||
span{
|
||||
font-weight: 200;
|
||||
margin-left:5px;
|
||||
font-size: 12px;
|
||||
color:#888;
|
||||
}
|
||||
}
|
||||
.label-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: baseline;
|
||||
padding: 8px 0px;
|
||||
|
||||
label {
|
||||
width: 70px;
|
||||
flex-shrink: 0;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1.5715;
|
||||
text-align: end;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow {
|
||||
height: 100%;
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.flow-group {
|
||||
min-width: 300px;
|
||||
height: 100%;
|
||||
border-right: 1px #eee solid;
|
||||
padding: 20px;
|
||||
|
||||
.group-head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.add-icon {
|
||||
margin-left: 20px;
|
||||
font-size: 24px;
|
||||
color: #737070;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow-cert {
|
||||
max-width: 400px;
|
||||
|
||||
.cert-display {
|
||||
position: relative;
|
||||
|
||||
.cert-edit-btn {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.flow-deploy {
|
||||
flex-shrink: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.deploy-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.deploy-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// min-width:70%;
|
||||
.deploy-name {
|
||||
max-width: 300px;
|
||||
|
||||
.edit-icon {
|
||||
color: #737070;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ant-form.task-form {
|
||||
padding: 10px 24px;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.task-item-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
//border: 1px solid #eee;
|
||||
//padding: 10px 20px;
|
||||
//border-radius: 20px;
|
||||
}
|
||||
|
||||
.task-add-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.task-next-icon {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-type-selector {
|
||||
|
||||
}
|
||||
|
||||
.task-form {
|
||||
.task-plugin-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.task-plugin {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user