Merge branch 'master' into jym_dev

This commit is contained in:
jym503558564
2020-04-08 16:57:45 +08:00
12 changed files with 173 additions and 66 deletions

View File

@@ -2,5 +2,5 @@
ENV = 'production' ENV = 'production'
# base api # base api
VUE_APP_BASE_API = '/prod-api' VUE_APP_BASE_API = ''

View File

@@ -1,19 +1,20 @@
<template> <template>
<DataForm v-loading="loading" :fields="totalFields" v-bind="$attrs" v-on="$listeners"> <DataForm v-loading="loading" :fields="totalFields" v-bind="$attrs" v-on="$listeners">
<slot v-for="item in fields" :slot="`id:${item}`" :name="`id:${item}`" /> <FormGroupHeader v-for="(group, i) in groups" :slot="'id:'+group.name" :key="'group-'+group.name" :title="group.title" :line="i != 0" />
<slot v-for="item in fields" :slot="`$id:${item}`" :name="`$id:${item}`" />
</DataForm> </DataForm>
</template> </template>
<script> <script>
import DataForm from '../DataForm' import DataForm from '../DataForm'
import FormGroupHeader from '@/components/formGroupHeader'
import { optionUrlMeta } from '@/api/common' import { optionUrlMeta } from '@/api/common'
import rules from '@/components/DataForm/rules' import rules from '@/components/DataForm/rules'
import Select2 from '@/components/Select2' import Select2 from '@/components/Select2'
export default { export default {
name: 'AutoDataForm', name: 'AutoDataForm',
components: { components: {
DataForm DataForm,
FormGroupHeader
}, },
props: { props: {
url: { url: {
@@ -39,7 +40,8 @@ export default {
return { return {
meta: {}, meta: {},
totalFields: [], totalFields: [],
loading: true loading: true,
groups: []
} }
}, },
computed: { computed: {
@@ -52,18 +54,12 @@ export default {
optionUrlMeta() { optionUrlMeta() {
optionUrlMeta(this.url).then(data => { optionUrlMeta(this.url).then(data => {
this.meta = data.actions[this.method.toUpperCase()] || {} this.meta = data.actions[this.method.toUpperCase()] || {}
this.generateFields() this.generateColumns()
this.loading = false this.loading = false
}) })
}, },
generateField(name) { generateFieldByType(type, field, fieldMeta) {
let field = {} switch (type) {
const fieldMeta = this.meta[name] || {}
field.id = name
field.label = fieldMeta.label
let type = 'input'
switch (fieldMeta.type) {
case 'choice': case 'choice':
type = 'radio-group' type = 'radio-group'
field.options = fieldMeta.choices.map(v => { field.options = fieldMeta.choices.map(v => {
@@ -85,26 +81,74 @@ export default {
break break
} }
field.type = type field.type = type
return field
},
generateFieldByName(name, field) {
switch (name) {
case 'email':
field.el = { type: 'email' }
break
case 'password':
field.el = { type: 'password' }
break
case 'comment':
field.el = { type: 'textarea' }
break
}
return field
},
generateFieldByOther(field, fieldMeta) {
if (fieldMeta.required) { if (fieldMeta.required) {
if (type === 'input') { if (field.type === 'input') {
field.rules = [rules.Required] field.rules = [rules.Required]
} else { } else {
field.rules = [rules.RequiredChange] field.rules = [rules.RequiredChange]
} }
} }
return field
},
generateField(name) {
let field = {}
const fieldMeta = this.meta[name] || {}
field.id = name
field.prop = name
field.label = fieldMeta.label
field = this.generateFieldByType(fieldMeta.type, field, fieldMeta)
field = this.generateFieldByName(name, field)
field = this.generateFieldByOther(field, fieldMeta)
field = Object.assign(field, this.fieldsMeta[name] || {}) field = Object.assign(field, this.fieldsMeta[name] || {})
return field return field
}, },
generateFields() { generateFieldGroup(data) {
const fields = [] const [groupTitle, fields] = data
for (let field of this.fields) { this.groups.push({
if (typeof field === 'object') { id: groupTitle,
fields.push(field) title: groupTitle,
name: fields[0]
})
const items = this.generateFields(fields)
return items
},
generateFields(data) {
let fields = []
for (let field of data) {
console.log('is array', field instanceof Array)
console.log('is string', typeof field === 'string')
console.log('is object', field instanceof Object)
if (field instanceof Array) {
const items = this.generateFieldGroup(field)
fields = [...fields, ...items]
} else if (typeof field === 'string') { } else if (typeof field === 'string') {
field = this.generateField(field) field = this.generateField(field)
fields.push(field) fields.push(field)
} else if (field instanceof Object) {
fields.push(field)
} }
} }
return fields
},
generateColumns() {
const fields = this.generateFields(this.fields)
this.totalFields = fields this.totalFields = fields
} }
} }

View File

@@ -13,6 +13,7 @@
<slot v-for="item in fields" :slot="`$id:${item.id}`" :name="`$id:${item.id}`" /> <slot v-for="item in fields" :slot="`$id:${item.id}`" :name="`$id:${item.id}`" />
<el-form-item v-if="defaultButton"> <el-form-item v-if="defaultButton">
<slot name="button-start" />
<el-button size="small" @click="resetForm('dataForm')">{{ $tc('Reset') }}</el-button> <el-button size="small" @click="resetForm('dataForm')">{{ $tc('Reset') }}</el-button>
<el-button size="small" type="primary" @click="submitForm('dataForm')">{{ $tc('Submit') }}</el-button> <el-button size="small" type="primary" @click="submitForm('dataForm')">{{ $tc('Submit') }}</el-button>
</el-form-item> </el-form-item>
@@ -56,6 +57,7 @@ export default {
if (valid) { if (valid) {
this.$emit('submit', this.$refs[formName].getFormValue()) this.$emit('submit', this.$refs[formName].getFormValue())
} else { } else {
this.$emit('invalid', valid)
return false return false
} }
}) })

View File

@@ -6,21 +6,35 @@
<Dialog :title="$t('Export')" :visible.sync="showExportDialog" @comfirm="handleDialogConfirm('export')" @cancel="handleDialogCancel('export')"> <Dialog :title="$t('Export')" :visible.sync="showExportDialog" @comfirm="handleDialogConfirm('export')" @cancel="handleDialogCancel('export')">
<el-form> <el-form>
<el-form-item label="导出范围" :label-width="'100px'"> <el-form-item label="导出范围" :label-width="'100px'">
<el-radio v-model="importOption" class="export-item" label="1">导出全部</el-radio> <p>{{ $d(new Date(), 'short') }}</p>
<el-radio v-model="importOption" class="export-item" label="2">仅导出选中项</el-radio>
<el-radio v-model="importOption" class="export-item" label="3">仅导出搜索项</el-radio>
</el-form-item>
</el-form>
</Dialog>
<Dialog :title="$t('Import')" :visible.sync="showImportDialog" @comfirm="handleDialogConfirm('import')" @cancel="handleDialogCancel('import')">
<el-form>
<el-form-item label="导出范围" :label-width="'100px'">
<el-radio v-model="exportOption" class="export-item" label="1">导出全部</el-radio> <el-radio v-model="exportOption" class="export-item" label="1">导出全部</el-radio>
<el-radio v-model="exportOption" class="export-item" label="2">仅导出选中项</el-radio> <el-radio v-model="exportOption" class="export-item" label="2">仅导出选中项</el-radio>
<el-radio v-model="exportOption" class="export-item" label="3">仅导出搜索项</el-radio> <el-radio v-model="exportOption" class="export-item" label="3">仅导出搜索项</el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</Dialog> </Dialog>
<Dialog :title="$t('Import')" :visible.sync="showImportDialog" @comfirm="handleDialogConfirm('import')" @cancel="handleDialogCancel('import')">
<el-form>
<el-form-item label="导入/更新" :label-width="'100px'">
<el-radio v-model="importOption" class="export-item" label="1">导出全部</el-radio>
<el-radio v-model="importOption" class="export-item" label="2">仅导出选中项</el-radio>
<el-radio v-model="importOption" class="export-item" label="3">仅导出搜索项</el-radio>
</el-form-item>
</el-form>
<div>
<el-upload
class="upload-card"
:action="upLoadUrl"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-exceed="handleExceed"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件且不超过500kb</div>
</el-upload>
</div>
</Dialog>
</el-card> </el-card>
</div> </div>
</template> </template>
@@ -30,6 +44,7 @@
import AutoDataTable from '../AutoDataTable' import AutoDataTable from '../AutoDataTable'
import Dialog from '../Dialog' import Dialog from '../Dialog'
import TableAction from './TableAction' import TableAction from './TableAction'
import { createSourceIdCache } from '@/api/common'
export default { export default {
name: 'ListTable', name: 'ListTable',
@@ -49,6 +64,7 @@ export default {
type: Object, type: Object,
default: () => ({}) default: () => ({})
} }
}, },
data() { data() {
return { return {
@@ -63,6 +79,9 @@ export default {
computed: { computed: {
hasSelected() { hasSelected() {
return this.selectedRows.length > 0 return this.selectedRows.length > 0
},
upLoadUrl() {
return process.env.VUE_APP_BASE_API + this.tableConfig.url
} }
}, },
mounted() { mounted() {
@@ -74,6 +93,12 @@ export default {
}) })
}, },
methods: { methods: {
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
},
handleSelectionChange(val) { handleSelectionChange(val) {
this.selectedRows = val this.selectedRows = val
}, },
@@ -83,7 +108,7 @@ export default {
search(attrs) { search(attrs) {
return this.$refs.dataTable.search(attrs) return this.$refs.dataTable.search(attrs)
}, },
handleExport() { async handleExport() {
let data let data
var resources = [] var resources = []
if (this.exportOption === '1') { if (this.exportOption === '1') {
@@ -96,16 +121,21 @@ export default {
for (let index = 0; index < data.length; index++) { for (let index = 0; index < data.length; index++) {
resources.push(data[index].id) resources.push(data[index].id)
} }
console.log(resources) const spm = await createSourceIdCache(resources)
const url = process.env.VUE_APP_BASE_API + `${this.tableConfig.url}?format=csv&?spm=` + spm.spm
return this.downloadCsv(url)
}, },
handleDialogConfirm(val) { handleImport() {
},
async handleDialogConfirm(val) {
switch (val) { switch (val) {
case 'export': case 'export':
this.handleExport() await this.handleExport()
this.showExportDialog = false this.showExportDialog = false
break break
case 'import': case 'import':
this.handleImport() await this.handleImport()
this.showImportDialog = false this.showImportDialog = false
break break
default: default:

20
src/i18n/date.js Normal file
View File

@@ -0,0 +1,20 @@
export default {
'en': {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
long: {
year: 'numeric', month: 'short', day: 'numeric',
weekday: 'short', hour: 'numeric', minute: 'numeric'
}
},
'cn': {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
long: {
year: 'numeric', month: 'short', day: 'numeric',
weekday: 'short', hour: 'numeric', minute: 'numeric', hour12: true
}
}
}

View File

@@ -3,10 +3,12 @@ import Vue from 'vue'
import locale from 'element-ui/lib/locale' import locale from 'element-ui/lib/locale'
import VueI18n from 'vue-i18n' import VueI18n from 'vue-i18n'
import messages from './langs' import messages from './langs'
import date from './date'
Vue.use(VueI18n) Vue.use(VueI18n)
const i18n = new VueI18n({ const i18n = new VueI18n({
locale: localStorage.lang || 'cn', locale: localStorage.lang || 'cn',
dateTimeFormats: date,
messages messages
}) })
locale.i18n((key, value) => i18n.t(key, value)) // 重点: 为了实现element插件的多语言切换 locale.i18n((key, value) => i18n.t(key, value)) // 重点: 为了实现element插件的多语言切换

View File

@@ -51,7 +51,8 @@ const cn = {
'Submit': '提交', 'Submit': '提交',
'Reset': '重置', 'Reset': '重置',
'This field is required': '这个字段是必填项', 'This field is required': '这个字段是必填项',
'Validity': '有效性' 'Validity': '有效性',
'Other': '其它'
}, },
route: { route: {
'dashboard': '仪表盘', 'dashboard': '仪表盘',
@@ -154,7 +155,10 @@ const cn = {
'remote_app_granted': '授权的远程应用', 'remote_app_granted': '授权的远程应用',
'remote_app_permission': '远程应用授权', 'remote_app_permission': '远程应用授权',
'database_app_granted': '授权的数据库应用', 'database_app_granted': '授权的数据库应用',
'database_app_permission': '数据库应用授权' 'database_app_permission': '数据库应用授权',
'Account': '账户',
'Authentication': '认证',
'Secure': '安全'
}, },
// 用户组 // 用户组
usergroup: { usergroup: {

View File

@@ -19,7 +19,7 @@ export default {
props: { props: {
url: { url: {
type: String, type: String,
required: true, required: true
}, },
method: { method: {
type: String, type: String,
@@ -34,16 +34,34 @@ export default {
form: { form: {
type: Object, type: Object,
default: () => { return {} } default: () => { return {} }
} },
}, onSubmit: {
methods: { type: Function,
handleSubmit(values) { default: null
console.log('submit', values)
} }
}, },
mounted() { mounted() {
console.log('generic', this.$attrs) console.log('generic', this.$attrs)
console.log(this.fields) console.log(this.fields)
},
methods: {
handleSubmit(values) {
let handler = this.onSubmit || this.defaultOnSubmit
handler = handler.bind(this)
console.log('submit', values)
return handler(values)
},
defaultOnSubmit(validValues) {
this.$axios.post(this.url, validValues).then(
() => {
const msg = this.$tc('Create success')
this.$message.success(msg)
setTimeout(() => {
this.$router.push({ name: 'UserList' })
}, 500)
}
)
}
} }
} }
</script> </script>

View File

@@ -104,7 +104,7 @@ export const constantRoutes = [
component: () => import('@/views/users/UserCreateUpdate.vue'), // Parent router-view component: () => import('@/views/users/UserCreateUpdate.vue'), // Parent router-view
name: 'UserGroupCreate', name: 'UserGroupCreate',
hidden: true, hidden: true,
meta: { title: 'UserGroupCreate' } meta: { title: 'UserGroupCreate', activeMenu: '/users/groups' }
}, },
{ {
path: 'groups/:id', path: 'groups/:id',

0
src/utils/jumpserver.js Normal file
View File

View File

@@ -1,19 +1,17 @@
<template> <template>
<GenericCreateUpdatePage :fields="fields" :form="form" :fields-meta="fieldsMeta" :url="url"> <GenericCreateUpdatePage :fields="fields" :form="form" :fields-meta="fieldsMeta" :url="url">
<FormGroupHeader slot="id:name" title="账户" :line="false" /> <!-- <FormGroupHeader slot="id:name" title="账户" :line="false" />-->
<FormGroupHeader slot="id:password_strategy" title="认证" :line="true" /> <!-- <FormGroupHeader slot="id:password_strategy" title="认证" :line="true" />-->
<FormGroupHeader slot="id:role" title="角色安全" :line="true" /> <!-- <FormGroupHeader slot="id:role" title="角色安全" :line="true" />-->
<FormGroupHeader slot="id:phone" title="认证" :line="true" /> <!-- <FormGroupHeader slot="id:phone" title="认证" :line="true" />-->
</GenericCreateUpdatePage> </GenericCreateUpdatePage>
</template> </template>
<script> <script>
import FormGroupHeader from '@/components/formGroupHeader'
import { GenericCreateUpdatePage } from '@/layout/components' import { GenericCreateUpdatePage } from '@/layout/components'
export default { export default {
components: { components: {
GenericCreateUpdatePage, GenericCreateUpdatePage
FormGroupHeader
}, },
data() { data() {
return { return {
@@ -25,15 +23,14 @@ export default {
date_expired: '2099-12-31 00:00:00 +0800' date_expired: '2099-12-31 00:00:00 +0800'
}, },
fields: [ fields: [
'name', 'username', 'email', 'groups', 'password_strategy', 'password', 'mfa_level', [this.$t('users.' + 'Account'), ['name', 'username', 'email', 'groups']],
'source', 'role', 'date_expired', 'phone', 'wechat', 'comment', [this.$t('users.' + 'Authentication'), ['password_strategy', 'password', 'mfa_level', 'source']],
[this.$t('users.' + 'Secure'), ['role', 'date_expired']],
[this.$tc('Other'), ['phone', 'wechat', 'comment']]
], ],
url: '/api/v1/users/users/', url: '/api/v1/users/users/',
fieldsMeta: { fieldsMeta: {
password: { password: {
el: {
type: 'password'
},
hidden: (formValue, item) => { hidden: (formValue, item) => {
console.log('hidden password', formValue.password_strategy) console.log('hidden password', formValue.password_strategy)
if (this.$route.params.id === undefined) { if (this.$route.params.id === undefined) {
@@ -43,21 +40,11 @@ export default {
} }
} }
}, },
email: {
el: {
type: 'email'
}
},
groups: { groups: {
el: { el: {
value: [], value: [],
url: '/api/v1/users/groups/' url: '/api/v1/users/groups/'
} }
},
comment: {
el: {
type: 'textarea'
}
} }
} }
} }

View File

@@ -14,7 +14,7 @@ export default {
tableConfig: { tableConfig: {
url: '/api/v1/users/users/', url: '/api/v1/users/users/',
columns: [ columns: [
'name', 'username', 'role', 'groups_display', 'source', 'is_valid', 'actions' 'name', 'username', 'role', 'groups_display', 'source', 'is_active', 'actions'
], ],
detailRoute: 'UserDetail' detailRoute: 'UserDetail'
}, },