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'
# base api
VUE_APP_BASE_API = '/prod-api'
VUE_APP_BASE_API = ''

View File

@@ -1,19 +1,20 @@
<template>
<DataForm v-loading="loading" :fields="totalFields" v-bind="$attrs" v-on="$listeners">
<slot v-for="item in fields" :slot="`id:${item}`" :name="`id:${item}`" />
<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" />
</DataForm>
</template>
<script>
import DataForm from '../DataForm'
import FormGroupHeader from '@/components/formGroupHeader'
import { optionUrlMeta } from '@/api/common'
import rules from '@/components/DataForm/rules'
import Select2 from '@/components/Select2'
export default {
name: 'AutoDataForm',
components: {
DataForm
DataForm,
FormGroupHeader
},
props: {
url: {
@@ -39,7 +40,8 @@ export default {
return {
meta: {},
totalFields: [],
loading: true
loading: true,
groups: []
}
},
computed: {
@@ -52,18 +54,12 @@ export default {
optionUrlMeta() {
optionUrlMeta(this.url).then(data => {
this.meta = data.actions[this.method.toUpperCase()] || {}
this.generateFields()
this.generateColumns()
this.loading = false
})
},
generateField(name) {
let field = {}
const fieldMeta = this.meta[name] || {}
field.id = name
field.label = fieldMeta.label
let type = 'input'
switch (fieldMeta.type) {
generateFieldByType(type, field, fieldMeta) {
switch (type) {
case 'choice':
type = 'radio-group'
field.options = fieldMeta.choices.map(v => {
@@ -85,26 +81,74 @@ export default {
break
}
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 (type === 'input') {
if (field.type === 'input') {
field.rules = [rules.Required]
} else {
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] || {})
return field
},
generateFields() {
const fields = []
for (let field of this.fields) {
if (typeof field === 'object') {
fields.push(field)
generateFieldGroup(data) {
const [groupTitle, fields] = data
this.groups.push({
id: groupTitle,
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') {
field = this.generateField(field)
fields.push(field)
} else if (field instanceof Object) {
fields.push(field)
}
}
return fields
},
generateColumns() {
const fields = this.generateFields(this.fields)
this.totalFields = fields
}
}

View File

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

View File

@@ -6,21 +6,35 @@
<Dialog :title="$t('Export')" :visible.sync="showExportDialog" @comfirm="handleDialogConfirm('export')" @cancel="handleDialogCancel('export')">
<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>
</Dialog>
<Dialog :title="$t('Import')" :visible.sync="showImportDialog" @comfirm="handleDialogConfirm('import')" @cancel="handleDialogCancel('import')">
<el-form>
<el-form-item label="导出范围" :label-width="'100px'">
<p>{{ $d(new Date(), 'short') }}</p>
<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="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="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>
</div>
</template>
@@ -30,6 +44,7 @@
import AutoDataTable from '../AutoDataTable'
import Dialog from '../Dialog'
import TableAction from './TableAction'
import { createSourceIdCache } from '@/api/common'
export default {
name: 'ListTable',
@@ -49,6 +64,7 @@ export default {
type: Object,
default: () => ({})
}
},
data() {
return {
@@ -63,6 +79,9 @@ export default {
computed: {
hasSelected() {
return this.selectedRows.length > 0
},
upLoadUrl() {
return process.env.VUE_APP_BASE_API + this.tableConfig.url
}
},
mounted() {
@@ -74,6 +93,12 @@ export default {
})
},
methods: {
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
},
handleSelectionChange(val) {
this.selectedRows = val
},
@@ -83,7 +108,7 @@ export default {
search(attrs) {
return this.$refs.dataTable.search(attrs)
},
handleExport() {
async handleExport() {
let data
var resources = []
if (this.exportOption === '1') {
@@ -96,16 +121,21 @@ export default {
for (let index = 0; index < data.length; index++) {
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) {
case 'export':
this.handleExport()
await this.handleExport()
this.showExportDialog = false
break
case 'import':
this.handleImport()
await this.handleImport()
this.showImportDialog = false
break
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 VueI18n from 'vue-i18n'
import messages from './langs'
import date from './date'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: localStorage.lang || 'cn',
dateTimeFormats: date,
messages
})
locale.i18n((key, value) => i18n.t(key, value)) // 重点: 为了实现element插件的多语言切换

View File

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

View File

@@ -19,7 +19,7 @@ export default {
props: {
url: {
type: String,
required: true,
required: true
},
method: {
type: String,
@@ -34,16 +34,34 @@ export default {
form: {
type: Object,
default: () => { return {} }
}
},
methods: {
handleSubmit(values) {
console.log('submit', values)
onSubmit: {
type: Function,
default: null
}
},
mounted() {
console.log('generic', this.$attrs)
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>

View File

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

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

View File

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

View File

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