ptzControl.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. <template>
  2. <div style="display: flex; justify-content: left;">
  3. <div class="control-mic">
  4. <microphone :channel-id="channelId" :device-id="deviceId" :enable-debug="enableDebug"
  5. :http-hook="httpHook"
  6. :https-hook="httpsHook"
  7. :push-key="pushKey"
  8. ></microphone>
  9. </div>
  10. <div class="control-wrapper">
  11. <div class="control-btn control-top" @mousedown="ptzControlHandleDown('up')" @mouseup="ptzControlHandleUp('up')">
  12. <i class="el-icon-caret-top"></i>
  13. <div class="control-inner-btn control-inner"></div>
  14. </div>
  15. <div class="control-btn control-left" @mousedown="ptzControlHandleDown('left')" @mouseup="ptzControlHandleUp('left')">
  16. <i class="el-icon-caret-left"></i>
  17. <div class="control-inner-btn control-inner"></div>
  18. </div>
  19. <div class="control-btn control-bottom" @mousedown="ptzControlHandleDown('down')" @mouseup="ptzControlHandleUp('down')">
  20. <i class="el-icon-caret-bottom"></i>
  21. <div class="control-inner-btn control-inner"></div>
  22. </div>
  23. <div class="control-btn control-right" @mousedown="ptzControlHandleDown('right')" @mouseup="ptzControlHandleUp('right')">
  24. <i class="el-icon-caret-right"></i>
  25. <div class="control-inner-btn control-inner"></div>
  26. </div>
  27. <div class="control-round">
  28. <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
  29. </div>
  30. <!-- 放大 -->
  31. <div style="position: absolute; left: 7.25rem; top: -1.1rem" @mousedown="ptzCamera('zoomin')"
  32. @mouseup="ptzCamera('stop')">
  33. <i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.875rem;"></i>
  34. </div>
  35. <!-- 聚焦 -->
  36. <div style="position: absolute; left: 7.25rem; top: 1.25rem"
  37. @click="ptzCameraFocus('focus')" >
  38. <i class="el-icon-aim control-zoom-btn" style="font-size: 1.875rem;"></i>
  39. </div>
  40. <!-- 缩小 -->
  41. <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-out control-zoom-btn"></i></div>
  42. <div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
  43. <el-slider v-model="controSpeed" :max="255"></el-slider>
  44. </div>
  45. </div>
  46. <div class="control-panel">
  47. <!-- 预置位 -->
  48. <el-tabs tab-position="left" style="height: 210px;width:100%">
  49. <el-tab-pane :label="$t('device.preset.title')" >
  50. <!-- 预置位查询 -->
  51. <el-table
  52. v-loading="presetLoading"
  53. :data="presetList"
  54. height="260px"
  55. width="100%"
  56. :row-style="{height: '20px',fontSize: '12px'}"
  57. style="width: 100%;font-size: 16px">
  58. <el-table-column
  59. prop="ind"
  60. label="No"
  61. width="90">
  62. </el-table-column>
  63. <el-table-column
  64. prop="remark"
  65. :label="$t('comment')"
  66. width="100">
  67. </el-table-column>
  68. <el-table-column
  69. prop="operation"
  70. >
  71. <template slot-scope="scope">
  72. <el-button class="ml-2" type="primary" size="mini" @click="presetPosition(129, scope.row.ind)">
  73. {{ $t('setting') }}
  74. </el-button>
  75. <el-button v-if="scope.row.load" class="ml-2" type="primary" size="mini" @click="presetPosition(130, scope.row.ind)">
  76. {{ $t('run') }}
  77. </el-button>
  78. <el-button v-if="scope.row.load" class="ml-2" type="danger" size="mini" @click="presetPosition(131, scope.row.ind)">
  79. {{ $t('delete') }}
  80. </el-button>
  81. </template>
  82. <template slot="header" slot-scope="scope">
  83. <div class="w-full flex">
  84. <!-- input number -->
  85. <el-popover
  86. placement="bottom"
  87. :title="$t('device.preset.fastPreset')"
  88. width="400"
  89. trigger="click">
  90. <div class="w-full flex justify-center items-center">
  91. <el-input-number size="mini" :min="1" :max="255" v-model="presetPos"></el-input-number>
  92. <el-button class="ml-2" type="primary" size="mini" @click="presetPosition(129, presetPos)">
  93. {{ $t('setting') }}
  94. </el-button>
  95. <el-button class="ml-2" type="primary" size="mini" @click="presetPosition(130, presetPos)">
  96. {{ $t('run') }}
  97. </el-button>
  98. <el-button class="ml-2" type="danger" size="mini" @click="presetPosition(131, presetPos)">
  99. {{ $t('delete') }}
  100. </el-button>
  101. </div>
  102. <el-button slot="reference">
  103. {{ $t('device.preset.fastPreset') }}
  104. </el-button>
  105. </el-popover>
  106. <el-button class="ml-2" type="primary" size="mini" icon="el-icon-refresh" @click="queryPresetPos"></el-button>
  107. </div>
  108. </template>
  109. </el-table-column>
  110. </el-table>
  111. </el-tab-pane>
  112. <!-- <el-tab-pane label="巡航" >-->
  113. <!-- <el-button-group>-->
  114. <!-- <el-tag size="medium">巡航速度</el-tag>-->
  115. <!-- <el-input-number size="mini" v-model="cruisingSpeed" :precision="0" :min="1" :max="4095"></el-input-number>-->
  116. <!-- <el-button size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button>-->
  117. <!-- <br/>-->
  118. <!-- <hr/>-->
  119. <!-- <el-tag size="medium">停留时间</el-tag>-->
  120. <!-- <el-input-number size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>-->
  121. <!-- <el-button size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button>-->
  122. <!-- <br/>-->
  123. <!-- <hr/>-->
  124. <!-- <el-tag size="medium">巡航组编号</el-tag>-->
  125. <!-- <el-input-number size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number>-->
  126. <!-- <el-button size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button>-->
  127. <!-- <el-button size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button>-->
  128. <!-- <el-button size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button>-->
  129. <!-- <el-button size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button>-->
  130. <!-- <br/>-->
  131. <!-- <hr/>-->
  132. <!-- <el-tag size="medium">扫描速度</el-tag>-->
  133. <!-- <el-input-number size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>-->
  134. <!-- <el-button size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>-->
  135. <!-- <br/>-->
  136. <!-- <hr/>-->
  137. <!-- <el-tag size="medium">扫描组编号</el-tag>-->
  138. <!-- <el-input-number size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number>-->
  139. <!-- <el-button size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button>-->
  140. <!-- <el-button size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button>-->
  141. <!-- <el-button size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描</el-button>-->
  142. <!-- <el-button size="mini" type="danger" icon="el-icon-switch-button" @click="ptzCamera('stop')">停止</el-button>-->
  143. <!-- </el-button-group>-->
  144. <!-- </el-tab-pane>-->
  145. <!-- <el-tab-pane label="扫描">角色管理</el-tab-pane>-->
  146. </el-tabs>
  147. </div>
  148. </div>
  149. </template>
  150. <script>
  151. import handle from "@/until/handle";
  152. import Microphone from "@/components/common/microphone"
  153. let queryTimer = null;
  154. let isZoom = null;
  155. let sendStopTimer = null;
  156. let changToLongDownStateTimer = null;
  157. // 是否长按
  158. let isLongDown = null;
  159. // 长按与连续短按的时间
  160. let pressDuration = 1000;
  161. // 从第一次按下按钮到执行命令时的重复时间
  162. let clickDuration = 700;
  163. // 连点持续时间
  164. let conClickDuration = 350;
  165. // 计时器
  166. let clickTimer = null;
  167. let conClickTimer = null;
  168. let pressTimer = null;
  169. let sendEndTimer = null;
  170. /**
  171. * @description 云台控制组件
  172. * @param deviceId 设备id
  173. * @param channelId 通道id
  174. */
  175. export default {
  176. name: "ptzControl",
  177. components: {Microphone},
  178. props:{
  179. deviceId:{require:true},
  180. channelId:{require:true}
  181. },
  182. data(){
  183. return {
  184. enableDebug: true,
  185. isLoading: false,
  186. tabActiveName: 'control',
  187. controSpeed: 30,
  188. zoomSpeed: 30,
  189. presetPos: 1,
  190. cruisingSpeed: 100,
  191. cruisingTime: 5,
  192. cruisingGroup: 0,
  193. scanSpeed: 100,
  194. scanGroup: 0,
  195. direction: '',// 当前按下的按钮的方向
  196. step:0,//步长
  197. stepValue:5,//
  198. clickCount: 0,// 连续点击数量
  199. isClick: true,// 是否为点击
  200. isLongClick: false,// 是否为连续点击
  201. isPress: false,// 是否为长按
  202. isSendAutoMove: false,// 是否为长按命令倒计时中
  203. pushKey: "",
  204. httpsHook: "",
  205. httpHook: "",
  206. presetList: [],
  207. presetLoading: false,
  208. presetInd: 1,// 选择的预置位id
  209. }
  210. },
  211. beforeMount() {
  212. // this.queryPushParam();
  213. },
  214. mounted() {
  215. this.queryPresetPos();
  216. },
  217. methods:{
  218. timeSendFocus(){
  219. queryTimer = setTimeout(async ()=>{
  220. await this.ptzCameraFocus();
  221. queryTimer = null;
  222. },700)
  223. },
  224. timeSendStop(){
  225. console.log('短按自动跟发stop');
  226. this.ptzCamera('stop');
  227. },
  228. testHandle(key, item){
  229. console.log('testHandle')
  230. console.log(key)
  231. console.log(item)
  232. },
  233. async ptzCamera(command){
  234. console.log('云台控制:' + command);
  235. let isSendFocus = false;
  236. let that = this;
  237. let isAutoSendStop = false;
  238. if(command === 'zoomin' || command === 'zoomout'){
  239. isZoom = true;
  240. if (queryTimer!=null){
  241. // 中止
  242. clearTimeout(queryTimer);
  243. }
  244. }else if(command === 'stop' && isZoom){
  245. // 发送待定值
  246. isSendFocus = true;
  247. }else{
  248. isZoom = false;
  249. // down 发送特定指令值.
  250. }
  251. clearTimeout(sendStopTimer);
  252. sendStopTimer = null;
  253. // 不连续发送指令
  254. if(command !== 'stop'){
  255. isLongDown = false;
  256. // 非停止指令
  257. changToLongDownStateTimer = setTimeout(()=>{
  258. isLongDown = true;
  259. },1000)
  260. }else{
  261. // 确保不是自动对焦
  262. if(!isLongDown && !isSendFocus){
  263. // 短按阻止立即发送end,等待900ms发送end
  264. sendStopTimer = setTimeout(()=>{
  265. this.timeSendStop();
  266. },1200);
  267. console.log('进行短按操作');
  268. return;
  269. }
  270. // 清除定时器
  271. clearTimeout(changToLongDownStateTimer);
  272. changToLongDownStateTimer = null;
  273. }
  274. let [err,res] = await handle(this.$axios.axios({
  275. method: 'post',
  276. url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.zoomSpeed
  277. }));
  278. if(err){
  279. console.error(err)}
  280. if(isSendFocus){
  281. // TODO 在变焦后自动跟发聚焦指令
  282. // this.timeSendFocus();
  283. }
  284. },
  285. async ptzCameraFocus(){
  286. // todo 发送聚焦http指令
  287. console.log("摄像头聚焦");
  288. let url = `/api/ptz/focus/`
  289. url+=`${this.deviceId}/`;
  290. url+=`${this.channelId}/`;
  291. let [err,res] = await handle(this.$axios.axios({
  292. method: 'post',
  293. url: url
  294. }));
  295. if(err){
  296. console.error(err)}
  297. },
  298. presetPosition: function (cmdCode, presetPos) {
  299. console.log('预置位控制:' + this.presetPos + ' : 0x' + cmdCode.toString(16));
  300. let that = this;
  301. this.$axios.axios({
  302. method: 'post',
  303. url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=0&parameter2=' + presetPos + '&combindCode2=0'
  304. }).then(function (res) {
  305. if (cmdCode === 129 || cmdCode === 131){
  306. this.queryPresetPos();
  307. }
  308. });
  309. },
  310. setSpeedOrTime: function (cmdCode, groupNum, parameter) {
  311. let that = this;
  312. let parameter2 = parameter % 256;
  313. let combindCode2 = Math.floor(parameter / 256) * 16;
  314. console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter2.toString(16) + ' 0x' + combindCode2.toString(16));
  315. this.$axios.axios({
  316. method: 'post',
  317. url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter2 + '&combindCode2=' + combindCode2
  318. }).then(function (res) {});
  319. },
  320. setCommand: function (cmdCode, groupNum, parameter) {
  321. let that = this;
  322. console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter.toString(16) + ' 0x0');
  323. this.$axios.axios({
  324. method: 'post',
  325. url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter + '&combindCode2=0'
  326. }).then(function (res) {});
  327. },
  328. /**
  329. * 按下云台控制按钮逻辑
  330. * @param direction 方向
  331. */
  332. ptzControlHandleDown(direction){
  333. if(sendStopTimer){
  334. clearTimeout(sendStopTimer);
  335. }
  336. console.log('--------------按下')
  337. this.direction = direction;
  338. this.isPress = false;
  339. this.isClick = true;
  340. // 1200
  341. pressTimer = setTimeout(()=>{
  342. console.log('长按')
  343. // 长按
  344. this.isPress = true;
  345. this.isClick=false;
  346. this.clickCount = 0;
  347. this.isSendAutoMove = false;
  348. // 发送云台移动命令,步长为0,视作连续移动
  349. this.sendCommand(this.direction,0);
  350. // 等待850毫秒,如果850毫秒内的抬起了鼠标,则发送停止命令
  351. setTimeout(()=>{
  352. console.log(`按下的延迟isSendAutoMove: ${this.isSendAutoMove}`)
  353. if(this.isSendAutoMove){
  354. console.log('长按抬起延迟结束');
  355. this.sendCommand('stop',0);
  356. }
  357. this.isSendAutoMove = true;
  358. },600);
  359. },pressDuration);
  360. // 处于连点状态,刷新连点计时器
  361. clearTimeout(conClickTimer);
  362. conClickTimer = null;
  363. },
  364. /**
  365. * 松开按钮逻辑
  366. * @param command
  367. */
  368. ptzControlHandleUp(command){
  369. if(this.isPress){
  370. // 如果没有抬起
  371. console.log(111111111)
  372. let _isSendAutoMove = this.isSendAutoMove;
  373. if(!_isSendAutoMove){
  374. console.log('长按')
  375. this.isSendAutoMove = true;
  376. return;
  377. }
  378. console.log('长按抬起结束')
  379. this.sendCommand('stop',0)
  380. }else{
  381. clearTimeout(pressTimer);
  382. this.clickCount ++;
  383. conClickTimer = setTimeout(()=>{
  384. // 结束连点,合并命令.
  385. this.isClick = false;
  386. conClickTimer=null;
  387. clickTimer=null;
  388. // 发送
  389. console.log(`快速点按${this.clickCount}`)
  390. clearTimeout(clickTimer);
  391. this.sendCommand(this.direction,this.clickCount);
  392. },conClickDuration);
  393. if(!clickTimer){
  394. clickTimer = setTimeout(()=>{
  395. // 连点超时
  396. if(this.isClick){
  397. clearTimeout(conClickTimer);
  398. conClickTimer=null;
  399. clickTimer=null;
  400. // 强制发送当前的
  401. console.log(`连按缓存${this.clickCount}`);
  402. this.sendCommand(this.direction,this.clickCount);
  403. }
  404. },clickDuration)
  405. }
  406. }
  407. },
  408. /**
  409. * 发送命令至服务器
  410. * @param command
  411. * @param step
  412. * @returns {Promise<void>}
  413. */
  414. async sendCommand(command,step=0)
  415. {
  416. console.log(`[send] ${command} - ${step}`);
  417. // step = step * 5
  418. let url = `/api/ptz/c/${this.deviceId}/${this.channelId}/?c=${command}&step=${step*this.stepValue}`
  419. console.log(url);
  420. let [err,res] = await handle(this.$axios.axios({
  421. method: 'post',
  422. url: url
  423. }));
  424. this.clickCount = 0;
  425. if(err){console.error(err)}
  426. },
  427. async queryPushParam()
  428. {
  429. let url = `/api/server/pushConfig`
  430. let [err,res] = await handle(this.$axios.axios({
  431. method: 'get',
  432. url: url
  433. }));
  434. if(err){
  435. console.error(err)
  436. return this.$message.error(err.message);
  437. }
  438. console.log(res);
  439. let response = res.data;
  440. if(response.code === 0){
  441. this.httpsHook = response.data["httpsHook"];
  442. this.httpHook = response.data["httpHook"];
  443. this.pushKey = response.data["pushKey"];
  444. }else{
  445. this.$message.warning(response.msg)
  446. }
  447. },
  448. async queryPresetPos(){
  449. console.log('请求预置位');
  450. this.presetLoading = true;
  451. let n_presetLength = 255;
  452. let presetTitle = this.$t('device.preset.title');
  453. let presetList = new Array(n_presetLength).fill({}).map((item,i)=>{
  454. return {
  455. ind: i+1,
  456. remark: `${presetTitle}${i+1}`,
  457. load: false
  458. }
  459. })
  460. console.log( presetList);
  461. let queryUrl = `/api/ptz/preset/query/${this.deviceId}/${this.channelId}`
  462. // 加载预置位
  463. let [err,res] = await handle(this.$axios.axios({
  464. method: 'get',
  465. url: queryUrl
  466. }));
  467. this.presetLoading = false;
  468. if (err){
  469. if(err){
  470. console.error(err)
  471. this.presetList = presetList;
  472. return this.$message.error(err.message);
  473. }
  474. }
  475. console.log(res);
  476. /**
  477. * res = {
  478. * "code": 0,
  479. * "msg": "success",
  480. * "data": [
  481. * {
  482. * "presetId": 1,
  483. * "presetName": "预置位1",
  484. * }
  485. * ]
  486. */
  487. let response = res.data;
  488. if (response.code === 0){
  489. response.data.forEach(item=>{
  490. console.log(item.presetId)
  491. console.log(presetList[item.presetId-1])
  492. presetList[item.presetId-1] = {
  493. ind: item.presetId,
  494. remark: item.presetName,
  495. load: true,
  496. }
  497. });
  498. }else{
  499. this.$message.warning(response.msg)
  500. }
  501. console.log(presetList);
  502. this.presetList = presetList;
  503. }
  504. },
  505. }
  506. </script>
  507. <style scoped>
  508. .control-mic{
  509. width: 4rem;
  510. /*height: 100%;*/
  511. display: flex;
  512. justify-content: center;
  513. align-items: center;
  514. }
  515. .control-wrapper {
  516. position: relative;
  517. width: 6.25rem;
  518. height: 6.25rem;
  519. max-width: 6.25rem;
  520. max-height: 6.25rem;
  521. border-radius: 100%;
  522. margin-top: 1.5rem;
  523. margin-left: 0.5rem;
  524. flex-shrink: 0;
  525. float: left;
  526. }
  527. .control-panel {
  528. position: relative;
  529. width: 100%;
  530. top: 0;
  531. left: 5rem;
  532. height: 11rem;
  533. max-height: 11rem;
  534. }
  535. .control-btn {
  536. display: flex;
  537. justify-content: center;
  538. position: absolute;
  539. width: 44%;
  540. height: 44%;
  541. border-radius: 5px;
  542. border: 1px solid #78aee4;
  543. box-sizing: border-box;
  544. transition: all 0.3s linear;
  545. }
  546. .control-btn:hover {
  547. cursor:pointer
  548. }
  549. .control-btn i {
  550. font-size: 20px;
  551. color: #78aee4;
  552. display: flex;
  553. justify-content: center;
  554. align-items: center;
  555. }
  556. .control-btn i:hover {
  557. cursor:pointer
  558. }
  559. .control-zoom-btn:hover {
  560. cursor:pointer
  561. }
  562. .control-round {
  563. position: absolute;
  564. top: 21%;
  565. left: 21%;
  566. width: 58%;
  567. height: 58%;
  568. background: #fff;
  569. border-radius: 100%;
  570. }
  571. .control-round-inner {
  572. position: absolute;
  573. left: 13%;
  574. top: 13%;
  575. display: flex;
  576. justify-content: center;
  577. align-items: center;
  578. width: 70%;
  579. height: 70%;
  580. font-size: 40px;
  581. color: #78aee4;
  582. border: 1px solid #78aee4;
  583. border-radius: 100%;
  584. transition: all 0.3s linear;
  585. }
  586. .control-inner-btn {
  587. position: absolute;
  588. width: 60%;
  589. height: 60%;
  590. background: #fafafa;
  591. }
  592. .control-top {
  593. top: -8%;
  594. left: 27%;
  595. transform: rotate(-45deg);
  596. border-radius: 5px 100% 5px 0;
  597. }
  598. .control-top i {
  599. transform: rotate(45deg);
  600. border-radius: 5px 100% 5px 0;
  601. }
  602. .control-top .control-inner {
  603. left: -1px;
  604. bottom: 0;
  605. border-top: 1px solid #78aee4;
  606. border-right: 1px solid #78aee4;
  607. border-radius: 0 100% 0 0;
  608. }
  609. .control-top .fa {
  610. transform: rotate(45deg) translateY(-7px);
  611. }
  612. .control-left {
  613. top: 27%;
  614. left: -8%;
  615. transform: rotate(45deg);
  616. border-radius: 5px 0 5px 100%;
  617. }
  618. .control-left i {
  619. transform: rotate(-45deg);
  620. }
  621. .control-left .control-inner {
  622. right: -1px;
  623. top: -1px;
  624. border-bottom: 1px solid #78aee4;
  625. border-left: 1px solid #78aee4;
  626. border-radius: 0 0 0 100%;
  627. }
  628. .control-left .fa {
  629. transform: rotate(-45deg) translateX(-7px);
  630. }
  631. .control-right {
  632. top: 27%;
  633. right: -8%;
  634. transform: rotate(45deg);
  635. border-radius: 5px 100% 5px 0;
  636. }
  637. .control-right i {
  638. transform: rotate(-45deg);
  639. }
  640. .control-right .control-inner {
  641. left: -1px;
  642. bottom: -1px;
  643. border-top: 1px solid #78aee4;
  644. border-right: 1px solid #78aee4;
  645. border-radius: 0 100% 0 0;
  646. }
  647. .control-right .fa {
  648. transform: rotate(-45deg) translateX(7px);
  649. }
  650. .control-bottom {
  651. left: 27%;
  652. bottom: -8%;
  653. transform: rotate(45deg);
  654. border-radius: 0 5px 100% 5px;
  655. }
  656. .control-bottom i {
  657. transform: rotate(-45deg);
  658. }
  659. .control-bottom .control-inner {
  660. top: -1px;
  661. left: -1px;
  662. border-bottom: 1px solid #78aee4;
  663. border-right: 1px solid #78aee4;
  664. border-radius: 0 0 100% 0;
  665. }
  666. .control-bottom .fa {
  667. transform: rotate(-45deg) translateY(7px);
  668. }
  669. .trank {
  670. width: 80%;
  671. height: 180px;
  672. text-align: left;
  673. padding: 0 10%;
  674. overflow: auto;
  675. }
  676. .trankInfo {
  677. width: 80%;
  678. padding: 0 10%;
  679. }
  680. </style>