学习收藏: Flex之State状态的…

本文介绍了Flex4中的状态管理概念及其实现方式,包括状态定义、改变状态、为状态设定属性和样式、添加或移除组件等内容,并展示了如何通过示例代码实现界面状态的转换。
原文: http://www.cnblogs.com/orchid/archive/2009/07/18/1526221.html

什么是状态(States)?

在很多富互联网应用,网页外观的改变基于用户的行为。一个状态就定义了组件的一种表现样式。要想使用状态,你应该首先定义一个默认的状态,然后在此基础上重写或者更改,这样就形成了一系列的其他样式。你可以添加、移除某些子元素,更改CSS或者属性的值,更改触发的事件。这些如何做到,我们会一一介绍。

下面我们先通过一个例子大概看一下如何使用State。
下面这段代码呈现的首先是一个登陆的界面,当你单击注册的链接,它就会变成一个注册界面。它是新的界面但不是新的页面。通过浏览器的后退按钮你是回不到原来的状态的。当然,我们也会有办法实现这个功能,以后会介绍。

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768">
<s:layout>
<s:BasicLayout/>
</s:layout>

<s:states>
<s:State name="default"/>
<s:State name="Register"/>
</s:states>

<!-- Set title of the Panel container based on the view state.-->
<s:Panel id="loginPanel"
title="Login" title.Register="Register">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<mx:Form id="loginForm">
<mx:FormItem label="Username:">
<s:TextInput/>
</mx:FormItem>
<mx:FormItem label="Password:">
<s:TextInput/>
</mx:FormItem>
<mx:FormItem id="confirm" label="Confirm:" includeIn="Register">
<!-- Add a TextInput control to the form for the Register view state. -->
<s:TextInput/>
</mx:FormItem>
<mx:FormItem direction="horizontal">
<!-- Use the LinkButton to change view state.-->
<mx:Spacer width="100%" id="spacer1"/>
<!-- Set label of the control based on the view state.-->
<mx:LinkButton id="registerLink"
label="Need to Register?"
label.Register="Return to Login"
click="currentState='Register'"
click.Register="currentState=''"/>
<s:Button id="loginButton"
label="Login" label.Register="Register"/>
</mx:FormItem>
</mx:Form>
</s:Panel>
</s:Application>


 

定义状态

flex4里状态的定义与flex3不同,你只需在<states></states>标签内定义状态就可以了。添加子元素和设置属性之类,不在这里进行了。如下所示: 

<s:states>
<s:State name="State1"/>
<s:State name="State2"/>
<s:State name="State3"/>
.
.
</s:states>

改变状态

UIComponent类定义了一个currentState属性,通过改变这个属性的值来更换状态,这个属性的默认值是定义在需在<states></states>标签内的第一个状态。例如:

<s:Button id="b1" label="Change to State 1" click="currentState='State2';"/>
<s:Button id="b2" label="Change to the default" click="currentState='State1';"/>
当然也可以通过UIComponent类的setCurrentState方法。

 

为一个状态设定属性,样式和事件

这是与flex3很不同的地方。在flex4里通过点语法来设定一个组件属于某个状态的属性值。例如:

<s:Button label="Default State" label.State2="New State"/>
上述的代码的意思是,这个按钮的lable值在State2状态下是New State,在其他状态下是Default State。
上述代码也可以这样写:
<s:Button >
<s:label>Default State</s:label>
<s:label.State2>new State</s:label.State2>
</s:Button>
要想在某个状态里清除某个属性的值,可以让属性值等于@clear。如下:
<Button color="0xFF0000" color.State1="@Clear"/>
对于事件也一样可以用点语法,例如:
<s:Button id="b1" label="Click Me"
click="ta1.text='hello';"
click.State1="ta1.text='goodbye'"/>

 

添加或移除组件

在flex4里,添加或移除某个组件,直接在这组件的属性里搞定。组件多了两个属性,includeIn和excludeFrom。includeIn,表示这个组件要被添加到属性值所指的状态,excludeFrom,表示这个组件要从属性值所指的状态中删除,includeIn和excludeFrom不能在同一个组件标签里出现,他们的值可以是多个状态,之间用逗号隔开。例如:

<s:states>
<s:State name="default"/>
<s:State name="addCheckBox"/>
<s:State name="addTextInput"/>
<s:State name="addCheckBoxandButton"/>
</s:states>

<s:CheckBox id="myCB" label="Checkbox"
includeIn="addCheckBox, addCheckBoxandButton"/>

<s:TextArea text="Exclude from addTextInput"
excludeFrom="addTextInput"/>

更改一个组件的父元素

一个组件的父元素也能变,你信吗?不过flex4真的做到了。用的就是这个<fx:Reparent>标签。还是看段代码吧!

<?xml version="1.0" encoding="utf-8"?>
<!-- states\NewStatesReparent.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/halo"
xmlns:s="library://ns.adobe.com/flex/spark">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<s:states>
<s:State name="Parent1"/>
<s:State name="Parent2"/>
</s:states>
<s:HGroup>
<s:Panel id="Panel1"
height="100" width="100"
title="Panel 1">
<s:Button id="setCB" includeIn="Parent1"/>
</s:Panel>
<s:Panel id="Panel2"
height="100" width="100"
title="Panel 2">
<fx:Reparent target="setCB" includeIn="Parent2"/>
</s:Panel>
</s:HGroup>
<s:HGroup>
<s:Button label="Parent 1"
click="currentState='Parent1'"
enabled.Parent1="false"/>
<s:Button label="Parent 2"
click="currentState='Parent2'"
enabled.Parent2="false"/>
</s:HGroup>
</s:Application>
这句话:<fx:Reparent target="setCB" includeIn="Parent2"/>什么意思呢?target的值setCB,就是我们要换父亲的组件就是setCB,看上面代码,你看它在哪里?setCB是第一个panel的Button的id吧!当转换到状态Parent2时,这个Button就成了第二个Panle的子元素。因为includeIn="Parent2"告诉我们了,在状态Parent2时,就要换了setCB的父元素,换成fx:Reparent的父元素,即第二个panel。

 

添加子元素时的控制

或许,你有这样的疑问。我为某个状态添加了一个子元素,这个元素在原来的状态是不存在的,至少,你看不见。那么,这个子元素时是在程序一运行时就被创建了还是在切换状态时才创建呢?
flex4的答案是在第一次换到需要这个子元素出现的状态时,它被创建。这是默认的情况。但是,如果这个子元素的创建需要很长时间,那么用户肯定在切换状态时感到延迟,这是不好的。所以,默认的情况,可以改。通过更改itemCreationPolicy,这是组件的一个属性,它有两个值:deferred,默认值,意思是在第一次需要这个组件出现时创建; immediate,它是在程序一被启动就会创建这个子元素。
一旦子元素被创建了,它就会一直存在了。

界面的改变不是仅仅只能通过State。通过导航器,如:Accordion,Tab Navigator,ViewStack containers你也能做到。到底用哪个,取决于你的应用需求以及用户界面的设计。

创建状态组

在flex4里,可以给状态分组,比如第一个状态和第二个状态是一组,第三个状态和第四个状态是一组等等。我们先看怎么分组?

<s:State name="default"/>
<s:State name="addCheckBox" stateGroups="Group1"/>
<s:State name="addTextInput"/>
<s:State name="addCheckBoxandButton" stateGroups="Group1"/>
只需添加一个stateGrooups这个属性。
添加分组的好处是什么呢?

