TACS.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. <template>
  2. <div class="XFpdding" style="height: 510px;">
  3. <ul>
  4. <li
  5. class="item"
  6. v-for="(tab, index) in tabslist2"
  7. :key="index"
  8. :class="{ active: currentTab2 === index }"
  9. @click="selectTab2(index)"
  10. >
  11. <img :src="tab.imgSrc" style="width: 22px;"/>
  12. {{ tab.name }}
  13. </li>
  14. </ul>
  15. <!-- 网格文件 -->
  16. <!-- 执行 -->
  17. <div
  18. class="classtable"
  19. style="margin-top: 10px; "
  20. v-show="currentTab2 == '0'"
  21. >
  22. <el-form label-position="left">
  23. <el-form-item label="网格文件:" :label-width="formLabelWidth120">
  24. <el-input v-model="fname">
  25. <template #append>
  26. <fileUploads
  27. :projectId="125"
  28. solverType="exampleSolver"
  29. accept=".bdf"
  30. upId="uniqueId1"
  31. :imgSrc="tacsfileSrc"
  32. name="点击选择文件"
  33. @update-percentage="updatePercentage"
  34. @upload-success="handleFileUploadSuccess"
  35. @upload-status="getUploadStatus"
  36. />
  37. </template>
  38. </el-input>
  39. <!-- 进度条 -->
  40. <el-row v-if="showProgress" style="width: 100%; margin-top: 10px;">
  41. <el-col :span="20">
  42. <el-progress :percentage="percentage"></el-progress>
  43. </el-col>
  44. <el-col :span="4">
  45. <div style="line-height: 15px">{{uploadStatus}}</div>
  46. </el-col>
  47. </el-row>
  48. </el-form-item>
  49. </el-form>
  50. <div style="flex-grow: 1;"
  51. v-loading="isLoading"
  52. element-loading-text="拼命加载中...">
  53. <cloudChart :data="bdfData" :height=canvasHeight />
  54. </div>
  55. </div>
  56. <!-- 设置参数 -->
  57. <div
  58. class="classtable"
  59. style="margin-top: 10px;"
  60. v-show="currentTab2 == '2'"
  61. >
  62. <el-form-item label="分析对象名称:" :label-width="formLabelWidth1">
  63. <el-input
  64. v-model="tacsvalue.proname"
  65. :step="100"
  66. :min="0"
  67. :max="1000"
  68. controls-position="right"
  69. />
  70. </el-form-item>
  71. <el-collapse v-model="TACSactiveNames">
  72. <el-collapse-item title="材料属性" name="1">
  73. <template #title>
  74. <span class="collapse-title">材料属性</span>
  75. </template>
  76. <el-form-item label="材料密度:" :label-width="formLabelWidth1">
  77. <el-input
  78. v-model="tacsvalue.rho"
  79. :step="100"
  80. :min="0"
  81. :max="1000"
  82. controls-position="right"
  83. />
  84. </el-form-item>
  85. <el-form-item label="杨氏模量:" :label-width="formLabelWidth1">
  86. <el-input
  87. v-model="tacsvalue.e"
  88. :step="100"
  89. :min="0"
  90. :max="1000"
  91. controls-position="right"
  92. />
  93. </el-form-item>
  94. <el-form-item label="泊松比:" :label-width="formLabelWidth1">
  95. <el-input
  96. v-model="tacsvalue.nu"
  97. :step="100"
  98. :min="0"
  99. :max="1000"
  100. controls-position="right"
  101. />
  102. </el-form-item>
  103. <el-form-item label="屈服应力:" :label-width="formLabelWidth1">
  104. <el-input
  105. v-model="tacsvalue.ys"
  106. :step="100"
  107. :min="0"
  108. :max="1000"
  109. controls-position="right"
  110. />
  111. </el-form-item>
  112. <el-form-item label="启用FFD参数化:" :label-width="formLabelWidth1">
  113. <el-radio-group v-model="tacsvalue.useffd">
  114. <el-radio :label='1'>是</el-radio>
  115. <el-radio :label='0'>否</el-radio>
  116. </el-radio-group>
  117. </el-form-item>
  118. </el-collapse-item>
  119. <el-collapse-item title="收敛选项" name="2">
  120. <template #title>
  121. <span class="collapse-title">收敛选项</span>
  122. </template>
  123. <el-form-item label="绝对收敛参数:" :label-width="formLabelWidth1">
  124. <el-input
  125. v-model="tacsvalue.l2convergence "
  126. :step="100"
  127. :min="0"
  128. :max="1000"
  129. controls-position="right"
  130. />
  131. </el-form-item>
  132. <el-form-item label="相对收敛参数:" :label-width="formLabelWidth1">
  133. <el-input
  134. v-model="tacsvalue.l2convergencerel "
  135. :step="100"
  136. :min="0"
  137. :max="1000"
  138. controls-position="right"
  139. />
  140. </el-form-item>
  141. </el-collapse-item>
  142. </el-collapse>
  143. </div>
  144. <!-- 结构参数 v-show="currentTab=='0'"-->
  145. <div v-show="currentTab2 == '1'">
  146. <div class="eldesign classtable" style="margin-top: 10px">
  147. <el-row :gutter="5">
  148. <el-col :span="21">
  149. <el-form-item label="个数:" :label-width="70">
  150. <el-input v-model="jiegouNum" />
  151. </el-form-item>
  152. </el-col>
  153. <el-col :span="3">
  154. <el-button @click="addJiegou" style="width: 100%;">应用</el-button>
  155. </el-col>
  156. </el-row>
  157. <el-table
  158. :data="inParams"
  159. border
  160. style="width: 100%;height: 450px;"
  161. >
  162. <el-table-column label="启用">
  163. <el-table-column type="index" width="170" label="编号">
  164. </el-table-column>
  165. </el-table-column>
  166. <el-table-column>
  167. <template v-slot:header>
  168. <!-- 表头显示复选框 -->
  169. <el-checkbox
  170. :false-label="0"
  171. :true-label="1"
  172. v-model="writesolution"
  173. />
  174. </template>
  175. <el-table-column prop="value" label="厚度分布" >
  176. <template #default="{ row }">
  177. <el-input v-model="row.value" />
  178. </template>
  179. </el-table-column>
  180. </el-table-column>
  181. <!-- <el-table-column>
  182. <el-table-column prop="con" label=""> </el-table-column>
  183. </el-table-column> -->
  184. </el-table>
  185. </div>
  186. </div>
  187. <!-- 分析参数 v-show="currentTab=='0'"-->
  188. <div v-show="currentTab2 == '3'">
  189. <div class="eldesign classtable" style="margin-top: 10px">
  190. <el-table
  191. :data="outParams"
  192. border
  193. style="width: 100%; "
  194. :header-cell-class-name="headerCellClassName"
  195. >
  196. <el-table-column type="index" width="70" label="编号" />
  197. <el-table-column prop="name" label="参数名称">
  198. <!-- <template #default="{ row }">
  199. <el-input v-model="row.name" @change="handleEdit(row)" />
  200. </template> -->
  201. </el-table-column>
  202. <el-table-column prop="value" label="参数值">
  203. <template #default="{ row }">
  204. <el-input v-model="row.value" />
  205. </template>
  206. </el-table-column>
  207. <el-table-column prop="flag" label="启用" width="100">
  208. <template v-slot="scope">
  209. <el-checkbox
  210. :false-label="0"
  211. :true-label="1"
  212. v-model="scope.row.flag"
  213. />
  214. </template>
  215. </el-table-column>
  216. </el-table>
  217. </div>
  218. </div>
  219. </div>
  220. </template>
  221. <script setup>
  222. import { ref, onMounted, reactive, provide, nextTick } from "vue"
  223. import { ElMessage, ElButton, ElDialog, ElSelect } from "element-plus"
  224. import { Edit, CaretBottom } from "@element-plus/icons-vue"
  225. import fileUploads from '../components/fileuploads.vue'
  226. import cloudChart from "../threejsView/index.vue" // 云图
  227. import { request, uploadFile } from "@/utils/request"
  228. import emitter from "@/utils/emitter"
  229. import meshFile from "@/assets/img/meshFile.png";
  230. import jiegoucanshu from "@/assets/img/jiegoucanshu.png";
  231. import configParams from "@/assets/img/configParams.png";
  232. import analysisParams from "@/assets/img/analysisParams.png";
  233. let fid = ref()
  234. let formLabelWidth1 = ref(170)
  235. let formLabelWidth120 = ref(120)
  236. let bdfData = ref()
  237. let currentTab2=ref(0);
  238. let pid = ref();
  239. let wid = ref();
  240. let tacsid = ref();
  241. let tabslist2= ref([
  242. { id: '0', name: '网格文件' ,imgSrc:meshFile},
  243. { id: '1', name: '结构参数' ,imgSrc:jiegoucanshu},
  244. { id: '2', name: '设置参数' ,imgSrc:configParams},
  245. { id: '3', name: '分析参数' ,imgSrc:analysisParams},
  246. ])
  247. const tacsfileSrc = new URL('@/assets/flowimg/ffdFileSave.png', import.meta.url).href;
  248. let TACSactiveNames = ref(['1','2']);
  249. let jiegouNum = ref(0);
  250. let tacsvalue = ref({
  251. proname:'CRM',
  252. rho:'2780.0',
  253. e:'73.1e9',
  254. nu:'0.33',
  255. ys:'262.0e6',
  256. useffd: 1,
  257. l2convergence:'1e3',
  258. l2convergencerel:'1e3'
  259. })
  260. let writesolution = ref(1);
  261. let fname = ref('')
  262. let inParams=ref([
  263. {
  264. code:'thickness',
  265. name: '厚度分布',
  266. value: '0.005',
  267. flag: 1,
  268. con: ''
  269. },
  270. {
  271. code:'thickness',
  272. name: '厚度分布',
  273. value: '0.003',
  274. flag: 1,
  275. con: ''
  276. },
  277. {
  278. code:'thickness',
  279. name: '厚度分布',
  280. value: '0.003',
  281. flag: 1,
  282. con: ''
  283. },
  284. {
  285. code:'thickness',
  286. name: '厚度分布',
  287. value: '0.003',
  288. flag: 1,
  289. con: ''
  290. },
  291. {
  292. code:'thickness',
  293. name: '厚度分布',
  294. value: '0.003',
  295. flag: 1,
  296. con: ''
  297. },
  298. {
  299. code:'thickness',
  300. name: '厚度分布',
  301. value: '0.003',
  302. flag: 1,
  303. con: ''
  304. },
  305. {
  306. code:'thickness',
  307. name: '厚度分布',
  308. value: '0.003',
  309. flag: 1,
  310. con: ''
  311. },
  312. {
  313. code:'thickness',
  314. name: '厚度分布',
  315. value: '0.003',
  316. flag: 1,
  317. con: ''
  318. },
  319. {
  320. code:'thickness',
  321. name: '厚度分布',
  322. value: '0.003',
  323. flag: 1,
  324. con: ''
  325. },
  326. {
  327. code:'thickness',
  328. name: '厚度分布',
  329. value: '0.003',
  330. flag: 1,
  331. con: ''
  332. },
  333. {
  334. code:'thickness',
  335. name: '厚度分布',
  336. value: '0.003',
  337. flag: 1,
  338. con: ''
  339. },
  340. {
  341. code:'thickness',
  342. name: '厚度分布',
  343. value: '0.003',
  344. flag: 1,
  345. con: ''
  346. },
  347. {
  348. code:'thickness',
  349. name: '厚度分布',
  350. value: '0.003',
  351. flag: 1,
  352. con: ''
  353. },
  354. {
  355. code:'thickness',
  356. name: '厚度分布',
  357. value: '0.003',
  358. flag: 1,
  359. con: ''
  360. },
  361. {
  362. code:'thickness',
  363. name: '厚度分布',
  364. value: '0.003',
  365. flag: 1,
  366. con: ''
  367. },
  368. {
  369. code:'thickness',
  370. name: '厚度分布',
  371. value: '0.003',
  372. flag: 1,
  373. con: ''
  374. },
  375. ])
  376. let outParams=ref([
  377. {
  378. code:'mass',
  379. name: '结构质量',
  380. value: 13972.2123,
  381. flag: 1
  382. },
  383. {
  384. code:'ks_vmfailure',
  385. name: 'KS聚合应力',
  386. value: 0.3337,
  387. flag: 1
  388. },
  389. {
  390. code:'maxdeform',
  391. name: '最大变形',
  392. value: 0.0,
  393. flag: 1
  394. },
  395. ])
  396. let percentage = ref(0)
  397. let uploadStatus = ref('')
  398. // 控制进度条显隐
  399. const showProgress = computed(() => percentage.value > 0 && percentage.value <= 100);
  400. let canvasHeight = ref("400px");
  401. let isLoading = ref(false); // 控制 loading 状态
  402. let websock = shallowRef(null);
  403. let times = ref({
  404. lockReconnect: false, //是否真正建立连接
  405. timeout: 60 * 1000, //30秒一次心跳
  406. heartBeatInterval: 30 * 1000, // 添加心跳发送间隔(30秒发一次)
  407. timeoutObj: null, //心跳倒计时
  408. serverTimeoutObj: null, //
  409. timeoutnum: null, //断开重连倒计时
  410. })
  411. // 更新进度条
  412. const updatePercentage = (newValue) => {
  413. percentage.value = newValue
  414. }
  415. // 更新上传状态
  416. const getUploadStatus = (newValue) => {
  417. uploadStatus.value = newValue
  418. }
  419. const selectTab2=(index)=>{
  420. currentTab2.value = index;
  421. }
  422. const headerCellClassName = ({ column }) => {
  423. // console.log('列:',column.property)
  424. if (column.property === 'name') {
  425. console.log('yanse',column.property)
  426. return 'header-blue';
  427. } else if (column.property === 'value') {
  428. return 'header-green';
  429. } else if (column.property === 'flag') {
  430. return 'header-yellow';
  431. }
  432. return '';
  433. };
  434. const addJiegou = () => {
  435. const targetLength = jiegouNum.value
  436. const currentLength = inParams.value.length
  437. if (targetLength > currentLength) {
  438. const diff = targetLength - currentLength
  439. for (let i = 0; i < diff; i++) {
  440. inParams.value.push({
  441. code: 'thickness',
  442. name: '厚度分布',
  443. value: '0.003',
  444. flag: 1,
  445. con: ''
  446. })
  447. }
  448. } else if (targetLength < currentLength) {
  449. const diff = currentLength - targetLength
  450. inParams.value.splice(-diff, diff)
  451. }
  452. }
  453. const gettacs = (id,nowid) => {
  454. pid.value = id;
  455. if(nowid){
  456. wid.value = nowid;
  457. }
  458. // console.log('pid-1:',pid.value)
  459. const params = {
  460. transCode: "MDO0066",
  461. pid: pid.value,
  462. wid: wid.value
  463. };
  464. request(params).then((res) => {
  465. if (res.hasOwnProperty("tacsid")) {
  466. gettacsAssign(res);
  467. emitter.emit('tacsidFromTACS', tacsid);
  468. // 默认加载cgns文件
  469. if (res.fid) {
  470. openWebSocket(res.fid);
  471. }
  472. }
  473. })
  474. .catch((err) => {
  475. console.log('TACS初始化失败err:',err)
  476. ElMessage.error('TACS初始化失败')
  477. })
  478. }
  479. const gettacsAssign = (data) => {
  480. // console.log('getfsiAssign-data:', data);
  481. pid.value = data.pid;
  482. tacsid.value = data.tacsid;
  483. fid.value = data.fid;
  484. fname.value = data.fname;
  485. tacsvalue.value.proname = data.proname;
  486. tacsvalue.value.rho = data.rho;
  487. tacsvalue.value.e = data.e;
  488. tacsvalue.value.nu = data.nu;
  489. tacsvalue.value.ys = data.ys;
  490. tacsvalue.value.useffd = data.useffd;
  491. tacsvalue.value.l2convergence = data.l2convergence;
  492. tacsvalue.value.l2convergencerel = data.l2convergencerel;
  493. writesolution.value = data.writesolution;
  494. // inParams.value = parseStringToArray(data.inParams);
  495. outParams.value = data.outParams;
  496. applyValueStringToParams(inParams,data.thickness);
  497. }
  498. const applyValueStringToParams = (params, valueStr) => {
  499. const values = valueStr.split(',').map(Number); // 把字符串拆成数字数组
  500. params.value.forEach((item, index) => {
  501. if (index < values.length) {
  502. item.value = values[index];
  503. }
  504. });
  505. }
  506. const convertToStringArray = (result, Data) => {
  507. console.log('Data:', Data);
  508. // 安全检查 Data,确保它是一个数组
  509. if (!Array.isArray(Data)) {
  510. console.error('Data should be an array');
  511. return result; // 返回原 result 或者根据需要返回其他默认值
  512. }
  513. result = Data.map(row => {
  514. // 获取每一行的 `code`, `name`, `value` 和 `flag`
  515. const code = row.code || ' ';
  516. const name = row.name || ' ';
  517. const value = (row.value === null || row.value === undefined || row.value === '') ? ' ' : row.value;
  518. const flag = (row.value === null || row.flag === undefined || row.flag === '') ? ' ' : row.flag;
  519. // 将字段连接为一个以逗号分隔的字符串
  520. return `${code},${name},${value},${flag}`;
  521. }).join(';'); // 每行之间用分号分隔
  522. return result;
  523. }
  524. // "code,name,value#value#value,flag" 格式的字符串
  525. const convertToStringArray1 = (result, Data) => {
  526. console.log('Data:', Data);
  527. // 安全检查 Data,确保它是一个数组
  528. if (!Array.isArray(Data)) {
  529. console.error('Data should be an array');
  530. return result; // 返回原 result 或者根据需要返回其他默认值
  531. }
  532. if (Data.length === 0) {
  533. return result; // 如果数组为空,直接返回原 result
  534. }
  535. // 获取第一行的 code, name 和 writesolution
  536. const code = Data[0].code || ' ';
  537. const name = Data[0].name || ' ';
  538. const flag = writesolution.value !== undefined && writesolution.value !== null
  539. ? writesolution.value
  540. : ' ';
  541. // 收集所有行的 value,用 # 拼接
  542. const values = Data.map(row => {
  543. return (row.value === null || row.value === undefined || row.value === '') ? ' ' : row.value;
  544. }).join('#');
  545. // 构建最终字符串
  546. result = `${code},${name},${values},${flag}`;
  547. return result;
  548. }
  549. // 将 "code,name,value#value#value,flag" 格式的字符串转回数组格式
  550. const parseStringToArray = (inParamsArray) => {
  551. if (!Array.isArray(inParamsArray)) return [];
  552. return inParamsArray.map(item => {
  553. const values = item.value.split('#').map(v => v === ' ' ? '' : v);
  554. return values.map(value => ({
  555. ...item, // 保留原对象所有属性
  556. value: value, // 覆盖为单个值
  557. }));
  558. }).flat(); // 将二维数组展平
  559. }
  560. const gettacssave = (id,nowid) => {
  561. if(nowid){
  562. wid.value = nowid
  563. }
  564. pid.value = id;
  565. // console.log("pid:",pid.value);
  566. const params = {
  567. transCode: "MDO0067",
  568. pid: pid.value,
  569. wid: wid.value,
  570. proname: tacsvalue.value.proname,
  571. rho: tacsvalue.value.rho,
  572. e: tacsvalue.value.e,
  573. nu: tacsvalue.value.nu,
  574. ys: tacsvalue.value.ys,
  575. useffd: tacsvalue.value.useffd,
  576. l2convergence: tacsvalue.value.l2convergence,
  577. l2convergencerel: tacsvalue.value.l2convergencerel,
  578. writesolution: writesolution.value,
  579. fid: fid.value,
  580. fname: fname.value,
  581. // inParams: convertToStringArray1([],inParams.value),
  582. inParams:'',
  583. thickness: inParams.value.map(item => item.value).join(','),
  584. outParams: convertToStringArray([],outParams.value)
  585. };
  586. request(params).then((res) => {
  587. ElMessage({
  588. message: '保存成功',
  589. type: 'success',
  590. })
  591. gettacs(pid.value,wid.value);
  592. })
  593. .catch((err) => {
  594. ElMessage.error('保存失败')
  595. })
  596. }
  597. defineExpose({ gettacs, gettacsAssign, gettacssave })
  598. const fetchBdfData = async (fpid) =>{
  599. isLoading.value = true;
  600. try {
  601. const response = await fetch('https://www.adicn.com/airopt/TransServlet', {
  602. method: 'POST',
  603. headers: {
  604. 'Content-Type': 'application/json',
  605. },
  606. body: JSON.stringify({
  607. channelNo: 'service',
  608. clientToken: 'e47b87eec69545559d1e81e56626da68',
  609. transCode: 'MDO0072',
  610. userId: '5f06c8bc77234f969d13e160b54c27e3',
  611. fid: fpid
  612. }),
  613. });
  614. }catch (error) {
  615. isLoading.value = false;
  616. console.error("请求失败:", error.response || error);
  617. }
  618. }
  619. function getUrl(channelNo = 'service') {
  620. let url = ''
  621. if (channelNo == 'service') {
  622. url = '/TransServlet'
  623. } else if (channelNo == 'manager') {
  624. url = '/managersvr/TransServlet'
  625. }
  626. return url
  627. }
  628. const getBdfData = async (fpid) =>{
  629. let url = import.meta.env.VITE_BASE_URL + getUrl();
  630. try {
  631. const response = await fetch(url, {
  632. method: 'POST',
  633. headers: {
  634. 'Content-Type': 'application/json',
  635. },
  636. body: JSON.stringify({
  637. channelNo: 'service',
  638. clientToken: 'e47b87eec69545559d1e81e56626da68',
  639. transCode: 'MDO0062',
  640. userId: '5f06c8bc77234f969d13e160b54c27e3',
  641. fid: fpid
  642. }),
  643. });
  644. // 解析 JSON 数据
  645. const data = await response.json(); //
  646. console.log('接口返回的数据:', data); // 正确打印数据
  647. bdfData.value = data;
  648. isLoading.value = false;
  649. }catch (error) {
  650. isLoading.value = false;
  651. console.error("请求失败:", error.response || error);
  652. }
  653. }
  654. const handleFileUploadSuccess = (data) => {
  655. //隐藏进度条
  656. setTimeout(() => {
  657. percentage.value = 0;
  658. }, 1000);
  659. fname.value = data.fname
  660. fid.value = data.bfid
  661. console.log("文件上传成功,bfid:", data.bfid, "fname:", data.fname)
  662. // 开启websocket
  663. openWebSocket(data.bfid);
  664. }
  665. //websockct的连接
  666. const openWebSocket = (fid) => {
  667. if (websock.value?.readyState === 1) return; // 避免重复创建
  668. const wsurl = import.meta.env.VITE_WEBSOCKET_URL + fid;
  669. const ws = new WebSocket(wsurl);
  670. websock.value = ws;
  671. ws.onopen = websocketonopen;
  672. ws.onmessage = websocketonmessage;
  673. ws.onerror = websocketonerror;
  674. ws.onclose = websocketclose;
  675. }
  676. // Websoket连接成功事件
  677. const websocketonopen = (res) => {
  678. console.log("bdfWebSocket连接成功", res);
  679. // 开启心跳
  680. start();
  681. // 调用文件解析
  682. fetchBdfData(fid.value);
  683. };
  684. // Websoket接收消息事件
  685. const websocketonmessage = (res) => {
  686. try {
  687. const receivedData = JSON.parse(res.data);
  688. console.log('receivedData', receivedData);
  689. if (receivedData.status === 0) {
  690. console.log("转换成功,准备关闭");
  691. // 1. 清除所有计时器
  692. clearAllTimers();
  693. // 2. 解除事件监听
  694. websock.value.onclose = null;
  695. websock.value.onerror = null;
  696. // 3. 主动关闭(code 1000)
  697. websock.value.close(1000, "Normal closure");
  698. // 4. 置空引用
  699. websock.value = null;
  700. // 5. 获取数据
  701. getBdfData(fid.value);
  702. return; // 直接返回
  703. }
  704. } catch (e) {
  705. // 非JSON消息(如连接成功的文本提示或心跳回复)
  706. if (res.data === "建立服务端连接成功!") {
  707. console.log("WebSocket连接已建立");
  708. }
  709. else if (res.data === "服务端已经接收到消息,msg=heartCheck") {
  710. console.log("心跳确认");
  711. }
  712. else {
  713. console.warn("未知的非JSON消息:", res.data);
  714. }
  715. }
  716. reset();
  717. };
  718. // Websoket连接错误事件
  719. const websocketonerror = (res) => {
  720. console.log("连接错误", res);
  721. websock.close();
  722. reconnect();
  723. };
  724. // Websoket断开事件
  725. const websocketclose = (res) => {
  726. console.log("断开连接", res);
  727. // 只有当非正常关闭时才重连(code 1000=正常关闭)
  728. if (res.code !== 1000) {
  729. reconnect();
  730. }
  731. };
  732. // 心跳包
  733. const reconnect = () => {
  734. // 增加多个防护条件
  735. if (
  736. times.value.lockReconnect ||
  737. websock.value?.readyState === 1 ||
  738. !adflowvalue.value?.fid
  739. ) return;
  740. console.log("尝试重连...");
  741. times.value.lockReconnect = true;
  742. clearTimeout(times.value.timeoutnum);
  743. times.value.timeoutnum = setTimeout(() => {
  744. if (!websock.value || websock.value?.readyState > 1) {
  745. openWebSocket(fid.value);
  746. }
  747. times.value.lockReconnect = false;
  748. }, 10000);
  749. };
  750. const reset = () => {
  751. //重置心跳
  752. clearTimeout(times.value.timeoutObj);
  753. clearTimeout(times.value.serverTimeoutObj);
  754. start();
  755. }
  756. const start = () => {
  757. clearTimeout(times.value.timeoutObj);
  758. clearTimeout(times.value.serverTimeoutObj);
  759. // 增加连接状态检查
  760. if (!websock.value || websock.value.readyState !== 1) return;
  761. times.value.timeoutObj = setTimeout(() => {
  762. if (websock.value?.readyState === 1) {
  763. websock.value.send("heartCheck");
  764. times.value.serverTimeoutObj = setTimeout(() => {
  765. if (websock.value?.readyState === 1) {
  766. websock.value.close(1006, "Heartbeat timeout");
  767. }
  768. }, times.value.timeout);
  769. }
  770. }, times.value.heartBeatInterval);
  771. }
  772. const clearAllTimers = () => {
  773. clearTimeout(times.value.timeoutObj);
  774. clearTimeout(times.value.serverTimeoutObj);
  775. clearTimeout(times.value.timeoutnum);
  776. times.value.lockReconnect = true; // 永久锁定重连
  777. };
  778. </script>
  779. <style scoped lang="scss">
  780. :deep(.collapse-title) {
  781. flex: 1 0 90%;
  782. order: 1;
  783. text-align: left;
  784. }
  785. :deep(.collapse-title) .el-collapse-item__header {
  786. flex: 1 0 auto;
  787. order: -1;
  788. }
  789. </style>