This commit is contained in:
parent
ec51cb6a35
commit
72e7d51398
|
|
@ -21,7 +21,10 @@ const i18ns: I18n[] = [
|
||||||
module: "new-demo",
|
module: "new-demo",
|
||||||
position: "src/pages/console/comment/list",
|
position: "src/pages/console/comment/list",
|
||||||
data: {
|
data: {
|
||||||
"pageTitle": "评论管理"
|
"pageTitle": "评论管理",
|
||||||
|
"content": "评论内容",
|
||||||
|
"creator": "评论人",
|
||||||
|
"essay": "文章"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -658,7 +661,9 @@ const i18ns: I18n[] = [
|
||||||
"publish": "发布",
|
"publish": "发布",
|
||||||
"withdraw": "撤回",
|
"withdraw": "撤回",
|
||||||
"setTop": "置顶",
|
"setTop": "置顶",
|
||||||
"cancelTop": "取消置顶"
|
"cancelTop": "取消置顶",
|
||||||
|
"like": "点赞",
|
||||||
|
"unlike": "取消点赞"
|
||||||
},
|
},
|
||||||
"v": {
|
"v": {
|
||||||
"iState": {
|
"iState": {
|
||||||
|
|
@ -783,6 +788,21 @@ const i18ns: I18n[] = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "b9691c35f8511fb42aa59cbcc8645610",
|
||||||
|
namespace: "like",
|
||||||
|
language: "zh-CN",
|
||||||
|
module: "",
|
||||||
|
position: "oak-app-domain/Like",
|
||||||
|
data: {
|
||||||
|
"name": "点赞",
|
||||||
|
"attr": {
|
||||||
|
"creator": "创建者",
|
||||||
|
"essay": "文章",
|
||||||
|
"meta": "元数据"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "f3c966c0e1deb4ca4c10e10117143849",
|
id: "f3c966c0e1deb4ca4c10e10117143849",
|
||||||
namespace: "livestream",
|
namespace: "livestream",
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@ export const IActionDef: ActionDef<IAction, IState> = {
|
||||||
// 用户行为操作
|
// 用户行为操作
|
||||||
export type CommonAction = 'setTop' | 'cancelTop';
|
export type CommonAction = 'setTop' | 'cancelTop';
|
||||||
|
|
||||||
type Action = IAction | CommonAction;
|
export type LikeAction = 'like' | 'unlike';
|
||||||
|
|
||||||
|
type Action = IAction | CommonAction | LikeAction;
|
||||||
|
|
||||||
export const entityDesc: EntityDesc<
|
export const entityDesc: EntityDesc<
|
||||||
Schema,
|
Schema,
|
||||||
|
|
@ -65,6 +67,8 @@ export const entityDesc: EntityDesc<
|
||||||
withdraw: '撤回',
|
withdraw: '撤回',
|
||||||
setTop: '置顶',
|
setTop: '置顶',
|
||||||
cancelTop: '取消置顶',
|
cancelTop: '取消置顶',
|
||||||
|
like: '点赞',
|
||||||
|
unlike: '取消点赞',
|
||||||
},
|
},
|
||||||
v: {
|
v: {
|
||||||
iState: {
|
iState: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { EntityDesc, EntityShape } from "oak-domain/lib/types";
|
||||||
|
import { Schema as User } from 'oak-general-business/lib/entities/User';
|
||||||
|
import { Schema as Essay } from './Essay';
|
||||||
|
|
||||||
|
// Like.ts
|
||||||
|
// 关联文章和用户
|
||||||
|
export interface Schema extends EntityShape {
|
||||||
|
creator: User;
|
||||||
|
essay: Essay;
|
||||||
|
meta: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const entityDesc: EntityDesc<Schema> = {
|
||||||
|
locales: {
|
||||||
|
zh_CN: {
|
||||||
|
name: '点赞',
|
||||||
|
attr: {
|
||||||
|
creator: '创建者',
|
||||||
|
essay: '文章',
|
||||||
|
meta: '元数据',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { FastBackwardFilled } from '@ant-design/icons';
|
||||||
import { essayProjection } from '@project/utils/projection';
|
import { essayProjection } from '@project/utils/projection';
|
||||||
|
|
||||||
export default OakComponent({
|
export default OakComponent({
|
||||||
|
|
@ -8,6 +9,15 @@ export default OakComponent({
|
||||||
const fileIndex = data?.extraFile$entity?.findIndex(
|
const fileIndex = data?.extraFile$entity?.findIndex(
|
||||||
(item) => item.tag1 === 'cover'
|
(item) => item.tag1 === 'cover'
|
||||||
);
|
);
|
||||||
|
let isSelfLiked = false;
|
||||||
|
if (data?.like$essay) {
|
||||||
|
for (let i = 0; i < data?.like$essay?.length; i++) {
|
||||||
|
if (data?.like$essay?.[i].creator.id === this.features.token.getUserId()) {
|
||||||
|
isSelfLiked = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (fileIndex !== undefined && fileIndex > -1) {
|
if (fileIndex !== undefined && fileIndex > -1) {
|
||||||
const url = this.features.extraFile.getUrl(
|
const url = this.features.extraFile.getUrl(
|
||||||
data!.extraFile$entity![fileIndex]
|
data!.extraFile$entity![fileIndex]
|
||||||
|
|
@ -16,11 +26,15 @@ export default OakComponent({
|
||||||
item: data,
|
item: data,
|
||||||
// 获取封面的url地址
|
// 获取封面的url地址
|
||||||
cover: url,
|
cover: url,
|
||||||
|
likeCount: data?.like$essay?.length,
|
||||||
|
isSelfLiked
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
item: data,
|
item: data,
|
||||||
cover: '',
|
cover: '',
|
||||||
|
isSelfLiked,
|
||||||
|
likeCount: data?.like$essay?.length,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,15 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.like {
|
||||||
|
// 和返回按钮对齐
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment {
|
.comment {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import FrontendFooter from '@project/components/frontend/home/FrontendFooter';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { VerticalAlignTopOutlined } from '@ant-design/icons';
|
import { VerticalAlignTopOutlined } from '@ant-design/icons';
|
||||||
import List from '@project/components/frontend/home/comment/list';
|
import List from '@project/components/frontend/home/comment/list';
|
||||||
|
import useFeatures from '@project/hooks/useFeatures';
|
||||||
|
import OakIcon from 'oak-frontend-base/es/components/icon';
|
||||||
|
|
||||||
const EssayDetails = (
|
const EssayDetails = (
|
||||||
props: WebComponentProps<
|
props: WebComponentProps<
|
||||||
|
|
@ -16,12 +18,15 @@ const EssayDetails = (
|
||||||
{
|
{
|
||||||
item: RowWithActions<EntityDict, 'essay'>;
|
item: RowWithActions<EntityDict, 'essay'>;
|
||||||
cover: string;
|
cover: string;
|
||||||
|
likeCount: number;
|
||||||
|
isSelfLiked: boolean;
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
const { oakFullpath } = props.data;
|
const { oakFullpath } = props.data;
|
||||||
|
const { update, execute } =props.methods;
|
||||||
|
|
||||||
const { item, cover } = props.data;
|
const { item, cover, isSelfLiked } = props.data;
|
||||||
const [showScrollTop, setShowScrollTop] = useState(false);
|
const [showScrollTop, setShowScrollTop] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -44,6 +49,16 @@ const EssayDetails = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const HandleLike = () => {
|
||||||
|
if (isSelfLiked) {
|
||||||
|
update({}, "unlike")
|
||||||
|
execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
update({}, "like")
|
||||||
|
execute();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Styles.essay}>
|
<div className={Styles.essay}>
|
||||||
{item && (
|
{item && (
|
||||||
|
|
@ -69,7 +84,6 @@ const EssayDetails = (
|
||||||
</div>
|
</div>
|
||||||
{/* viewer */}
|
{/* viewer */}
|
||||||
<div className={Styles.viewer}>
|
<div className={Styles.viewer}>
|
||||||
{/* 操作按钮,返回 */}
|
|
||||||
<div className={Styles.btns}>
|
<div className={Styles.btns}>
|
||||||
<Button
|
<Button
|
||||||
className={Styles.back}
|
className={Styles.back}
|
||||||
|
|
@ -77,9 +91,17 @@ const EssayDetails = (
|
||||||
>
|
>
|
||||||
返回
|
返回
|
||||||
</Button>
|
</Button>
|
||||||
|
<div className={Styles.like} onClick={HandleLike}>
|
||||||
|
{isSelfLiked ? (
|
||||||
|
<OakIcon name='praise_fill' size={25} />
|
||||||
|
) : (
|
||||||
|
<OakIcon name='praise' size={25} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MdViewer md={item.content!} />
|
<MdViewer md={item.content!} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* comment */}
|
{/* comment */}
|
||||||
<div className={Styles.comment}>
|
<div className={Styles.comment}>
|
||||||
<List
|
<List
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { EntityDict } from '@oak-app-domain';
|
||||||
import { Trigger } from 'oak-domain/lib/types';
|
import { Trigger } from 'oak-domain/lib/types';
|
||||||
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||||
|
|
||||||
const triggers: Trigger<EntityDict, 'essay', BackendRuntimeContext>[] = [
|
const triggers: Trigger<EntityDict, 'essay', BackendRuntimeContext>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -52,6 +53,49 @@ const triggers: Trigger<EntityDict, 'essay', BackendRuntimeContext>[] = [
|
||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// action: like的时候,增加点赞记录
|
||||||
|
{
|
||||||
|
name: "like的时候,增加点赞记录",
|
||||||
|
entity: "essay",
|
||||||
|
action: "like",
|
||||||
|
when: "before",
|
||||||
|
fn: async ({ operation }, context, option) => {
|
||||||
|
const userId = context.getCurrentUserId();
|
||||||
|
const { filter } = operation;
|
||||||
|
assert(userId && filter, 'id is required');
|
||||||
|
const opres = await context.operate("like", {
|
||||||
|
id: await generateNewIdAsync(),
|
||||||
|
data: {
|
||||||
|
id: await generateNewIdAsync(),
|
||||||
|
essayId: filter.id as string,
|
||||||
|
creatorId: userId,
|
||||||
|
},
|
||||||
|
action: "create",
|
||||||
|
}, option);
|
||||||
|
return opres.like?.create || 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "取消点赞的时候,删除点赞记录",
|
||||||
|
entity: "essay",
|
||||||
|
action: "unlike",
|
||||||
|
when: "before",
|
||||||
|
fn: async ({ operation }, context, option) => {
|
||||||
|
const userId = context.getCurrentUserId();
|
||||||
|
const { filter } = operation;
|
||||||
|
assert(userId && filter, 'id is required');
|
||||||
|
const opres = await context.operate("like", {
|
||||||
|
data: {},
|
||||||
|
id: await generateNewIdAsync(),
|
||||||
|
filter: {
|
||||||
|
essayId: filter.id as string,
|
||||||
|
creatorId: userId,
|
||||||
|
},
|
||||||
|
action: "remove",
|
||||||
|
}, option);
|
||||||
|
return opres.like?.remove || 0;
|
||||||
|
},
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default triggers;
|
export default triggers;
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||||
import essayTriggers from './essay';
|
import essayTriggers from './essay';
|
||||||
import essayLabelsTriggers from './essayLabels';
|
import essayLabelsTriggers from './essayLabels';
|
||||||
import labelTriggers from './label';
|
import labelTriggers from './label';
|
||||||
|
import likeTriggers from './like';
|
||||||
|
|
||||||
const triggers = [
|
const triggers = [
|
||||||
...essayTriggers,
|
...essayTriggers,
|
||||||
...essayLabelsTriggers,
|
...essayLabelsTriggers,
|
||||||
...labelTriggers,
|
...labelTriggers,
|
||||||
|
...likeTriggers,
|
||||||
] as Trigger<EntityDict, keyof EntityDict, BackendRuntimeContext>[];
|
] as Trigger<EntityDict, keyof EntityDict, BackendRuntimeContext>[];
|
||||||
|
|
||||||
export default triggers;
|
export default triggers;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { EntityDict } from '@oak-app-domain';
|
||||||
|
import { OakUserException, Trigger } from 'oak-domain/lib/types';
|
||||||
|
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||||
|
import assert from 'assert';
|
||||||
|
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||||
|
|
||||||
|
|
||||||
|
const triggers: Trigger<EntityDict, 'like', BackendRuntimeContext>[] = [
|
||||||
|
{
|
||||||
|
name: '创建like时自动补全内容',
|
||||||
|
entity: 'like',
|
||||||
|
action: 'create',
|
||||||
|
when: 'before',
|
||||||
|
fn: async ({ operation }, context, option) => {
|
||||||
|
const { id, data } = operation;
|
||||||
|
assert(data && !(data instanceof Array));
|
||||||
|
data.meta = data.meta || {};
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default triggers;
|
||||||
|
|
@ -28,6 +28,17 @@ export const essayProjection: EntityDict['essay']['Selection']['data'] = {
|
||||||
name: 1,
|
name: 1,
|
||||||
nickname: 1,
|
nickname: 1,
|
||||||
},
|
},
|
||||||
|
like$essay: {
|
||||||
|
$entity: 'like',
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
essayId: 1,
|
||||||
|
creatorId: 1,
|
||||||
|
creator: {
|
||||||
|
id: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
meta: 1,
|
meta: 1,
|
||||||
extraFile$entity: {
|
extraFile$entity: {
|
||||||
$entity: 'extraFile',
|
$entity: 'extraFile',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue