Merge branch 'master' into jym_dev

This commit is contained in:
jym503558564
2020-04-07 16:28:51 +08:00
26 changed files with 822 additions and 456 deletions

View File

@@ -22,6 +22,7 @@
"js-cookie": "2.2.0", "js-cookie": "2.2.0",
"less": "^3.10.3", "less": "^3.10.3",
"less-loader": "^5.0.0", "less-loader": "^5.0.0",
"lodash": "^4.17.15",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.frompairs": "^4.0.1", "lodash.frompairs": "^4.0.1",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",

9
src/api/common.js Normal file
View File

@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function createSourceIdCache(ids) {
return request({
url: '/api/v1/common/resources/cache/',
method: 'post',
data: {resources: ids}
})
}

View File

@@ -1,62 +1,91 @@
<template> <template>
<elFormRender ref="dataForm" :content="content" v-bind="$attrs" v-on="$listeners"> <ElFormRender
ref="dataForm"
:content="fields"
v-bind="$attrs"
:form="basicForm"
label-position="right"
label-width="17%"
v-on="$listeners"
>
<!-- slot 透传 --> <!-- slot 透传 -->
<slot v-for="item in content" :slot="`id:${item.id}`" :name="`id:${item.id}`" /> <slot v-for="item in fields" :slot="`id:${item.id}`" :name="`id:${item.id}`" />
<slot v-for="item in content" :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">
<el-button size="small" type="primary" @click="submitForm('dataForm')">submit</el-button> <el-button size="small" @click="resetForm('dataForm')">{{ $tc('Reset') }}</el-button>
<el-button size="small" @click="resetForm('dataForm')">reset</el-button> <el-button size="small" type="primary" @click="submitForm('dataForm')">{{ $tc('Submit') }}</el-button>
</el-form-item> </el-form-item>
<slot name="Actions" /> <slot name="Actions" />
</elFormRender> </ElFormRender>
</template> </template>
<script> <script>
import elFormRender from './components/el-form-renderer' import ElFormRender from './components/el-form-renderer'
export default { export default {
components: { components: {
elFormRender ElFormRender
}, },
props: { props: {
defaultButton: { defaultButton: {
type: Boolean, type: Boolean,
default: true default: true
}, },
content: { fields: {
type: Array, type: Array,
default: () => [] default: () => []
},
// 初始值
form: {
type: Object,
default: () => { return {} }
} }
}, },
data() {
return {
basicForm: {}
}
},
mounted() {
this.basicForm = this.form
},
methods: { methods: {
// 获取表单数据
submitForm(formName) { submitForm(formName) {
this.$refs[formName].validate((valid) => { this.$refs[formName].validate((valid) => {
if (valid) { if (valid) {
this.$emit('submit') this.$emit('submit', this.$refs[formName].getFormValue())
} else { } else {
console.log('error submit!!')
return false return false
} }
}) })
}, },
// 重置表单
resetForm(formName) { resetForm(formName) {
this.$refs[formName].resetFields() this.$refs[formName].resetFields()
},
getBasic() {
if (this.url) {
console.log('has Url')
}
} }
} }
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.el-form /deep/ .el-form-item { .el-form /deep/ .el-form-item {
margin-bottom: 12px; margin-bottom: 12px;
}
margin-left:12%; .el-form /deep/ .el-form-item__content {
width:73%; width: 75%;
} }
.el-form /deep/ .el-form-item__label {
padding: 0 30px 0 0;
}
.el-form /deep/ .el-form-item__error {
position: inherit;
}
.el-form /deep/ .form-group-header {
margin-left: 50px;
}
</style> </style>

View File

@@ -0,0 +1,14 @@
import i18n from '@/i18n/i18n'
export const Required = {
required: true, message: i18n.t('common.' + 'This field is required'), trigger: 'blur'
}
export const RequiredChange = {
required: true, message: i18n.t('common.' + 'This field is required'), trigger: 'change'
}
export default {
Required,
RequiredChange
}

View File

@@ -102,8 +102,9 @@
<div <div
:is="col.formatter" :is="col.formatter"
:key="row.id" :key="row.id"
:setting="data" :table-data="data"
:row="row" :row="row"
:reload="getList"
:col="col" :col="col"
:cell-value="row[col.prop]" :cell-value="row[col.prop]"
> >
@@ -730,7 +731,8 @@ export default {
// JSON.stringify是为了后面深拷贝作准备 // JSON.stringify是为了后面深拷贝作准备
initExtraQuery: JSON.stringify(this.extraQuery || this.customQuery || {}), initExtraQuery: JSON.stringify(this.extraQuery || this.customQuery || {}),
isSearchCollapse: false, isSearchCollapse: false,
showNoData: false showNoData: false,
innerQuery: {}
} }
}, },
computed: { computed: {
@@ -852,6 +854,7 @@ export default {
formValue = this.$refs.searchForm.getFormValue() formValue = this.$refs.searchForm.getFormValue()
Object.assign(query, formValue) Object.assign(query, formValue)
} }
Object.assign(query, this.innerQuery)
Object.assign(query, this._extraQuery) Object.assign(query, this._extraQuery)
query[this.pageSizeKey] = this.hasPagination query[this.pageSizeKey] = this.hasPagination
@@ -942,18 +945,9 @@ export default {
this.loading = false this.loading = false
}) })
}, },
async search() { search(attrs) {
const form = this.$refs.searchForm this.innerQuery = Object.assign(this.innerQuery, attrs)
const valid = await new Promise(r => form.validate(r)) return this.getList()
if (!valid) return
try {
await this.beforeSearch(form.getFormValue())
this.page = defaultFirstPage
this.getList()
} catch (err) {
this.$emit('error', err)
}
}, },
/** /**
* 重置查询,相当于点击「重置」按钮 * 重置查询,相当于点击「重置」按钮
@@ -1168,15 +1162,12 @@ export default {
return record[this.treeChildKey] && record[this.treeChildKey].length > 0 return record[this.treeChildKey] && record[this.treeChildKey].length > 0
}, },
onSortChange({ column, prop, order }) { onSortChange({ column, prop, order }) {
if (!this.extraQuery) {
this.extraQuery = {}
}
if (!order) { if (!order) {
delete this.extraQuery['sort'] delete this.innerQuery['sort']
delete this.extraQuery['direction'] delete this.innerQuery['direction']
} else { } else {
this.extraQuery['sort'] = prop this.innerQuery['sort'] = prop
this.extraQuery['direction'] = order this.innerQuery['direction'] = order
} }
this.getList() this.getList()
} }

View File

@@ -1,92 +0,0 @@
<template>
<ActionsGroup :size="'mini'" :actions="actions" :more-actions="moreActions" @actionClick="handleActionClick"></ActionsGroup>
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup'
import BaseFormatter from './base'
export default {
name: 'ActionsFormatter',
components: { ActionsGroup },
extends: BaseFormatter,
data() {
const defaultActions = [
{
name: 'update',
title: this.$tc('Update'),
type: 'primary',
has: this.col.actions.hasUpdate || true,
can: this.col.actions.canUpdate || true,
callback: this.col.actions.onUpdate
},
{
name: 'delete',
title: this.$tc('Delete'),
type: 'danger',
has: this.col.actions.hasDelete || true,
can: this.col.actions.canDelete || true,
callback: this.col.actions.onDelete
}
]
const extraActions = this.col.actions.extraActions || []
return {
defaultActions: defaultActions,
extraActions: extraActions
}
},
computed: {
validActions() {
const actions = [...this.defaultActions, ...this.extraActions]
const validActions = []
for (const v of actions) {
const has = this.checkItem(v, 'has')
if (!has) {
continue
}
const can = this.checkItem(v, 'can')
v.disabled = !can
validActions.push(v)
}
return validActions
},
actions() {
return this.validActions.slice(0, 2)
},
moreActions() {
return this.validActions.slice(2, this.validActions.length)
},
namedValidActions() {
const actions = {}
for (const action of this.validActions) {
if (!action || !action.hasOwnProperty('name')) {
continue
}
actions[action.name] = action
}
return actions
}
},
methods: {
handleActionClick(item) {
const action = this.namedValidActions[item]
if (action && action.callback) {
console.log(this.setting)
action.callback(this.row, this.col, this.cellValue)
}
},
checkItem(item, attr) {
let ok = item[attr]
if (typeof ok === 'function') {
ok = ok(this.row, this.cellValue)
} else if (ok == null) {
ok = true
}
return ok
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<ElDatableTable class="el-table" v-bind="tableConfig" v-on="$listeners"></ElDatableTable> <ElDatableTable ref="table" class="el-table" v-bind="tableConfig" v-on="$listeners"></ElDatableTable>
</template> </template>
<script> <script>
@@ -36,16 +36,12 @@ export default {
hasDelete: userTableActions.hasDelete !== false, hasDelete: userTableActions.hasDelete !== false,
hasNew: false, hasNew: false,
// editText: this.$t('action.update'), // 编辑按钮文案 // editText: this.$t('action.update'), // 编辑按钮文案
operationAttrs: {
align: 'center',
width: '150px'
},
operationButtonType: 'button',
buttonSize: 'mini', buttonSize: 'mini',
tableAttrs: { tableAttrs: {
stripe: true, // 斑马纹表格 stripe: true, // 斑马纹表格
border: true, // 表格边框 border: true, // 表格边框
fit: true // 宽度自适应 fit: true, // 宽度自适应,
tooltipEffect: 'dark'
}, },
extraButtons: userTableActions.extraButtons, extraButtons: userTableActions.extraButtons,
onEdit: (row) => { onEdit: (row) => {
@@ -90,6 +86,15 @@ export default {
const config = Object.assign(this.defaultConfig, this.config) const config = Object.assign(this.defaultConfig, this.config)
return config return config
} }
},
methods: {
getList() {
this.$refs.table.clearSelection()
return this.$refs.table.getList()
},
search(attrs) {
return this.$refs.table.search(attrs)
}
} }
} }
</script> </script>
@@ -102,7 +107,6 @@ export default {
} }
.el-table /deep/ .el-table__row > td> div > span { .el-table /deep/ .el-table__row > td> div > span {
text-overflow: ellipsis; text-overflow: ellipsis;
-moz-text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
} }

View File

@@ -0,0 +1,40 @@
<template>
<el-dialog :title="title" v-bind="$attrs" v-on="$listeners">
<slot></slot>
<div slot="footer" class="dialog-footer">
<slog name="footer">
<el-button size="small" @click="onCancel">{{ $t('Cancel') }}</el-button>
<el-button type="primary" size="small" @click="onConfirm">{{ $tc('Ok') }}</el-button>
</slog>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'Dialog',
props: {
title: {
type: String,
default: ''
}
},
data() {
return {
visible: false
}
},
methods: {
onCancel() {
},
onConfirm() {
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,96 +1,237 @@
<template> <template>
<div class="table-header"> <div class="table-header">
<slot name="header"> <slot name="header">
<!--TODO: 事件交互 -->
<div class="table-header-left-side"> <div class="table-header-left-side">
<ActionsGroup :actions="actions" :more-actions="moreActions" class="header-action" @actionClick="handleActionClick"></ActionsGroup> <ActionsGroup :actions="actions" :more-actions="moreActions" class="header-action" @actionClick="handleActionClick"></ActionsGroup>
</div> </div>
<!-- TODO: 事件交互 -->
<div class="table-action-right-side"> <div class="table-action-right-side">
<el-input v-model="keyword" suffix-icon="el-icon-search" :placeholder="$tc('Search')" class="right-side-item action-search" size="small" clearable @change="handleSearch" @input="handleSearch"></el-input> <el-input v-model="keyword" suffix-icon="el-icon-search" :placeholder="$tc('Search')" class="right-side-item action-search" size="small" clearable @change="handleSearch" @input="handleSearch"></el-input>
<ActionsGroup :is-fa="true" :actions="rightSideActions" class="right-side-actions right-side-item" @actionClick="handleActionClick"></ActionsGroup> <ActionsGroup :is-fa="true" :actions="defaultRightSideActions" class="right-side-actions right-side-item" @actionClick="handleActionClick"></ActionsGroup>
</div> </div>
<Dialog :title="$t('Export')">
<el-form>
<el-form-item label="导出范围" :label-width="'100px'">
<el-radio v-model="exportValue" class="export-item" label="1">导出全部</el-radio>
<el-radio v-model="exportValue" class="export-item" label="2">仅导出选中项</el-radio>
<el-radio v-model="exportValue" class="export-item" label="3">仅导出搜索项</el-radio>
</el-form-item>
</el-form>
</Dialog>
</slot> </slot>
</div> </div>
</template> </template>
<script> <script>
import ActionsGroup from '@/components/ActionsGroup' import ActionsGroup from '@/components/ActionsGroup'
import { Dialog } from '../Dialog'
import _ from 'lodash'
import { createSourceIdCache } from '@/api/common'
const defaultTrue = { type: Boolean, default: true } const defaultTrue = { type: Boolean, default: true }
const defaultFalse = { type: Boolean, default: false }
export default { export default {
name: 'TableAction', name: 'TableAction',
components: { components: {
ActionsGroup ActionsGroup,
Dialog
}, },
props: { props: {
hasExport: defaultTrue, hasExport: defaultTrue,
hasImport: defaultTrue, hasImport: defaultTrue,
hasRefresh: defaultTrue, hasRefresh: defaultTrue,
hasCreate: defaultTrue, hasCreate: defaultTrue,
hasDelete: defaultTrue, hasBulkDelete: defaultTrue,
hasUpdate: defaultTrue, hasBulkUpdate: defaultFalse,
hasLeftActions: defaultTrue, hasLeftActions: defaultTrue,
hasSearch: defaultTrue, hasSearch: defaultTrue,
hasRightActions: defaultTrue hasRightActions: defaultTrue,
tableUrl: {
type: String,
default: ''
},
createRoute: {
type: String,
default: '404'
},
reloadTable: {
type: Function,
default: () => {}
},
performBulkDelete: {
type: Function,
default: () => {}
},
searchTable: {
type: Function,
default: () => {}
},
selectedRows: {
type: Array,
default: () => ([])
},
extraActions: {
type: Array,
default: () => ([])
},
extraMoreActions: {
type: Array,
default: () => ([])
},
extraRightSideActions: {
type: Array,
default: () => ([])
}
}, },
data() { data() {
return { return {
defaultRightSideActions: {
Export: { name: 'actionExport', fa: 'fa-download' },
Import: { name: 'actionImport', fa: 'fa-upload' },
Refresh: { name: 'actionRefresh', fa: 'fa-refresh' }
},
defaultCreateAction: {
name: 'actionCreate',
title: this.$tc('Create'),
type: 'primary'
},
keyword: '', keyword: '',
defaultMoreActions: { defaultRightSideActions: [
Delete: { { name: 'actionExport', fa: 'fa-download', has: this.hasExport, callback: this.handleExport },
title: this.$tc('Delete selected'), { name: 'actionImport', fa: 'fa-upload', has: this.hasImport, callback: this.handleImport },
name: 'actionDeleteSelected' { name: 'actionRefresh', fa: 'fa-refresh', has: this.hasRefresh, callback: this.handleRefresh }
}, ],
Update: { defaultActions: [
title: this.$tc('Update selected'), {
name: 'actionUpdateSelected' name: 'actionCreate',
title: this.$tc('Create'),
type: 'primary',
has: this.hasCreate,
can: true,
callback: this.handleCreate
} }
} ],
defaultMoreActions: [
{
title: this.$tc('Delete selected'),
name: 'actionDeleteSelected',
can: (rows) => rows.length > 0,
has: this.hasBulkDelete,
callback: this.defaultBulkDeleteCallback
},
{
title: this.$tc('Update selected'),
name: 'actionUpdateSelected',
can: (rows) => rows.length > 0,
has: this.hasBulkUpdate,
callback: this.handleBulkUpdate
}
],
dialogExportVisible: false,
exportValue: 2
} }
}, },
computed: { computed: {
rightSideActions() { rightSideActions() {
const actions = [] const actions = [...this.defaultRightSideActions, ...this.extraRightSideActions]
for (const k in this.defaultRightSideActions) { return this.cleanActions(actions)
if (this['has' + k]) {
actions.push(this.defaultRightSideActions[k])
}
}
return actions
}, },
actions() { actions() {
const actions = [] const actions = [...this.defaultActions, ...this.extraActions]
if (this.hasCreate) { return this.cleanActions(actions)
actions.push(this.defaultCreateAction)
}
return actions
}, },
moreActions() { moreActions() {
const actions = [] const actions = [...this.defaultMoreActions, ...this.extraMoreActions]
for (const k in this.defaultMoreActions) { return this.cleanActions(actions)
if (this['has' + k]) { },
actions.push(this.defaultMoreActions[k])
namedActions() {
const totalActions = [...this.actions, ...this.moreActions, ...this.rightSideActions]
const actions = {}
for (const action of totalActions) {
if (!action || !action.hasOwnProperty('name')) {
continue
} }
actions[action.name] = action
} }
return actions return actions
} }
}, },
methods: { methods: {
handleSearch(keyword) { handleSearch: _.debounce(function() {
console.log('Search: ', keyword) this.searchTable({search: this.keyword})
}, }, 500),
handleActionClick(item) { handleActionClick(item) {
this.$emit('clickAction', item) console.log('name cations', this.namedActions)
let handler = this.namedActions[item] ? this.namedActions[item].callback : null
if (!handler) {
handler = () => {
console.log('No handler found for ', item)
}
}
handler(this.selectedRows)
},
handleCreate() {
const routeName = this.createRoute
this.$router.push({ name: routeName })
console.log('handle create')
},
defaultBulkDeleteCallback(rows) {
const msg = this.$tc('Are you sure to delete') + ' ' + rows.length + ' ' + this.$tc('rows')
const title = this.$tc('Info')
const performDelete = this.performBulkDelete || this.defaultPerformBulkDelete
this.$alert(msg, title, {
type: 'warning',
confirmButtonClass: 'el-button--danger',
showCancelButton: true,
beforeClose: async(action, instance, done) => {
if (action !== 'confirm') return done()
instance.confirmButtonLoading = true
try {
await performDelete(rows)
done()
this.reloadTable()
this.$message.success(this.$tc('Delete success'))
} catch (error) {
this.$message.error(this.$tc('Delete failed'))
console.warn(error)
} finally {
instance.confirmButtonLoading = false
}
}
}).catch(() => {
/* 取消*/
})
},
async defaultPerformBulkDelete(rows) {
const ids = rows.map((v) => {
return v.id
})
const data = await createSourceIdCache(ids)
const url = `${this.tableUrl}?spm=` + data.spm
return this.$axios.delete(url)
},
handleBulkUpdate(rows) {
},
handleExport() {
this.dialogExportVisible = true
},
handleImport() {
},
handleRefresh() {
this.reloadTable()
},
cleanActions(actions) {
const validActions = []
for (const action of actions) {
let ok = this.checkItem(action, 'has')
if (!ok) {
continue
}
ok = this.checkItem(action, 'can')
action.disabled = !ok
validActions.push(action)
}
return validActions
},
checkItem(item, attr) {
let ok = item[attr]
if (typeof ok === 'function') {
ok = ok(this.selectedRows)
} else if (ok == null) {
ok = true
}
return ok
} }
} }
} }
@@ -141,4 +282,9 @@ export default {
justify-content:center; justify-content:center;
} }
.export-item {
display: block;
padding: 5px 20px;
}
</style> </style>

