| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- <!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
- <route lang="json5">
- {
- style: {
- navigationBarTitleText: '打印任务列表',
- },
- }
- </route>
- <template>
- <view class="page-list">
- <wd-sticky>
- <view class="sticky-box">
- <wd-tabs v-model="queryParams.completed" auto-line-width @change="resetDataList">
- <wd-tab v-for="item of completedList" :key="item.value" :title="item.label" :name="item.value">
- </wd-tab>
- </wd-tabs>
- <!-- <wd-search
- placeholder="请输入打印任务名称"
- placeholder-left
- hide-cancel
- v-model="queryParams.title"
- @change="onSearch"
- @clear="onSearch"
- >
- <template v-slot:prefix>
- <wd-drop-menu custom-class="search-menu">
- <wd-drop-menu-item v-model="queryParams.completed" :options="completedList" @change="onSearch" />
- </wd-drop-menu>
- </template>
- </wd-search> -->
- </view>
- </wd-sticky>
- <view class="list-contain">
- <view v-if="queryParams.printer" class="printer">{{ `当前打印机: ${queryParams.printer} ${queryParams.asname ? `(${queryParams.asname})` : ''}` }}</view>
- <template v-if="dataList.length > 0 || loadStatus == 'loading'">
- <view v-for="item of dataList" :key="item.id" class="item-contain" @click="toDetail(item)">
- <view class="item-info">
- <view class="image">
- <wd-img
- :src="getFileType(item.title)"
- :width="60"
- :height="60"
- mode="aspectFit"
- ></wd-img>
- </view>
- <view class="name">
- <view class="main-text">
- <view>{{ item.title }}</view>
- </view>
- <view class="sub-text">
- <text>任务状态: </text>
- <text :class="`color-${getLabelColor(item.jobStatus, jobStatus)}`">{{ getLabel(item.jobStatus, jobStatus) + (item.statusMsg ? `(${item.statusMsg})` : "") }}</text>
- </view>
- <view class="sub-text">
- {{ `创建时间: ${item.createTime}` }}
- </view>
- <!-- <view class="sub-text flex-center">
- <view class="flex-1">xxx</view>
- <view class="flex-1">xxx</view>
- </view> -->
- </view>
- </view>
- <view class="item-opt">
- <view v-if="item.reprint == '1'" class="opt-btn" @click.stop="handleReprint(item.id)">重打</view>
- <view v-if="['3', '4', '5', '6'].includes(item.jobStatus)" class="opt-btn" @click.stop="handleCancel(item.id)">取消</view>
- <view class="opt-btn delete-btn" @click.stop="handleDelete(item)">删除</view>
- </view>
- </view>
- <!-- 加载更多 -->
- <wd-loadmore custom-class="loadmore" :state="loadStatus" />
- </template>
- <wd-status-tip v-else image="content" tip="暂无内容" />
- </view>
- </view>
- </template>
- <script lang="ts" setup>
- import { useMessage, useToast } from 'wot-design-uni'
- import { getUserHubJobsPage, reprintUserHubJobs, cancelUserHubJobs, deleteUserHubJobs } from '@/service/api'
- import { getLabel, getLabelColor } from '@/utils'
- import { debounce } from 'wot-design-uni/components/common/util'
- import { useUserStore } from '@/store'
- import { toLoginWait } from '@/utils'
- let socketBaseUrl = import.meta.env.VITE_SOCKET_BASEURL
- const userStore = useUserStore()
- const token = userStore.token
- const message = useMessage()
- const toast = useToast()
- const total = ref(0)
- const queryParams: any = reactive({
- pageNo: 1,
- pageSize: 10,
- completed: "all",
- title: '',
- printer: "",
- hubId: '',
- asname: '', // 打印助手名称
- })
- const completedList: any = ref([
- { label: '全部', value: 'all' },
- { label: '当前任务', value: '0' },
- { label: '历史任务', value: '1' }
- ])
- // 0连接中 3 等待中 4 已暂停 5 处理中 6已停止 7已取消 8已中止 9已完成
- const jobStatus: any = ref([
- { label: '连接中', value: '0', color: 'primary' },
- { label: '等待中', value: '3', color: 'info' },
- { label: '已暂停', value: '4', color: 'info' },
- { label: '处理中', value: '5', color: 'primary' },
- { label: '已停止', value: '6', color: 'danger' },
- { label: '已取消', value: '7', color: 'danger' },
- { label: '已中止', value: '8', color: 'danger' },
- { label: '已完成', value: '9', color: 'success' },
- ])
- const dataList = ref([])
- // 加载中: loading; 没有数据: finished; 错误: error;
- const loadStatus: any = ref('loading')
- const isToTop = ref(false)
- // 文件类型
- const imageSrc = '/static/images/image.png'
- const docSrc = '/static/images/doc.png'
- const xlsSrc = '/static/images/xls.png'
- const pptSrc = '/static/images/ppt.png'
- const pdfSrc = '/static/images/pdf.png'
- const txtSrc = '/static/images/txt.png'
- const otherSrc = '/static/images/other.png'
- // websocket连接对象
- const socketTask = ref(null)
- // 是否正在重连
- const isReconnecting = ref(false)
- // 当前重连次数
- const reconnectAttempts = ref(0)
- // 最大重连次数
- const maxReconnectAttempts = ref(5)
- // 重连间隔时间
- const reconnectInterval = 1000
- defineOptions({
- name: 'Job',
- })
- // 根据文件类型获取图片
- // 允许的文件类型 allowType = ['txt', 'pdf', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'pptx', 'gif', 'png', 'jpg', 'jpeg', 'webp']
- function getFileType(fileName) {
- let src = otherSrc
- let fileType = ""
- if (fileName) {
- const lastIndex = fileName.lastIndexOf('.')
- if (lastIndex != -1) fileType = fileName.substring(lastIndex + 1)
- switch (fileType) {
- case "doc":
- case "docx":
- src = docSrc
- break;
- case "xls":
- case "xlsx":
- src = xlsSrc;
- break;
- case "ppt":
- case "pptx":
- src = pptSrc;
- break;
- case "png":
- case "jpg":
- case "jpeg":
- case "gif":
- case "webp":
- src = imageSrc;
- break;
- case "txt":
- src = txtSrc;
- break;
- case "pdf":
- src = pdfSrc;
- break;
- }
- }
- return src
- }
- // 获取列表数据
- function getDataList() {
- loadStatus.value = 'loading'
- const params = { ...queryParams }
- if (params.title) {
- params.title = '*' + params.title + '*'
- } else {
- delete params.title
- }
- if (params.completed === '' || params.completed === 'all') delete params.completed
- if (!params.printer) delete params.printer
- if (!params.hubId) delete params.hubId
- getUserHubJobsPage(params)
- .then((res: any) => {
- if (res.code === 0 && res.body) {
- total.value = res.body.total
- if (queryParams.pageNo === 1) {
- dataList.value = res.body.records || []
- } else {
- dataList.value.push(...(res.body.records || []))
- }
- }
- })
- .catch((e) => {})
- .finally(() => {
- loadStatus.value = 'finished'
- })
- }
- const onSearch = debounce(() => {
- queryParams.pageNo = 1
- getDataList()
- }, 500)
- // 查看详情
- function toDetail(item: any) {
- if (['0'].includes(item.jobStatus)) return
- uni.navigateTo({
- url: `/pages/print/jobDetail?id=${item.id}`,
- })
- }
- // 重打任务
- function handleReprint(id: string) {
- reprintUserHubJobs({id}).then((res: any) => {
- if (res.code === 0) {
- toast.success('已发送重新打印')
- getDataList()
- }
- })
- .catch((error) => {
- console.log(error)
- })
- }
- // 取消任务
- function handleCancel(id: string) {
- cancelUserHubJobs({id}).then((res: any) => {
- if (res.code === 0) {
- toast.success('取消打印任务成功')
- getDataList()
- }
- })
- .catch((error) => {
- console.log(error)
- })
- }
- // 删除任务
- function handleDelete(item) {
- message
- .confirm({
- title: '提示',
- msg: `是否删除打印任务: ${item.title} ?`,
- })
- .then(() => {
- deleteUserHubJobs({id: item.id})
- .then((res: any) => {
- if (res.code === 0) {
- toast.success('删除成功')
- queryParams.pageNo = 1
- getDataList()
- }
- })
- .catch((error) => {
- console.log(error)
- })
- })
- .catch((error) => {
- console.log(error)
- })
- }
- // 查询列表, 滚动到顶部, 返回第一页
- function resetDataList() {
- uni.pageScrollTo({
- scrollTop: 0,
- duration: 300,
- })
- dataList.value = []
- onSearch()
- }
- // 页面加载
- onLoad((option) => {
- initWebSocket()
- // 指定打印机 & 助手ID
- if (option && option.printer && option.hubId) {
- queryParams.printer = option.printer
- queryParams.hubId = option.hubId
- queryParams.asname = option.asname
- }
- if (option && option.tab) {
- queryParams.completed = option.tab
- }
- getDataList()
- // 监听刷新
- uni.$on('refreshData', () => {
- resetDataList()
- isToTop.value = true
- })
- })
- onUnload(() => {
- closeWebSocket()
- })
- // 页面显示
- onShow(() => {
- if (isToTop.value) {
- uni.pageScrollTo({
- scrollTop: 0,
- duration: 300,
- })
- isToTop.value = false
- }
- })
- // 滚动到底部
- onReachBottom(() => {
- if (dataList.value.length < total.value) {
- queryParams.pageNo++
- getDataList()
- } else {
- loadStatus.value = 'finished'
- }
- })
- // 创建websocket连接
- function initWebSocket() {
- socketTask.value = uni.connectSocket({
- url: `${socketBaseUrl}/print/ws`,
- header: {
- 'token': token
- },
- success: () => {
- console.log("连接成功");
- },
- fail: (err) => {
- console.log("连接失败: ", err);
- handleReconnect()
- }
- })
- // 监听 WebSocket 连接打开事件
- socketTask.value.onOpen(() => {
- console.log('WebSocket 连接已打开');
- // 发送数据到服务器(心跳)
- // socketTask.send({
- // data: JSON.stringify({ message: 'Hello, Server!' }),
- // success: () => {
- // console.log('消息发送成功');
- // },
- // fail: (err) => {
- // console.error('消息发送失败', err);
- // }
- // });
- })
- // 监听 WebSocket 接收到消息事件
- socketTask.value.onMessage((res) => {
- console.log('Received message:', res.data);
- // 处理接收到的数据
- const lines = res.data.split(/\r?\n|\r/).filter(line => line.trim() !== '');
- const message = lines.reduce((obj, item) => {
- const [key, value] = item.split(/:(.*)/);
- obj[key] = key == "data" ? JSON.parse(value) : value;
- return obj;
- }, {});
- console.log('解析后的消息:', message);
- handleWebsocketMessage(message)
- })
- // 监听 WebSocket 错误事件
- socketTask.value.onError((err) => {
- console.error('WebSocket 发生错误', err);
- });
- // 监听 WebSocket 连接关闭事件 (线上存在一分钟就断开重连的问题)
- socketTask.value.onClose((res) => {
- console.log('WebSocket 连接已关闭:', res); // 异常断开 {code: 1006, reason: "abnormal closure"}
- if (res.code !== 1000) handleReconnect()
- });
- }
- // 断开websocket连接
- function closeWebSocket() {
- if (socketTask.value) {
- socketTask.value.close({
- success: () => {
- console.log('断开 WebSocket 连接成功');
- },
- fail: (err) => {
- console.error('断开 WebSocket 连接失败', err);
- },
- });
- socketTask.value = null
- }
- }
- // 处理重连机制
- function handleReconnect() {
- console.log('reconnectAttempts.value: ', reconnectAttempts.value);
- if (isReconnecting.value || reconnectAttempts.value >= maxReconnectAttempts.value) {
- console.log('已达到最大重连次数,停止重连');
- return;
- }
- isReconnecting.value = true
- reconnectAttempts.value = reconnectAttempts.value++
-
- setTimeout(() => {
- initWebSocket();
- isReconnecting.value = false;
- }, reconnectInterval)
- }
- // 处理Websocket消息
- function handleWebsocketMessage(message: any) {
- switch (message.event) {
- case "job-update":
- handleUpdate(message.data)
- break;
- case "not-verify":
- toast.warning('token失效, 请重新登录')
- toLoginWait(1500)
- break;
- }
- }
- // 处理更新数据
- function handleUpdate(data) {
- if (data.id && dataList.value.findIndex(i => i.id == data.id) !== -1) {
- // 更新状态
- let target: any = dataList.value.find(i => i.id == data.id)
- target.jobStatus = String(data.jobStatus)
- target.statusMsg = data.statusMsg
- // 通知
- switch (String(data.jobStatus)) {
- case "9":
- toast.success(`打印完成: ${data.title}`)
- break;
- case "6":
- toast.error(`打印错误: ${data.title}`)
- break;
- default:
- toast.info(`打印信息: ${data.title} ${getLabel(data.jobStatus, jobStatus.value)}`)
- break;
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .sticky-box {
- width: 100vw;
- height: 100rpx;
- background-color: #ffffff;
- }
- .search-type {
- position: relative;
- height: 60rpx;
- padding: 0 16rpx 0 32rpx;
- line-height: 60rpx;
- }
- .search-type::after {
- position: absolute;
- top: 10rpx;
- right: 0;
- bottom: 10rpx;
- width: 2rpx;
- content: '';
- background: rgba(0, 0, 0, 0.25);
- }
- .search-type {
- :deep(.icon-arrow) {
- display: inline-block;
- font-size: 40rpx;
- vertical-align: middle;
- }
- }
- .printer {
- margin-bottom: 20rpx;
- }
- .list-contain {
- padding: 10rpx 30rpx 30rpx;
- overflow-y: auto;
- .item-contain {
- padding: 30rpx;
- margin-bottom: 30rpx;
- border: 2rpx solid #eee;
- .item-info {
- display: flex;
- align-items: flex-start;
- .image {
- display: flex;
- padding-right: 14rpx;
- }
- .name {
- flex: 1;
- .main-text {
- display: flex;
- align-items: center;
- word-break: break-all;
- }
- .sub-text {
- margin-top: 14rpx;
- font-size: 28rpx;
- color: #00000073;
- }
- }
- .opt {
- width: 80rpx;
- text-align: center;
- }
- }
- .item-opt {
- margin-top: 20rpx;
- border-top: 2rpx solid #eee;
- padding-top: 20rpx;
- text-align: right;
- font-size: 28rpx;
-
- .opt-btn {
- display: inline-block;
- padding: 4rpx 20rpx;
- margin-left: 20rpx;
- color: var(--wot-color-theme);
- border: 2rpx solid var(--wot-color-theme);
- border-radius: 8rpx;
- }
- .opt-btn:first-of-type {
- margin-left: 0;
- }
- .delete-btn {
- color: #fa4350;
- border-color: #fa4350;
- }
- }
- }
- }
- //搜索下拉菜单
- :deep(.search-menu .wd-drop-menu__list) {
- background: none;
- }
- :deep(.search-menu .wd-drop-menu__item) {
- height: 62rpx;
- line-height: 62rpx;
- }
- :deep(.search-menu .wd-drop-menu__item-title::after) {
- content: none;
- }
- :deep(.wd-message-box__content) {
- word-break: break-all;
- }
- </style>
|