refactor: form input

master
xiaojunnuo 2021-01-31 02:09:54 +08:00
parent b4ee3d0dfc
commit eab0c3be60
14 changed files with 6826 additions and 3090 deletions

View File

@ -1,6 +1,5 @@
module.exports = { module.exports = {
presets: [ presets: [
'@vue/cli-plugin-babel/preset' '@vue/cli-plugin-babel/preset'
], ]
plugins: ['@babel/plugin-proposal-optional-chaining']
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,36 +9,43 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
"@certd/plugins": "^0.1.11", "@certd/plugins": "^0.1.11",
"@certd/providers": "^0.1.11", "@certd/providers": "^0.1.11",
"ant-design-vue": "^2.0.0-rc.8", "ant-design-vue": "^2.0.0-rc.8",
"core-js": "^3.6.5",
"lodash-es": "^4.17.20", "lodash-es": "^4.17.20",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0-rc.2", "vue-i18n": "^9.0.0-rc.2",
"vue-router": "^4.0.0-0" "core-js": "^3.8.1",
"vue": "^3.0.4",
"vue-router": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0", "@babel/core": "^7.12.10",
"@vue/cli-plugin-eslint": "~4.5.0", "@babel/eslint-parser": "^7.12.1",
"@vue/cli-plugin-router": "~4.5.0", "@vue/cli-plugin-babel": "~5.0.0-alpha.3",
"@vue/cli-plugin-unit-mocha": "~4.5.0", "@vue/cli-plugin-eslint": "~5.0.0-alpha.3",
"@vue/cli-service": "~4.5.0", "@vue/cli-plugin-router": "~5.0.0-alpha.3",
"@vue/compiler-sfc": "^3.0.0", "@vue/cli-plugin-unit-mocha": "~5.0.0-alpha.3",
"@vue/eslint-config-standard": "^5.1.2", "@vue/cli-plugin-webpack-4": "^5.0.0-alpha.3",
"@vue/cli-service": "~5.0.0-alpha.3",
"@vue/compiler-sfc": "^3.0.4",
"@vue/eslint-config-standard": "^6.0.0",
"@vue/test-utils": "^2.0.0-0", "@vue/test-utils": "^2.0.0-0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"chai": "^4.1.2", "chai": "^4.2.0",
"eslint": "^6.7.2", "eslint": "^7.15.0",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^7.0.0-0", "eslint-plugin-vue": "^7.2.0",
"less": "^3.0.4", "less": "^3.0.4",
"less-loader": "^5.0.0", "less-loader": "^5.0.0",
"lint-staged": "^9.5.0" "lint-staged": "^9.5.0",
"postcss": "^8.2.4",
"webpack": "^4.0.0"
},
"resolutions": {
"@vue/cli-*/webpack": "^4.0.0"
}, },
"gitHooks": { "gitHooks": {
"pre-commit": "lint-staged" "pre-commit": "lint-staged"

View File

@ -14,7 +14,7 @@
<a-list <a-list
class="list" class="list"
item-layout="horizontal" item-layout="horizontal"
:data-source="providerList" :data-source="getProviders()"
> >
<template #renderItem="{ item ,index }"> <template #renderItem="{ item ,index }">
<a-list-item> <a-list-item>
@ -23,7 +23,7 @@
<a-button type="danger" @click="remove(item,index)"><template #icon ><DeleteOutlined /></template></a-button> <a-button type="danger" @click="remove(item,index)"><template #icon ><DeleteOutlined /></template></a-button>
</template> </template>
<a-radio :checked="item.key===selectedKey" @update:checked="selectedKey = item.key"> <a-radio :disabled="isDisabled(item)" :checked="item.key===selectedKey" @update:checked="selectedKey = item.key" >
{{ item.name }} ({{item.type}}) {{ item.name }} ({{item.type}})
</a-radio> </a-radio>
@ -39,10 +39,12 @@
<a-modal v-model:visible="editVisible" dialogClass="d-dialog" width="700px" title="编辑授权" @ok="onSubmit"> <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 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-form-item label="类型" name="type" :rules="rules.type">
<a-radio-group :disabled="editIndex!=null" v-model:value="formData.type" @change="onTypeChanged" > <a-radio-group :disabled="editIndex!=null" v-model:value="formData.type" @change="onTypeChanged" >
<a-radio-button v-for="(option) of providerDefineList" :key="option.name" :value="option.name"> <a-radio-button v-for="(option) of providerDefineList" :disabled="isDisabled(option,'name')" :key="option.name" :value="option.name">
{{option.label}} {{option.label}}
</a-radio-button> </a-radio-button>
</a-radio-group> </a-radio-group>
@ -58,7 +60,9 @@
:label="item.label || key" :label="item.label || key"
:name="key"> :name="key">
<component-render v-model:value="formData[key]" v-bind="item.component || {}"></component-render> <component-render v-model:value="formData[key]" v-bind="item.component || {}"></component-render>
<div class="helper">{{item.desc}}</div> <template #extra >
<div v-if="item.desc" class="helper">{{item.desc}}</div>
</template>
</a-form-item> </a-form-item>
</template> </template>
@ -68,12 +72,12 @@
</template> </template>
<script> <script>
import { ref, reactive, nextTick, watch } from 'vue' import { ref, reactive, nextTick, watch, inject } from 'vue'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { useForm } from '@ant-design-vue/use' import { useForm } from '@ant-design-vue/use'
import _ from 'lodash-es' import _ from 'lodash-es'
import providerApi from '@/api/api.providers' import providerApi from '@/api/api.providers'
function useEdit (props, context, providerList, onEditSave) { function useEdit (props, context, onEditSave) {
const formData = reactive({ const formData = reactive({
key: '', key: '',
name: '', name: '',
@ -155,6 +159,13 @@ function useEdit (props, context, providerList, onEditSave) {
} }
} }
const isDisabled = (item, keyName = 'type') => {
if (!props.filter) {
return false
}
return item[keyName
] !== props.filter
}
return { return {
labelCol: { span: 6 }, labelCol: { span: 6 },
wrapperCol: { span: 16 }, wrapperCol: { span: 16 },
@ -168,7 +179,8 @@ function useEdit (props, context, providerList, onEditSave) {
editIndex, editIndex,
openEdit, openEdit,
onTypeChanged, onTypeChanged,
add add,
isDisabled
} }
} }
@ -192,9 +204,9 @@ export default {
name: 'provider-manager', name: 'provider-manager',
props: { props: {
value: {}, value: {},
providers: {} filter: {}
}, },
emits: ['update:value', 'update:providers'], emits: ['update:value'],
setup (props, context) { setup (props, context) {
const visible = ref(false) const visible = ref(false)
@ -205,47 +217,55 @@ export default {
const onAfterVisibleChange = () => { const onAfterVisibleChange = () => {
} }
const providerList = ref([])
const getProviders = inject('get:accessProviders')
// const providerList = ref([])
const selectedKey = ref(null) const selectedKey = ref(null)
watch(() => props.providers, () => {
providerList.value = _.cloneDeep(props.providers || [])
}, { immediate: true })
watch(() => props.value, () => { watch(() => props.value, () => {
selectedKey.value = props.value selectedKey.value = props.value
}, { immediate: true }) }, { immediate: true })
const onEditSave = (newProvider, editIndex) => { const onEditSave = (newProvider, editIndex) => {
const providerList = getProviders()
if (editIndex == null) { if (editIndex == null) {
// add key // add key
newProvider.key = generateNewKey(providerList.value) newProvider.key = generateNewKey(providerList)
providerList.value.push(newProvider) providerList.push(newProvider)
} else { } else {
_.merge(providerList.value[editIndex], newProvider) _.merge(providerList[editIndex], newProvider)
} }
} }
const editModule = useEdit(props, context, providerList, onEditSave) const editModule = useEdit(props, context, onEditSave)
const open = () => { const open = () => {
visible.value = true visible.value = true
if (providerList.value.length === 0) { const providerList = getProviders()
if (providerList.length === 0) {
nextTick(() => { nextTick(() => {
editModule.add() editModule.add()
}) })
} }
} }
const remove = (item, index) => { const remove = (item, index) => {
providerList.value.splice(index, 1) const providerList = getProviders()
providerList.splice(index, 1)
} }
const updateProviders = inject('update:accessProviders')
// watch(() => providers, () => {
// providerList.value = _.cloneDeep(props.providers || [])
// }, { immediate: true })
const onProviderSelectSubmit = () => { const onProviderSelectSubmit = () => {
context.emit('update:providers', providerList.value) const providerList = getProviders()
updateProviders(providerList)
context.emit('update:value', selectedKey.value) context.emit('update:value', selectedKey.value)
close() close()
} }
return { return {
providerList,
visible, visible,
open, open,
close, close,
@ -253,6 +273,7 @@ export default {
remove, remove,
selectedKey, selectedKey,
onProviderSelectSubmit, onProviderSelectSubmit,
getProviders,
...editModule ...editModule
} }
} }

View File

@ -4,7 +4,7 @@
:value="value" :value="value"
@update:value="valueUpdate" @update:value="valueUpdate"
> >
<a-select-option v-for="item of providers" :key="item.key" :value="item.key" :disabled="isDisabled(item)"> <a-select-option v-for="item of getProviders()" :key="item.key" :value="item.key" :disabled="isDisabled(item)">
{{ item.name }} {{ item.name }}
</a-select-option> </a-select-option>
</a-select> </a-select>
@ -13,15 +13,14 @@
</a-button> </a-button>
</div> </div>
<provider-manager ref="providerManagerRef" <provider-manager ref="providerManagerRef"
:providers="providers"
:value="value" :value="value"
:filter="filter"
@update:value="valueUpdate" @update:value="valueUpdate"
@update:providers="providersUpdate"
></provider-manager> ></provider-manager>
</template> </template>
<script> <script>
import { ref } from 'vue' import { ref, inject } from 'vue'
import ProviderManager from './provider-manager' import ProviderManager from './provider-manager'
export default { export default {
@ -33,9 +32,6 @@ export default {
value: { value: {
type: String type: String
}, },
providers: {
type: Object
},
filter: {} filter: {}
}, },
setup (props, context) { setup (props, context) {
@ -58,14 +54,18 @@ export default {
if (!props.filter) { if (!props.filter) {
return false return false
} }
return item.type === props.filter return item.type !== props.filter
} }
const getProviders = inject('get:accessProviders')
return { return {
providersUpdate, providersUpdate,
valueUpdate, valueUpdate,
providerManagerOpen, providerManagerOpen,
providerManagerRef, providerManagerRef,
isDisabled isDisabled,
getProviders
} }
} }
} }

View File

@ -36,6 +36,25 @@ h1, h2, h3, h4, h5, h6 {
height: 100% 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{ .ant-drawer-body{
padding:0px; padding:0px;

View File

@ -19,7 +19,9 @@
v-model:value="formData.domains" v-model:value="formData.domains"
:open="false" :open="false"
></a-select> ></a-select>
<div class="helper">例如*.yourdomain.com</div> <template #extra >
例如*.yourdomain.com输入完成后回车支持多个
</template>
</a-form-item> </a-form-item>
<a-form-item :label="$t('email')" v-bind="validateInfos.email"> <a-form-item :label="$t('email')" v-bind="validateInfos.email">
@ -27,10 +29,14 @@
</a-form-item> </a-form-item>
<a-form-item label="dns验证" v-bind="validateInfos.dnsProvider"> <a-form-item label="dns验证" v-bind="validateInfos.dnsProvider">
<provider-selector v-model:value="formData.dnsProvider" <provider-selector v-model:value="formData.dnsProvider"></provider-selector>
:providers="accessProviders" </a-form-item>
@update:providers="accessProvidersUpdate" <a-form-item label="CA" v-bind="validateInfos.ca">
></provider-selector> <a-radio-group v-model:value="formData.ca" >
<a-radio value="LetEncrypt">
LetEncrypt
</a-radio>
</a-radio-group>
</a-form-item> </a-form-item>
<h3>CSR <span>必须全英文</span></h3> <h3>CSR <span>必须全英文</span></h3>
@ -110,6 +116,7 @@ export default {
domains: [], domains: [],
email: undefined, email: undefined,
dnsProvider: '', dnsProvider: '',
ca: 'LetEncrypt',
csr: { csr: {
country: '', country: '',
state: 'GuangDong', state: 'GuangDong',

View File

@ -31,16 +31,19 @@
</a-button> </a-button>
</d-container> </d-container>
<d-container v-else class="d-container" > <d-container v-else class="d-container" >
<a-form class="task-form" :label-col="labelCol" :wrapper-col="wrapperCol"> <a-form ref="taskFormRef" class="task-form" :model="currentTask" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-item label="任务名称"> <a-form-item label="任务名称" name="taskName" :rules="rules.name">
<a-input <a-input
placeholder="请输入任务名称" placeholder="请输入任务名称"
v-model:value="currentTask.taskName" v-model:value="currentTask.taskName"
></a-input> ></a-input>
</a-form-item> </a-form-item>
<a-form-item v-for="(item,key) in currentPlugin.input" :key="key" :label="item.label"> <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> <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-item>
</a-form> </a-form>
@ -78,13 +81,19 @@ function useTaskForm (context) {
onCreated() onCreated()
const currentTask = ref() const currentTask = ref({ taskName: undefined })
const currentTaskIndex = ref() const currentTaskIndex = ref()
const currentDeploy = ref() const currentDeploy = ref()
const currentPlugin = ref(null) const currentPlugin = ref(null)
const taskFormRef = ref(null) const taskFormRef = ref(null)
const taskDrawerVisible = ref(false) const taskDrawerVisible = ref(false)
const rules = ref({
name: [{
type: 'string',
required: true,
message: '请输入名称'
}]
})
const taskAdd = (deploy) => { const taskAdd = (deploy) => {
const task = { taskName: '新任务', type: undefined, _isAdd: true } const task = { taskName: '新任务', type: undefined, _isAdd: true }
currentDeploy.value = deploy currentDeploy.value = deploy
@ -142,8 +151,11 @@ function useTaskForm (context) {
currentPlugin.value = currentPlugins[0] currentPlugin.value = currentPlugins[0]
} }
const taskSave = () => { const taskSave = async (e) => {
console.log('currentTask', currentTask) console.log('currentTask', currentTask)
e.preventDefault()
await taskFormRef.value.validate()
// context.emit('update', currentTask.value) // context.emit('update', currentTask.value)
taskDrawerClose() taskDrawerClose()
} }
@ -161,7 +173,8 @@ function useTaskForm (context) {
currentTask, currentTask,
currentTaskIndex, currentTaskIndex,
currentPlugin, currentPlugin,
taskSave taskSave,
rules
} }
} }

View File

@ -37,38 +37,47 @@
</div> </div>
</div> </div>
<div class="title">CSR <br/>
<span>必须全英文</span></div> <div class="title">
CSR
<span>必须全英文</span>
</div>
<div class="label-item"> <div class="label-item">
<label>country:</label> <label>国家:</label>
<div> <div>
{{ options.cert.csr.country }} {{ options.cert.csr.country }}
</div> </div>
</div> </div>
<div class="label-item"> <div class="label-item">
<label>state:</label> <label>省份:</label>
<div> <div>
{{ options.cert.csr.state }} {{ options.cert.csr.state }}
</div> </div>
</div> </div>
<div class="label-item"> <div class="label-item">
<label>locality:</label> <label>市区:</label>
<div> <div>
{{ options.cert.csr.locality }} {{ options.cert.csr.locality }}
</div> </div>
</div> </div>
<div class="label-item"> <div class="label-item">
<label>org:</label> <label>组织:</label>
<div> <div>
{{ options.cert.csr.organization }} {{ options.cert.csr.organization }}
</div> </div>
</div> </div>
<div class="label-item"> <div class="label-item">
<label>orgUnit:</label> <label>部门:</label>
<div> <div>
{{ options.cert.csr.organizationUnit }} {{ options.cert.csr.organizationUnit }}
</div> </div>
</div> </div>
<div class="label-item">
<label>邮箱:</label>
<div>
{{ options.cert.csr.emailAddress }}
</div>
</div>
</div> </div>
</div> </div>
@ -136,7 +145,7 @@
<script> <script>
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { reactive, ref, toRef } from 'vue' import { reactive, ref, toRef, provide, readonly } from 'vue'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import CertForm from '@/views/detail/components/cert-form' import CertForm from '@/views/detail/components/cert-form'
@ -169,6 +178,15 @@ function useDeploy (options) {
} }
} }
function useProvideAccessProviders (options) {
provide('get:accessProviders', () => {
return options.accessProviders
})
provide('update:accessProviders', (providers) => {
options.accessProviders = providers
})
}
export default { export default {
components: { CertForm, TaskForm }, components: { CertForm, TaskForm },
setup () { setup () {
@ -189,6 +207,7 @@ export default {
deploy: [] deploy: []
} }
_.merge(optionsDefault, optionParams) _.merge(optionsDefault, optionParams)
// optionsDefault.accessProviders = reactive(optionsDefault.accessProviders)
const options = reactive(optionsDefault) const options = reactive(optionsDefault)
const certFormChanged = (value) => { const certFormChanged = (value) => {
@ -209,6 +228,8 @@ export default {
taskFormRef.value.taskEdit(deploy, task, index) taskFormRef.value.taskEdit(deploy, task, index)
} }
useProvideAccessProviders(options)
return { return {
options, options,
certFormChanged, certFormChanged,

View File

@ -3,6 +3,9 @@
"env": { "env": {
"mocha": true "mocha": true
}, },
"parserOptions": {
"ecmaVersion": 2020
},
"overrides": [ "overrides": [
{ {
"files": ["*.test.js", "*.spec.js"], "files": ["*.test.js", "*.spec.js"],

View File

@ -8,23 +8,31 @@ const define = {
input: { input: {
domainName: { domainName: {
label: 'cdn加速域名', label: 'cdn加速域名',
required: true, component: {
attrs: {
placeholder: 'cdn加速域名', placeholder: 'cdn加速域名',
rules: [{ required: true, message: '该项必填' }] rules: [{ required: true, message: '该项必填' }]
} }
}, },
certName: { certName: {
label: '证书名称' label: '证书名称',
component: {
placeholder: '上传后将以此名称作为前缀'
}
}, },
from: { from: {
value: 'upload', value: 'upload',
label: '证书来源', label: '证书来源',
component: {
placeholder: '证书来源',
required: true,
name: 'a-select',
options: [ options: [
{ value: 'upload', label: '直接上传' }, { value: 'upload', label: '直接上传' },
{ value: 'cas', label: '从证书库', desc: '需要uploadCertToAliyun作为前置任务' } { value: 'cas', label: '从证书库', title: '需要uploadCertToAliyun作为前置任务' }
], ]
required: true },
desc: '如果选择cas类型则需要以《上传证书到阿里云》作为前置任务'
}, },
// serverCertificateStatus: { // serverCertificateStatus: {
// label: '启用https', // label: '启用https',

View File

@ -3,6 +3,10 @@
"env": { "env": {
"mocha": true "mocha": true
}, },
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
},
"overrides": [ "overrides": [
{ {
"files": ["*.test.js", "*.spec.js"], "files": ["*.test.js", "*.spec.js"],

View File

@ -10,7 +10,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
input: { input: {
accessKeyId: { accessKeyId: {
type: String, type: String,
attrs: { component: {
placeholder: 'accessKeyId', placeholder: 'accessKeyId',
rules: [{ required: true, message: '必填项' }] rules: [{ required: true, message: '必填项' }]
}, },
@ -18,7 +18,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
}, },
accessKeySecret: { accessKeySecret: {
type: String, type: String,
attrs: { component: {
placeholder: 'accessKeySecret', placeholder: 'accessKeySecret',
rules: [{ required: true, message: '必填项' }] rules: [{ required: true, message: '必填项' }]
} }

View File

@ -5,12 +5,12 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
static define () { static define () {
return { return {
name: 'dnspod', name: 'dnspod',
label: 'dnspod', label: 'dnspod(腾讯云)',
desc: '腾讯云的域名解析接口已迁移到dnspod', desc: '腾讯云的域名解析接口已迁移到dnspod',
input: { input: {
id: { id: {
type: String, type: String,
attrs: { component: {
placeholder: 'dnspod接口账户id', placeholder: 'dnspod接口账户id',
rules: [{ required: true, message: '该项必填' }] rules: [{ required: true, message: '该项必填' }]
} }
@ -18,7 +18,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
token: { token: {
type: String, type: String,
label: 'token', label: 'token',
attrs: { component: {
placeholder: '开放接口token', placeholder: '开放接口token',
rules: [{ required: true, message: '该项必填' }] rules: [{ required: true, message: '该项必填' }]
} }