如果一个组件在多个状态中出现,你就可以把这些状态划分到一组。那么,当出现这个族里任何一个状态时,这个组件的设置都会有效。点语法后面的值可以是组的名称,excludeFrom和includeIn的值也可以使组的名称。 

<?php date_default_timezone_set('Asia/Shanghai'); // 强制使用北京时间 require_once './include/conn.php'; // 获取当前用户历史消息并按时间排序 $h_user = getUserIP(); $rs = $db->get_one("select count(*) as tj from `h_kefu` where h_user = '{$h_user}' and h_who = 2 and h_isread = 0"); if($rs['tj'] > 0){ $query = "select * from `h_kefu` where h_user = '{$h_user}' and h_isread = 1 order by h_addTime asc,id asc"; }else{ $query = "select * from `h_kefu` where h_user = '{$h_user}' order by h_addTime asc,id asc"; } $result = $db->query($query); $messages = []; while($rs_list = $db->fetch_array($result)){ $messages[] = $rs_list; } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>安全访问</title> <link rel="stylesheet" href="/css/style7881.css"> <link rel="stylesheet" href="/iconfont/iconfont.css"> <link rel="stylesheet" href="/css/78812.6.0.css"> <link rel="stylesheet" href="https://res.qiyukf.net/sdk/tr/t_trade_mobile_m0_6dd2352f062861470694467bda09b417.css"> <link rel="icon" href="" type="image/x-icon"> <style> /* 原有主题样式保持不变 */ /** * =========================== * Visitor-end - Skin variable theme color - Common layout * =========================== */ /* trade */ .g-mask{background-color: #337eff;} .g-slide .slide_hd{background-color: #337eff;} .body_hd{background-color: #337eff;} .head-minsize{background-color: #337eff;} .m-msglist .msg_right .bot .text+.arrow:before{border-left-color:#337eff;} /* trade end */ .g-hd{background-color: #337eff;} .g-td.now_robot .u-btn, .g-td.now_robot .u-btn:link, .g-td.now_robot .u-btn:hover, .g-td.now_robot .u-btn:active{background-color: #337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .g-td.now_robot .form_cnt_send .u-btn{background-color: #337eff;} .g-td.now_robot .form_cnt_send .u-btn:link{background-color:#337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .g-td.now_robot .form_cnt_send .u-btn:hover{background-color:#337eff; -moz-opacity:0.8;-khtml-opacity: 0.8;opacity: 0.8;} .g-td.now_robot .form_cnt_send .u-btn:active{background-color:#337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} /** * ================== * module * ================== */ .m-msglist .msg_bubble .textcolor {color: #337eff !important} .m-msglist .msg .bordercolor{border: 1px solid #337eff} .m-msglist .msg_right .text, .m-msglist .msg_right .qa, .m-msglist .msg_right .qa-list, .m-msglist .msg_right .action, .m-msglist .msg_right .rich, .m-msglist .msg_right .mobile-file, .m-msglist .msg_right .question{background-color: #337eff;border-color: #337eff;} .m-msglist .msg_right .un-read{color: #337eff} .m-msglist .msg_right .text .arrow, .m-msglist .msg_right .text .arrow:before, .m-msglist .msg_right .image .arrow, .m-msglist .msg_right .image .arrow:before, .m-msglist .msg_right .qa .arrow, .m-msglist .msg_right .qa .arrow:before, .m-msglist .msg_right .qa-list .arrow, .m-msglist .msg_right .qa-list .arrow:before, .m-msglist .msg_right .action .arrow, .m-msglist .msg_right .action .arrow:before, .m-msglist .msg_right .rich .arrow, .m-msglist .msg_right .rich .arrow:before, .m-msglist .msg_right .question .arrow, .m-msglist .msg_right .question .arrow:before, .m-msglist .msg_right .audio .audio-wrap .arrow, .m-msglist .msg_right .audio .audio-wrap .arrow:before, .m-msglist .msg_right .mobile-file .arrow:before, .m-msglist .msg_right .mobile-file .arrow, .m-msglist .msg_right .crm-thirdorder .arrow{border-left-color:#337eff;} .m-msglist .msg_right .crm-thirdorder .text+.arrow:before{border-left-color:#337eff;} .m-msglist .msg_right .crm-thirdorder-bd{ border-color: #337eff;} .m-msglist .msg_left .u-icon-avater{background-color: #fff;} .m-msglist .msg_right .u-icon-avater{background-color: #337eff;} .m-msglist .msg_right .audio .audio-wrap, /* Bot/FAQ fusion template - Message - Web version */ .m-msglist .msg_right .crm-thirdorder .qiyu_template_mixReply .crm-thirdorder-bd{ border-color: #337eff; background-color: #337eff; color:#fff; } .m-msglist .msg_right .qiyu_template_mixReply+.arrow{ border-left-color: transparent; } .m-msglist .msg_right .qiyu_template_mixReply+.arrow:before{ border-left-color: #337eff; } /* Bot/FAQ fusion template - Message - Web version - Trade adaptation */ .m-msglist .msg_right .qiyu_template_mixReply .bot-bd{ border-color: #337eff; background-color: #337eff; color:#fff; } /* Bot/FAQ fusion template - Message - Mobile version */ .m-msglist .msg_right .crm-thirdorder .qiyu_template_mixReply .crm-thirdorder-bd+.arrow{ border-left-color: transparent; } .m-msglist .msg_right .crm-thirdorder .qiyu_template_mixReply .crm-thirdorder-bd+.arrow:before{ border-left-color: #337eff; } .m-modal-confirmphoto .ok{background-color: #337eff;} .m-modal-confirmphoto .ok:hover{background-color:#337eff; -moz-opacity:0.8;-khtml-opacity: 0.8;opacity: 0.8;} .m-modal-confirmphoto .ok:active{background-color:#337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .m-form-evaluate .form_item.itm-tag-list .tag-item.active{color: #337eff; border-color: #337eff} .m-form-evaluate .m-solve .z-sel{color: #337eff; border-color: #337eff} .m-modal-filetrans .ok{background-color: #337eff;} .m-modal-filetrans .ok:hover{background-color:#337eff; -moz-opacity:0.8;-khtml-opacity: 0.8;opacity: 0.8;} .m-modal-filetrans .ok:active{background-color:#337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .m-comment .comment_btn{background-color: #337eff;} .m-comment .comment_btn:hover{background-color:#337eff; -moz-opacity:0.8;-khtml-opacity: 0.8;opacity: 0.8;} .m-comment .comment_btn:active{background-color:#337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .m-botentry li {border-color : #337eff} .m-botentry li.highLight:after {background-color : #337eff} .radio_button .button-multiple .enter-content-item .reply-button-li.highlight{color:#337eff;border-color:#337eff} .radio_button .button-multiple-enter.highlight{background:#337eff} .bot .date-picker {background-color : #337eff} .rmc-picker-popup-header .rmc-picker-popup-item { color: #337eff} /* Theme color of pagination button */ .g-chat-body .chat_show_bot .icon-triangle-left, .g-chat-body .chat_show_bot .icon-triangle-right, .radio_button .icon-triangle-left, .radio_button .icon-triangle-right { background-color:#337eff; } /* Theme color of title of robot's frequently asked question card style */ .m-msglist .msg .qa-list >.p.p_card >.qa_label {color: #337eff} .m-msglist .msg .qa-list >.p.p_card >.qa_label-contain >.qa_label {color: #337eff} .m-msglist .msg .qa-list >.p.p_card >.qa_label-contain >.qa_select > .tabs > .z-current {color: #337eff} /* Theme color of robot's frequently used shortcut entry */ .m-msglist .msg .quick-entry-icon {color: #337eff} /* Theme color of robot's evaluation */ .m-msglist .msg_bubble .eval-robot-answer.z-sel {color: #337eff} .m-msglist .msg .qa .qa_eval_tag.sel {color: #337eff; border: 1px solid #337eff} .m-msglist .msg .qa-list .qa_eval_tag.sel {color: #337eff; border: 1px solid #337eff} .z-disabled button, .z-disabled button:link, .z-disabled button:hover, .z-disabled button:active{background-color: #337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .u-btn, .u-btn-loadding, .u-btn:link, .u-btn:hover{background-color:#337eff;} .u-btn:active, .u-btn.z-disabled, .u-btn.z-disabled:hover, .u-btn.z-disabled:active, .u-btn.z-disabled:link{background-color: #337eff; -moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .u-btn-disabled, .u-btn-disabled:link, .u-btn-disabled:hover, .u-btn-disabled:active{background-color: #337eff;-moz-opacity:0.6;-khtml-opacity: 0.6;opacity: 0.6;} .u-border-btn, .u-border-btn:hover{background-color: #fff;border: 1px solid #337eff; color: #337eff !important} .service-action, .service-action:link, .service-action:hover{background-color:#337eff !important; color: #fff !important; border: 0 !important} .m-pre-evaluation .pre-evaluation-box .evaluation{background-color: #337eff}, </style> <style type="text/css"> ::-webkit-scrollbar { width: 4px; height: 4px; } ::-webkit-scrollbar-button { display: none; } ::-webkit-scrollbar-thumb { border-radius: 3px; background: #dddfe0; } ::-webkit-scrollbar-track { width: 7px; height: 7px; } ::-webkit-scrollbar-track-piece { background: transparent; } .vc-switch { bottom: 170px !important; } </style> <style>.rmc-picker, .rmc-multi-picker { height: 238px; /*34*7*/ } .rmc-multi-picker { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; } .rmc-picker-item { font-size: 16px; height: 34px; line-height: 34px; padding: 0 10px; white-space: nowrap; position: relative; overflow: hidden; text-overflow: ellipsis; color: #9b9b9b; width: 100%; -webkit-box-sizing: border-box; box-sizing: border-box; } .rmc-picker { display: block; position: relative; overflow: hidden; width: 100%; -webkit-box-flex: 1; -ms-flex: 1; flex: 1; text-align: center; } .rmc-picker-mask { position: absolute; left: 0; top: 0; height: 100%; margin: 0 auto; width: 100%; z-index: 3; background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.95)), to(rgba(255, 255, 255, 0.6))), -webkit-gradient(linear, left bottom, left top, from(rgba(255, 255, 255, 0.95)), to(rgba(255, 255, 255, 0.6))); background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6)), linear-gradient(to top, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.6)); background-position: top, bottom; background-size: 100% 204px; background-repeat: no-repeat; } .rmc-picker-content { position: absolute; left: 0; top: 0; width: 100%; z-index: 1; } .rmc-picker-indicator { -webkit-box-sizing: border-box; box-sizing: border-box; width: 100%; height: 34px; position: absolute; left: 0; top: 102px; z-index: 3; border-top: 1PX solid #ddd; border-bottom: 1PX solid #ddd; } </style> <style>.rmc-date-picker { display: -ms-flexbox; display: -webkit-box; display: flex; -ms-flex-align: center; -webkit-box-align: center; align-items: center; padding: 10px 0; } .rmc-date-picker-item { -ms-flex: 1; -webkit-box-flex: 1; flex: 1; text-align: center; } </style> <style>.rmc-picker-popup { left: 0; bottom: 0; position: fixed; width: 100%; background-color: #fff; } .rmc-picker-popup-close { display: none; } .rmc-picker-popup-wrap { position: fixed; overflow: auto; top: 0; right: 0; bottom: 0; left: 0; z-index: 1050; overflow-scrolling: touch; outline: 0; } .rmc-picker-popup-mask { position: fixed; top: 0; right: 0; left: 0; bottom: 0; background-color: #373737; background-color: rgba(55, 55, 55, 0.6); height: 100%; filter: alpha(opacity=50); z-index: 1050; } .rmc-picker-popup-mask-hidden { display: none; } .rmc-picker-popup-header { background-image: -webkit-gradient(linear, left top, left bottom, from(#e7e7e7), color-stop(#e7e7e7), color-stop(transparent), to(transparent)); background-image: linear-gradient(to bottom, #e7e7e7, #e7e7e7, transparent, transparent); background-position: bottom; background-size: 100% 1px; background-repeat: no-repeat; position: relative; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: horizontal; -webkit-box-direction: normal; -ms-flex-direction: row; flex-direction: row; } .rmc-picker-popup-header-left, .rmc-picker-popup-header-right { padding-left: 15px; padding-right: 15px; } .rmc-picker-popup-item { color: #0ae; font-size: 18px; height: 44px; line-height: 44px; cursor: pointer; -webkit-box-sizing: border-box; box-sizing: border-box; text-align: center; -webkit-tap-highlight-color: transparent; } .rmc-picker-popup-item-active { background-color: #ddd; } .rmc-picker-popup-title { -webkit-box-flex: 1; -ms-flex: 1; flex: 1; color: #666; cursor: default; } .rmc-picker-popup-fade-enter, .rmc-picker-popup-fade-appear { opacity: 0; -webkit-animation-duration: 0.3s; animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2); animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2); -webkit-animation-play-state: paused; animation-play-state: paused; } .rmc-picker-popup-fade-leave { -webkit-animation-duration: 0.3s; animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2); animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2); -webkit-animation-play-state: paused; animation-play-state: paused; } .rmc-picker-popup-fade-enter.rmc-picker-popup-fade-enter-active, .rmc-picker-popup-fade-appear.rmc-picker-popup-fade-appear-active { -webkit-animation-name: rmcPopupPickerFadeIn; animation-name: rmcPopupPickerFadeIn; -webkit-animation-play-state: running; animation-play-state: running; } .rmc-picker-popup-fade-leave.rmc-picker-popup-fade-leave-active { -webkit-animation-name: rmcPopupPickerFadeOut; animation-name: rmcPopupPickerFadeOut; -webkit-animation-play-state: running; animation-play-state: running; } @-webkit-keyframes rmcPopupPickerFadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes rmcPopupPickerFadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @-webkit-keyframes rmcPopupPickerFadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } @keyframes rmcPopupPickerFadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } .rmc-picker-popup-slide-fade-enter, .rmc-picker-popup-slide-fade-appear { -webkit-transform: translate(0, 100%); -ms-transform: translate(0, 100%); transform: translate(0, 100%); } .rmc-picker-popup-slide-fade-enter, .rmc-picker-popup-slide-fade-appear, .rmc-picker-popup-slide-fade-leave { -webkit-animation-duration: 0.3s; animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2); animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2); -webkit-animation-play-state: paused; animation-play-state: paused; } .rmc-picker-popup-slide-fade-enter.rmc-picker-popup-slide-fade-enter-active, .rmc-picker-popup-slide-fade-appear.rmc-picker-popup-slide-fade-appear-active { -webkit-animation-name: rmcPopupPickerSlideFadeIn; animation-name: rmcPopupPickerSlideFadeIn; -webkit-animation-play-state: running; animation-play-state: running; } .rmc-picker-popup-slide-fade-leave.rmc-picker-popup-slide-fade-leave-active { -webkit-animation-name: rmcPopupPickerSlideFadeOut; animation-name: rmcPopupPickerSlideFadeOut; -webkit-animation-play-state: running; animation-play-state: running; } @-webkit-keyframes rmcPopupPickerSlideFadeIn { 0% { -webkit-transform: translate(0, 100%); transform: translate(0, 100%); } 100% { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } @keyframes rmcPopupPickerSlideFadeIn { 0% { -webkit-transform: translate(0, 100%); transform: translate(0, 100%); } 100% { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } @-webkit-keyframes rmcPopupPickerSlideFadeOut { 0% { -webkit-transform: translate(0, 0); transform: translate(0, 0); } 100% { -webkit-transform: translate(0, 100%); transform: translate(0, 100%); } } @keyframes rmcPopupPickerSlideFadeOut { 0% { -webkit-transform: translate(0, 0); transform: translate(0, 0); } 100% { -webkit-transform: translate(0, 100%); transform: translate(0, 100%); } } </style> <style>.fishd-spin { font-family: "Monospaced Number", "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.5; color: #333; -webkit-box-sizing: border-box; box-sizing: border-box; margin: 0; padding: 0; list-style: none; color: #337eff; opacity: 0; position: absolute; -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); display: none; } .fishd-spin-container { height: 100%; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center; } .fishd-spin-text-loading { color: #999; text-align: center; } .fishd-spin-text-loading-dot i { -webkit-animation: fishdDot 2s steps(1, end) infinite; animation: fishdDot 2s steps(1, end) infinite; } .fishd-spin-spinning { height: 100%; opacity: 1; position: static; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center; } .fishd-spin-nested-loading { position: relative; } .fishd-spin-nested-loading > div > .fishd-spin { position: absolute; height: 100%; max-height: 320px; width: 100%; z-index: 4; } .fishd-spin-nested-loading > div > .fishd-spin-sm .fishd-spin-text { font-size: 12px; } .fishd-spin-nested-loading > div > .fishd-spin-lg .fishd-spin-text { font-size: 18px; } .fishd-spin-nested { position: relative; zoom: 1; } .fishd-spin-nested:before, .fishd-spin-nested:after { content: ""; display: table; } .fishd-spin-nested:after { clear: both; } .fishd-spin-nested:before, .fishd-spin-nested:after { content: ""; display: table; } .fishd-spin-nested:after { clear: both; } .fishd-spin-blur { pointer-events: none; user-select: none; overflow: hidden; opacity: 0.7; -webkit-filter: blur(0.5px); filter: blur(0.5px); /* autoprefixer: off */ filter: progid\:DXImageTransform\.Microsoft\.Blur(PixelRadius\=1, MakeShadow\=false); } .fishd-spin-blur:after { content: ''; position: absolute; left: 0; right: 0; top: 0; bottom: 0; background: #fff; opacity: 0.3; -webkit-transition: all 0.3s; transition: all 0.3s; z-index: 10; } .fishd-spin-tip { color: #666; } .fishd-spin-dot { position: relative; display: inline-block; font-size: 20px; width: 20px; height: 20px; } .fishd-spin-dot i { width: 9px; height: 9px; border-radius: 100%; background-color: #337eff; -webkit-transform: scale(0.75); -ms-transform: scale(0.75); transform: scale(0.75); display: block; position: absolute; opacity: 0.3; -webkit-animation: fishdSpinMove 1s infinite linear alternate; animation: fishdSpinMove 1s infinite linear alternate; -webkit-transform-origin: 50% 50%; -ms-transform-origin: 50% 50%; transform-origin: 50% 50%; } .fishd-spin-dot i:nth-child(1) { left: 0; top: 0; } .fishd-spin-dot i:nth-child(2) { right: 0; top: 0; -webkit-animation-delay: 0.4s; animation-delay: 0.4s; } .fishd-spin-dot i:nth-child(3) { right: 0; bottom: 0; -webkit-animation-delay: 0.8s; animation-delay: 0.8s; } .fishd-spin-dot i:nth-child(4) { left: 0; bottom: 0; -webkit-animation-delay: 1.2s; animation-delay: 1.2s; } .fishd-spin-dot-spin { -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); -webkit-animation: fishdRotate 1.2s infinite linear; animation: fishdRotate 1.2s infinite linear; } .fishd-spin-sm .fishd-spin-dot { font-size: 14px; width: 14px; height: 14px; } .fishd-spin-sm .fishd-spin-dot i { width: 6px; height: 6px; } .fishd-spin-lg .fishd-spin-dot { font-size: 32px; width: 32px; height: 32px; } .fishd-spin-lg .fishd-spin-dot i { width: 14px; height: 14px; } .fishd-spin.fishd-spin-show-text .fishd-spin-text { display: inline-block; margin-left: 10px; color: #999; } @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { /* IE10+ */ .fishd-spin-blur { background: #fff; opacity: 0.5; } } @-webkit-keyframes fishdDot { 0%, 20% { opacity: 0; } 40% { opacity: 1; } 60% { text-shadow: 0.5em 0; } 80%, 100% { text-shadow: 0.5em 0, 1em 0; } } @keyframes fishdDot { 0%, 20% { opacity: 0; } 40% { opacity: 1; } 60% { text-shadow: 0.5em 0; } 80%, 100% { text-shadow: 0.5em 0, 1em 0; } } @-webkit-keyframes fishdSpinMove { to { opacity: 1; } } @keyframes fishdSpinMove { to { opacity: 1; } } @-webkit-keyframes fishdRotate { to { -webkit-transform: rotate(405deg); transform: rotate(405deg); } } @keyframes fishdRotate { to { -webkit-transform: rotate(405deg); transform: rotate(405deg); } } </style> <style type="text/css">.auto-1691801729900{font-size:12px;line-height:160%;} .auto-1691801729900 a{margin:0 2px;padding:2px 8px;color:#333;border:1px solid #aaa;text-decoration:none;} .auto-1691801729900 .js-disabled{cursor:default;} .auto-1691801729900 .js-selected{cursor:default;background-color:#bbb;} .auto-1691801729918{position:absolute;width:0;height:0;overflow:hidden;} .auto-1691801729919-parent{position:relative;} .m-check-item { cursor: pointer; display: inline-block; position: relative; height: 26px; line-height: 16px; width: 120px; font-size: 14px; color: #333333; border: 1px solid #e8e8e8; border-radius: 2px; padding: 4px 20px; box-sizing: border-box; text-align: center; margin-top: 10px; margin-right: 9px; } .m-check-item:last-child { margin-right: 0px; } .m-check-item.checked { border-color: #5092e1; } .m-check-item>input { display: inline-block\9; position: absolute; left: 5px; top: 3px; } .m-check-item>span { display: inline-block; width: 100%; overflow: hidden; word-wrap: normal; white-space: nowrap; text-overflow: ellipsis; } .m-check-item>i { position: absolute; display: none; top: 6px; right: 4px; } .m-check-item.checked>i { border-color: #5092e1; display: block; }</style> <style> .bd_title{ position: relative; font-size: 16px; color: #fff; height: 48px; line-height: 48px; background: #5092e1; zoom: 1; background-color: #337eff; font-size: 16px; } .bd_title .left{ position: relative; padding: 0 16px; overflow: hidden; height: 100%; } .bd_title .left .icon{ float: left; margin-top: 12px; margin-right: 10px; line-height: initial; height: 100%; } .bd_title .left .text{ width: auto; text-align: center; margin-right: 30px; } .kf_box{ margin-top: 15px; width: 100%; margin: 10px 0; text-align: center; } .kf_box h3{ display: inline-block; padding: 0 9px; border-radius: 15px; word-break: break-all; font-size: 12px; color: #fff; line-height: 2; background: #d0d3d9; } .bd_title .but{ position: relative; top: -50px; float: right; margin-right: 8px; } .conts{ display: inline-block; margin-top: 0px; padding: 3px 8px; border: 1px solid #fff; border-radius: 3px; color: #fff; font-size: 13px; background-color: transparent; vertical-align: middle; } ul { display: block; list-style-type: disc; padding: 4px 16px 12px 12px; } li{ list-style: none; } /* 聊天容器调整 */ .msg-box { padding: 0; } .PullToRefresh-content { padding: 0 5px; } /* 头像样式 */ .img1, .img2 { width: 45px; height: 45px; overflow: hidden; border-radius: 50%; display: flex; align-items: center; justify-content: center; } .img1 { float: left; margin-left: 0 !important; margin-right: 8px; } .img2 { float: right; margin-right: 0 !important; margin-left: 8px; } .img1 img, .img2 img, .Avatar img, .u-icon-avater { border-radius: 50% !important; width: 80% !important; height: 80% !important; object-fit: cover !important; display: block; } .Message-main .Avatar { width: 45px !important; height: 45px !important; border-radius: 50% !important; overflow: hidden !important; display: flex !important; align-items: center !important; justify-content: center !important; } /* 消息气泡样式 */ .msg .content { max-width: 70%; padding: 8px 12px; border-radius: 18px; margin-top: 5px; } .msg.clearfix .content { float: left; margin-left: 0; background-color: #f0f0f0; color: #333; } .msg.clearfix.on .content { float: right; margin-right: 0; background-color: #337eff; color: #fff; } /* 时间样式:小装饰,固定中间,不独占一行 */ .time-separator { font-size: 10px !important; color: #999 !important; margin: 8px auto !important; padding: 2px 8px !important; background: rgba(0,0,0,0.05) !important; border-radius: 10px !important; display: inline-block !important; text-align: center !important; clear: both !important; position: relative !important; left: 50% !important; transform: translateX(-50%) !important; z-index: 1 !important; } </style> </head> <body class="m-mobile-frame" id="auto-id-1691801730040" style="font-size:14px"> <div class="service"> <div class="chat_show_loading u-errtip j-loading f-hide" id="jz"><img src="https://qiyukf.nosdn.127.net/sdk/res/default/loading_3782900ab9d04a1465e574a7d50af408.gif"> <span class="j-loading-txt">正在连接,请稍等...</span></div> <div class="service"> <div class="chat_show_loading u-errtip j-loading f-hide" id="ljcg" style="margin-left: -50px;"><span class="j-loading-txt">连接成功</span></div> <div class="bd_title"> <div class="left"> <div class="icon"> <a href="javascript:;" onclick="history.go(-1);"><img src="https://qiyukf.nosdn.127.net/sdk/res/img/prev_fb1146ba7f3ea25f1bbb8db373fb710f.png" alt></a> </div> <div class="text"> <span class="name">闲鱼官方在线客服中心</span> </div> </div> </div> <div class="msg-box"> <div class="PullToRefresh-content PullToRefresh-transition" style="transform: translate3d(0px, 0px, 0px);"> <div class="PullToRefresh-indicator"></div> <div class="Message left" data-id="167012827668700566" data-type="card"> <div class="Message-main"> <div class="Message-inner"> <div class="Message-content" role="alert" aria-live="assertive" aria-atomic="false"> <div class="ComponentMessage" data-spm-anchor-id="a311a.7996332.0.i0.7f0e3f5dNPC0Mt"> </div> </div> </div> </div> </div> <div class="kf_box"> <h3>闲鱼人工客服&nbsp;为您服务</h3> </div> <!-- 初始客服消息 --> <div class="Message left" data-id="169177542802256502" data-type="text"> <div class="Message-main"> <span class="Avatar Avatar--md Avatar--circle"> <img src="/img/xy1.png"></span> <div class="Message-inner"> <div class="Message-content" role="alert" aria-live="assertive" aria-atomic="false"> <div class="Bubble text" data-type="text"><p><span style="color:red;">闲鱼网在线授权客服工号10788号很高兴为您服务,请简单描述您需要咨询的问题!您好请问有什么可以帮到您呢?</span></p> </div> </div> </div> </div> </div> </div> <!-- 历史消息(带时间间隔判断) --> <?php $lastTime = 0; // 上一条消息时间戳 foreach($messages as $rs_list){ $currentTime = strtotime($rs_list['h_addTime']); $sendTimeText = date('H:i', $currentTime); // 时间间隔超过60秒则显示时间分隔符 if($lastTime == 0 || ($currentTime - $lastTime) > 60){ echo '<div class="time-separator">' . $sendTimeText . '</div>'; } // 输出消息内容 if($rs_list['h_who'] == 2){ // 客服消息 echo ' <div class="msg clearfix"> <div class="img1"> <img src="/img/xy1.png" alt=""> </div> <div class="content"> <span>' . $rs_list['h_content'] . '</span> </div> </div> '; } else { // 客户消息 echo ' <div class="msg clearfix on"> <div class="img2"> <img src="http://tc.chuzhong.icu/i/2025/11/10/wei91.png" alt=""> </div> <div class="content"> <span>' . $rs_list['h_content'] . '</span> </div> </div> '; } $lastTime = $currentTime; // 更新上一条消息时间戳 } ?> <p id="msgzone"></p> </div> <div class="msg-post"> <textarea enterkeyhint="send" rows="1" placeholder="输入消息..." type="text" class="Input Input--outline Composer-input" id="cont"></textarea> <svg id="bd" onclick="bd5()" style="width: 33px; height: 33px; margin: 5px 0px 5px 5px; display: block;" t="1687901693741" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8335" width="200" height="200"> <path d="M696 480H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z" p-id="8336"></path> <path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" p-id="8337"></path> </svg> <svg t="1687903493556" id="bd999" onclick="bd6()" style="width: 33px; height: 33px; margin: 5px 0px 5px 5px; display: none;" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2305" width="200" height="200"> <path d="M512 42.666667c259.2 0 469.333333 210.133333 469.333333 469.333333s-210.133333 469.333333-469.333333 469.333333S42.666667 771.2 42.666667 512 252.8 42.666667 512 42.666667z m0 64C288.149333 106.666667 106.666667 288.149333 106.666667 512s181.482667 405.333333 405.333333 405.333333 405.333333-181.482667 405.333333-405.333333S735.850667 106.666667 512 106.666667z m-104.746667 256a8.533333 8.533333 0 0 1 6.037334 2.496L512 463.850667l98.688-98.688a8.533333 8.533333 0 0 1 6.037333-2.496h66.346667a8.533333 8.533333 0 0 1 6.037333 14.570666l-131.84 131.861334 137.642667 137.664a8.533333 8.533333 0 0 1-6.037333 14.570666h-66.346667a8.533333 8.533333 0 0 1-6.037333-2.496L512 554.346667l-104.512 104.490666a8.533333 8.533333 0 0 1-6.037333 2.496h-66.346667a8.533333 8.533333 0 0 1-6.016-14.570666l137.664-137.664-131.861333-131.861334A8.533333 8.533333 0 0 1 340.906667 362.666667h66.346666z" fill="#333333" p-id="2306"></path> </svg> <input class="Btn Btn--primary Composer-sendBtn" type="button" data-spm-anchor-id="a311a.7996332.0.i13.46163080lIYNkZ" onclick="comment();" value="发送"> </div> <div style="display: none;" id="bd777"> <div class="Toolbar"> <div class="Toolbar-item" data-type="album"> <button class="Btn Toolbar-btn" type="button"> <span class="Toolbar-btnIcon"> <label for="file"> <img src="/img/kjs.png"> </label> </span> <span class="Toolbar-btnText">相册</span> <input type="file" accept="image/*" id="file" onchange="upload()" style="width:0;height:0"> </button> </div> </div> </div> </div> <script src="https://cdn.staticfile.org/jquery/3.5.1/jquery.min.js"></script> <script src="https://g.alicdn.com/chatui/icons/2.1.0/index.js"></script> <script src="js/jquery.min.js"></script> <script src="layer/layer.js"></script> <script src="js/indexsjk.js"></script> <script> // 记录最后一条消息的时间戳(用于新消息时间判断) let lastMessageTime = <?php echo empty($messages) ? 0 : strtotime(end($messages)['h_addTime']); ?>; // 重写发送消息函数,添加时间间隔判断 function comment() { const cont = $('#cont').val().trim(); if (!cont) return; // 获取当前北京时间 const now = new Date(); const currentTime = now.getTime() / 1000; // 转换为秒级时间戳 const timeText = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0'); // 间隔超过60秒显示时间分隔符 if (lastMessageTime === 0 || (currentTime - lastMessageTime) > 60) { $('#msgzone').before(`<div class="time-separator">${timeText}</div>`); } // 发送消息(保持原有逻辑) $.post('ajax.php?act=send', {content: cont}, function(data) { if (data.code === 1) { $('#msgzone').before(` <div class="msg clearfix on"> <div class="img2"> <img src="http://tc.chuzhong.icu/i/2025/11/10/wei91.png" alt=""> </div> <div class="content"> <span>${cont}</span> </div> </div> `); $('#cont').val(''); lastMessageTime = currentTime; // 更新最后一条消息时间 // 滚动到底部 $('.PullToRefresh-content').scrollTop($('.PullToRefresh-content')[0].scrollHeight); } }, 'json'); } </script> </body> </html> 点击发送按钮没反应
最新发布
11-11
<template> <div :class="['talk-item', isLeft? 'left' : 'right']" :key="data.timestamp" v-if="isTemp == 1? true : text" > <!-- 遍历布局节点 --> <template v-for="it in layout[isLeft? 'left' : 'right']"> <!-- 头像部分 --> <div v-if="it === LayoutNode.AVATAR" :key="`${data.startFrame}-${it}`" class="talk-avatar" > <el-avatar class="avatar" size="large" :style="{ 'background-image': backgroundImg }" > <!-- avatar 用于展示头像 --> </el-avatar> </div> <!-- 文本段落部分 --> <div v-if="it === LayoutNode.PARAGRAPH" :key="`${data.startFrame}-${it}`" class="highlighted" :class="{ 'talk-paragraph': true, 'highlight': highlight, 'blink': isBlink }" > <!-- 时间和其他操作按钮 --> <div class="time"> <!-- 左侧时间显示 --> <span v-if="isLeft" style="font-size: 12px;"> {{ timestamp | formatDate('hh:mm:ss') }} </span> <!-- 复制按钮 --> <i v-if="canCopy && data.text.length > 1" class="el-icon-s-claim" @click="setAnswer" ></i> <!--编辑按钮--> <el-tooltip v-if="canEdit" effect="dark" content="编辑" placement="top-start" class="item" > <i class="el-icon-edit" @click="changeEditStatus" v-if="(data.text.length > 0 && status == RealTimeStatus.History) || (data.text.length > 0 && status == RealTimeStatus.RealTime)" ></i> </el-tooltip> <!-- 右侧时间显示 --> <span v-if="!isLeft" style="font-size: 11px;"> {{ timestamp | formatDate('hh:mm:ss') }} </span> </div> <!-- 文本内容容器 --> <div :class="['text-container']" ref="textContainer" @dblclick="fetchTalkItem"> <!-- 统一前置 --> <i class="isIcon iconfont icon-yinbo"></i> <!-- 编辑模式下的文本框 --> <el-input v-if="isEdit" :class="['text-box',{highlighted: isPlaying }]" type="textarea" style="font-size: 16px;" :rows="3" v-model="data.text" @input="handleInput" ></el-input> <!-- 非编辑模式下的文本显示 --> <div v-else v-html="`${text} &nbsp;`" :class="['text-box']" style="font-size: 16px;"></div> <!-- 段落进度条 --> <div class="progress-highlight" :style="{ width: progressWidth }"></div> </div> <!-- 命中标签 --> <div class="isHit" v-if="matchedHitRuleNames.length > 0"> <i style="color:#007bff;" class="el-icon-circle-check"></i> <span v-for="(name, index) in matchedHitRuleNames" :key="index"> &nbsp;{{ name }} </span> </div> </div> </template> </div> </template> <script lang="ts"> import { Message, MessageBox } from "element-ui"; import { Component, Prop, Vue, Watch } from "nuxt-property-decorator"; import { Keyword, TalkItem, TalkState } from "../../../types"; import * as dayjs from "dayjs"; import '../../../assets/iconFont/iconfont.css' // import img_police from "~/assets/img/anonymity-police.jpg" // 设置谈话人 被谈话人的位置 import img_police from "../../../assets/img/anonymity-telephonist.jpg"; import img_usr from "../../../assets/img/anonymity-square.png"; import { State } from "vuex-class"; const enum LayoutNode { AVATAR, PARAGRAPH, } /** * ZKer 设置谈话人是否在左边 */ const IsLeft = true; @Component({ name: "talkItem", components: {}, filters: { formatDate(value) { return dayjs(new Date(value)).format("YYYY-MM-DD HH:mm:ss"); }, }, }) export default class extends Vue { @Prop({ type: Object, required: true, default: {} }) data!: TalkItem; @Prop({ type: Boolean, required: true }) highlight: boolean; @Prop({ type: Boolean, required: true }) isPolice: boolean; @Prop({ type: Boolean, required: true }) canCopy: boolean; @Prop({ required: true }) canEdit; // 判断编辑是否可修改(历史谈话可/实时谈话) @Prop({ type: [], required: true }) RealTimeStatus; // 历史 实时谈话状态 @Prop({ required: true }) status; @Prop() busGroups!: any; isEdit: boolean = false; isLeft: boolean = false; isBlink: boolean = false; isTemp: any = null; // 判断用户删除(监听input的输入事件时 证明在删除 否则反之) timestamp: string | number = "&nbsp;"; text: string = "&nbsp;"; backgroundImg: string = `url(${this.isPolice? img_police : img_usr}`; LayoutNode: any = LayoutNode; layout: any = { // 统计左(对话数据)右() left: [LayoutNode.AVATAR, LayoutNode.PARAGRAPH], right: [LayoutNode.PARAGRAPH, LayoutNode.AVATAR], }; @State talk!: TalkState; matchedHitRuleNames: string[] = []; // -=-= @Prop({ type: Boolean, default: false }) isPlaying!: boolean; progressWidth: string = '0%'; duration: number = null; // 播放完一段 animationFrameId: number | null = null; @Watch('isPlaying') onIsPlayingChange(isPlaying: boolean) { if (isPlaying) { this.startProgressAnimation(); } else { this.stopProgressAnimation(); } } startProgressAnimation() { const startTime = performance.now(); const animate = (now: number) => { const elapsed = now - startTime; const progress = Math.min(elapsed / this.duration, 1); this.progressWidth = `${progress * 100}%`; if (progress < 1) { this.animationFrameId = requestAnimationFrame(animate); } }; this.animationFrameId = requestAnimationFrame(animate); } stopProgressAnimation() { if (this.animationFrameId !== null) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; } this.progressWidth = '0%'; } beforeDestroy() { this.stopProgressAnimation(); } // -=-= @Watch("data", { deep: true, immediate: true }) onDataChange(newData: TalkItem) { // if (!newData.policy) { // this.console.debug(newData,newData.last, newData.text, JSON.stringify(newData.keywords), newData.startFrame) // } this.text = newData.text; this.setKeyword(true); } @Watch("canEdit") onCanEditChange(newValue: boolean) { if (newValue) { this.$store.dispatch("talk/FetchKeywords"); } } // handleClick(){//单击文本字段 // if (this.status == this.RealTimeStatus.History) { // this.$emit('jump', this.data) // console.log('this.newData',this.data); // // } // console.log(121212); // // } created() { // 对讲内容 // console.log(this.data, "opop"); // 命中次数 // console.log(this.busGroups.hitRules, "1212121"); } /** * 统一中英文标点符号为英文格式 */ normalizePunctuation(text: string): string { const punctuationMap: { [key: string]: string } = { ',': ',', '。': '.', '?': '?', '!': '!', ';': ';', ':': ':', '“': '"', '”': '"', '‘': "'", '’': "'", '(': '(', ')': ')', '【': '[', '】': ']', '《': '<', '》': '>', '、': '\\', '——': '-', '…': '...', '—': '-', '·': '.' }; return text.replace(/[^\u0000-\u00ff]/g, ch => punctuationMap[ch] || ch); } scrollToParagraph() {//添加 scrollToParagraph 方法,用于滚动到指定段落 const element = this.$el; element.scrollIntoView({ behavior: 'smooth', block: 'center' }); } mounted() { this.text = this.data.text; this.isLeft = Boolean(Number(this.isPolice) ^ Number(IsLeft)); this.timestamp = this.data.timestamp; this.setKeyword(); if (this.data.startFrame && this.data.endFrame) { this.duration = this.data.endFrame - this.data.startFrame; // 动态计算段落的时间长度 } // **新增延迟逻辑:通过 $watch 监听 busGroups 变化** this.$watch('busGroups', (newGroups) => { if (newGroups && newGroups.hitRules) { this.matchHitRules(newGroups.hitRules); // 自定义标签匹配方法 } }, { deep: true, immediate: true }); } // 新增标签匹配方法 matchHitRules(hitRules: any[]) { const matchedRuleNamesSet = new Set<string>(); const normalizedDataText = this.normalizePunctuation(this.text).toLowerCase(); const currentSpeakerPrefix = this.data.policy ? '管教民警:' : '在押人员:'; hitRules.forEach(rule => { const sentences = this.splitSentences(rule.hitSentence); sentences.forEach(sentence => { const normalizedSentence = this.normalizePunctuation(sentence).toLowerCase(); if (normalizedSentence.startsWith(currentSpeakerPrefix.toLowerCase()) && normalizedSentence.includes(normalizedDataText)) { matchedRuleNamesSet.add(rule.hitRuleName); } }); }); this.matchedHitRuleNames = Array.from(matchedRuleNamesSet); } // 按管教民警或被监管人分割句子 splitSentences(text: string): string[] { const pattern = /(管教民警|被监管人):/g; const matches = text.match(pattern); const sentences: string[] = []; let startIndex = 0; if (matches) { matches.forEach((match, index) => { const endIndex = index === matches.length - 1 ? text.length : text.indexOf(matches[index + 1]); const sentence = text.substring(startIndex, endIndex).trim(); if (sentence) { sentences.push(sentence); } startIndex = endIndex; }); } return sentences; } handleInput(value) { this.isTemp = 1; // 如果输入框的输入发生改变证明在删除 赋值为1 {上面的判断证明如果输入框变化 为true 否则循环的text(文本)有值显示 没值不显示} // 监听input输入的文本长度 if (value.length == 1) { this.$message.error("输入字符长度至少1位"); // 阻止表单提交 } else { this.data.text = value; } } changeEditStatus() { const initialText = this.data.text; this.$prompt("", "文本修正", { confirmButtonText: "确定", cancelButtonText: "取消", inputPlaceholder: "", inputValue: initialText, // 设置$prompt的input初始值为编辑的文本 }) .then(({ value }) => { if (value.length < 1) { Message({ message: "最少保留1个字符内容!", type: "error" }); return; } else { // 正常输入 this.data.text = value; this.submitChange(); } }) .catch(() => { this.$message({ type: "info", message: "取消操作", }); }); } /** * 修改文本提交 */ async submitChange() { this.$emit("editContent", Number(this.data.startFrame), this.data.text, this.data.policy); this.setKeyword(false, this.talk.keywords.map((it: Keyword) => { return it.text; })); } /** * 关键字用label标签 */ setKeyword(forceUpdate: boolean = false, keywords: string[] = this.data.keywords) { // const keywords = this.canEdit? this.talk.keywords.map((it: Keyword) => { // return it.text // }) : this.data.keywords if (keywords == null || keywords.length == 0) { return; } if (this.data.policy /*|| !this.data.last*/) { return; } keywords.forEach((it) => { if (it.length == 0) { return; } this.text = this.text.replace(new RegExp(it, "gm"), `<label class='keyword'>${it}</label>`); }); if (forceUpdate) { this.$nextTick(() => { this.$forceUpdate(); }); } } /** * 搜索字用span标签 */ setSearchWord(word: string) { if (word == null || word.length == 0) { return; } this.text = this.text.replace(new RegExp(word, "gm"), `<span class='searched'>${word}</span>`); } setGaugesWord(word: string) { if (word == null || word.length == 0) { return; } this.text = this.text.replace(new RegExp(word, "gm"), `<span class='gauges'>${word}</span>`); } clearSearchWord() { this.text = this.text.replace(new RegExp("<span class='searched'>", "gm"), ""); this.text = this.text.replace(new RegExp("</span>", "gm"), ""); } copy() { this.$copyText(this.text) .then(() => { this.$notify({ title: "成功", message: "谈话内容已成功复制到粘贴板!", duration: 3000, type: "success", }); }) .catch(() => { this.$notify({ title: "失败", message: "谈话内容复制到粘贴板失败!", duration: 3000, type: "error", }); }); } fetchTalkItem() {//单个段落 console.log("this.data",this.data); // 确保有有效的startFrame和endFrame if (this.data.startFrame && this.data.endFrame && this.data.endFrame - this.data.startFrame > 10) { // 最小10ms播放 this.$emit("fetchTalkItem", this.data); } else { console.warn("段落时间太短,不播放:", this.data); } } setAnswer() { this.$emit("setAnswer", this.data.text); } blink() { this.isBlink = true; this.console.debug(this); setTimeout(() => { this.isBlink = false; }, 1500); } } </script> <style lang="less" scoped> @import "../../../assets/styles/variables"; // 对讲内容是否命中 .isHit { font-size: 13px; color: #8b9199; margin: 8px 0 0 0; align-self: flex-start; } // 谈话框样式 公共配置 .talk-item { color: #47494e; padding: 0 0.5rem; .talk-avatar { display: inline-block; .el-avatar { position: relative; } .avatar { border-radius: 50%; background-size: 40px; } } .talk-paragraph { display: inline-block; padding: 0 1rem 10px 1rem!important; max-width: 70%!important; margin-bottom:8px!important; box-sizing: border-box; .time { padding-left: 1rem; padding-bottom: 0.3rem; font-size: 1rem; color: #ccc; /*min-height: 14px;*/ i { cursor: pointer; color: #7ea1de; } i:hover { color: slateblue; } } .progress-highlight{ position: absolute; top: 0; left: 0; border-radius: 8px; height: 93%; background-color: rgba(61, 111, 205, 0.5); /* 半透明蓝色 */ z-index: 0; transition: width 0.1s linear; } .text-container { display: flow-root; border-radius: 8px; min-width:120px; height: fit-content !important; overflow-x: hidden; .text-box { position: relative; display: inline-block; border-radius: 8px; padding: 10px; user-select: text; word-wrap: break-word; // 确保长单词换行 word-break: break-word; // 处理中文换行 overflow-wrap: break-word; // 处理长URL等 min-width:85%; max-width: 28ch; /* 限制最大宽度为大约28个字符的宽度包括符号(宽度自适应) */ overflow-x: hidden; /* 隐藏水平溢出 */ white-space: pre-wrap; /* 保留空白符序列,但正常地进行换行 */ } } } // -=-=-=-=-=-start播放命中高亮 .text-box.highlighted { background-color: #3d6fcd !important; color: white !important; transition: background-color 0.2s ease; } .talk-item.left .text-container:hover .isIcon.iconfont.icon-yinbo, .talk-item.right .text-container:hover .isIcon.iconfont.icon-yinbo, .text-container .isIcon.iconfont.icon-yinbo[style*="display: inline-block"] { display: inline-block !important; } // -=-=--=-=-end .talk-paragraph::before { display: inline-block; content: ""; width: 0; height: 0; line-height: normal; // border-width: 10px; // border-style: solid; // border-color: transparent; position: relative; top: 49px; } .highlight { .text-box { background-color: @talkItemHighlightBGColor !important; // background-color: #3d6fcd !important; // 原为 @talkItemHighlightBGColor,改为悬停色 // color: white !important; // 新增文字颜色 } } .blink { .text-box { background-color: @talkItemHighlightBGColor !important; animation: blink 0.5s 3; -webkit-animation-name: blink; -webkit-animation-duration: 500ms; -webkit-animation-iteration-count: 3; -webkit-animation-timing-function: ease-in-out; } } @keyframes blink { 0% { color: #fab4b4; } 25% { color: #fa6161; } 50% { color: #ff0000; } 75% { color: #fa6161; } 100% { color: #fab4b4; } } &:last-child { margin-bottom: 30px; } } ////////////////////////////////////////////////////////////////////////// // 左侧谈话框样式 .talk-item.left { display: flex; box-sizing: border-box; // &:hover { // width: 100% !important; // cursor: pointer; // border-radius: 10px; // background-color: #d7ecff !important; // border-left: 3px solid #409EFF !important; // transition: background-color 0.3s ease; // } .talk-avatar { .el-avatar { top: 38px; } } .talk-paragraph { .time { i { margin-left: 1rem; } } .text-container { position: relative; .isIcon.iconfont.icon-yinbo{ display: none; /* 默认隐藏 */ position: absolute; right: -28px; top: 8px; z-index: 1; font-size: 20px !important; border-radius: 50% !important; background: white !important; border: 1px solid rgb(219, 215, 215) !important; font-size: 20px !important; color: #3d6fcd !important; } &:hover .isIcon.iconfont.icon-yinbo { display: inline-block !important; /* 悬停时显示图标 */ } .text-box { background-color: @talkItemLeftBGColor; &:hover { cursor: pointer; color: white; background-color:#3d6fcd; } &:hover .isIcon.iconfont.icon-yinbo { display: inline-block; /* 悬停时显示图标 */ } } } } .talk-paragraph::before { border-right-width: 10px; border-right-color: @talkItemLeftBGColor; left: -19px; } .highlight::before, .blink::before { border-right-color: @talkItemHighlightBGColor !important; } } ////////////////////////////////////////////////////////////////////////// // 右侧谈话框样式 .talk-item.right { text-align: right; box-sizing: border-box; // &:hover { // width: 100% !important; // cursor: pointer; // border-radius: 10px; // background-color: #d7ecff !important; // border-left: 3px solid #409EFF !important; // transition: background-color 0.3s ease; // } .talk-avatar { float: right; .el-avatar { top: 36px; } } .talk-paragraph { .time { i { margin-right: 1rem; } } .text-container {//右侧谈话 position: relative; .isIcon.iconfont.icon-yinbo { display: none; /* 默认隐藏 */ position: absolute; left: -28px; top: 8px; z-index: 1; font-size: 20px !important; border-radius: 50% !important; background: white !important; border: 1px solid rgb(219, 215, 215) !important; font-size: 20px !important; color: #3d6fcd !important; } &:hover .isIcon.iconfont.icon-yinbo { display: inline-block !important; /* 悬停时显示图标 */ } .text-box { text-align: left; background-color: @talkItemRightBGColor; &:hover { cursor: pointer; color: white; background-color:#3d6fcd; } } } } .talk-paragraph::before { border-left-width: 10px; border-left-color: @talkItemRightBGColor; right: -19px; } .highlight::before, .blink::before { border-left-color: @talkItemHighlightBGColor !important; } } </style> <style lang="less"> .text-container { .text-box { .searched { color: #f54646; background-color: #f3f35d; } .keyword { color: red; font-weight: bold; } .gauges { color: #2fefd8; } } .text-box.el-input { padding: 5px !important; .el-input__inner { color: #560692; background: inherit; padding: 0; border-width: 0; } } .text-box.el-textarea { width: 350px; padding: 5px !important; .el-textarea__inner { color: #560692; background: inherit; padding: 0; border-width: 0; } } } .talk-item.right { .text-box.el-input, .text-box.el-textarea { float: right; right: 0px; } } </style> import jsonData from '../public/123.json' 基于引用数据来操作<template> <div class="main"> <div class="sec"> <div v-for="item in list" :key="item.timestamp"> {{ item.text }} </div> </div> </div> </template> <script lang="ts"> import { reactive, toRefs, onMounted } from 'vue' import { useRouter, useRoute } from 'vue-router' import jsonData from '../public/123.json' // 定义 content 中每项的类型 interface ContentItem { count: number policy: boolean speaker: string timestamp: number text: string startFrame: string endFrame: string emotions: null keywords: null last: boolean emotionSeg: Record<string, unknown> } export default { name: '', setup() { const router = useRouter() const route = useRoute() const data = reactive<{ list: ContentItem[] }>({ list: [] // 初始化为空数组但有明确类型 }) onMounted(() => { data.list = jsonData.data.content as ContentItem[] console.log(data.list) console.log(jsonData.data.content, 'jsonData') }) const refData = toRefs(data) return { ...refData } } } </script> <style lang="scss" scoped> .main{ width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; .sec{ width: 800px; height: 600px; border: 1px solid red; } } </style> 修复一下吧亲 基于我提供代码
08-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值