View File

@@ -0,0 +1,139 @@
<template>
<ActionsGroup :size="'mini'" :actions="actions" :more-actions="moreActions" @actionClick="handleActionClick"></ActionsGroup>
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup/index'
import BaseFormatter from './base'
const defaultPerformDelete = function({row, col}) {
const id = row.id
const url = `/api/v1/users/groups/${id}/`
return this.$axios.delete(url)
}
const defaultUpdateCallback = function({row, col}) {
const id = row.id
const routeName = col.actions.updateRoute || '404'
this.$router.push({name: routeName, params: {id: id}})
}
const defaultDeleteCallback = function({row, col, cellValue, reload}) {
const msg = this.$tc('Are you sure to delete') + ' "' + row.name + '"'
const title = this.$tc('Info')
const performDelete = col.actions.performDelete || defaultPerformDelete.bind(this)
this.$alert(msg, title, {
type: 'warning',
confirmButtonClass: 'el-button--danger',
showCancelButton: true,
beforeClose: async(action, instance, done) => {
if (action !== 'confirm') return done()
instance.confirmButtonLoading = true
try {
await performDelete({row: row, col: col})
done()
reload()
this.$message.success(this.$tc('Delete success'))
} catch (error) {
this.$message.error(this.$tc('Delete failed'))
console.warn(error)
} finally {
instance.confirmButtonLoading = false
}
}
}).catch(() => {
/* 取消*/
})
}
export default {
name: 'ActionsFormatter',
components: { ActionsGroup },
extends: BaseFormatter,
data() {
const colActions = this.col.actions || {}
const defaultActions = [
{
name: 'update',
title: this.$tc('Update'),
type: 'primary',
has: colActions.hasUpdate || true,
can: colActions.canUpdate || true,
callback: colActions.onUpdate || defaultUpdateCallback.bind(this)
},
{
name: 'delete',
title: this.$tc('Delete'),
type: 'danger',
has: colActions.hasDelete || true,
can: colActions.canDelete || true,
callback: colActions.onDelete || defaultDeleteCallback.bind(this)
}
]
const extraActions = colActions.extraActions || []
return {
defaultActions: defaultActions,
extraActions: extraActions
}
},
computed: {
validActions() {
const actions = [...this.defaultActions, ...this.extraActions]
const validActions = []
for (const v of actions) {
const has = this.checkItem(v, 'has')
if (!has) {
continue
}
const can = this.checkItem(v, 'can')
v.disabled = !can
validActions.push(v)
}
return validActions
},
actions() {
return this.validActions.slice(0, 2)
},
moreActions() {
return this.validActions.slice(2, this.validActions.length)
},
namedValidActions() {
const actions = {}
for (const action of this.validActions) {
if (!action || !action.hasOwnProperty('name')) {
continue
}
actions[action.name] = action
}
return actions
}
},
methods: {
handleActionClick(item) {
const action = this.namedValidActions[item]
if (action && action.callback) {
const attrs = {
reload: this.reload,
row: this.row,
col: this.col,
cellValue: this.cellValue,
tableData: this.tableData
}
action.callback(attrs)
}
},
checkItem(item, attr) {
let ok = item[attr]
if (typeof ok === 'function') {
ok = ok(this.row, this.cellValue)
} else if (ok == null) {
ok = true
}
return ok
}
}
}
</script>
<style scoped>
</style>

View File

@@ -5,6 +5,10 @@
export default { export default {
name: 'BaseFormatter', name: 'BaseFormatter',
props: { props: {
reload: {
type: Function,
default: ({reloading}) => ({})
},
row: { row: {
type: Object, type: Object,
default: () => ({}) default: () => ({})
@@ -17,7 +21,7 @@ export default {
type: [String, Boolean, Number, Object], type: [String, Boolean, Number, Object],
default: null default: null
}, },
setting: { tableData: {
type: Array, type: Array,
default: () => ({}) default: () => ({})
} }

View File

@@ -1,11 +1,8 @@
<template> <template>
<div> <div>
<TableAction v-bind="headerActions" @clickAction="handleActionClick"></TableAction> <TableAction :table-url="tableConfig.url" :search-table="search" v-bind="headerActions" :selected-rows="selectedRows" :reload-table="reloadTable"></TableAction>
<el-card class="table-content" shadow="never"> <el-card class="table-content" shadow="never">
<DataTable :config="tableConfig" @selection-change="handleSelectionChange"> <DataTable ref="dataTable" :config="tableConfig" @selection-change="handleSelectionChange">
<template v-slot:actions="row">
{{ row.id }}
</template>
</DataTable> </DataTable>
</el-card> </el-card>
</div> </div>
@@ -15,6 +12,7 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
import DataTable from '../DataTable' import DataTable from '../DataTable'
import TableAction from './TableAction' import TableAction from './TableAction'
export default { export default {
name: 'ListTable', name: 'ListTable',
components: { components: {
@@ -35,56 +33,24 @@ export default {
}, },
data() { data() {
return { return {
selectRows: [], selectedRows: []
} }
}, },
computed: { computed: {
actionColumn() { hasSelected() {
const actions = [] return this.selectedRows.length > 0
let tc = this.tableConfig
if (tc.hasEdit !== false) {
actions.push({
name: 'update',
title: this.$tc('Update')
})
}
if (tc.hasDelete !== false) {
actions.push({
name: 'delete',
title: this.$tc('Delete')
})
}
} }
}, },
methods: { methods: {
handleSelectionChange(val) { handleSelectionChange(val) {
this.selectRows = val this.selectedRows = val
this.multipleSelection = val; console.log(this.selectedRows)
(val.length > 0) ? (this.selectDisable = false) : (this.selectDisable = true)
}, },
handleActionClick(item) { reloadTable() {
const handler = this.getActionHandler(item) this.$refs.dataTable.getList()
handler(this.selectRows)
}, },
handleActionCreate() { search(attrs) {
const routeName = this.headerActions.createRoute || '' return this.$refs.dataTable.search(attrs)
this.$router.push({ name: routeName })
console.log('handle create')
},
getActionHandler(item) {
let handler = this.headerActions.item
const defaultHandlerName = 'handle' + item[0].toUpperCase() + item.slice(1, item.length)
if (!handler) {
handler = this[defaultHandlerName]
}
if (!handler) {
handler = () => {
console.log('No handler found for ', item)
}
}
console.log(handler)
return handler
} }
} }
} }

View File

@@ -144,7 +144,6 @@ export default {
} }
data.results.forEach((v) => { data.results.forEach((v) => {
this.options.push(v) this.options.push(v)
console.log(v)
}) })
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)

View File

@@ -1,5 +1,5 @@
<template> <template>
<div> <div class="form-group-header">
<div v-if="line" class="hr-line-dashed" /> <div v-if="line" class="hr-line-dashed" />
<h3>{{ title }}</h3> <h3>{{ title }}</h3>
</div> </div>

View File

@@ -38,9 +38,19 @@ const cn = {
'More actions': '更多操作', 'More actions': '更多操作',
'Delete selected': '删除所选', 'Delete selected': '删除所选',
'Update selected': '更新所选', 'Update selected': '更新所选',
'Delete success': '删除成功',
'Search': '搜索', 'Search': '搜索',
'Source': '来源', 'Source': '来源',
'Status': '状态' 'Status': '状态',
'Actions': '动作',
'Monitor': '监控',
'Run': '执行',
'Are you sure to delete': '你确定要删除',
'Info': '提示',
'More': '更多',
'Submit': '提交',
'Reset': '重置',
'This field is required': '这个字段是必填项'
}, },
route: { route: {
'dashboard': '仪表盘', 'dashboard': '仪表盘',
@@ -81,6 +91,7 @@ const cn = {
'OperateLog': '操作日志', 'OperateLog': '操作日志',
'PasswordChangeLog': '改密日志', 'PasswordChangeLog': '改密日志',
'Settings': '系统设置', 'Settings': '系统设置',
'UserCreate': '创建用户'
}, },
// 用户模块翻译 // 用户模块翻译
users: { users: {

View File

@@ -67,13 +67,20 @@ export const constantRoutes = [
}, },
{ {
path: 'users/create', path: 'users/create',
component: () => import('@/views/users/UserEdit.vue'), // Parent router-view component: () => import('@/views/users/UserCreateUpdate.vue'), // Parent router-view
name: 'UserCreate', name: 'UserCreate',
hidden: true, hidden: true,
meta: { title: 'UserCreate' } meta: { title: 'UserCreate', activeMenu: '/users/users'}
}, },
{ {
path: 'users/:id', path: 'users/update/:id',
component: () => import('@/views/users/UserCreateUpdate.vue'), // Parent router-view
name: 'UserEdit',
hidden: true,
meta: { title: 'UserEdit' }
},
{
path: 'users/detail/:id',
component: () => import('@/views/users/UserDetail.vue'), // Parent router-view component: () => import('@/views/users/UserDetail.vue'), // Parent router-view
name: 'UserDetail', name: 'UserDetail',
hidden: true, hidden: true,
@@ -87,10 +94,10 @@ export const constantRoutes = [
}, },
{ {
path: 'groups/:id/update', path: 'groups/:id/update',
component: () => import('@/views/users/UserGroupEdit.vue'), // Parent router-view component: () => import('@/views/users/UserGroupUpdate.vue'), // Parent router-view
name: 'UserGroupEdit', name: 'UserGroupUpdate',
hidden: true, hidden: true,
meta: { title: 'UserGroupEdit' } meta: { title: 'UserGroupUpdate' }
}, },
{ {
path: 'groups/:id', path: 'groups/:id',
@@ -101,7 +108,7 @@ export const constantRoutes = [
}, },
{ {
path: 'groups/create', path: 'groups/create',
component: () => import('@/views/users/UserEdit.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' }

View File

@@ -162,7 +162,15 @@ td .el-button.el-button--mini {
width: 100%; width: 100%;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
height: 35px; height: 34px;
}
.el-input--small .el-input__inner {
height: 34px;
}
.el-input--small .el-input__icon {
line-height: 34px;
} }
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected { .el-select-dropdown.is-multiple .el-select-dropdown__item.selected {
@@ -223,3 +231,7 @@ td .el-button.el-button--mini {
.text-success { .text-success {
color: $--color-success; color: $--color-success;
} }
.el-radio__input.is-checked+.el-radio__label {
color: inherit;
}

View File

@@ -0,0 +1,254 @@
<template>
<Page>
<IBox>
<DataForm :form="form" :fields="fields">
<FormGroupHeader slot="id:name" title="账户" :line="false" />
<FormGroupHeader slot="id:passwordrule" title="认证" :line="true" />
<FormGroupHeader slot="id:role" title="角色安全" :line="true" />
<FormGroupHeader slot="id:phone" title="认证" :line="true" />
</DataForm>
</IBox>
</page>
</template>
<script>
/* eslint-disable vue/no-unused-components */
import FormGroupHeader from '@/components/formGroupHeader'
import { Page, IBox } from '@/layout/components'
import DataForm from '@/components/DataForm'
import rules from '@/components/DataForm/rules'
import select2 from '@/components/Select2'
export default {
components: {
Page,
IBox,
DataForm,
select2,
FormGroupHeader
},
data() {
return {
form: {
passwordrule: '1',
mfa_level: 0,
source: 'local',
role: 'Admin',
date_expired: '2099-12-31 00:00:00 +0800'
},
fields: [
{
type: 'input',
id: 'name',
label: this.$t('users.name'),
el: {
},
rules: [
rules.Required
]
},
{
type: 'input',
id: 'username',
label: this.$t('users.username'),
el: {
},
rules: [
rules.Required
]
},
{
type: 'input',
id: 'email',
label: this.$t('users.email'),
el: {
},
rules: [
rules.Required
]
},
{
id: 'users',
label: '用户组',
el: {
placeholder: '添加到用户组',
value: [
{
label: 'hello',
value: '1a775bbf-6861-4acb-8ae4-2f684794c8cc'
},
{
label: 'test',
value: '4dccdf84-7728-4de0-a507-67c905b3091b'
},
{
label: 'whold',
value: 'c5ec4b91-1fb2-478e-89bc-5a4abc0f9c6c'
}
],
url: '/api/v1/users/users/'
},
// 自定义组件
// 可以取到自定义组件的值
// https://femessage.github.io/el-form-renderer/#/Guide?id=guide-custom-component
component: select2
}, {
type: 'radio-group',
id: 'passwordrule',
label: '密码策略',
el: {
},
hidden: (formValue, item) => {
return this.$route.params.id
},
options: [{
label: '生成重置密码链接,通过邮件发送给用户',
value: '1'
}, {
label: '设置密码',
value: '2'
}],
rules: [
{ required: true, message: 'miss resource', trigger: 'change' }
]
}, {
type: 'input',
id: 'password',
label: '密码',
hidden: (formValue, item) => {
if (this.$route.params.id === undefined) {
return (formValue.passwordrule !== '2')
} else {
return true
}
},
el: {
type: 'password'
}
},
{
type: 'input',
id: 'sshkey',
label: 'ssh公钥',
hidden: (formValue, item) => {
return !this.$route.params.id
},
el: {
placeholder: 'ssh-rsa AAAA...',
type: 'textarea',
rows: 3
}
},
{
type: 'radio-group',
id: 'mfa_level',
label: '多因子认证',
el: {
},
size: 0,
options: [{
label: '禁用',
value: 0
}, {
label: '启用',
value: 1
}, {
label: '强制启用',
value: 2
}],
rules: [
rules.Required
]
}, {
type: 'select',
id: 'source',
label: '来源',
el: {
},
default: '数据库',
options: [{
label: '数据库',
value: 'local'
}],
rules: [
rules.Required
]
},
{
type: 'select',
id: 'role',
label: '角色',
el: {
},
default: 'User',
options: [{
label: '管理员',
value: 'Admin'
}, {
label: '用户',
value: 'User'
}, {
label: '审计员',
value: 'Auditor'
}],
rules: [
rules.Required
]
},
{
type: 'date-picker',
id: 'date_expired',
label: '过期时间',
el: {
type: 'datetime',
placeholder: 'select date'
},
rules: [
rules.Required
]
},
{
type: 'input',
id: 'phone',
label: '手机',
el: {
}
}, {
type: 'input',
id: 'wechat',
label: '微信',
el: {
}
}, {
type: 'input',
id: 'comment',
label: '备注',
el: {
type: 'textarea',
row: '4'
}
}
]
}
},
methods: {
debug() {
console.log(this)
}
},
mounted() {
console.log('>>>>>>>>>>')
console.log(rules)
console.log(rules.Required)
}
}
</script>
<style lang="less" scoped>
.el-form /deep/ .el-select{
width:100%;
}
.el-form /deep/ .el-form-item__content > .el-date-editor{
width:100%;
}
</style>

View File

@@ -1,162 +0,0 @@
<template>
<Page>
<template>
<el-card>
<dataform :content="content" label-position="left" label-width="140px" :form="form">
<formgroupheader slot="id:name" title="账户" :line="false" style="margin:0 50px;" />
<formgroupheader slot="id:passwordrule" title="认证" :line="true" style="margin:0 50px;" />
</dataform>
</el-card>
</template>
</page>
</template>
<script>
/* eslint-disable vue/no-unused-components */
import formgroupheader from '@/components/formGroupHeader'
import { Page } from '@/layout/components'
import dataform from '@/components/DataForm'
import select2 from '@/components/Select2'
export default {
components: {
Page,
dataform,
select2,
formgroupheader
},
data() {
return {
content: [
{
type: 'input',
id: 'name',
label: this.$t('users.name'),
el: {
size: 'mini'
},
rules: [
{ required: true, message: 'miss name', trigger: 'blur' }
]
},
{
type: 'input',
id: 'username',
label: this.$t('users.username'),
el: {
size: 'mini'
},
rules: [
{ required: true, message: 'miss name', trigger: 'blur' }
]
},
{
type: 'input',
id: 'email',
label: this.$t('users.email'),
el: {
size: 'mini'
},
rules: [
{ required: true, message: 'miss name', trigger: 'blur' }
]
},
{
id: 'users',
label: '用户组',
el: {
size: 'mini',
placeholder: '添加到用户组',
value: [
{
label: 'hello',
value: '1a775bbf-6861-4acb-8ae4-2f684794c8cc'
},
{
label: 'test',
value: '4dccdf84-7728-4de0-a507-67c905b3091b'
},
{
label: 'whold',
value: 'c5ec4b91-1fb2-478e-89bc-5a4abc0f9c6c'
}
],
url: '/api/v1/users/users/'
},
// 自定义组件
// 可以取到自定义组件的值
// https://femessage.github.io/el-form-renderer/#/Guide?id=guide-custom-component
component: select2
}, {
type: 'radio-group',
id: 'passwordrule',
label: '密码策略',
el: {
size: 'small'
},
options: [{
label: '生成重置密码链接,通过邮件发送给用户',
value: '1'
}, {
label: '设置密码',
value: '2'
}],
rules: [
{ required: true, message: 'miss resource', trigger: 'change' }
]
}, {
type: 'input',
id: 'password',
label: '密码',
hidden: (formValue, item) => {
console.log(formValue, item)
formValue.passwordrule !== '2'
},
el: {
size: 'mini',
type: 'password'
},
rules: [
{ required: true, message: 'miss name', trigger: 'blur' }
]
},
{
type: 'radio-group',
id: 'mfa',
label: '密码策略',
el: {
size: 'small'
},
options: [{
label: '禁用'
}, {
label: '启用'
}, {
label: '强制启用'
}],
rules: [
{ required: true, message: 'miss resource', trigger: 'change' }
]
}, {
type: 'select',
id: 'source',
label: '来源',
el: {
size: 'small'
},
options: [{
label: 'area1',
value: 'shanghai'
}]
}
]
}
}
}
</script>
<style lang="less" scoped>
.el-form /deep/ .el-select{
width:100%;
}
</style>

View File

@@ -4,7 +4,7 @@
<script> <script>
import { GenericListPage } from '@/layout/components' import { GenericListPage } from '@/layout/components'
import { DetailFormatter, ActionsFormatter } from '@/components/DataTable/formatters/index' import { DetailFormatter, ActionsFormatter } from '@/components/ListTable/formatters/index'
export default { export default {
components: { components: {
@@ -15,21 +15,17 @@ export default {
tableConfig: { tableConfig: {
url: '/api/v1/users/groups/', url: '/api/v1/users/groups/',
columns: [ columns: [
{
label: 'ID',
type: 'index'
},
{ {
prop: 'name', prop: 'name',
label: this.$tc('Name'), label: this.$tc('Name'),
formatter: DetailFormatter, formatter: DetailFormatter,
sortable: true, sortable: true,
route: 'UserDetail' route: 'UserGroupDetail'
}, },
{ {
prop: 'users_amount', prop: 'users_amount',
label: this.$t('users.User'), label: this.$t('users.User'),
key: 'users_amount', key: 'users_amount'
}, },
{ {
prop: 'comment', prop: 'comment',
@@ -38,55 +34,27 @@ export default {
}, },
{ {
prop: 'id', prop: 'id',
label: this.$tc('Action'), label: this.$tc('Actions'),
align: 'center', align: 'center',
formatter: ActionsFormatter, formatter: ActionsFormatter,
width: '200px',
actions: { actions: {
hasUpdate: (row, cellValue) => { updateRoute: 'UserGroupUpdate',
return true
},
canUpdate: (row, cellValue) => {
console.log('On table update')
return true
},
hasDelete: true,
canDelete: (row, cellValue) => {
return true
},
onDelete: (row, cellValue) => {
this.$confirm('你好啊', '提示', {
type: 'warning',
confirmButtonClass: 'el-button--danger',
beforeClose: async(action, instance, done) => {
}
}).catch(() => {
/* 取消*/
})
},
extraActions: [ extraActions: [
{ {
name: 'run', name: 'run',
title: this.$tc('Run'), title: this.$tc('Run')
callback: (row, cellValue) => {
console.log('On run')
}
},
{
name: 'monitor',
title: this.$tc('Monitor')
} }
], ]
order: []
} }
} }
], ]
// 写路由名字table组件会自动传作为参数
tableActions: {
editRoute: '404'
}
}, },
headerActions: { headerActions: {
createRoute: 'UserGroupCreate' createRoute: 'UserGroupCreate',
performBulkDelete: function(rows) {
console.log('hello')
}
} }
} }
} }

View File

@@ -2,7 +2,7 @@
<Page> <Page>
<template> <template>
<el-card> <el-card>
<dataform :content="content" label-position="right" label-width="100px" :form="form" /> <dataform fields="content" label-position="right" label-width="100px" :form="form" />
</el-card> </el-card>
</template> </template>
</page> </page>
@@ -11,7 +11,7 @@
<script> <script>
/* eslint-disable vue/no-unused-components */ /* eslint-disable vue/no-unused-components */
import { Page } from '@/layout/components' import { Page } from '@/layout/components'
import dataform from '@/components/DataForm' import DataForm from '@/components/DataForm'
import select2 from '@/components/Select2' import select2 from '@/components/Select2'
export default { export default {
components: { components: {

View File

@@ -4,7 +4,7 @@
<script> <script>
import { GenericListPage } from '@/layout/components' import { GenericListPage } from '@/layout/components'
import { DetailFormatter, DisplayFormatter, ChoicesFormatter } from '@/components/DataTable/formatters/index' import { DetailFormatter, DisplayFormatter, ChoicesFormatter, ActionsFormatter } from '@/components/ListTable/formatters/index'
export default { export default {
components: { components: {
@@ -49,6 +49,22 @@ export default {
formatter: ChoicesFormatter, formatter: ChoicesFormatter,
align: 'center', align: 'center',
width: '80px' width: '80px'
},
{
prop: 'id',
label: this.$tc('Actions'),
align: 'center',
formatter: ActionsFormatter,
width: '200px',
actions: {
updateRoute: 'UserUpdate',
extraActions: [
{
name: 'run',
title: this.$tc('Run')
}
]
}
} }
], ],
// 写路由名字table组件会自动传作为参数 // 写路由名字table组件会自动传作为参数
@@ -57,7 +73,17 @@ export default {
} }
}, },
headerActions: { headerActions: {
createRoute: 'UserGroupCreate' createRoute: 'UserCreate',
extraMoreActions: [
{
name: 'deactiveSelected',
title: this.$tc('Deactive selected')
},
{
name: 'activeSelected',
title: this.$tc('Active selected')
}
]
} }
} }
} }