Commit 8f53cdb0 authored by cwuyiqing's avatar cwuyiqing
Browse files

feat: support connect nest

parent b5fc32fc
Showing with 115 additions and 78 deletions
+115 -78
......@@ -5,6 +5,7 @@ import { useConcent } from 'concent'
import { getSchema } from '@/services/schema'
import { getContents, Options } from '@/services/content'
import { calculateFieldWidth } from '@/utils'
import { ContentCtx } from 'typings/store'
const { Option } = Select
const { Text, Paragraph } = Typography
......@@ -13,6 +14,7 @@ interface Doc {
_id: string
[key: string]: any
}
type IConnectSingleValue = string | Doc
type IConnectMultiValue = string[] & Doc[]
type IConnectValue = IConnectSingleValue | IConnectMultiValue
......@@ -27,15 +29,17 @@ export const IConnectRender: React.FC<{
field: SchemaField
}> = (props) => {
const { value, field } = props
const { connectField, connectMany } = field
const { connectMany } = field
const width = calculateFieldWidth(field)
const ctx = useConcent<{}, ContentCtx>('content')
const { schemas } = ctx.state
if (!value || typeof value === 'string' || typeof value?.[0] === 'string') return <span>-</span>
if (!connectMany) {
return (
<Text ellipsis style={{ width }}>
{value[connectField]}
{getConnectFieldDisplayText(value, schemas, field)}
</Text>
)
}
......@@ -45,7 +49,7 @@ export const IConnectRender: React.FC<{
{value
.filter((_: any) => _)
.map((record: any, index: number) => (
<Tag key={index}>{record?.[connectField]}</Tag>
<Tag key={index}>{getConnectFieldDisplayText(record, schemas, field)}</Tag>
))}
</Paragraph>
)
......@@ -59,10 +63,11 @@ export const IConnectEditor: React.FC<{
field: SchemaField
onChange?: (v: string | string[]) => void
}> = (props) => {
const { projectId } = useParams<any>()
const ctx = useConcent('content')
const { value = [], onChange, field } = props
const { projectId } = useParams<any>()
const ctx = useConcent<{}, ContentCtx>('content')
const { connectField, connectResource, connectMany } = field
const { schemas } = ctx.state
// 加载关联的文档列表
const [docs, setDocs] = useState<Doc[]>([])
......@@ -72,13 +77,12 @@ export const IConnectEditor: React.FC<{
useRequest(
async () => {
setLoading(true)
const { schemas } = ctx.state
let schema = schemas.find((_: Schema) => _._id === connectResource)
let connectSchema = schemas.find((_: Schema) => _._id === connectResource)
// 后台获取 Schema
if (!schema) {
if (!connectSchema) {
const { data } = await getSchema(projectId, connectResource)
schema = data
connectSchema = data
}
const fetchOptions: Options = {
......@@ -92,7 +96,7 @@ export const IConnectEditor: React.FC<{
}
}
const { data } = await getContents(projectId, schema.collectionName, fetchOptions)
const { data } = await getContents(projectId, connectSchema.collectionName, fetchOptions)
setDocs(data)
setLoading(false)
......@@ -130,11 +134,14 @@ export const IConnectEditor: React.FC<{
<Spin size="small" />
</Option>
) : docs?.length ? (
docs?.map((doc) => (
<Option value={doc._id} key={doc._id}>
{doc[connectField]}
</Option>
))
<>
<Option value=""></Option>
{docs?.map((doc) => (
<Option value={doc._id} key={doc._id}>
{getConnectFieldDisplayText(doc, schemas, field)}
</Option>
))}
</>
) : (
<Option value="" disabled>
......@@ -144,6 +151,27 @@ export const IConnectEditor: React.FC<{
)
}
/**
* 处理关联字段的展示信息,可能为多层嵌套
*/
const getConnectFieldDisplayText = (doc: any, schemas: Schema[], field: SchemaField) => {
// 当前关联字段的信息
const { connectField, connectResource } = field
// 当前关联字段 => 关联 schema 的信息
const connectedSchema = schemas.find((_: Schema) => _._id === connectResource)
// 关联字段的信息
const connectedFieldInfo = connectedSchema?.fields.find((_) => _.name === connectField)
// 关联的字段,又是一个关联类型,则展示关联字段关联的字段
if (connectedFieldInfo?.connectResource) {
return doc[connectField][connectedFieldInfo.connectField]
} else {
return doc[connectField]
}
}
// 将关联的数据转换成关联数据对应的 _id 数据
const transformConnectValues = (value: IConnectValue, connectMany: boolean): string | string[] => {
if (connectMany) {
......
import path from 'path'
import dayjs from 'dayjs'
import { nanoid } from 'nanoid'
import { IFile } from './types'
import 'dayjs/locale/zh-cn'
import { Injectable } from '@nestjs/common'
import { CloudBaseService } from '@/services'
import 'dayjs/locale/zh-cn'
import { randomId } from '@/utils'
import { IFile } from './types'
// 本地时间
dayjs.locale('zh-cn')
......@@ -31,7 +30,7 @@ export class FileService {
const day = dayjs().format('YYYY-MM-DD')
// 上传文件
const { fileID } = await this.cloudbaseService.app.uploadFile({
cloudPath: `cloudbase-cms/upload/${day}/${nanoid(16)}-${file.originalname}`,
cloudPath: `cloudbase-cms/upload/${day}/${randomId(16)}-${file.originalname}`,
fileContent: file.buffer,
})
return {
......
......@@ -2,10 +2,15 @@ import _ from 'lodash'
import R from 'ramda'
import { Injectable } from '@nestjs/common'
import { CloudBaseService } from '@/services'
import { dateToUnixTimestampInMs, formatPayloadDate } from '@/utils'
import {
dateToUnixTimestampInMs,
formatPayloadDate,
getCollectionSchema,
isNotEmpty,
} from '@/utils'
import { Collection } from '@/constants'
import { Schema, SchemaField } from '../schemas/types'
import { BadRequestException, RecordNotExistException } from '@/common'
import { Schema, SchemaField } from '../schemas/types'
@Injectable()
export class ContentsService {
......@@ -40,13 +45,7 @@ export class ContentsService {
where._id = db.command.in(filter.ids)
}
const {
data: [schema],
}: { data: Schema[] } = await this.collection(Collection.Schemas)
.where({
collectionName: resource,
})
.get()
const schema = await getCollectionSchema(resource)
// 模糊搜索
if (fuzzyFilter && schema) {
......@@ -108,8 +107,7 @@ export class ContentsService {
if (schema) {
// 存在关联类型字段
const connectFields = schema.fields.filter((field) => field.type === 'Connect')
if (connectFields?.length) {
if (!R.isEmpty(connectFields)) {
res.data = await this.transformConnectField(res.data, connectFields)
}
}
......@@ -358,10 +356,10 @@ export class ContentsService {
/**
* 处理数据返回结果
* 将数据中的关联字段解析后返回
* 将数据中的关联字段转换成原始 Doc 后返回
*/
private async transformConnectField(rawData: any[], connectFields: SchemaField[]) {
let resData = rawData
private async transformConnectField(docs: any[], connectFields: SchemaField[]) {
let resData: any[] = docs
const $ = this.cloudbaseService.db.command
// 获取所有 Schema 数据
......@@ -375,59 +373,63 @@ export class ContentsService {
// 获取数据中所有的关联资源 Id
let ids = []
// 关联多个对象,将 Doc 数组中的 id 数组合并、去重
if (connectMany) {
// 合并数组
ids = resData
.filter((record) => record[fieldName]?.length)
.map((record) => record[fieldName])
.reduce((ret, current) => [...ret, ...current], [])
ids = R.pipe(
R.reject<any>(R.where({ [fieldName]: R.isEmpty })),
R.map(R.prop(fieldName)),
R.reduce(R.union, []),
R.filter(isNotEmpty)
)(resData)
} else {
ids = resData.map((record) => record[fieldName]).filter((_) => _)
// 关联单个对象,取 Doc 数组中的 id,过滤
ids = R.pipe(R.map(R.prop(fieldName)), R.filter(isNotEmpty))(resData)
}
// 集合名
const collectionName = schemas.find((schema) => schema._id === field.connectResource)
.collectionName
// 获取关联的数据,分页最大条数 50
const { data: connectData } = await this.collection(collectionName)
.where({ _id: $.in(ids) })
.limit(1000)
.get()
// 关联的 Schema
const connectSchema = schemas.find((schema) => schema._id === field.connectResource)
// 获取关联 id 对应的 Doc
// 使用 getMany 获取数据,自动转换 Connect 字段
const { data: connectData } = await this.getMany(connectSchema.collectionName, {
page: 1,
pageSize: 1000,
filter: {
ids,
},
})
// 修改 resData 中的关联字段
resData = resData.map((record) => {
if (!record[fieldName]) return record
let connectRecord
// 关联字段的值:id 或 [id]
const connectValue = record[fieldName]
if (!connectValue) return record
// 关联的数据被删除
if (!connectData) {
return {
...record,
[fieldName]: null,
}
record[fieldName] = null
return record
}
let connectRecord
// id 数组
if (connectMany) {
// id 数组
connectRecord = record[fieldName]?.length
? record[fieldName]?.map((id) => connectData.find((_) => _._id === id))
connectRecord = connectValue?.length
? connectValue?.map((id) => connectData.find((_) => _._id === id))
: []
} else {
connectRecord = connectData.find((_) => _._id === record[fieldName])
connectRecord = connectData.find((_) => _._id === connectValue)
}
return {
...record,
[fieldName]: connectRecord,
}
record[fieldName] = connectRecord
return record
})
}
// 转换 connectField
const promises = connectFields.map(transformDataByField)
await Promise.all(promises)
const tasks = connectFields.map(transformDataByField)
await Promise.all(tasks)
return resData
}
......
import { dateToUnixTimestampInMs, nanoid } from '@/utils'
import { dateToUnixTimestampInMs, randomId } from '@/utils'
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'
@Injectable()
......@@ -13,7 +13,7 @@ export class SchemaTransfromPipe implements PipeTransform {
if (this.action === 'create') {
value.fields =
value?.fields?.map((v) => {
const id = v.id || nanoid()
const id = v.id || randomId()
return {
...v,
id,
......@@ -31,7 +31,7 @@ export class SchemaTransfromPipe implements PipeTransform {
if (value.fields?.length) {
// 为 field 添加 id
value.fields = value?.fields?.map((v) => {
const id = v.id || nanoid()
const id = v.id || randomId()
return {
...v,
id,
......
......@@ -84,7 +84,4 @@ export class Schema {
_creatTime: number
_updateTime: number
// Schema 协议版本 v2
_version: '2.0'
}
......@@ -114,7 +114,7 @@ export const getUserFromCredential = async (credential: string, origin: string)
/**
* 获取集合的 Schema
*/
export const getCollectionSchema = async (collection: string) => {
export const getCollectionSchema = async (collection: string): Promise<Schema> => {
const app = getCloudBaseApp()
const {
data: [schema],
......@@ -125,6 +125,7 @@ export const getCollectionSchema = async (collection: string) => {
collectionName: collection,
})
.get()
return schema
}
......
import _ from 'lodash'
import { customAlphabet } from 'nanoid'
export const isDevEnv = () =>
process.env.NODE_ENV === 'development' && !process.env.TENCENTCLOUD_RUNENV
export const nanoid = customAlphabet(
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-',
32
)
/**
* 生成随机 id
*/
export const randomId = (len = 32) =>
customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-', len)()
/**
* 检查 ele 不为 null,undefined,空数组,空对象,仅包含 null、undefined 的数组
*/
export const isNotEmpty = (ele: any | any[]) => {
if (Array.isArray(ele)) {
return !_.isEmpty(ele) && !_.isEmpty(ele.filter((_) => _))
}
return !_.isEmpty(ele)
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment