创建了评论页面

This commit is contained in:
unknown 2024-10-31 14:46:49 +08:00
parent bc543ad502
commit 13750d1fc8
27 changed files with 618 additions and 60 deletions

View File

@ -88,6 +88,21 @@ const checkers: Checker<EntityDict, 'essay', RuntimeCxt>[] = [
},
option
);
},
() => {
// 删除相关的comment
return context.operate(
'comment',
{
action: 'remove',
id: generateNewId(),
data: {},
filter: {
essay: filter,
},
},
option
);
}
);
},

View File

@ -0,0 +1,5 @@
{
"enablePullDownRefresh": false,
"usingComponents": {
}
}

View File

@ -0,0 +1,11 @@
export default OakComponent({
entity: 'likes',
isList: true,
projection: {
},
formData({ data }) {
return {
list: data,
};
},
});

View File

@ -0,0 +1,3 @@
{
}

View File

@ -0,0 +1,7 @@
.id {
font-size: 18px;
}
.item {
font-size: 18px;
}

View File

@ -0,0 +1,31 @@
import React from 'react';
import { EntityDict } from '@project/oak-app-domain';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import Styles from './styles.module.less';
const Likes = (
props: WebComponentProps<
EntityDict,
'likes',
true,
{
list: RowWithActions<EntityDict, 'likes'>[];
}
>
) => {
const { list } = props.data;
return (
<>
{list && (
<>
{list.map((item) => {
return <div key={item.id} className={Styles.item}>{item.id}</div>;
})}
</>
)}
</>
);
};
export default Likes;

View File

@ -0,0 +1,5 @@
{
"enablePullDownRefresh": false,
"usingComponents": {
}
}

View File

@ -0,0 +1,13 @@
export default OakComponent({
entity: 'comment',
isList: true,
projection: {
content: 1,
},
formData({ data }) {
return {
list: data,
essayId: this.props.essayId
};
},
});

View File

@ -0,0 +1,3 @@
{
}

View File

@ -0,0 +1,36 @@
.id {
font-size: 18px;
}
.item {
font-size: 18px;
width: 80%;
margin-left: auto;
margin-right: auto;
margin-bottom: 30px;
}
.TextArea {
font-size: 18px;
width: 80%;
margin-left: auto;
margin-right: auto;
.btu{
margin-top: 20px;
margin-left: 50%;
}
}
.content{
margin-top: 30px;
margin-bottom: 70px;
width: 80%;
margin-left: auto;
margin-right: auto;
font-size: 25px;
background-color: #ffffffd3;
border-radius: 20px;
padding: 10px;
height: auto;
}

View File

@ -0,0 +1,102 @@
import React, { useState } from 'react';
import { EntityDict } from '@project/oak-app-domain';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import Styles from './styles.module.less';
import { Input } from 'antd';
import { Button } from 'antd';
import { set } from 'lodash';
import useFeatures from '@project/hooks/useFeatures';
const { TextArea } = Input;
const Comment = (
props: WebComponentProps<
EntityDict,
'comment',
true,
{
list: RowWithActions<EntityDict, 'comment'>[];
essayId: string;
}
>
) => {
const features = useFeatures();
const { t, addItem, execute } = props.methods;
const { list } = props.data;
const [newCommentText, setNewCommentText] = React.useState('');
const [likePeople, setLikePeople] = React.useState<string[]>([]);
const handleSubmit = () => {
addItem({
content: newCommentText,
creatorId: features.token.getUserId(),
essayId: props.data.essayId,
});
execute();
setNewCommentText('');
};
const MyButton = () => {
const clickPeople = features.token.getUserId();
const handleClick = () => {
if (!clickPeople) {
return;
}
setLikePeople((prevLikePeople) => {
if (prevLikePeople.includes(clickPeople)) {
return prevLikePeople.filter(person => person !== clickPeople);
} else {
return [...prevLikePeople, clickPeople];
}
});
};
const count = likePeople.length;
return (
<Button onClick={handleClick}>
{t('common::like')} ({count})
</Button>
);
}
return (
<>
<div className={Styles.content}>
<pre>
Saber
Archer
Rider
Berserker
Lancer
Caster
Assassin
</pre>
</div>
{list && (
<>
{list.map((item) => {
return <div key={item.id} className={Styles.item}>{item.content}</div>;
})}
{/* textarea */}
<div className={Styles.TextArea}>
<TextArea
value={newCommentText}
onChange={(e) => setNewCommentText(e.target.value)}
></TextArea>
{/* button */}
<div className={Styles.btu}>
<Button
onClick={handleSubmit}
> {t('common::submit')} </Button>
<MyButton />
</div>
</div>
</>
)}
</>
);
};
export default Comment;

View File

