Skip to content
切换导航条
切换导航条
当前项目
正在载入...
登录
孙克
/
smf-core
转到一个项目
切换导航栏
切换导航栏固定状态
项目
群组
代码片段
帮助
项目
活动
版本库
流水线
图表
问题
0
合并请求
0
维基
网络
创建新的问题
作业
提交
问题看板
文件
提交
网络
比较
分支
标签
Commit 55a40d22
由
LN
编写于
2025-04-16 14:51:13 +0800
浏览文件
选项
浏览文件
标签
下载
电子邮件补丁
差异文件
1.入库最后验证api
2.retry时显示详细结果。 3.有ng和fail的不能end.
1 个父辈
49f28933
隐藏空白字符变更
内嵌
并排
正在显示
9 个修改的文件
包含
123 行增加
和
32 行删除
src/main/java/com/neotel/smfcore/core/device/handler/impl/RobotBoxHandler.java
src/main/java/com/neotel/smfcore/custom/micron1053/bean/MicronResult.java
src/main/java/com/neotel/smfcore/custom/micron1053/loading/LoadingController.java
src/main/java/com/neotel/smfcore/custom/micron1053/loading/util/LoadingUtil.java
src/main/resources/messages.properties
src/main/resources/messages_en_US.properties
src/main/resources/messages_ja_JP.properties
src/main/resources/messages_zh_CN.properties
src/main/resources/messages_zh_TW.properties
src/main/java/com/neotel/smfcore/core/device/handler/impl/RobotBoxHandler.java
查看文件 @
55a40d2
...
...
@@ -453,6 +453,8 @@ public class RobotBoxHandler extends BaseDeviceHandler {
storagePosManager
.
save
(
hasPos
);
}
errorMsg
=
MessageUtils
.
getText
(
"smfcore.error.barcode.exist"
,
new
String
[]{
barcode
.
getBarcode
(),
storage
.
getName
(),
hasPos
.
getPosName
()},
MessageUtils
.
getDefaultLocal
(),
"[{0}}]已在{1}}[{2}}]中"
);
errorMsg
=
"Serial No.(S)[HZ001WZ.QV] has been existing in KTS/WMS already"
;
resultMap
.
put
(
"result"
,
"96"
);
resultMap
.
put
(
"msg"
,
errorMsg
);
...
...
@@ -506,6 +508,7 @@ public class RobotBoxHandler extends BaseDeviceHandler {
//已有入库任务,返回NG,标记原来的入库任务
resultMap
.
put
(
"result"
,
"106"
);
errorMsg
=
"Serial No.(S)["
+
barcode
.
getBarcode
()
+
"] already have storage task"
;
errorMsg
=
"Serial No.(S)["
+
barcode
.
getBarcode
()
+
"] has been existing in KTS/WMS already"
;
resultMap
.
put
(
"msg"
,
errorMsg
);
loadingUtil
.
AddInListItem
(
rfid
,
barcode
,
""
,
INITEM_STATUS
.
NG
,
errorMsg
);
...
...
@@ -628,6 +631,19 @@ public class RobotBoxHandler extends BaseDeviceHandler {
}
}
//先验证下是否有空位,如果无空位直接返回,不需要调用API
StoragePos
testFindPos
=
taskService
.
findEmptyPosForPutIn
(
storages
,
barcode
,
""
,
lastPosId
);
if
(
testFindPos
==
null
)
{
resultMap
.
put
(
"result"
,
"104"
);
String
size
=
getBarcodeSize
(
barcode
);
// errorMsg = "[" + barcode.getBarcode() + "]未找到可用的[" + barcode.getPlateSize() + "x" + barcode.getHeight() + "]仓位";
errorMsg
=
"["
+
barcode
.
getBarcode
()
+
"]Not found available["
+
size
+
"]Position"
;
resultMap
.
put
(
"msg"
,
errorMsg
);
loadingUtil
.
updateItemState
(
barcode
.
getBarcode
(),
""
,
INITEM_STATUS
.
NG
,
errorMsg
);
log
.
info
(
"获取["
+
code
+
"]的入库库位, ["
+
barcode
.
getBarcode
()
+
"]未找到可用的["
+
barcode
.
getPlateSize
()
+
"x"
+
barcode
.
getHeight
()
+
"]仓位"
);
return
resultMap
;
}
barcode
=
ApiCheck
(
rfid
,
barcode
);
apiBarcode
=
barcode
.
getBarcode
();
...
...
@@ -731,17 +747,7 @@ public class RobotBoxHandler extends BaseDeviceHandler {
}
else
{
resultMap
.
put
(
"result"
,
"104"
);
// 310*80 pcb 330*120 tray 386*86 shoe box 400*60 Pizza box 其他的都是料盘
String
size
=
barcode
.
getPlateSize
()
+
"x"
+
barcode
.
getHeight
();
if
(
size
.
equals
(
"310x80"
))
{
size
=
"pcb"
;
}
else
if
(
size
.
equals
(
"330x120"
))
{
size
=
"tray"
;
}
else
if
(
size
.
equals
(
"386x86"
))
{
size
=
"shoe box"
;
}
else
if
(
size
.
equals
(
"400x70"
))
{
size
=
"Pizza box"
;
}
String
size
=
getBarcodeSize
(
barcode
);
// errorMsg = "[" + barcode.getBarcode() + "]未找到可用的[" + barcode.getPlateSize() + "x" + barcode.getHeight() + "]仓位";
errorMsg
=
"["
+
barcode
.
getBarcode
()
+
"]Not found available["
+
size
+
"]Position"
;
resultMap
.
put
(
"msg"
,
errorMsg
);
...
...
@@ -813,7 +819,20 @@ public class RobotBoxHandler extends BaseDeviceHandler {
}
return
resultMap
;
}
private
String
getBarcodeSize
(
Barcode
barcode
){
// 310*80 pcb 330*120 tray 386*86 shoe box 400*60 Pizza box 其他的都是料盘
String
size
=
barcode
.
getPlateSize
()
+
"x"
+
barcode
.
getHeight
();
if
(
size
.
equals
(
"310x80"
))
{
size
=
"pcb"
;
}
else
if
(
size
.
equals
(
"330x120"
))
{
size
=
"tray"
;
}
else
if
(
size
.
equals
(
"386x86"
))
{
size
=
"shoe box"
;
}
else
if
(
size
.
equals
(
"400x70"
))
{
size
=
"Pizza box"
;
}
return
size
;
}
private
Barcode
ApiCheck
(
String
rfid
,
Barcode
barcode
)
throws
ApiException
{
barcode
.
setToXray
(
false
);
...
...
src/main/java/com/neotel/smfcore/custom/micron1053/bean/MicronResult.java
查看文件 @
55a40d2
...
...
@@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
import
lombok.extern.slf4j.Slf4j
;
import
java.io.Serializable
;
import
java.util.HashMap
;
import
java.util.Map
;
@Data
...
...
@@ -74,7 +75,12 @@ public class MicronResult implements Serializable {
return
null
;
}
if
(
resultMap
==
null
)
{
resultMap
=
JsonUtil
.
toMap
(
responseData
);
if
(
responseData
.
equals
(
"Update Successfully"
)){
resultMap
=
new
HashMap
<>();
}
else
{
resultMap
=
JsonUtil
.
toMap
(
responseData
);
}
}
if
(
needCheck
)
{
if
(!
IsSuccess
())
{
...
...
src/main/java/com/neotel/smfcore/custom/micron1053/loading/LoadingController.java
查看文件 @
55a40d2
...
...
@@ -385,7 +385,7 @@ public class LoadingController {
@ApiOperation
(
"Retry"
)
@PostMapping
(
"/Retry"
)
@AnonymousAccess
public
ResultBean
retry
(
@RequestBody
(
required
=
false
)
Map
<
String
,
Object
>
params
)
{
public
ResultBean
retry
(
HttpServletRequest
request
,
@RequestBody
(
required
=
false
)
Map
<
String
,
Object
>
params
)
{
String
mode
=
getParam
(
params
,
"mode"
,
APIMODE
.
MATRET
);
// String mode=APIMODE.MATRET;
...
...
@@ -394,15 +394,15 @@ public class LoadingController {
return
ResultBean
.
newErrorResult
(-
1
,
"smfcore.micron.operationFailure"
,
"操作失败"
);
}
else
{
if
(!
inList
.
getMode
().
equals
(
mode
))
{
return
ResultBean
.
newErrorResult
(-
1
,
"smfcore.micron.modeError"
,
inList
.
getMode
()
+
" Loading 未完成"
,
new
String
[]{
inList
.
getMode
()});
return
ResultBean
.
newErrorResult
(-
1
,
"smfcore.micron.modeError"
,
inList
.
getMode
()
+
" Loading 未完成"
,
new
String
[]{
inList
.
getMode
()});
}
//重试,重发指令
log
.
info
(
"重试:"
+
inList
.
getMode
()
+
","
+
inList
.
getOperationId
()
+
" "
+
inList
.
getName
());
loadingUtil
.
Retry
(
);
return
ResultBean
.
newOkResult
(
getList
(
inList
))
;
ResultBean
returnResult
=
loadingUtil
.
Retry
(
request
.
getLocale
()
);
returnResult
.
setData
(
getList
(
inList
));
return
returnResult
;
}
}
...
...
@@ -482,6 +482,11 @@ public class LoadingController {
}
}
}
//如果是ng或者提交失败的料,不能end
if
(
item
.
isFailure
()||
item
.
isNg
()){
return
ResultBean
.
newErrorResult
(-
1
,
"smfcore.micron.inlisttaskhasfail"
,
"操作失败:物料全部入库成功才能End"
);
}
}
if
(!
inList
.
getMode
().
equals
(
APIMODE
.
MATRET
))
{
...
...
src/main/java/com/neotel/smfcore/custom/micron1053/loading/util/LoadingUtil.java
查看文件 @
55a40d2
package
com
.
neotel
.
smfcore
.
custom
.
micron1053
.
loading
.
util
;
import
cn.hutool.core.util.ObjectUtil
;
import
com.neotel.smfcore.common.bean.ResultBean
;
import
com.neotel.smfcore.common.utils.Constants
;
import
com.neotel.smfcore.common.utils.StringUtils
;
import
com.neotel.smfcore.core.barcode.service.po.Barcode
;
...
...
@@ -13,22 +14,20 @@ import com.neotel.smfcore.core.inList.service.manager.IInListManager;
import
com.neotel.smfcore.core.inList.service.po.InList
;
import
com.neotel.smfcore.core.inList.service.po.InListItem
;
import
com.neotel.smfcore.core.inList.util.InListCache
;
import
com.neotel.smfcore.core.language.util.MessageUtils
;
import
com.neotel.smfcore.core.storage.service.manager.IStoragePosManager
;
import
com.neotel.smfcore.core.storage.service.po.StoragePos
;
import
com.neotel.smfcore.core.system.service.po.DataLog
;
import
com.neotel.smfcore.custom.micron1053.api.MicronApi
;
import
com.neotel.smfcore.custom.micron1053.bean.ML5NgReelInfo
;
import
com.neotel.smfcore.custom.micron1053.loading.Bean.LoadingInfo
;
import
com.neotel.smfcore.custom.micron1053.loading.dto.MaterialLoadingDto
;
import
com.neotel.smfcore.custom.micron1053.util.MicronDataCache
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
javax.swing.text.html.InlineView
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
import
java.util.*
;
@Service
@Slf4j
...
...
@@ -495,14 +494,22 @@ public class LoadingUtil {
inListCache
.
addInListToMap
(
inList
);
return
true
;
}
public
boolean
Retry
()
{
public
ResultBean
Retry
(
Locale
local
)
{
//TODO rfid入库完成
InList
inList
=
getInlist
();
ResultBean
bean
=
ResultBean
.
newErrorResult
(-
1
,
"smfcore.loading.retry.msgnone"
,
"没有需要Retry的物料"
);
if
(
inList
==
null
)
{
return
false
;
return
bean
;
}
//需要retry数量
int
aCount
=
0
;
//retry后失败数量
int
lCount
=
0
;
for
(
String
rfid
:
inList
.
getRfidList
())
{
List
<
StoragePos
>
posList
=
new
ArrayList
<>();
...
...
@@ -517,10 +524,39 @@ public class LoadingUtil {
}
}
if
(
posList
.
size
()
>
0
)
{
aCount
=
posList
.
size
();
PushToMes
(
rfid
,
inList
,
posList
);
}
}
return
true
;
if
(
aCount
>
0
)
{
for
(
String
rfid
:
inList
.
getRfidList
())
{
List
<
StoragePos
>
posList
=
new
ArrayList
<>();
//查找所有失败的重新推送
for
(
InListItem
item
:
inList
.
getInListItems
())
{
if
(
item
.
getRfid
().
equals
(
rfid
)
&&
item
.
getState
()
==
INITEM_STATUS
.
Fail
)
{
if
(
ObjectUtil
.
isEmpty
(
item
.
getPosName
()))
{
continue
;
}
lCount
++;
}
}
}
log
.
info
(
inList
.
getName
()
+
" retry , 总共retry数量="
+
aCount
+
", retry 后仍Fail的数量="
+
lCount
);
if
(
aCount
==
lCount
)
{
//全部失败
bean
=
ResultBean
.
newErrorResult
(
9
,
"smfcore.loading.retry.msgfail"
,
"Retry完成,{0}/{1}Retry失败"
,
new
String
[]{
aCount
+
""
,
aCount
+
""
});
}
else
if
(
lCount
==
0
)
{
//全部成功
bean
=
ResultBean
.
newOkResult
(
"smfcore.loading.retry.msgok"
,
"Retry完成,{0}/{1}Retry成功"
,
new
String
[]{
aCount
+
""
,
aCount
+
""
},
new
HashMap
<>());
}
else
{
//部分成功
bean
=
ResultBean
.
newOkResult
(
"smfcore.loading.retry.msg"
,
"Retry完成,{0}/{1}Retry成功,{2}/{3}Retry失败"
,
new
String
[]{(
aCount
-
lCount
)
+
""
,
aCount
+
""
,
lCount
+
""
,
aCount
+
""
},
new
HashMap
<>());
}
}
return
bean
;
}
...
...
src/main/resources/messages.properties
查看文件 @
55a40d2
...
...
@@ -373,4 +373,9 @@ smfcore.micron.inlisttaskNotEnd=\u64CD\u4F5C\u5931\u8D25\uFF1A\u5165\u5E93\u4EFB
#smfclient.loadMaterialFinished=loading material is finished: {0}
#smfclient.loadMaterialFailed=loading material failed:{0}
#smfclient.checkingMaterialOk=checking material is ok:{0}
#
smfclient.checkNg
=
checking material is ng:{0}
\ No newline at end of file
#smfclient.checkNg=checking material is ng:{0}
smfcore.loading.retry.msgnone
=
\u
6CA1
\u6709\u9700\u8981
Retry
\u7684\u7269\u6599
smfcore.loading.retry.msg
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u6210\u
529F
\u
FF0C{2}/{3}Retry
\u5931\u
8D25
smfcore.loading.retry.msgok
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u6210\u
529F
smfcore.loading.retry.msgfail
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u5931\u
8D25
smfcore.micron.inlisttaskhasfail
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u7269\u6599\u5168\u
90E8
\u5165\u
5E93
\u6210\u
529F
\u
624D
\u
80FDEnd
src/main/resources/messages_en_US.properties
查看文件 @
55a40d2
...
...
@@ -362,4 +362,9 @@ smfcore.error.barcode.hasOutTask=Serial No.(S)[0] already have retrieval task, c
smfcore.error.noRetryReel
=
No material found to retry
smfcore.micron.inlistCannotAbort
=
Operation failed: there are still inbound tasks to be completed
smfcore.micron.modeError
=
{0} Loading not finished.
smfcore.micron.inlisttaskNotEnd
=
Operation Failed: Task {0} Not Completed
\ No newline at end of file
smfcore.micron.inlisttaskNotEnd
=
Operation Failed: Task {0} Not Completed
smfcore.loading.retry.msgnone
=
No materials need to be retried
smfcore.loading.retry.msg
=
Retry completed, {0}/{1} retries succeeded, {2}/{3} retries failed
smfcore.loading.retry.msgok
=
Retry completed, {0}/{1} retries succeeded
smfcore.loading.retry.msgfail
=
Retry completed, {0}/{1} retries failed
smfcore.micron.inlisttaskhasfail
=
Operation failed: All materials must be successfully stored before ending
\ No newline at end of file
src/main/resources/messages_ja_JP.properties
查看文件 @
55a40d2
...
...
@@ -357,4 +357,9 @@ smfcore.error.barcode.hasOutTask=Serial No.(S)[0] already have retrieval task, c
smfcore.error.noRetryReel
=
No material found to retry
smfcore.micron.inlistCannotAbort
=
\u
64CD
\u
4F5C
\u
306B
\u5931\u6557\u3057\u
307E
\u3057\u
305F
\u
FF1A
\u
5B8C
\u
4E86
\u3059\u3079\u
304D
\u
53D7
\u
4FE1
\u
30BF
\u
30B9
\u
30AF
\u
304C
\u
6B8B
\u3063\u3066\u3044\u
307E
\u3059
smfcore.micron.modeError
=
{0}
\u5165\u
5E93
\u
672A
\u
5B8C
\u6210
smfcore.micron.inlisttaskNotEnd
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u5165\u
5E93
\u
4EFB
\u
52A1{0}
\u
672A
\u
5B8C
\u6210
\ No newline at end of file
smfcore.micron.inlisttaskNotEnd
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u5165\u
5E93
\u
4EFB
\u
52A1{0}
\u
672A
\u
5B8C
\u6210
smfcore.loading.retry.msgnone
=
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u3059\u
308B
\u
5FC5
\u8981\u
306E
\u3042\u
308B
\u
7D20
\u6750\u
306A
\u3057
smfcore.loading.retry.msg
=
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u
5B8C
\u
4E86
\u3001
{0}/{1}
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u6210\u
529F
\u3001
{2}/{3}
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u5931\u6557
smfcore.loading.retry.msgok
=
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u
5B8C
\u
4E86
\u3001
{0}/{1}
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u6210\u
529F
smfcore.loading.retry.msgfail
=
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u
5B8C
\u
4E86
\u3001
{0}/{1}
\u
30EA
\u
30C8
\u
30E9
\u
30A4
\u5931\u6557
smfcore.micron.inlisttaskhasfail
=
\u
64CD
\u
4F5C
\u5931\u6557\u
FF1A
\u3059\u3079\u3066\u
306E
\u
7D20
\u6750\u
304C
\u
6B63
\u
5E38
\u
306B
\u
30B9
\u
30C8
\u
30A2
\u3055\u
308C
\u
305F
\u
5F8C
\u3067\u
306A
\u3044\u3068\u
7D42
\u
4E86
\u3067\u
304D
\u
307E
\u
305B
\u3093
\ No newline at end of file
src/main/resources/messages_zh_CN.properties
查看文件 @
55a40d2
...
...
@@ -357,4 +357,9 @@ smfcore.error.barcode.hasOutTask=\u5E8F\u5217\u53F7[0] \u5DF2\u6709\u51FA\u5E93\
smfcore.error.noRetryReel
=
\u
6CA1
\u6709\u
53EF
\u
4EE5
\u
91CD
\u
8BD5
\u7684\u7269\u6599
smfcore.micron.inlistCannotAbort
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u
8FD8
\u6709\u5165\u
5E93
\u
4EFB
\u
52A1
\u
672A
\u
5B8C
\u6210
smfcore.micron.modeError
=
{0}
\u5165\u
5E93
\u
672A
\u
5B8C
\u6210
smfcore.micron.inlisttaskNotEnd
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u5165\u
5E93
\u
4EFB
\u
52A1{0}
\u
672A
\u
5B8C
\u6210
\ No newline at end of file
smfcore.micron.inlisttaskNotEnd
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u5165\u
5E93
\u
4EFB
\u
52A1{0}
\u
672A
\u
5B8C
\u6210
smfcore.loading.retry.msgnone
=
\u
6CA1
\u6709\u9700\u8981
Retry
\u7684\u7269\u6599
smfcore.loading.retry.msg
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u6210\u
529F
\u
FF0C{2}/{3}Retry
\u5931\u
8D25
smfcore.loading.retry.msgok
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u6210\u
529F
smfcore.loading.retry.msgfail
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u5931\u
8D25
smfcore.micron.inlisttaskhasfail
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u7269\u6599\u5168\u
90E8
\u5165\u
5E93
\u6210\u
529F
\u
624D
\u
80FDEnd
\ No newline at end of file
src/main/resources/messages_zh_TW.properties
查看文件 @
55a40d2
...
...
@@ -358,4 +358,9 @@ smfcore.error.barcode.hasOutTask=\u5E8F\u5217\u865F[0] \u5DF2\u6709\u51FA\u5EAB\
smfcore.error.noRetryReel
=
\u
6C92
\u6709\u
53EF
\u
4EE5
\u
91CD
\u
8A66
\u7684\u7269\u6599
smfcore.micron.inlistCannotAbort
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u
8FD8
\u6709\u5165\u
5E93
\u
4EFB
\u
52A1
\u
672A
\u
5B8C
\u6210
smfcore.micron.modeError
=
{0}
\u5165\u
5E93
\u
672A
\u
5B8C
\u6210
smfcore.micron.inlisttaskNotEnd
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u5165\u
5E93
\u
4EFB
\u
52A1{0}
\u
672A
\u
5B8C
\u6210
\ No newline at end of file
smfcore.micron.inlisttaskNotEnd
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u5165\u
5E93
\u
4EFB
\u
52A1{0}
\u
672A
\u
5B8C
\u6210
smfcore.loading.retry.msgnone
=
\u
6CA1
\u6709\u9700\u8981
Retry
\u7684\u7269\u6599
smfcore.loading.retry.msg
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u6210\u
529F
\u
FF0C{2}/{3}Retry
\u5931\u
8D25
smfcore.loading.retry.msgok
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u6210\u
529F
smfcore.loading.retry.msgfail
=
Retry
\u
5B8C
\u6210\u
FF0C{0}/{1}Retry
\u5931\u
8D25
smfcore.micron.inlisttaskhasfail
=
\u
64CD
\u
4F5C
\u5931\u
8D25
\u
FF1A
\u7269\u6599\u5168\u
90E8
\u5165\u
5E93
\u6210\u
529F
\u
624D
\u
80FDEnd
\ No newline at end of file
编写
预览
支持
Markdown
格式
附加文件
你添加了
0
人
到此讨论。请谨慎行事。
Finish editing this message first!
Cancel
请
注册
或
登录
后发表评论