chore: 初始开源发布
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
<script lang="ts" setup>
|
||||
import { createWorkOrder } from '../api/workOrder'
|
||||
import RouterPath from '@/constant/RouterPath'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useTokenStore } from '@/store/token'
|
||||
import { hasAnyFeature } from '@/utils/permission'
|
||||
import { getEnvBaseUrl } from '@/utils'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '提交工单',
|
||||
},
|
||||
})
|
||||
|
||||
const problemTypes = [
|
||||
{ label: '卡片问题', value: 0 },
|
||||
{ label: '模组问题', value: 1 },
|
||||
{ label: '设备问题', value: 2 },
|
||||
{ label: '订单问题', value: 3 },
|
||||
{ label: '余额问题', value: 4 },
|
||||
{ label: '提现问题', value: 5 },
|
||||
]
|
||||
|
||||
const userStore = useUserStore()
|
||||
const noPermission = ref(false)
|
||||
const submitting = ref(false)
|
||||
const uploading = ref(false)
|
||||
const attachmentUrls = ref<string[]>([])
|
||||
const attachmentNames = ref<string[]>([])
|
||||
const canCreateWorkOrder = computed(() => hasAnyFeature(userStore.userInfo, 'workorder:create'))
|
||||
const formData = reactive({
|
||||
woType: [0],
|
||||
title: '',
|
||||
content: '',
|
||||
})
|
||||
|
||||
const typeText = computed(() => {
|
||||
const value = Number(formData.woType[0] ?? 0)
|
||||
return problemTypes.find(item => item.value === value)?.label || '卡片问题'
|
||||
})
|
||||
|
||||
function chooseAttachment() {
|
||||
// #ifdef H5
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '*/*'
|
||||
input.onchange = (event) => {
|
||||
const file = (event.target as HTMLInputElement).files?.[0]
|
||||
if (file)
|
||||
void uploadAttachment(file, file.name)
|
||||
}
|
||||
input.click()
|
||||
return
|
||||
// #endif
|
||||
|
||||
const choose = (uni as any).chooseFile || (uni as any).chooseMessageFile
|
||||
if (typeof choose !== 'function') {
|
||||
uni.showToast({ title: '当前平台暂不支持选择附件', icon: 'none' })
|
||||
return
|
||||
}
|
||||
choose({
|
||||
count: 1,
|
||||
type: 'all',
|
||||
success: (res: any) => {
|
||||
const file = res.tempFiles?.[0] || res.tempFilePaths?.[0]
|
||||
const filePath = file?.path || file?.tempFilePath || file
|
||||
const fileName = file?.name || String(filePath || '').split('/').pop() || '附件'
|
||||
if (filePath)
|
||||
void uploadAttachment(filePath, fileName)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function uploadAttachment(filePath: string | File, fileName: string) {
|
||||
uploading.value = true
|
||||
try {
|
||||
if (typeof filePath !== 'string') {
|
||||
const token = useTokenStore().updateNowTime().validToken
|
||||
const form = new FormData()
|
||||
form.append('file', filePath, fileName)
|
||||
form.append('fileType', 'file')
|
||||
const response = await fetch(`${getEnvBaseUrl()}/work-order/upload`, {
|
||||
method: 'POST',
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
||||
body: form,
|
||||
})
|
||||
const body = await response.json()
|
||||
if (!response.ok || Number(body.code) !== 0)
|
||||
throw new Error(body.message || body.msg || '附件上传失败')
|
||||
const data = body.data || body
|
||||
const url = data.url || data.path
|
||||
if (!url)
|
||||
throw new Error('上传接口未返回文件地址')
|
||||
attachmentUrls.value.push(url)
|
||||
attachmentNames.value.push(fileName)
|
||||
return
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: `${getEnvBaseUrl()}/work-order/upload`,
|
||||
filePath,
|
||||
name: 'file',
|
||||
formData: { fileType: 'file' },
|
||||
success: (res) => {
|
||||
try {
|
||||
const body = JSON.parse(res.data)
|
||||
const data = body.data || body
|
||||
const url = data.url || data.path
|
||||
if (!url)
|
||||
throw new Error('上传接口未返回文件地址')
|
||||
attachmentUrls.value.push(url)
|
||||
attachmentNames.value.push(fileName)
|
||||
resolve()
|
||||
}
|
||||
catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
uni.showToast({ title: getErrorMessage(error, '附件上传失败'), icon: 'none' })
|
||||
}
|
||||
finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
function removeAttachment(index: number) {
|
||||
attachmentUrls.value.splice(index, 1)
|
||||
attachmentNames.value.splice(index, 1)
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!canCreateWorkOrder.value) {
|
||||
uni.showToast({ title: '暂无提交工单权限', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const title = formData.title.trim()
|
||||
const content = formData.content.trim()
|
||||
if (!title) {
|
||||
uni.showToast({ title: '请输入工单标题', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!content) {
|
||||
uni.showToast({ title: '请输入问题描述', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const res = await createWorkOrder({
|
||||
woType: Number(formData.woType[0] ?? 0),
|
||||
title,
|
||||
content,
|
||||
attachmentUrls: attachmentUrls.value,
|
||||
})
|
||||
const woNo = res.workOrder?.woNo || ''
|
||||
uni.showToast({ title: '工单已提交', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
if (woNo) {
|
||||
uni.redirectTo({
|
||||
url: `${RouterPath.BusinessViewsWorkOrderDetail}?woNo=${encodeURIComponent(woNo)}`,
|
||||
})
|
||||
}
|
||||
else {
|
||||
uni.navigateBack()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
catch (error) {
|
||||
uni.showToast({ title: getErrorMessage(error, '工单提交失败'), icon: 'none' })
|
||||
}
|
||||
finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown, fallback: string) {
|
||||
if (error && typeof error === 'object') {
|
||||
const data = error as { msg?: unknown, message?: unknown, errMsg?: unknown }
|
||||
if (typeof data.msg === 'string' && data.msg)
|
||||
return data.msg
|
||||
if (typeof data.message === 'string' && data.message)
|
||||
return data.message
|
||||
if (typeof data.errMsg === 'string' && data.errMsg)
|
||||
return data.errMsg
|
||||
}
|
||||
if (error instanceof Error && error.message)
|
||||
return error.message
|
||||
return fallback
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await userStore.fetchUserInfo().catch(() => undefined)
|
||||
if (!canCreateWorkOrder.value) {
|
||||
noPermission.value = true
|
||||
uni.showToast({ title: '当前账号不能提交工单', icon: 'none' })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen p-24rpx pb-40rpx">
|
||||
<view class="mx-auto max-w-920rpx flex flex-col gap-20rpx">
|
||||
<wd-card v-if="noPermission" custom-class="!mx-0 !mb-0">
|
||||
<view class="text-26rpx text-#64748b leading-38rpx">
|
||||
当前账号不能提交工单。
|
||||
</view>
|
||||
</wd-card>
|
||||
|
||||
<template v-else>
|
||||
<wd-card custom-class="!mx-0 !mb-0">
|
||||
<view class="flex items-start gap-16rpx">
|
||||
<view class="h-64rpx w-64rpx center shrink-0 rounded-20rpx bg-#eff6ff text-#2563eb">
|
||||
<view class="i-carbon-chat text-36rpx" />
|
||||
</view>
|
||||
<view class="min-w-0 flex-1">
|
||||
<text class="block text-34rpx text-#111827 font-900">提交工单</text>
|
||||
<text class="mt-8rpx block text-24rpx text-#64748b leading-36rpx">
|
||||
工单会分派到平台服务部门处理
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</wd-card>
|
||||
|
||||
<wd-form :model="formData" error-type="toast">
|
||||
<wd-card custom-class="!mx-0 !mb-0" custom-content-class="!p-0">
|
||||
<template #title>
|
||||
<view class="h-88rpx flex items-center justify-between">
|
||||
<text class="text-30rpx text-#111827 font-900">基础信息</text>
|
||||
<wd-tag type="primary" variant="plain">
|
||||
{{ typeText }}
|
||||
</wd-tag>
|
||||
</view>
|
||||
</template>
|
||||
<wd-cell-group :border="false">
|
||||
<wd-picker
|
||||
v-model="formData.woType"
|
||||
label="问题类型"
|
||||
label-width="96px"
|
||||
:columns="problemTypes"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
/>
|
||||
<wd-input
|
||||
v-model="formData.title"
|
||||
label="工单标题"
|
||||
label-width="96px"
|
||||
placeholder="请输入简短标题"
|
||||
clearable
|
||||
/>
|
||||
</wd-cell-group>
|
||||
</wd-card>
|
||||
|
||||
<wd-card custom-class="!mx-0 !mb-0">
|
||||
<template #title>
|
||||
<view class="h-88rpx flex items-center">
|
||||
<text class="text-30rpx text-#111827 font-900">问题描述</text>
|
||||
</view>
|
||||
</template>
|
||||
<wd-textarea
|
||||
v-model="formData.content"
|
||||
placeholder="请描述问题现象、发生时间和已尝试的处理方式"
|
||||
:maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<view class="mt-20rpx flex items-center justify-between">
|
||||
<text class="text-24rpx text-#64748b">附件(可选)</text>
|
||||
<wd-button size="small" type="info" :loading="uploading" @click="chooseAttachment">选择附件</wd-button>
|
||||
</view>
|
||||
<view v-if="attachmentNames.length" class="mt-16rpx flex flex-col gap-12rpx">
|
||||
<view
|
||||
v-for="(name, index) in attachmentNames"
|
||||
:key="name + index"
|
||||
class="flex items-center justify-between rounded-12rpx bg-#f8fafc px-16rpx py-12rpx"
|
||||
>
|
||||
<text class="min-w-0 flex-1 truncate text-24rpx text-#334155">{{ name }}</text>
|
||||
<wd-button size="small" type="info" plain @click="removeAttachment(index)">移除</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-card>
|
||||
</wd-form>
|
||||
|
||||
<wd-button block type="primary" :loading="submitting" :disabled="!canCreateWorkOrder || uploading" @click="handleSubmit">
|
||||
提交工单
|
||||
</wd-button>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
Reference in New Issue
Block a user