index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <template>
  2. <div class="log-viewer">
  3. <s-header :name="$t('device.viewLogs')" :noback="false" />
  4. <!-- 设备名称标题 -->
  5. <div class="device-header">
  6. <div class="vertical-indicator"></div>
  7. <h3 class="device-name">
  8. {{ $t("device.equipmentName") }}:{{ deviceName }}
  9. </h3>
  10. </div>
  11. <!-- 操作卡片 -->
  12. <div class="operation-card">
  13. <!-- 时间选择 -->
  14. <div class="time-picker-card">
  15. <van-field
  16. v-model="logsTime"
  17. is-link
  18. readonly
  19. label="选择日期"
  20. placeholder="点击选择时间"
  21. @click="chooseTime"
  22. class="time-field"
  23. />
  24. <van-popup v-model:show="showPicker" round position="bottom">
  25. <van-date-picker
  26. :min-date="minDate"
  27. :max-date="maxDate"
  28. @confirm="selectTime"
  29. @cancel="showPicker = false"
  30. />
  31. </van-popup>
  32. </div>
  33. <!-- 使用说明 -->
  34. <div class="tutorial-card">
  35. <van-field
  36. v-model="message"
  37. readonly
  38. rows="2"
  39. autosize
  40. label="使用教程"
  41. type="textarea"
  42. class="tutorial-field"
  43. />
  44. </div>
  45. <!-- 新增通道选择 -->
  46. <div class="channel-selector">
  47. <van-radio-group v-model="channelType" direction="horizontal">
  48. <van-radio name="1" icon-size="16px" class="channel-option">
  49. 默认通道
  50. </van-radio>
  51. <van-radio name="2" icon-size="16px" class="channel-option">
  52. 新通道
  53. </van-radio>
  54. </van-radio-group>
  55. </div>
  56. <!-- 操作按钮 -->
  57. <div class="action-buttons">
  58. <van-button
  59. round
  60. type="primary"
  61. icon="back-top"
  62. @click="uploadLogBtn"
  63. class="action-btn"
  64. >
  65. 上传日志
  66. </van-button>
  67. <van-button
  68. round
  69. type="success"
  70. icon="search"
  71. @click="queryLogBtn"
  72. class="action-btn"
  73. >
  74. 查询日志
  75. </van-button>
  76. </div>
  77. <!-- 文件下载卡片 -->
  78. <div v-if="fileName" class="file-card">
  79. <van-cell
  80. :title="fileName"
  81. clickable
  82. @click="downloadFile"
  83. class="file-item"
  84. >
  85. <template #right-icon>
  86. <van-icon name="down" class="download-icon" />
  87. </template>
  88. </van-cell>
  89. </div>
  90. </div>
  91. </div>
  92. </template>
  93. <script>
  94. import { onMounted, computed, ref } from "vue";
  95. import sHeader from "@/components/SimpleHeader.vue";
  96. import {
  97. downloadLog,
  98. getDeviceDetal,
  99. uploadLog,
  100. newUploadLog,
  101. queryLog,
  102. } from "@/service/device";
  103. import { $M_ExportFile } from "@/common/js/utils";
  104. import { showFailToast, showToast } from "vant";
  105. import { useRoute } from "vue-router";
  106. import dateUtil from "@/utils/dateUtil";
  107. // import { styleUrl } from "../../../common/js/utils";
  108. // import {useI18n} from "vue-i18n";
  109. export default {
  110. components: {
  111. sHeader,
  112. },
  113. setup() {
  114. // const { t } = useI18n();
  115. const route = useRoute();
  116. const deviceId = route.query.deviceId;
  117. const deviceDetail = ref(null);
  118. const showPicker = ref(false);
  119. const downloading = ref(false);
  120. const logsTime = ref(null);
  121. const fileName = ref(""); // 文件名
  122. const message = ref(
  123. "选择日期点击上传,等待几秒后点击查询,找到文件点击下载到本地查看。"
  124. );
  125. // 计算最小日期为当前日期7天前
  126. const minDate = computed(() => {
  127. const minTimestamp = new Date().getTime() - 6 * 24 * 3600 * 1000;
  128. const minDate = dateUtil.formateDate(
  129. new Date(minTimestamp),
  130. "yyyy-MM-dd"
  131. );
  132. return new Date(minDate);
  133. });
  134. // 计算最大日期为当前日期
  135. const maxDate = computed(() => {
  136. return new Date();
  137. });
  138. // 上传通道
  139. const channelType = ref("1");
  140. // 初始化页面获取列表
  141. onMounted(async () => {
  142. // 加载样式
  143. await getDeviceDetailFun();
  144. });
  145. // 选择时间
  146. const chooseTime = () => {
  147. showPicker.value = true;
  148. };
  149. const selectTime = ({ selectedValues }) => {
  150. logsTime.value = selectedValues.join("-");
  151. showPicker.value = false;
  152. };
  153. const getDeviceDetailFun = async () => {
  154. const { data } = await getDeviceDetal({
  155. id: deviceId,
  156. });
  157. if (data.code === "00000") {
  158. deviceDetail.value = data.data;
  159. } else {
  160. showFailToast(data.message);
  161. }
  162. };
  163. const downloadBtn = async () => {
  164. const pattern = /^\d{4}-\d{2}-\d{2}$/;
  165. if (logsTime.value == null) {
  166. showToast("请选择日期");
  167. return;
  168. }
  169. if (!pattern.test(logsTime.value)) {
  170. showToast("日期格式有误");
  171. return;
  172. }
  173. const formattedDate = logsTime.value.replace(/-/g, "");
  174. downloading.value = true;
  175. try {
  176. const { headers, data } = await downloadLog({
  177. equipmentId: deviceId,
  178. day: formattedDate,
  179. });
  180. console.log("请求成功", headers, data);
  181. $M_ExportFile(data, headers);
  182. } catch (error) {
  183. if (error.code === "ECONNABORTED") {
  184. // 处理请求超时的错误
  185. console.error("请求超时:", error);
  186. showFailToast("请求超时");
  187. } else {
  188. // 处理其他请求错误
  189. console.error("请求失败:", error);
  190. showFailToast("请求失败");
  191. }
  192. } finally {
  193. downloading.value = false;
  194. }
  195. };
  196. const uploadLogBtn = async () => {
  197. const pattern = /^\d{4}-\d{2}-\d{2}$/;
  198. if (logsTime.value == "") {
  199. showToast("请选择日期");
  200. return;
  201. }
  202. if (!pattern.test(logsTime.value)) {
  203. showToast("日期格式有误");
  204. return;
  205. }
  206. const formattedDate = logsTime.value.replace(/-/g, "");
  207. const { data } = await (channelType.value == "1"
  208. ? uploadLog
  209. : newUploadLog)({
  210. equipmentId: deviceId,
  211. day: formattedDate,
  212. });
  213. if (data.code == "00000") {
  214. showToast("上传信号发送成功,请等待几秒后查询下载");
  215. } else {
  216. showFailToast(data.message);
  217. }
  218. };
  219. // 查询日志是否上传成功
  220. const queryLogBtn = async () => {
  221. if (logsTime.value == "") {
  222. showToast("请选择日期");
  223. return;
  224. }
  225. const pattern = /^\d{4}-\d{2}-\d{2}$/;
  226. if (!pattern.test(logsTime.value)) {
  227. showToast("日期格式有误");
  228. return;
  229. }
  230. const formattedDate = logsTime.value.replace(/-/g, "");
  231. const { data } = await queryLog({
  232. equipmentId: deviceId,
  233. day: formattedDate,
  234. });
  235. if (data.code == "00000") {
  236. // console.log(data.data);
  237. showToast("查询成功,点击下载");
  238. fileName.value = data.data.substring(4);
  239. } else {
  240. fileName.value = "";
  241. showToast("找不到文件,请重试上传或检查机器网络");
  242. }
  243. };
  244. const downloadFile = async () => {
  245. // 文件链接
  246. const fileUrl = "https://qiniuyun.sunzee.com.cn/log/" + fileName.value;
  247. // 文件名
  248. const newFileName = fileName.value;
  249. try {
  250. const response = await fetch(fileUrl, {
  251. headers: {
  252. "Cache-Control": "no-store", // 禁用缓存
  253. Pragma: "no-cache",
  254. },
  255. });
  256. console.log("response", response);
  257. // 将文件内容创建为Blob
  258. const fileBlob = await response.blob();
  259. // 创建一个下载链接
  260. const downloadLink = document.createElement("a");
  261. downloadLink.href = URL.createObjectURL(fileBlob);
  262. downloadLink.download = newFileName;
  263. // 模拟点击下载链接
  264. document.body.appendChild(downloadLink);
  265. downloadLink.click();
  266. // 清理添加的链接元素
  267. document.body.removeChild(downloadLink);
  268. } catch (error) {
  269. console.error("请求超时:", error);
  270. }
  271. };
  272. return {
  273. deviceDetail,
  274. downloadBtn,
  275. chooseTime,
  276. selectTime,
  277. showPicker,
  278. logsTime,
  279. minDate,
  280. maxDate,
  281. downloading,
  282. downloadFile,
  283. uploadLogBtn,
  284. queryLogBtn,
  285. fileName,
  286. message,
  287. channelType,
  288. };
  289. },
  290. };
  291. </script>
  292. <style lang="less" scoped>
  293. @import "../../../common/style/common";
  294. @primary-color: #4dc294;
  295. @card-bg: #ffffff;
  296. @text-primary: #2d3436;
  297. @border-color: #e4e7ec;
  298. .log-viewer {
  299. background: #f8fafb;
  300. min-height: 100vh;
  301. }
  302. .device-header {
  303. display: flex;
  304. align-items: center;
  305. padding: 12px 15px;
  306. background: #fff;
  307. margin: 10px;
  308. border-radius: 8px;
  309. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  310. .vertical-indicator {
  311. width: 4px;
  312. height: 15px;
  313. background: var(--active-color, #4d6add);
  314. border-radius: 2px;
  315. margin-right: 10px;
  316. }
  317. .device-name {
  318. margin: 0;
  319. font-size: 15px;
  320. color: #404d74;
  321. font-weight: 550;
  322. // 长名称处理
  323. overflow: hidden;
  324. text-overflow: ellipsis;
  325. white-space: nowrap;
  326. max-width: 70vw;
  327. }
  328. }
  329. .operation-card {
  330. background: @card-bg;
  331. border-radius: 12px;
  332. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
  333. padding: 16px;
  334. margin: 10px;
  335. .time-picker-card {
  336. margin-bottom: 16px;
  337. .time-field {
  338. :deep(.van-field__label) {
  339. width: 80px;
  340. }
  341. }
  342. }
  343. .tutorial-card {
  344. border: 1px solid @border-color;
  345. border-radius: 8px;
  346. margin-bottom: 24px;
  347. .tutorial-field {
  348. :deep(.van-field__label) {
  349. color: @primary-color;
  350. font-weight: bold;
  351. }
  352. :deep(textarea) {
  353. color: #666;
  354. font-size: 13px;
  355. }
  356. }
  357. }
  358. .channel-selector {
  359. margin: 16px 0;
  360. padding: 12px 16px;
  361. background: #f8fafb;
  362. border-radius: 8px;
  363. .van-radio-group {
  364. display: flex;
  365. gap: 24px;
  366. justify-content: center;
  367. }
  368. .channel-option {
  369. :deep(.van-radio__label) {
  370. color: #2d3436;
  371. font-size: 14px;
  372. }
  373. :deep(.van-radio__icon) {
  374. color: #4dc294;
  375. }
  376. }
  377. }
  378. .action-buttons {
  379. display: grid;
  380. grid-template-columns: repeat(2, 1fr);
  381. gap: 12px;
  382. .action-btn {
  383. height: 44px;
  384. font-size: 15px;
  385. box-shadow: 0 4px 12px rgba(77, 194, 148, 0.2);
  386. &:active {
  387. transform: scale(0.98);
  388. }
  389. }
  390. }
  391. }
  392. .file-card {
  393. background: #666;
  394. border-radius: 12px;
  395. margin: 16px 0;
  396. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
  397. .file-item {
  398. :deep(.van-cell__title) {
  399. color: @text-primary;
  400. font-weight: 500;
  401. }
  402. .download-icon {
  403. color: @primary-color;
  404. font-size: 18px;
  405. margin-left: 8px;
  406. }
  407. &:active {
  408. background-color: #f8fafb;
  409. }
  410. }
  411. }
  412. @media (max-width: 480px) {
  413. .action-buttons {
  414. grid-template-columns: 1fr !important;
  415. .action-btn {
  416. width: 100%;
  417. }
  418. }
  419. .operation-card {
  420. padding: 12px;
  421. }
  422. }
  423. </style>