@ -14,6 +14,19 @@ const i18ns: I18n[] = [
"createCategory": "创建分类"
}
},
{
id: "63f061bd7f1010b396cadb4bb784734f",
namespace: "new-demo-p-console-comment-list",
language: "zh-CN",
module: "new-demo",
position: "src/pages/console/comment/list",
data: {
"pageTitle": "评论管理",
"content": "",
"creator": "",
"essay": ""
}
},
{
id: "4813a0a3acdfac90d3b3b3cfa37de82c",
namespace: "new-demo-p-console-essay-list",
@ -222,6 +235,14 @@ const i18ns: I18n[] = [
"categories": "分类"
}
},
{
id: "1900728959beaf0b8fd7ffd5bb758dbe",
namespace: "new-demo-c-frontend-home-comment",
language: "zh-CN",
module: "new-demo",
position: "src/components/frontend/home/comment",
data: {}
},
{
id: "6fd812246bb915f692124fc05bb9ffd4",
namespace: "new-demo-c-frontend-home-labels-byCategory",
@ -328,7 +349,8 @@ const i18ns: I18n[] = [
"userManage": "用户管理",
"relationManage": "关系管理",
"consoleHome": "控制台首页",
"labelManage": "标签管理"
"labelManage": "标签管理",
"commentManage": "评论管理"
}
},
{
@ -549,6 +571,21 @@ const i18ns: I18n[] = [
}
}
},
{
id: "f70261a2fc23af2a9f459353e5ac1ea3",
namespace: "comment",
language: "zh-CN",
module: "",
position: "oak-app-domain/Comment",
data: {
"name": "评论",
"attr": {
"creator": "创建者",
"content": "内容",
"essay": "文章"
}
}
},
{
id: "b06ea1d96db9f0a2d2f3ff3ad966c9c5",
namespace: "domain",

24
src/entities/Comment.ts Normal file
View File

@ -0,0 +1,24 @@
import { String, Text, Boolean } from 'oak-domain/lib/types/DataType';
import { ActionDef, EntityDesc, EntityShape } from 'oak-domain/lib/types';
import { Schema as User } from 'oak-general-business/lib/entities/User';
import { Schema as Essay } from './Essay';
//Comment.ts
export interface Schema extends EntityShape {
creator: User;
content: String<1024>;
essay: Essay;
}
export const entityDesc: EntityDesc<Schema> = {
locales: {
zh_CN: {
name: '评论',
attr: {
creator: '创建者',
content: '内容',
essay: '文章',
},
},
},
};

View File

@ -35,7 +35,13 @@ export const IActionDef: ActionDef<IAction, IState> = {
// 用户行为操作
export type CommonAction = 'setTop' | 'cancelTop';
type Action = IAction | CommonAction;
export type LikeAction =
| 'like'
| 'unlike';
export type Action = IAction | CommonAction | LikeAction;
export const entityDesc: EntityDesc<
Schema,
@ -65,6 +71,8 @@ export const entityDesc: EntityDesc<
withdraw: '撤回',
setTop: '置顶',
cancelTop: '取消置顶',
like: '点赞',
unlike: '取消点赞',
},
v: {
iState: {

37
src/entities/Likes.ts Normal file
View File

@ -0,0 +1,37 @@
import { String, Text, Boolean } from 'oak-domain/lib/types/DataType';
import { ActionDef, EntityDesc, EntityShape } from 'oak-domain/lib/types';
import { Schema as User } from 'oak-general-business/lib/entities/User';
import { Schema as Essay } from './Essay';
//Likes.ts
export interface Schema extends EntityShape {
creator: User;
essay: Essay;
}
type LikeAction =
| 'like'
| 'unlike';
export type Action = LikeAction;
export const entityDesc: EntityDesc<Schema, Action> = {
locales: {
zh_CN: {
name: '点赞',
attr: {
creator: '创建者',
essay: '文章',
},
action: {
like: '点赞',
unlike: '取消点赞',
},
},
},
style: {
icon: {
unlike: '',
},
},
};

View File

@ -55,7 +55,6 @@ const menus: IMenu[] = [
icon: 'share',
url: '/relation/entityList',
parent: 'System',
destEntity: 'userRelation',
actions: ['select'],
},
// category
@ -63,16 +62,16 @@ const menus: IMenu[] = [
name: 'categoryManage',
icon: 'stealth',
url: '/category/list',
order: 2,
destEntity: 'userRelation',
order: 2,
actions: ['select'],
},
{
name: 'essayManage',
icon: 'barrage',
url: '/essay/list',
order: 3,
destEntity: 'userRelation',
order: 3,
actions: ['select'],
},
// 标签关联
@ -80,10 +79,18 @@ const menus: IMenu[] = [
name: 'labelManage',
icon: 'accessory',
url: '/label/list',
order: 4,
destEntity: 'userRelation',
order: 4,
actions: ['select'],
},
// 评论管理
{
name: 'commentManage',
icon: 'message',
url: '/comment/list',
destEntity: 'userRelation',
order: 5,
},
];
export default class Console extends BaseConsole<EntityDict, IMenu> {

View File

@ -1,54 +1,55 @@
{
"name": "示例项目",
"unknown": "未知",
"status":"状态",
"ptrActivate": "松开刷新",
"ptrDeactivate": "下拉刷新",
"ptrRelease": "正在刷新...",
"ptrFinish": "刷新完成",
"noData": "暂无数据",
"areYouSure": "请确认",
"action": {
"create": "创建",
"update": "更新",
"delete": "删除",
"remove": "删除",
"cancel": "取消",
"grant": "授权",
"revoke": "回收",
"tip": "提示",
"detail": "详情",
"editor": "编辑",
"newAdd": "新增",
"add": "添加",
"commit": "提交",
"save": "保存",
"upload": "上传",
"import": "导入"
},
"confirm": "确定",
"submit": "提交",
"reset": "重置",
"select": "查询",
"expand": "展开",
"shrink": "收起",
"back": "返回",
"$$createAt$$": "创建时间",
"$$updateAt$$": "更新时间",
"$$deleteAt$$": "删除时间",
"$$seq$$": "序号",
"message": "消息",
"more": "更多",
"view": "查看",
"scan": "扫一扫",
"bind": "绑定",
"true": "是",
"false": "否",
"open": "开",
"close": "关",
"enter": "请输入",
"change": "修改",
"finish": "完成",
"creator": "创建人",
"top": "置顶"
}
"name": "示例项目",
"unknown": "未知",
"status": "状态",
"ptrActivate": "松开刷新",
"ptrDeactivate": "下拉刷新",
"ptrRelease": "正在刷新...",
"ptrFinish": "刷新完成",
"noData": "暂无数据",
"areYouSure": "请确认",
"action": {
"create": "创建",
"update": "更新",
"delete": "删除",
"remove": "删除",
"cancel": "取消",
"grant": "授权",
"revoke": "回收",
"tip": "提示",
"detail": "详情",
"editor": "编辑",
"newAdd": "新增",
"add": "添加",
"commit": "提交",
"save": "保存",
"upload": "上传",
"import": "导入"
},
"confirm": "确定",
"submit": "提交",
"reset": "重置",
"select": "查询",
"expand": "展开",
"shrink": "收起",
"back": "返回",
"$$createAt$$": "创建时间",
"$$updateAt$$": "更新时间",
"$$deleteAt$$": "删除时间",
"$$seq$$": "序号",
"message": "消息",
"more": "更多",
"view": "查看",
"scan": "扫一扫",
"bind": "绑定",
"true": "是",
"false": "否",
"open": "开",
"close": "关",
"enter": "请输入",
"change": "修改",
"finish": "完成",
"creator": "创建人",
"top": "置顶",
"like": "点赞"
}

View File

@ -5,5 +5,6 @@
"userManage": "用户管理",
"relationManage": "关系管理",
"consoleHome" : "控制台首页",
"labelManage": "标签管理"
"labelManage": "标签管理",
"commentManage": "评论管理"
}

View File

@ -0,0 +1,5 @@
{
"enablePullDownRefresh": false,
"usingComponents": {
}
}

View File

@ -0,0 +1,20 @@
export default OakComponent({
entity: 'comment',
isList: true,
projection: {
content: 1,
essay: {
title: 1,
},
creator: {
name: 1,
nickname: 1,
},
},
formData({ data }) {
return {
list: data,
};
},
actions: ['update', 'remove'],
});

View File

@ -0,0 +1,6 @@
{
"pageTitle": "评论管理",
"content": "",
"creator": "",
"essay": ""
}

View File

@ -0,0 +1,7 @@
.id {
font-size: 18px;
}
.item {
font-size: 18px;
}

View File

@ -0,0 +1,74 @@
import React from 'react';
import { EntityDict } from '@project/oak-app-domain';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import Styles from './styles.module.less';
import PageHeader from 'oak-frontend-base/es/components/pageHeader2';
import { ListPro } from '@project/components/AbstractComponents';
import { render } from 'nprogress';
import { exec } from 'child_process';
const List = (
props: WebComponentProps<
EntityDict,
'comment',
true,
{
list: RowWithActions<EntityDict, 'comment'>[];
}
>
) => {
const { list } = props.data;
const{ t, removeItem, execute } = props.methods;
const attrs = [
{
path: 'content',
label: t('content'),
render: (row:any) => {
return row.content;
},
},
{
path: 'creator',
label: t('creator'),
render: (row:any) => {
return row.creator?.name || row.creator?.nickname || '';
},
},
{
path: 'essay',
label: t('essay'),
render: (row:any) => {
return row.essay?.title || '';
},
},
];
const onAction = (row: any, action: string) => {
switch (action) {
case 'update':
break;
case 'remove':
removeItem(row.id);
execute();
break;
default:
break;
}
};
return (
<PageHeader title={t('pageTitle')}>
<ListPro
entity='comment'
attributes={attrs}
data={list}
oakPath={'${oakFullpath}'}
onAction={onAction}
></ListPro>
</PageHeader>
);
};
export default List;

View File

@ -6,6 +6,7 @@ import MdViewer from '@project/components/common/byteMD/MdViewer';
import FrontendFooter from '@project/components/frontend/home/FrontendFooter';
import { Button } from 'antd';
import { VerticalAlignTopOutlined } from '@ant-design/icons';
import List from '@project/components/frontend/home/comment';
const EssayDetails = (
props: WebComponentProps<
@ -18,6 +19,8 @@ const EssayDetails = (
}
>
) => {
const { oakFullpath } = props.data;
const { item, cover } = props.data;
const [showScrollTop, setShowScrollTop] = useState(false);
@ -77,6 +80,10 @@ const EssayDetails = (
</div>
<MdViewer md={item.content!} />
</div>
<List
oakPath={`${oakFullpath}.comment$essay`}
essayId={item.id}
></List>
<div className={Styles.blank}></div>
</>
)}
@ -87,6 +94,7 @@ const EssayDetails = (
<button
className={Styles.scrollTopButton}
onClick={scrollToTop}
title="Scroll to top"
>
<VerticalAlignTopOutlined />
</button>

View File

@ -52,6 +52,61 @@ const triggers: Trigger<EntityDict, 'essay', BackendRuntimeContext>[] = [
return 1;
},
},
{
name: '点赞',
entity: 'essay',
action: 'create', // 假设我们有一个 action 名称 `createLike` 表示点赞
when: 'after',
fn: async ({ operation }, context, option) => {
const { data } = operation;
assert(data && !Array.isArray(data));
// 获取当前用户 ID
const userId = context.getCurrentUserId();
// 初始化 meta 和 likes 列表
data.meta = data.meta || {};
data.meta.likes = data.meta.likes || [];
// 检查用户是否已点赞
if (!data.meta.likes.includes(userId)) {
// 添加用户 ID 到 likes 列表
data.meta.likes.push(userId);
// 增加点赞计数
data.likesCount = (data.likesCount || 0) + 1;
}
return 1;
},
},
{
name: '取消点赞',
entity: 'essay',
action: 'create', // 假设我们有一个 action 名称 `removeLike` 表示取消点赞
when: 'after',
fn: async ({ operation }, context, option) => {
const { data } = operation;
assert(data && !Array.isArray(data));
// 获取当前用户 ID
const userId = context.getCurrentUserId();
// 初始化 meta 和 likes 列表
data.meta = data.meta || {};
data.meta.likes = data.meta.likes || [];
// 检查用户是否已点赞
const index = data.meta.likes.indexOf(userId);
if (index !== -1) {
// 从 likes 列表中移除用户 ID
data.meta.likes.splice(index, 1);
// 减少点赞计数
data.likesCount = (data.likesCount || 0) - 1;
}
return 1;
},
}
];
export default triggers;

View File

@ -4,11 +4,13 @@ import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
import essayTriggers from './essay';
import essayLabelsTriggers from './essayLabels';
import labelTriggers from './label';
import likesTriggers from './likes';
const triggers = [
...essayTriggers,
...essayLabelsTriggers,
...labelTriggers,
...likesTriggers,
] as Trigger<EntityDict, keyof EntityDict, BackendRuntimeContext>[];
export default triggers;

35
src/triggers/likes.ts Normal file
View File

@ -0,0 +1,35 @@
import { EntityDict } from '@oak-app-domain';
import { OakUserException, Trigger } from 'oak-domain/lib/types';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
import assert from 'assert';
const triggers: Trigger<EntityDict, 'likes', BackendRuntimeContext>[] = [
{
name: '点赞',
entity: 'likes',
action: 'create',
when: 'after',
fn: async ({ operation }, context, option) => {
const { data } = operation;
assert(data && !Array.isArray(data));
return 1;
},
},
{
name: '取消点赞',
entity: 'likes',
action: 'remove',
when: 'after',
fn: async ({ operation }, context, option) => {
const { data } = operation;
assert(data && !Array.isArray(data));
const id = context.getCurrentUserId();
return 1;
},
},
];
export default triggers;