feat: kanban view
This commit is contained in:
parent
eba121b998
commit
330d08ab35
@ -23,6 +23,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/pro-layout": "^5.0.12",
|
"@ant-design/pro-layout": "^5.0.12",
|
||||||
"@formily/antd-components": "^1.3.6",
|
"@formily/antd-components": "^1.3.6",
|
||||||
|
"@lourenci/react-kanban": "^2.1.0",
|
||||||
"@types/react-big-calendar": "^0.24.8",
|
"@types/react-big-calendar": "^0.24.8",
|
||||||
"@umijs/preset-react": "1.x",
|
"@umijs/preset-react": "1.x",
|
||||||
"@umijs/test": "^3.2.23",
|
"@umijs/test": "^3.2.23",
|
||||||
|
@ -8,14 +8,14 @@ import set from 'lodash/set';
|
|||||||
|
|
||||||
export function Create(props) {
|
export function Create(props) {
|
||||||
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
|
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
|
||||||
const { title, viewName, transform } = schema;
|
const { title, pageTitle, viewName, transform, componentProps = {} } = schema;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
size={size}
|
size={size}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Drawer.open({
|
Drawer.open({
|
||||||
title: title,
|
title: pageTitle || title,
|
||||||
content: ({resolve, closeWithConfirm}) => (
|
content: ({resolve, closeWithConfirm}) => (
|
||||||
<div>
|
<div>
|
||||||
<View
|
<View
|
||||||
@ -53,6 +53,7 @@ export function Create(props) {
|
|||||||
}}
|
}}
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
type={'primary'}
|
type={'primary'}
|
||||||
|
{...componentProps}
|
||||||
>{ title }</Button>
|
>{ title }</Button>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -157,9 +158,9 @@ export function Add(props) {
|
|||||||
|
|
||||||
export function Destroy(props) {
|
export function Destroy(props) {
|
||||||
const { size, schema = {}, onFinish } = props;
|
const { size, schema = {}, onFinish } = props;
|
||||||
const { title } = schema;
|
const { title, componentProps = {} } = schema;
|
||||||
return (
|
return (
|
||||||
<Popconfirm title="确认删除吗?" onConfirm={async () => {
|
<Popconfirm title="确认删除吗?" onConfirm={async (e) => {
|
||||||
onFinish && await onFinish();
|
onFinish && await onFinish();
|
||||||
}}>
|
}}>
|
||||||
<Button
|
<Button
|
||||||
@ -167,6 +168,7 @@ export function Destroy(props) {
|
|||||||
danger
|
danger
|
||||||
type={'ghost'}
|
type={'ghost'}
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
|
{...componentProps}
|
||||||
>{ title }</Button>
|
>{ title }</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
)
|
)
|
||||||
|
@ -90,7 +90,7 @@ export function fields2properties(fields = [], options: any = {}) {
|
|||||||
const actions = createFormActions();
|
const actions = createFormActions();
|
||||||
|
|
||||||
export function Form(props: any) {
|
export function Form(props: any) {
|
||||||
const { onValueChange, onReset, __parent, noRequest = false, onFinish, onDraft, resolve, data: record = {}, associatedKey, schema = {} } = props;
|
const { initialValues = {}, onValueChange, onReset, __parent, noRequest = false, onFinish, onDraft, resolve, data: record = {}, associatedKey, schema = {} } = props;
|
||||||
console.log({ noRequest, record, associatedKey, __parent });
|
console.log({ noRequest, record, associatedKey, __parent });
|
||||||
const { statusable, resourceName, rowKey = 'id', fields = [], appends = [], associationField = {} } = schema;
|
const { statusable, resourceName, rowKey = 'id', fields = [], appends = [], associationField = {} } = schema;
|
||||||
|
|
||||||
@ -116,6 +116,7 @@ export function Form(props: any) {
|
|||||||
layout={'vertical'}
|
layout={'vertical'}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
...data,
|
...data,
|
||||||
|
...initialValues,
|
||||||
associatedKey,
|
associatedKey,
|
||||||
resourceKey,
|
resourceKey,
|
||||||
}}
|
}}
|
||||||
|
@ -0,0 +1,301 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useRequest, useHistory } from 'umi';
|
||||||
|
import api from '@/api-client';
|
||||||
|
import { Descriptions, Popconfirm, Card, Button } from 'antd';
|
||||||
|
import Board, { moveCard } from "@lourenci/react-kanban";
|
||||||
|
import "@lourenci/react-kanban/dist/styles.css";
|
||||||
|
import { PlusOutlined, CloseOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
|
import Drawer from '@/components/pages/AdminLoader/Drawer';
|
||||||
|
import { Actions, Create, Destroy } from '../../Actions';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
import Field from '@/components/views/Field';
|
||||||
|
import { Details, DetailsPage } from '../Table';
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import { Spin } from '@nocobase/client';
|
||||||
|
import './style.less';
|
||||||
|
|
||||||
|
export function Kanban(props: any) {
|
||||||
|
const { defaultFilter, associatedKey, schema = {} } = props;
|
||||||
|
const {
|
||||||
|
filter: schemaFilter,
|
||||||
|
sort,
|
||||||
|
appends,
|
||||||
|
groupField = {},
|
||||||
|
resourceName,
|
||||||
|
createViewName = 'form',
|
||||||
|
disableCardDrag = false,
|
||||||
|
// allowAddCard = false,
|
||||||
|
// allowRemoveCard = false,
|
||||||
|
labelField,
|
||||||
|
actions = [],
|
||||||
|
fields = [],
|
||||||
|
detailsOpenMode,
|
||||||
|
details = [],
|
||||||
|
rowKey = 'id',
|
||||||
|
} = schema;
|
||||||
|
const { dataSource = [] } = groupField;
|
||||||
|
console.log({groupField, dataSource})
|
||||||
|
|
||||||
|
const paginated = false;
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const { data, loading, mutate, refresh, run, params } = useRequest((params = {}, ...args) => {
|
||||||
|
const { current, pageSize, sorter, filter, ...restParams } = params;
|
||||||
|
console.log('paramsparamsparamsparamsparams', params, args);
|
||||||
|
return api.resource(resourceName).list({
|
||||||
|
associatedKey,
|
||||||
|
page: paginated ? current : 1,
|
||||||
|
perPage: paginated ? pageSize : -1,
|
||||||
|
sorter,
|
||||||
|
sort: [`${groupField.name}_sort`],
|
||||||
|
'fields[appends]': appends,
|
||||||
|
// filter,
|
||||||
|
// ...actionDefaultParams,
|
||||||
|
filter: {
|
||||||
|
and: [
|
||||||
|
defaultFilter,
|
||||||
|
schemaFilter,
|
||||||
|
filter,
|
||||||
|
// __parent ? {
|
||||||
|
// collection_name: __parent,
|
||||||
|
// } : null,
|
||||||
|
].filter(obj => obj && Object.keys(obj).length)
|
||||||
|
}
|
||||||
|
// ...args2,
|
||||||
|
})
|
||||||
|
.then(({data = [], meta = {}}) => {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
list: data,
|
||||||
|
total: meta.count||data.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
paginated,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spin/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = dataSource.map(group => {
|
||||||
|
return {
|
||||||
|
id: group.value,
|
||||||
|
title: group.label,
|
||||||
|
cards: filter(data?.list, (item) => {
|
||||||
|
return get(item, groupField.name) === group.value;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log({columns});
|
||||||
|
|
||||||
|
const bodyStyle: any = {};
|
||||||
|
|
||||||
|
if (fields.length === 0) {
|
||||||
|
bodyStyle.padding = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAction = actions.find(action => {
|
||||||
|
return action.type === 'create';
|
||||||
|
});
|
||||||
|
|
||||||
|
let allowAddCard = !!props.allowAddCard;
|
||||||
|
|
||||||
|
if (createAction) {
|
||||||
|
allowAddCard = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let allowRemoveCard = !!props.allowRemoveCard;
|
||||||
|
const destroyAction = actions.find(action => {
|
||||||
|
return action.type === 'destroy';
|
||||||
|
});
|
||||||
|
if (destroyAction) {
|
||||||
|
allowRemoveCard = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'noco-kanban-view'}>
|
||||||
|
<Actions
|
||||||
|
associatedKey={associatedKey}
|
||||||
|
style={{ marginBottom: 14 }}
|
||||||
|
actions={actions.filter(action => !['create', 'destroy'].includes(action.type))}
|
||||||
|
onTrigger={{
|
||||||
|
async filter(values) {
|
||||||
|
const items = values.filter.and || values.filter.or;
|
||||||
|
// @ts-ignore
|
||||||
|
run({...params[0], filter: values.filter});
|
||||||
|
// refresh();
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Board
|
||||||
|
allowRemoveLane
|
||||||
|
allowRenameColumn
|
||||||
|
allowRemoveCard={allowRemoveCard}
|
||||||
|
disableColumnDrag
|
||||||
|
disableCardDrag={disableCardDrag}
|
||||||
|
onLaneRemove={console.log}
|
||||||
|
renderCard={(data, {dragging, removeCard}) => {
|
||||||
|
const openDetails = (e) => {
|
||||||
|
if (!detailsOpenMode || !details.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (detailsOpenMode === 'window') {
|
||||||
|
const paths = history.location.pathname.split('/');
|
||||||
|
history.push(`/admin/${paths[2]}/${data[rowKey]}/0`);
|
||||||
|
} else {
|
||||||
|
Drawer.open({
|
||||||
|
headerStyle: details.length > 1 ? {
|
||||||
|
paddingBottom: 0,
|
||||||
|
borderBottom: 0,
|
||||||
|
// paddingTop: 16,
|
||||||
|
// marginBottom: -4,
|
||||||
|
} : {},
|
||||||
|
title: details.length > 1 ? undefined : data[labelField],
|
||||||
|
content: ({resolve, closeWithConfirm}) => (
|
||||||
|
<div>
|
||||||
|
<Details
|
||||||
|
associatedKey={associatedKey}
|
||||||
|
resourceName={resourceName}
|
||||||
|
onFinish={async () => {
|
||||||
|
await refresh();
|
||||||
|
resolve();
|
||||||
|
}}
|
||||||
|
onValueChange={() => {
|
||||||
|
closeWithConfirm && closeWithConfirm(true);
|
||||||
|
}}
|
||||||
|
onDraft={async () => {
|
||||||
|
await refresh();
|
||||||
|
resolve();
|
||||||
|
}}
|
||||||
|
onReset={resolve}
|
||||||
|
onDataChange={async () => {
|
||||||
|
await refresh();
|
||||||
|
}}
|
||||||
|
data={data}
|
||||||
|
resolve={resolve}
|
||||||
|
items={details}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
hoverable
|
||||||
|
size={'small'}
|
||||||
|
style={{background: '#fff', minWidth: 250, maxWidth: 250, marginBottom: 12}}
|
||||||
|
bordered={false}
|
||||||
|
title={(
|
||||||
|
<div onClick={openDetails}>
|
||||||
|
{data[labelField]}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
bodyStyle={bodyStyle}
|
||||||
|
extra={
|
||||||
|
allowRemoveCard && (
|
||||||
|
<Destroy
|
||||||
|
schema={merge(destroyAction, {
|
||||||
|
title: null,
|
||||||
|
componentProps: {
|
||||||
|
danger: false,
|
||||||
|
type: 'text',
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
onFinish={async () => {
|
||||||
|
await api.resource(resourceName).destroy({
|
||||||
|
associatedKey,
|
||||||
|
filter: {
|
||||||
|
[`${rowKey}.in`]: [data[rowKey]],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div onClick={openDetails}>
|
||||||
|
{fields.length > 0 && (
|
||||||
|
<Descriptions column={1} size={'small'} layout={'vertical'}>
|
||||||
|
{fields.map((field: any) => {
|
||||||
|
return (
|
||||||
|
<Descriptions.Item label={field.title||field.name}>
|
||||||
|
<Field data={data} viewType={'descriptions'} schema={field} value={get(data, field.name)}/>
|
||||||
|
</Descriptions.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Descriptions>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onCardDragEnd={async ({ columns }, card, source, destination) => {
|
||||||
|
await api.resource(resourceName).update({
|
||||||
|
associatedKey,
|
||||||
|
resourceKey: card[rowKey],
|
||||||
|
values: {
|
||||||
|
[groupField.name]: destination.toColumnId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const destColumn = columns.find( column => column.id === destination.toColumnId);
|
||||||
|
const targetIndex = get(destColumn, ['cards', destination.toPosition+1, rowKey]);
|
||||||
|
console.log({targetIndex, card, destination})
|
||||||
|
await api.resource(resourceName).sort({
|
||||||
|
associatedKey,
|
||||||
|
resourceKey: card[rowKey],
|
||||||
|
values: {
|
||||||
|
field: `${groupField.name}_sort`,
|
||||||
|
sticky: destination.toPosition === 0,
|
||||||
|
target: {
|
||||||
|
[rowKey]: targetIndex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
initialBoard={{columns}}
|
||||||
|
// allowAddCard={{ on: "bottom" }}
|
||||||
|
allowAddCard={false}
|
||||||
|
// onNewCardConfirm={draftCard => ({
|
||||||
|
// id: new Date().getTime(),
|
||||||
|
// ...draftCard
|
||||||
|
// })}
|
||||||
|
renderColumnHeader={({ title, id }, { addCard }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="react-kanban-column-header"><span>{ title }</span></div>
|
||||||
|
{allowAddCard && <Create
|
||||||
|
onFinish={(values: any) => {
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
initialValues={{
|
||||||
|
[groupField.name]: id,
|
||||||
|
}}
|
||||||
|
schema={merge(createAction, {
|
||||||
|
title: null,
|
||||||
|
pageTitle: '新增',
|
||||||
|
componentProps: {
|
||||||
|
type: 'text',
|
||||||
|
block: true,
|
||||||
|
className: 'noco-card-adder-button',
|
||||||
|
style: {
|
||||||
|
marginBottom: 10,
|
||||||
|
border: 0,
|
||||||
|
background: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})}/>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onCardNew={console.log}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
.react-kanban-board {
|
||||||
|
padding: 0;
|
||||||
|
margin: -24px -36px;
|
||||||
|
background-color: #eff2f5;
|
||||||
|
.react-kanban-column {
|
||||||
|
background-color: #eff2f5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-kanban-column-header {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noco-card-adder-button:hover {
|
||||||
|
background: rgba(255, 255, 255, .4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noco-kanban-view {
|
||||||
|
.action-buttons {
|
||||||
|
margin: -24px -24px 0 !important;
|
||||||
|
background-color: #eff2f5;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
.ant-btn {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.filter-action-button {
|
||||||
|
margin-left: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.action-buttons + .react-kanban-board {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.ant-descriptions-item-label {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.ant-descriptions-item {
|
||||||
|
// padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ import pathToRegexp from 'path-to-regexp'
|
|||||||
export const icon = <LoadingOutlined style={{ fontSize: 36 }} spin />;
|
export const icon = <LoadingOutlined style={{ fontSize: 36 }} spin />;
|
||||||
|
|
||||||
export function Details(props) {
|
export function Details(props) {
|
||||||
const { __parent, associatedKey, resourceName, onFinish, onReset, onDataChange, data, items = [], resolve, onValueChange } = props;
|
const { __parent, associatedKey, resourceName, onFinish, onDraft, onReset, onDataChange, data, items = [], resolve, onValueChange } = props;
|
||||||
if (!items || items.length === 0) {
|
if (!items || items.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -48,6 +48,7 @@ export function Details(props) {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
onDraft={onDraft}
|
||||||
onValueChange={onValueChange}
|
onValueChange={onValueChange}
|
||||||
__parent={__parent}
|
__parent={__parent}
|
||||||
associatedKey={associatedKey} onReset={onReset} onFinish={onFinish} onDataChange={onDataChange} data={data} viewName={viewName}/>
|
associatedKey={associatedKey} onReset={onReset} onFinish={onFinish} onDataChange={onDataChange} data={data} viewName={viewName}/>
|
||||||
|
@ -21,6 +21,7 @@ import { FilterForm } from './FilterForm';
|
|||||||
import { SubTable } from './SubTable';
|
import { SubTable } from './SubTable';
|
||||||
import { Wysiwyg } from './Wysiwyg';
|
import { Wysiwyg } from './Wysiwyg';
|
||||||
import { Calendar } from './Calendar';
|
import { Calendar } from './Calendar';
|
||||||
|
import { Kanban } from './Kanban';
|
||||||
|
|
||||||
const VIEWS = new Map();
|
const VIEWS = new Map();
|
||||||
|
|
||||||
@ -66,5 +67,6 @@ registerView('descriptions', Descriptions);
|
|||||||
registerView('association', Association);
|
registerView('association', Association);
|
||||||
registerView('wysiwyg', Wysiwyg);
|
registerView('wysiwyg', Wysiwyg);
|
||||||
registerView('calendar', Calendar);
|
registerView('calendar', Calendar);
|
||||||
|
registerView('kanban', Kanban);
|
||||||
|
|
||||||
export default View;
|
export default View;
|
||||||
|
@ -374,6 +374,16 @@ export const getInfo = async (ctx: actions.Context, next) => {
|
|||||||
data.sort = [];
|
data.sort = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.type === 'kanban' && data.groupField) {
|
||||||
|
const groupField = await Field.findOne({
|
||||||
|
where: {
|
||||||
|
name: data.groupField,
|
||||||
|
collection_name: data.collection_name,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
data.groupField = groupField;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.body = data;
|
ctx.body = data;
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,7 @@ export default {
|
|||||||
name: 'type',
|
name: 'type',
|
||||||
title: '操作类型',
|
title: '操作类型',
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{ label: '过滤', value: 'filter' },
|
{ label: '筛选', value: 'filter' },
|
||||||
{ label: '新增', value: 'create' },
|
{ label: '新增', value: 'create' },
|
||||||
{ label: '编辑', value: 'update' },
|
{ label: '编辑', value: 'update' },
|
||||||
{ label: '删除', value: 'destroy' },
|
{ label: '删除', value: 'destroy' },
|
||||||
|
@ -86,10 +86,12 @@ export default async function (options = {}) {
|
|||||||
const createDetailsViews = async (model, options) => {
|
const createDetailsViews = async (model, options) => {
|
||||||
const data = model.get();
|
const data = model.get();
|
||||||
const View = database.getModel('views_v2');
|
const View = database.getModel('views_v2');
|
||||||
const tableDetials = _.get(data, 'x-table-props.details') || [];
|
const types = ['table', 'calendar', 'kanban'];
|
||||||
if (tableDetials.length) {
|
for (const type of types) {
|
||||||
|
const items = _.get(data, `x-${type}-props.details`) || [];
|
||||||
|
if (items.length) {
|
||||||
const details = [];
|
const details = [];
|
||||||
for (const item of tableDetials) {
|
for (const item of items) {
|
||||||
if (item.view) {
|
if (item.view) {
|
||||||
if (!item.view.id) {
|
if (!item.view.id) {
|
||||||
const view = await View.create(item.view);
|
const view = await View.create(item.view);
|
||||||
@ -97,9 +99,11 @@ export default async function (options = {}) {
|
|||||||
item.view.id = view.id;
|
item.view.id = view.id;
|
||||||
} else {
|
} else {
|
||||||
const view = await View.findByPk(item.view.id);
|
const view = await View.findByPk(item.view.id);
|
||||||
|
if (view) {
|
||||||
await view.update(item.view);
|
await view.update(item.view);
|
||||||
await view.updateAssociations(item.view);
|
await view.updateAssociations(item.view);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const view = await View.findOne(View.parseApiJson({
|
const view = await View.findOne(View.parseApiJson({
|
||||||
filter: {
|
filter: {
|
||||||
id: item.view.id,
|
id: item.view.id,
|
||||||
@ -115,44 +119,51 @@ export default async function (options = {}) {
|
|||||||
}
|
}
|
||||||
details.push(item);
|
details.push(item);
|
||||||
}
|
}
|
||||||
model.set('options.x-table-props.details', details);
|
model.set(`options.x-${type}-props.details`, details);
|
||||||
}
|
}
|
||||||
const calendarDetials = _.get(data, 'x-calendar-props.details') || [];
|
|
||||||
if (calendarDetials.length) {
|
|
||||||
const details = [];
|
|
||||||
for (const item of calendarDetials) {
|
|
||||||
if (item.view) {
|
|
||||||
if (!item.view.id) {
|
|
||||||
const view = await View.create(item.view);
|
|
||||||
await view.updateAssociations(item.view);
|
|
||||||
item.view.id = view.id;
|
|
||||||
} else {
|
|
||||||
const view = await View.findByPk(item.view.id);
|
|
||||||
await view.update(item.view);
|
|
||||||
await view.updateAssociations(item.view);
|
|
||||||
}
|
|
||||||
const view = await View.findOne(View.parseApiJson({
|
|
||||||
filter: {
|
|
||||||
id: item.view.id,
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
appends: ['collection', 'targetField', 'targetView'],
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
if (view) {
|
|
||||||
console.log({view});
|
|
||||||
item.view = view.toJSON();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
details.push(item);
|
|
||||||
}
|
|
||||||
model.set('options.x-calendar-props.details', details);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
database.getModel('views_v2').addHook('beforeCreate', createDetailsViews);
|
database.getModel('views_v2').addHook('beforeCreate', createDetailsViews);
|
||||||
database.getModel('views_v2').addHook('beforeUpdate', createDetailsViews);
|
database.getModel('views_v2').addHook('beforeUpdate', createDetailsViews);
|
||||||
|
|
||||||
|
database.getModel('views_v2').addHook('beforeSave', async (model, options) => {
|
||||||
|
const data = model.get();
|
||||||
|
if (data.type !== 'kanban') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let groupField = _.get(data, `x-kanban-props.groupField`);
|
||||||
|
if (!groupField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof groupField === 'object' && groupField.name) {
|
||||||
|
groupField = groupField.name;
|
||||||
|
}
|
||||||
|
const Field = database.getModel('fields');
|
||||||
|
let field = await Field.findOne({
|
||||||
|
where: {
|
||||||
|
name: `${groupField}_sort`,
|
||||||
|
collection_name: data.collection_name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (field) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Field.create({
|
||||||
|
interface: 'sort',
|
||||||
|
type: 'sort',
|
||||||
|
name: `${groupField}_sort`,
|
||||||
|
// TODO: 不支持相关数据
|
||||||
|
collection_name: data.collection_name,
|
||||||
|
scope: [groupField],
|
||||||
|
title: '看板分组排序',
|
||||||
|
developerMode: true,
|
||||||
|
component: {
|
||||||
|
type: 'sort',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
database.getModel('menus').addHook('beforeSave', async (model, options) => {
|
database.getModel('menus').addHook('beforeSave', async (model, options) => {
|
||||||
const { transaction } = options;
|
const { transaction } = options;
|
||||||
// console.log('beforeSave', model.get('views'));
|
// console.log('beforeSave', model.get('views'));
|
||||||
|
@ -304,6 +304,15 @@ export const calendar = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
info3: {
|
info3: {
|
||||||
|
interface: 'description',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '操作按钮配置',
|
||||||
|
component: {
|
||||||
|
type: 'description',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions,
|
||||||
|
info4: {
|
||||||
interface: 'description',
|
interface: 'description',
|
||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
title: '单条数据页面配置',
|
title: '单条数据页面配置',
|
||||||
@ -319,6 +328,95 @@ export const calendar = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const kanban = {
|
||||||
|
title: '看板',
|
||||||
|
options: {
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
info1: {
|
||||||
|
interface: 'description',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '看板配置',
|
||||||
|
component: {
|
||||||
|
type: 'description',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
interface: 'select',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '作为单条数据标题的字段',
|
||||||
|
name: 'labelField',
|
||||||
|
required: true,
|
||||||
|
component: {
|
||||||
|
type: 'remoteSelect',
|
||||||
|
resourceName: 'collections.fields',
|
||||||
|
labelField: 'title',
|
||||||
|
valueField: 'name',
|
||||||
|
filter: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupField: {
|
||||||
|
interface: 'select',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '看板分组字段',
|
||||||
|
name: 'groupField',
|
||||||
|
required: true,
|
||||||
|
component: {
|
||||||
|
type: 'remoteSelect',
|
||||||
|
resourceName: 'collections.fields',
|
||||||
|
labelField: 'title',
|
||||||
|
valueField: 'name',
|
||||||
|
filter: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
...fields,
|
||||||
|
title: '显示在看板里的字段'
|
||||||
|
},
|
||||||
|
info2: {
|
||||||
|
interface: 'description',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '数据配置',
|
||||||
|
component: {
|
||||||
|
type: 'description',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
interface: 'json',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '只显示符合以下条件的数据',
|
||||||
|
mode: 'replace',
|
||||||
|
defaultValue: {},
|
||||||
|
component: {
|
||||||
|
type: 'filter',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
info3: {
|
||||||
|
interface: 'description',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '操作按钮配置',
|
||||||
|
component: {
|
||||||
|
type: 'description',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions,
|
||||||
|
info4: {
|
||||||
|
interface: 'description',
|
||||||
|
type: 'virtual',
|
||||||
|
title: '单条数据页面配置',
|
||||||
|
component: {
|
||||||
|
type: 'description',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
detailsOpenMode,
|
||||||
|
details,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const wysiwyg = {
|
export const wysiwyg = {
|
||||||
title: '富文本',
|
title: '富文本',
|
||||||
options: {
|
options: {
|
||||||
|
Loading…
Reference in New Issue
Block a user