This commit is contained in:
wangtianqi 2024-10-30 21:45:28 +08:00
parent ec51cb6a35
commit 72e7d51398
10 changed files with 180 additions and 6 deletions

View File

@ -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",

View File

@ -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: {

25
src/entities/Like.ts Normal file
View File

@ -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: '元数据',
},
},
},
};

View File

@ -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,
}; };
}, },
}); });

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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;

23
src/triggers/like.ts Normal file
View File

@ -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;

View File

@ -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',