多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
12 KiB

import { isVirtualCol, filterOutSystemColumns } from 'nocodb-sdk'
export default {
data: () => ({
showSystemFieldsLoc: false,
viewStatus: {},
fieldFilter: '',
filtersKey: 0,
filters: [],
sortList: [],
showFields: {},
// fieldList: [],
// meta: {},
data: [],
syncDataDebounce() {
// not implemented
}
}),
methods: {
mapFieldsAndShowFields() {
// this.fieldList = this.availableColumns.map(c => c.title);
this.showFields = this.fieldList.reduce((obj, k) => {
obj[k] = k in this.showFields ? this.showFields[k] : true
return obj
}, {})
},
onKeyDown(e) {
if (this.selected.col === null || this.selected.row === null) {
return
}
switch (e.keyCode) {
// left
case 37:
if (this.selected.col > 0) {
this.selected.col--
}
break
// right
case 39:
if (this.selected.col < this.colLength - 1) {
this.selected.col++
}
break
// up
case 38:
if (this.selected.row > 0) {
this.selected.row--
}
break
// down
case 40:
if (this.selected.row < this.rowLength - 1) {
this.selected.row++
}
break
// enter
case 13:
this.makeEditable(this.selected.col, this.selected.row)
break
}
}
},
computed: {
showSystemFields: {
get() {
return this.showSystemFieldsLoc
},
set(v) {
this.showSystemFieldsLoc = v
}
},
isLocked() {
return this.viewStatus && this.viewStatus.type === 'locked'
},
fieldList() {
return this.availableColumns.map((c) => {
return c.alias
})
},
realFieldList() {
return this.availableRealColumns.map((c) => {
return c.title
})
},
formulaFieldList() {
return this.availableColumns.reduce((arr, c) => {
if (c.formula) {
arr.push(c.title)
}
return arr
}, [])
},
availableRealColumns() {
return this.availableColumns && this.availableColumns.filter(c => !isVirtualCol(c))
},
allColumns() {
if (!this.meta) {
return []
}
let columns = this.meta.columns
if (this.meta && this.meta.v) {
columns = [...columns, ...this.meta.v.map(v => ({
...v,
virtual: 1
}))]
}
{
columns.forEach((c) => {
c.alias = c.title
})
}
return columns
},
availableColumns() {
let columns = []
if (!this.meta) {
return []
}
// todo: generate hideCols based on default values
if (this.showSystemFields) {
columns = this.meta.columns || []
} else {
columns = filterOutSystemColumns(this.meta.columns)
}
{
columns.forEach((c) => {
c.alias = c.title
})
}
if (this.fieldsOrder.length) {
return [...columns].sort((c1, c2) => {
const i1 = this.fieldsOrder.indexOf(c1.title)
const i2 = this.fieldsOrder.indexOf(c2.title)
return (i1 === -1 ? Infinity : i1) - (i2 === -1 ? Infinity : i2)
})
}
return columns
},
concatenatedXWhere() {
let where = ''
if (this.searchField && this.searchQuery.trim()) {
const col = this.availableColumns.find(({ alias }) => alias === this.searchField) || this.availableColumns[0]
// bigint values are displayed in string format in UI
// when searching bigint values, the operator should be 'eq' instead of 'like'
if (['text', 'string'].includes(this.sqlUi.getAbstractType(col)) && col.dt !== 'bigint') {
where = `(${this.searchField},like,%${this.searchQuery.trim()}%)`
} else {
where = `(${this.searchField},eq,${this.searchQuery.trim()})`
}
}
if (!where) {
return this.xWhere
}
return this.xWhere ? where + `~and(${this.xWhere})` : where
},
nestedAndRollupColumnParams() {
// generate params for nested columns
const nestedFields = ((this.meta && this.meta.v && this.meta.v) || []).reduce((obj, vc) => {
if (vc.hm) {
obj.hm.push(vc.hm.table_name)
} else if (vc.bt) {
obj.bt.push(vc.bt.rtn)
} else if (vc.mm) {
obj.mm.push(vc.mm.rtn)
}
return obj
}, {
hm: [],
bt: [],
mm: []
})
// todo: handle if virtual column missing
// construct fields args based on lookup columns
const fieldsObj = ((this.meta && this.meta.v && this.meta.v) || []).reduce((obj, vc) => {
if (!vc.lk) {
return obj
}
let key
let index
let column
switch (vc.lk.type) {
case 'mm':
index = nestedFields.mm.indexOf(vc.lk.ltn) + 1
key = `mfields${index}`
column = vc.lk.lcn
break
case 'hm':
index = nestedFields.hm.indexOf(vc.lk.ltn) + 1
key = `hfields${index}`
column = vc.lk.lcn
break
case 'bt':
index = nestedFields.bt.indexOf(vc.lk.ltn) + 1
key = `bfields${index}`
column = vc.lk.lcn
break
}
if (index && column) {
obj[key] = `${obj[key] ? `${obj[key]},` : ''}${column}`
}
return obj
}, {})
return {
...Object.entries(nestedFields).reduce((ro, [k, a]) => ({
...ro,
[k]: a.join(',')
}), {}),
...fieldsObj
}
},
queryParams() {
return {
limit: this.size,
offset: this.size * (this.page - 1),
// condition: this.condition,
where: this.concatenatedXWhere
// sort: this.sort,
// ...this.nestedAndRollupColumnParams
// ...Object.entries(nestedFields).reduce((ro, [k, a]) => ({ ...ro, [k]: a.join(',') })),
// ...fieldsObj
}
},
colLength() {
return (this.availableColumns && this.availableColumns.length) || 0
},
visibleColLength() {
return (this.availableColumns && this.availableColumns.length) || 0
},
rowLength() {
return (this.data && this.data.length) || 0
},
edited() {
return this.data && this.data.some(r => r.rowMeta && (r.rowMeta.new || r.rowMeta.changed))
},
hasMany() {
// todo: use cn alias
return this.meta && this.meta.hasMany
? this.meta.hasMany.reduce((hm, o) => {
const _rcn = this.meta.columns.find(c => c.column_name === o.rcn).title
hm[_rcn] = hm[_rcn] || []
hm[_rcn].push(o)
return hm
}, {})
: {}
},
haveHasManyrelation() {
return !!Object.keys(this.hasMany).length
},
belongsTo() {
return this.meta && this.meta.belongsTo
? this.meta.belongsTo.reduce((bt, o) => {
const _cn = (this.meta.columns.find(c => c.column_name === o.column_name) || {}).title
bt[_cn] = o
return bt
}, {})
: {}
},
table() {
if (this.relationType === 'hm') {
return this.relation.table_name
} else if (this.relationType === 'bt') {
return this.relation.rtn
}
return this.nodes.table_name || this.nodes.view_name
},
primaryValueColumn() {
if (!this.meta || !this.availableColumns || !this.availableColumns.length) {
return ''
}
return (this.availableColumns.find(col => col.pv) || { _cn: '' }).title
}
},
watch: {
meta() {
this.mapFieldsAndShowFields()
},
'viewStatus.type'() {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
showFields: {
handler(v) {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
deep: true
},
extraViewParams: {
handler(v) {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
deep: true
},
coverImageField(v) {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
feat: kanban view (#903) * feat: enable kanban button on nav drawer Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: move conditions to isKanban Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * wip: add basic layout & integrate with view data Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: add missing components Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: emit expandForm Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add boolean cell for kanban card Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: add :disable to boollean cell Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * style: kanban card Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: enable kanban share view on drawer Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: share links Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * wip: kanban shared page Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * wip: implement updateBlock logic Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * wip: reload kanban view after drag n drop Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: update CsvExportImport path Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add grouping field for kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add uncategorized stack Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * style: match stack title color with that of grouping field Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * style: kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: implement groupingField change logic Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: watch data prop change Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * style: fix center title n padding Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * wip: add footer Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * test(cypress): include kanban viewTest Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add insertNewRow logic to kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add new stack column Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: include loadTableData Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: reload issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * wip: fetch data using api Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add api filtering logic Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: inconsistent content Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: merge from upstream master Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: hide new stack button Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: populate the grouping field to new record for kanban Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: hide pagination in kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * style: display add new record as an icon Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add numbers of records under each stack Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: missing uncategorized records Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: kanban preset value issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: kanban layout & 0 record issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: set toast position Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: lint Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add v-skeleton-loader Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: rm toast if there is no grouping column Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: remove :loading Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: move kanban logic to rowsXcDataTable Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: update kanban block logic Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: remove unused code Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: update kanban ui for status and grouping field Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: move set kanban logic to rowsXcDataTable Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: new kanban record issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: infinite scrolling on kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: remove updateKanbanBlock Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: splash issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: empty kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: move skeleton-loader to rowsXcDataTable.vue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: avoid reload after saving in expanded form Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refak: remove unused clonedBlock Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: handle composite primary key Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: kanban refresh issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: update reload logic for kanban Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * feat: add total number of records for each kanban stack Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: kanban display issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * wip: kanban share view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: insert new row issue Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * fix: missing data after reload Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: temporarily disable share view for kanban view Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com> * chore: disable kanban viewTest temporarily Signed-off-by: Wing-Kam Wong <wingkwong.code@gmail.com>
3 years ago
groupingField(v) {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
fieldsOrder: {
handler(v) {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
deep: true
},
showSystemFields(v) {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
filters: {
async handler(filter) {
let defaultQuery = ''
let j = 0
const xWhere = filter.reduce((condition, filt, k) => {
if (filt.readOnly) {
defaultQuery += `(${filt.field},eq,${filt.value})`
j++
return condition
}
const i = k - j
if (i && !filt.logicOp) {
return condition
}
if (!(filt.field && filt.op)) {
return condition
}
condition += (i ? `~${filt.logicOp}` : '')
// if (this.allColumnsNames.includes(filt.field)) {
switch (filt.op) {
case 'is equal':
return condition + `(${filt.field},eq,${filt.value})`
case 'is not equal':
return condition + `~not(${filt.field},eq,${filt.value})`
case 'is like':
return condition + `(${filt.field},like,%${filt.value}%)`
case 'is not like':
return condition + `~not(${filt.field},like,%${filt.value}%)`
case 'is empty':
return condition + `(${filt.field},in,)`
case 'is not empty':
return condition + `~not(${filt.field},in,)`
case 'is null':
return condition + `(${filt.field},is,null)`
case 'is not null':
return condition + `~not(${filt.field},is,null)`
case '<':
return condition + `(${filt.field},lt,${filt.value})`
case '<=':
return condition + `(${filt.field},le,${filt.value})`
case '>':
return condition + `(${filt.field},gt,${filt.value})`
case '>=':
return condition + `(${filt.field},ge,${filt.value})`
}
// }
return condition
}, '')
this.xWhere = defaultQuery ? defaultQuery + (xWhere ? `~and(${xWhere})` : xWhere) : xWhere
// if (!this.progress) {
// await this.loadTableData();
// }
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
deep: true
},
sortList: {
async handler(sortList) {
// const sort = sortList.map((sort) => {
// // && this.allColumnsNames.includes(sort.field)
// return sort.field ? `${sort.order}${sort.field}` : ''
// }).filter(Boolean).join(',')
// this.sort = sort
// // if (!this.progress) {
// // await this.loadTableData();
// // }
// if (!this.loadingMeta || !this.loadingData) {
// this.syncDataDebounce(this)
// }
},
deep: true
},
columnsWidth() {
if (!this.loadingMeta || !this.loadingData) {
this.syncDataDebounce(this)
}
},
sort(n, o) {
if (o !== n) {
this.loadTableData()
}
},
concatenatedXWhere(n, o) {
if (o !== n) {
this.loadTableData()
}
}
}
}