Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
fac449c9
提交
fac449c9
authored
7月 20, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
refactor(admin): migrate books sale module
上级
632a69a5
隐藏空白字符变更
内嵌
并排
正在显示
10 个修改的文件
包含
56 行增加
和
16 行删除
+56
-16
routes.tsx
apps/admin/src/modules/books/routes.tsx
+2
-16
api.ts
apps/admin/src/modules/books/sale/api.ts
+10
-0
SaleDiscussionDetail.jsx
...rc/modules/books/sale/components/SaleDiscussionDetail.jsx
+1
-0
SaleEdit.jsx
apps/admin/src/modules/books/sale/components/SaleEdit.jsx
+1
-0
query.ts
apps/admin/src/modules/books/sale/query.ts
+8
-0
routes.tsx
apps/admin/src/modules/books/sale/routes.tsx
+7
-0
useSaleDiscussionList.ts
apps/admin/src/modules/books/sale/useSaleDiscussionList.ts
+4
-0
useSaleList.ts
apps/admin/src/modules/books/sale/useSaleList.ts
+4
-0
SaleDiscussionListView.tsx
...n/src/modules/books/sale/views/SaleDiscussionListView.tsx
+8
-0
SaleListView.tsx
apps/admin/src/modules/books/sale/views/SaleListView.tsx
+11
-0
没有找到文件。
apps/admin/src/modules/books/routes.tsx
浏览文件 @
fac449c9
...
...
@@ -10,11 +10,7 @@ const ChapterManagement = lazy(() => import("@/pages/books/section/index"));
const
AuditRoutes
=
lazy
(()
=>
import
(
"./audit/routes"
));
const
ClassifyRoutes
=
lazy
(()
=>
import
(
"./classify/routes"
));
const
LabelRoutes
=
lazy
(()
=>
import
(
"./label/routes"
));
const
SaleLayout
=
lazy
(()
=>
import
(
"@/pages/books/sale/index"
));
const
SaleTable
=
lazy
(()
=>
import
(
"@/pages/books/sale/table"
));
const
SaleEdit
=
lazy
(()
=>
import
(
"@/pages/books/sale/edit"
));
const
DiscussList
=
lazy
(()
=>
import
(
"@/pages/books/sale/discussList/index"
));
const
DiscussDetail
=
lazy
(()
=>
import
(
"@/pages/books/sale/discussDetail/index"
));
const
SaleRoutes
=
lazy
(()
=>
import
(
"./sale/routes"
));
const
QuestionBankRoutes
=
lazy
(()
=>
import
(
"./question-bank/routes"
));
const
DiscussionRoutes
=
lazy
(()
=>
import
(
"./discussion/routes"
));
const
OrderManagementLayout
=
lazy
(()
=>
import
(
"@/pages/books/order-management/index"
));
...
...
@@ -41,17 +37,7 @@ export default function BooksRoutes() {
{
path
:
"audit/*"
,
Component
:
AuditRoutes
},
{
path
:
"classify/*"
,
Component
:
ClassifyRoutes
},
{
path
:
"label/*"
,
Component
:
LabelRoutes
},
{
path
:
"sale"
,
Component
:
SaleLayout
,
children
:
[
{
index
:
true
,
Component
:
SaleTable
},
{
path
:
"dataset"
,
Component
:
SaleTable
},
{
path
:
"detail"
,
Component
:
SaleEdit
},
{
path
:
"discussP"
,
Component
:
DiscussList
},
{
path
:
"discuss-detail"
,
Component
:
DiscussDetail
},
],
},
{
path
:
"sale/*"
,
Component
:
SaleRoutes
},
{
path
:
"question-bank/*"
,
Component
:
QuestionBankRoutes
},
{
path
:
"discussion/*"
,
Component
:
DiscussionRoutes
},
{
...
...
apps/admin/src/modules/books/sale/api.ts
0 → 100644
浏览文件 @
fac449c9
import
{
message
}
from
"antd"
;
import
request
from
"@/utils/request"
;
import
type
{
SaleDiscussionQuery
,
SaleQuery
}
from
"./query"
;
async
function
post
(
path
:
string
,
data
:
Record
<
string
,
unknown
>
,
notify
=
false
)
{
try
{
const
response
=
(
await
request
.
post
(
path
,
new
URLSearchParams
(
data
as
Record
<
string
,
string
>
).
toString
()))
as
{
data
?:
{
code
?:
number
;
data
?:
any
;
message
?:
string
}
};
if
(
response
.
data
?.
code
===
200
)
{
if
(
notify
)
message
.
success
(
response
.
data
.
message
);
return
response
.
data
.
data
;
}
message
.
error
(
response
.
data
?.
message
||
"数据获取失败!"
);
}
catch
{}
return
false
;
}
export
const
getSaleList
=
(
query
:
SaleQuery
)
=>
post
(
"/book/book/getList"
,
query
);
export
const
changeSaleStatus
=
(
id
:
string
|
number
,
status
:
string
)
=>
post
(
"/book/book/changeStatus"
,
{
id
,
status
},
true
);
export
const
getSaleDiscussionList
=
(
query
:
SaleDiscussionQuery
)
=>
post
(
"/comment/comment/getCommentList"
,
query
);
apps/admin/src/modules/books/sale/components/SaleDiscussionDetail.jsx
0 → 100644
浏览文件 @
fac449c9
export
{
default
}
from
"@/pages/books/sale/discussDetail"
;
apps/admin/src/modules/books/sale/components/SaleEdit.jsx
0 → 100644
浏览文件 @
fac449c9
export
{
default
}
from
"@/pages/books/sale/edit"
;
apps/admin/src/modules/books/sale/query.ts
0 → 100644
浏览文件 @
fac449c9
import
type
{
Dayjs
}
from
"dayjs"
;
export
interface
SaleFilters
{
name
?:
string
;
authors
?:
string
;
status
?:
string
|
number
;
time
?:
[
Dayjs
|
null
,
Dayjs
|
null
]
|
null
;
}
export
interface
SaleQuery
{
page
:
number
;
page_size
:
number
;
name
?:
string
;
authors
?:
string
;
status
?:
string
|
number
;
start_time
?:
number
;
end_time
?:
number
;
}
export
interface
SaleDiscussionFilters
{
title
?:
string
;
user_name
?:
string
;
time
?:
[
Dayjs
|
null
,
Dayjs
|
null
]
|
null
;
}
export
interface
SaleDiscussionQuery
{
page
:
number
;
page_size
:
number
;
book_id
:
string
|
number
;
title
?:
string
;
user_name
?:
string
;
start_time
?:
number
;
end_time
?:
number
;
}
function
addTime
(
query
:
any
,
time
:
any
)
{
if
(
time
?.[
0
]
&&
time
[
1
])
{
query
.
start_time
=
time
[
0
].
startOf
(
"day"
).
unix
();
query
.
end_time
=
time
[
1
].
endOf
(
"day"
).
unix
();
}
return
query
;
}
export
const
buildSaleQuery
=
(
page
:
number
,
pageSize
:
number
,
filters
:
SaleFilters
):
SaleQuery
=>
addTime
({
page
,
page_size
:
pageSize
,
...(
filters
.
name
?
{
name
:
filters
.
name
}
:
{}),
...(
filters
.
authors
?
{
authors
:
filters
.
authors
}
:
{}),
...(
filters
.
status
!==
undefined
&&
filters
.
status
!==
null
?
{
status
:
filters
.
status
}
:
{})
},
filters
.
time
);
export
const
buildSaleDiscussionQuery
=
(
page
:
number
,
pageSize
:
number
,
bookId
:
string
|
number
,
filters
:
SaleDiscussionFilters
):
SaleDiscussionQuery
=>
addTime
({
page
,
page_size
:
pageSize
,
book_id
:
bookId
,
...(
filters
.
title
?
{
title
:
filters
.
title
}
:
{}),
...(
filters
.
user_name
?
{
user_name
:
filters
.
user_name
}
:
{})
},
filters
.
time
);
apps/admin/src/modules/books/sale/routes.tsx
0 → 100644
浏览文件 @
fac449c9
import
{
Navigate
,
Outlet
,
useRoutes
}
from
"react-router"
;
import
SaleDiscussionDetail
from
"./components/SaleDiscussionDetail"
;
import
SaleEdit
from
"./components/SaleEdit"
;
import
SaleDiscussionListView
from
"./views/SaleDiscussionListView"
;
import
SaleListView
from
"./views/SaleListView"
;
function
Layout
()
{
return
<
Outlet
/>;
}
export
default
function
SaleRoutes
()
{
return
useRoutes
([{
Component
:
Layout
,
children
:
[{
index
:
true
,
Component
:
SaleListView
},
{
path
:
"dataset"
,
Component
:
SaleListView
},
{
path
:
"detail"
,
Component
:
SaleEdit
},
{
path
:
"discussP"
,
Component
:
SaleDiscussionListView
},
{
path
:
"discuss-detail"
,
Component
:
SaleDiscussionDetail
},
{
path
:
"*"
,
element
:
<
Navigate
to=
"."
replace
/>
}]
}]);
}
apps/admin/src/modules/books/sale/useSaleDiscussionList.ts
0 → 100644
浏览文件 @
fac449c9
import
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
getSaleDiscussionList
}
from
"./api"
;
import
{
buildSaleDiscussionQuery
,
type
SaleDiscussionFilters
,
type
SaleDiscussionQuery
}
from
"./query"
;
export
function
useSaleDiscussionList
(
bookId
?:
string
|
number
)
{
const
[
filters
,
setFilters
]
=
useState
<
SaleDiscussionFilters
>
({});
const
[
applied
,
setApplied
]
=
useState
<
SaleDiscussionFilters
>
({});
const
[
page
,
setPage
]
=
useState
(
1
);
const
[
pageSize
,
setPageSize
]
=
useState
(
10
);
const
[
rows
,
setRows
]
=
useState
<
any
[]
>
([]);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
<
Error
|
null
>
(
null
);
const
last
=
useRef
<
SaleDiscussionQuery
|
null
>
(
null
);
const
load
=
useCallback
(
async
(
query
:
SaleDiscussionQuery
)
=>
{
last
.
current
=
query
;
setLoading
(
true
);
try
{
const
result
:
any
=
await
getSaleDiscussionList
(
query
);
if
(
!
result
)
throw
new
Error
(
"请求失败"
);
setRows
(
result
.
list
||
[]);
setTotal
(
result
.
total
||
0
);
setError
(
null
);
}
catch
(
e
)
{
setError
(
e
instanceof
Error
?
e
:
new
Error
(
"请求失败"
));
}
finally
{
setLoading
(
false
);
}
},
[]);
useEffect
(()
=>
{
if
(
bookId
)
void
load
(
buildSaleDiscussionQuery
(
1
,
10
,
bookId
,
{}));
else
setLoading
(
false
);
},
[
bookId
,
load
]);
const
run
=
(
p
:
number
,
s
:
number
,
f
:
SaleDiscussionFilters
)
=>
bookId
&&
void
load
(
buildSaleDiscussionQuery
(
p
,
s
,
bookId
,
f
));
const
search
=
()
=>
{
setApplied
(
filters
);
setPage
(
1
);
run
(
1
,
pageSize
,
filters
);
};
return
{
filters
,
setFilters
,
page
,
pageSize
,
rows
,
total
,
loading
,
error
,
search
,
reset
:
()
=>
{
setFilters
({});
setApplied
({});
setPage
(
1
);
run
(
1
,
pageSize
,
{});
},
refresh
:
()
=>
run
(
page
,
pageSize
,
filters
),
retry
:
()
=>
last
.
current
&&
void
load
(
last
.
current
),
changePage
:
(
p
:
number
)
=>
{
setPage
(
p
);
run
(
p
,
pageSize
,
applied
);
},
changePageSize
:
(
s
:
number
)
=>
{
setPage
(
1
);
setPageSize
(
s
);
run
(
1
,
s
,
applied
);
}
};
}
apps/admin/src/modules/books/sale/useSaleList.ts
0 → 100644
浏览文件 @
fac449c9
import
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
getSaleList
}
from
"./api"
;
import
{
buildSaleQuery
,
type
SaleFilters
,
type
SaleQuery
}
from
"./query"
;
export
function
useSaleList
()
{
const
[
filters
,
setFilters
]
=
useState
<
SaleFilters
>
({});
const
[
applied
,
setApplied
]
=
useState
<
SaleFilters
>
({});
const
[
page
,
setPage
]
=
useState
(
1
);
const
[
pageSize
,
setPageSize
]
=
useState
(
10
);
const
[
rows
,
setRows
]
=
useState
<
any
[]
>
([]);
const
[
total
,
setTotal
]
=
useState
(
0
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
error
,
setError
]
=
useState
<
Error
|
null
>
(
null
);
const
last
=
useRef
<
SaleQuery
>
(
buildSaleQuery
(
1
,
10
,
{}));
const
load
=
useCallback
(
async
(
query
:
SaleQuery
)
=>
{
last
.
current
=
query
;
setLoading
(
true
);
try
{
const
result
:
any
=
await
getSaleList
(
query
);
if
(
!
result
)
throw
new
Error
(
"请求失败"
);
setRows
(
result
.
list
||
[]);
setTotal
(
result
.
total
||
0
);
setError
(
null
);
}
catch
(
e
)
{
setError
(
e
instanceof
Error
?
e
:
new
Error
(
"请求失败"
));
}
finally
{
setLoading
(
false
);
}
},
[]);
useEffect
(()
=>
{
void
load
(
last
.
current
);
},
[
load
]);
const
run
=
(
p
:
number
,
s
:
number
,
f
:
SaleFilters
)
=>
void
load
(
buildSaleQuery
(
p
,
s
,
f
));
const
search
=
()
=>
{
setApplied
(
filters
);
setPage
(
1
);
run
(
1
,
pageSize
,
filters
);
};
return
{
filters
,
setFilters
,
page
,
pageSize
,
rows
,
total
,
loading
,
error
,
search
,
reset
:
()
=>
{
setFilters
({});
setApplied
({});
setPage
(
1
);
run
(
1
,
pageSize
,
{});
},
refresh
:
()
=>
{
setApplied
(
filters
);
run
(
page
,
pageSize
,
filters
);
},
retry
:
()
=>
void
load
(
last
.
current
),
changePage
:
(
p
:
number
)
=>
{
setPage
(
p
);
run
(
p
,
pageSize
,
applied
);
},
changePageSize
:
(
s
:
number
)
=>
{
setPage
(
1
);
setPageSize
(
s
);
run
(
1
,
s
,
applied
);
}
};
}
apps/admin/src/modules/books/sale/views/SaleDiscussionListView.tsx
0 → 100644
浏览文件 @
fac449c9
import
{
DatePicker
,
Input
,
Button
,
Space
}
from
"antd"
;
import
type
{
ColumnsType
}
from
"antd/es/table"
;
import
{
useLocation
,
useNavigate
}
from
"react-router"
;
import
{
AppList
,
type
AppListFilterItem
}
from
"@/shared/components/AppList"
;
import
type
{
SaleDiscussionFilters
}
from
"../query"
;
import
{
useSaleDiscussionList
}
from
"../useSaleDiscussionList"
;
const
{
RangePicker
}
=
DatePicker
;
export
default
function
SaleDiscussionListView
()
{
const
location
=
useLocation
();
const
navigate
=
useNavigate
();
const
state
=
(
location
.
state
||
{})
as
any
;
const
list
=
useSaleDiscussionList
(
state
.
id
);
const
filters
:
AppListFilterItem
<
SaleDiscussionFilters
>
[]
=
[{
name
:
"title"
,
label
:
"话题"
,
render
:
(
field
)
=>
<
Input
value=
{
field
.
value
||
""
}
onChange=
{
(
event
)
=>
field
.
onChange
(
event
.
target
.
value
)
}
placeholder=
"请输入话题"
/>
},
{
name
:
"user_name"
,
label
:
"发起人"
,
render
:
(
field
)
=>
<
Input
value=
{
field
.
value
||
""
}
onChange=
{
(
event
)
=>
field
.
onChange
(
event
.
target
.
value
)
}
placeholder=
"请输入发起人"
/>
},
{
name
:
"time"
,
label
:
"发起时间"
,
render
:
(
field
)
=>
<
RangePicker
value=
{
field
.
value
}
onChange=
{
(
value
)
=>
field
.
onChange
(
value
)
}
/>
}];
const
columns
:
ColumnsType
<
any
>
=
[{
title
:
"话题"
,
dataIndex
:
"title"
,
width
:
200
},
{
title
:
"发起人"
,
dataIndex
:
"name"
},
{
title
:
"发起时间"
,
dataIndex
:
"create_time"
},
{
title
:
"操作"
,
render
:
(
_
,
row
)
=>
<
Button
onClick=
{
()
=>
navigate
(
"../discuss-detail"
,
{
state
:
{
id
:
row
.
id
,
name
:
state
.
name
,
book_id
:
state
.
id
}
})
}
>
查看详情
</
Button
>
}];
if
(
!
state
.
id
)
return
<
div
>
缺少书籍信息,请返回销售列表。
</
div
>;
return
<
AppList
rowKey=
"id"
columns=
{
columns
}
data=
{
{
rows
:
list
.
rows
,
total
:
list
.
total
,
loading
:
list
.
loading
,
error
:
list
.
error
}
}
filters=
{
{
value
:
list
.
filters
,
items
:
filters
,
onChange
:
list
.
setFilters
,
onSearch
:
list
.
search
,
onReset
:
list
.
reset
,
onRefresh
:
list
.
refresh
}
}
pagination=
{
{
page
:
list
.
page
,
pageSize
:
list
.
pageSize
,
pageSizeOptions
:
[
10
,
20
,
30
,
40
],
onChange
:
list
.
changePage
,
onPageSizeChange
:
list
.
changePageSize
}
}
onRetry=
{
list
.
retry
}
/>;
}
apps/admin/src/modules/books/sale/views/SaleListView.tsx
0 → 100644
浏览文件 @
fac449c9
import
{
App
,
Button
,
DatePicker
,
Image
,
Input
,
Modal
,
Select
,
Space
}
from
"antd"
;
import
type
{
ColumnsType
}
from
"antd/es/table"
;
import
{
useState
}
from
"react"
;
import
{
useNavigate
}
from
"react-router"
;
import
{
useSelector
}
from
"react-redux"
;
import
{
AppList
,
type
AppListFilterItem
}
from
"@/shared/components/AppList"
;
import
{
changeSaleStatus
}
from
"../api"
;
import
type
{
SaleFilters
}
from
"../query"
;
import
{
useSaleList
}
from
"../useSaleList"
;
const
{
RangePicker
}
=
DatePicker
;
export
default
function
SaleListView
()
{
const
list
=
useSaleList
();
const
navigate
=
useNavigate
();
const
{
modal
}
=
App
.
useApp
();
const
{
operationPermissionsList
=
[]
}
=
useSelector
((
state
:
any
)
=>
state
.
user
);
const
filters
:
AppListFilterItem
<
SaleFilters
>
[]
=
[{
name
:
"name"
,
label
:
"书籍名称"
,
render
:
(
field
)
=>
<
Input
allowClear
value=
{
field
.
value
||
""
}
onChange=
{
(
event
)
=>
field
.
onChange
(
event
.
target
.
value
)
}
placeholder=
"请输入书籍名称"
/>
},
{
name
:
"authors"
,
label
:
"作者"
,
render
:
(
field
)
=>
<
Input
allowClear
value=
{
field
.
value
||
""
}
onChange=
{
(
event
)
=>
field
.
onChange
(
event
.
target
.
value
)
}
placeholder=
"请输入作者"
/>
},
{
name
:
"status"
,
label
:
"上架状态"
,
render
:
(
field
)
=>
<
Select
allowClear
value=
{
field
.
value
}
onChange=
{
field
.
onChange
}
placeholder=
"请选择上架状态"
style=
{
{
width
:
160
}
}
options=
{
[{
label
:
"上架"
,
value
:
1
},
{
label
:
"下架"
,
value
:
"0"
}]
}
/>
},
{
name
:
"time"
,
label
:
"创建时间"
,
render
:
(
field
)
=>
<
RangePicker
value=
{
field
.
value
}
onChange=
{
(
value
)
=>
field
.
onChange
(
value
)
}
/>
}];
const
columns
:
ColumnsType
<
any
>
=
[{
title
:
"ID"
,
dataIndex
:
"id"
,
align
:
"center"
},
{
title
:
"书籍图片"
,
dataIndex
:
"img"
,
align
:
"center"
,
render
:
(
src
)
=>
<
Image
width=
{
80
}
height=
{
80
}
src=
{
src
}
/>
},
{
title
:
"书籍名称"
,
dataIndex
:
"name"
,
align
:
"center"
},
{
title
:
"作者"
,
dataIndex
:
"authors"
,
align
:
"center"
},
{
title
:
"类型"
,
dataIndex
:
"status"
,
align
:
"center"
,
render
:
(
status
)
=>
status
===
0
?
"下架"
:
status
===
1
?
"上架"
:
""
},
{
title
:
"零售价"
,
dataIndex
:
"price"
,
align
:
"center"
},
{
title
:
"会员价"
,
dataIndex
:
"vip_price"
,
align
:
"center"
},
{
title
:
"创建时间"
,
dataIndex
:
"create_time"
,
align
:
"center"
},
{
title
:
"上架时间"
,
dataIndex
:
"onsale_time"
,
align
:
"center"
,
render
:
(
value
)
=>
value
||
"--"
},
{
title
:
"下架时间"
,
dataIndex
:
"offshelves_time"
,
align
:
"center"
,
render
:
(
value
)
=>
value
||
"--"
},
{
title
:
"操作"
,
render
:
(
_
,
row
)
=>
<
Space
>
{
row
.
status
&&
operationPermissionsList
.
includes
(
"/books/sale/getCommentList"
)
&&
<
Button
onClick=
{
()
=>
navigate
(
"discussP"
,
{
state
:
{
id
:
row
.
id
,
name
:
row
.
name
}
})
}
>
讨论
</
Button
>
}{
!
row
.
status
&&
operationPermissionsList
.
includes
(
"/books/sale/getInfoById"
)
&&
<
Button
onClick=
{
()
=>
navigate
(
"detail"
,
{
state
:
{
id
:
row
.
id
}
})
}
>
编辑
</
Button
>
}{
operationPermissionsList
.
includes
(
"/books/sale/changeStatus"
)
&&
<
Button
onClick=
{
()
=>
modal
.
confirm
({
title
:
"提示"
,
content
:
`书籍将${row.status ? "下架" : "上架"},是否继续?`
,
onOk
:
async
()
=>
{
if
(
await
changeSaleStatus
(
row
.
id
,
row
.
status
?
"0"
:
"1"
))
list
.
refresh
();
}
})
}
>
{
row
.
status
?
"下架"
:
"上架"
}
</
Button
>
}
</
Space
>
}];
return
<
AppList
rowKey=
"id"
columns=
{
columns
}
data=
{
{
rows
:
list
.
rows
,
total
:
list
.
total
,
loading
:
list
.
loading
,
error
:
list
.
error
}
}
filters=
{
{
value
:
list
.
filters
,
items
:
filters
,
onChange
:
list
.
setFilters
,
onSearch
:
list
.
search
,
onReset
:
list
.
reset
,
onRefresh
:
list
.
refresh
}
}
pagination=
{
{
page
:
list
.
page
,
pageSize
:
list
.
pageSize
,
pageSizeOptions
:
[
10
,
20
,
30
,
40
],
onChange
:
list
.
changePage
,
onPageSizeChange
:
list
.
changePageSize
}
}
onRetry=
{
list
.
retry
}
/>;
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论