fix(ts): 低风险类型修复并修复预览交互问题

- formatDate 入参放宽为 dayjs.ConfigType,删除冗余 formatDateByConfig
- 多处 ref([]) 补精确数组类型(BPM/AI workflow/SMS log/DiyEditor 等)
- 路由参数/模板 index 显式 Number(),Upload 响应补局部类型
- LeaveCreateData 局部扩展 startUserSelectAssignees,不污染共享 VO
- 修复 mall 订单详情 formatDate.deliveryTime typo(发货时间行此前不显示)
- 修复 FloatingActionButton 缺失 handleActive,预览态点击仅收起面板

ts:check 542 → 478,无新增类型错误
master
YunaiV 2026-06-20 21:46:59 -07:00
parent 3f779091be
commit bc25430fa5
23 changed files with 104 additions and 53 deletions

View File

@ -115,5 +115,7 @@ export const getOrderCountTrendComparison = (
/** 时间参数需要格式化, 确保接口能识别 */ /** 时间参数需要格式化, 确保接口能识别 */
const formatDateParam = (params: TradeTrendReqVO) => { const formatDateParam = (params: TradeTrendReqVO) => {
return { times: [formatDate(params.times[0]), formatDate(params.times[1])] } as TradeTrendReqVO return {
times: [formatDate(params.times[0]), formatDate(params.times[1])]
} as TradeTrendReqVO
} }

View File

@ -13,7 +13,7 @@
v-for="(item, index) in property.list" v-for="(item, index) in property.list"
:key="index" :key="index"
class="flex flex-col items-center" class="flex flex-col items-center"
@click="handleActive(index)" @click="handleActive"
> >
<el-image :src="item.imgUrl" fit="contain" class="h-27px w-27px"> <el-image :src="item.imgUrl" fit="contain" class="h-27px w-27px">
<template #error> <template #error>
@ -49,6 +49,10 @@ const expanded = ref(false)
const handleToggleFab = () => { const handleToggleFab = () => {
expanded.value = !expanded.value expanded.value = !expanded.value
} }
const handleActive = () => {
expanded.value = false
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -54,7 +54,7 @@
class="text-12px" class="text-12px"
:style="{ color: property.fields.price.color }" :style="{ color: property.fields.price.color }"
> >
{{ fenToYuan(spu.price) }} {{ fenToYuan(spu.price || 0) }}
</span> </span>
</div> </div>
</div> </div>
@ -76,7 +76,9 @@ const spuList = ref<ProductSpuApi.Spu[]>([])
watch( watch(
() => props.property.spuIds, () => props.property.spuIds,
async () => { async () => {
spuList.value = await ProductSpuApi.getSpuDetailList(props.property.spuIds) spuList.value = props.property.spuIds
? await ProductSpuApi.getSpuDetailList(props.property.spuIds)
: []
}, },
{ {
immediate: true, immediate: true,

View File

@ -35,7 +35,7 @@ const props = defineProps<{ modelValue: PromotionArticleProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const formData = useVModel(props, 'modelValue', emit) const formData = useVModel(props, 'modelValue', emit)
// //
const articles = ref<ArticleApi.ArticleVO>([]) const articles = ref<ArticleApi.ArticleVO[]>([])
// //
const loading = ref(false) const loading = ref(false)

View File

@ -301,7 +301,7 @@
ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY
" "
> >
<el-input-number v-model="configForm.multiInstanceSource" :min="1" /> <el-input-number v-model="multiInstanceSourceNumber" :min="1" />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
v-if=" v-if="
@ -453,6 +453,12 @@ const digitalFormFieldOptions = computed(() => {
const multiFormFieldOptions = computed(() => { const multiFormFieldOptions = computed(() => {
return formFieldOptions.filter((item) => item.type === 'select' || item.type === 'checkbox') return formFieldOptions.filter((item) => item.type === 'select' || item.type === 'checkbox')
}) })
const multiInstanceSourceNumber = computed({
get: () => Number(configForm.value.multiInstanceSource || 1),
set: (value?: number) => {
configForm.value.multiInstanceSource = String(value || '')
}
})
const childFormFieldOptions = ref() const childFormFieldOptions = ref()
// //

View File

@ -64,6 +64,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import type { ComponentPublicInstance } from 'vue'
import { SimpleFlowNode, NodeType, ConditionType, RouterSetting } from '../consts' import { SimpleFlowNode, NodeType, ConditionType, RouterSetting } from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node' import { useWatchNode, useDrawer, useNodeName } from '../node'
import Condition from './components/Condition.vue' import Condition from './components/Condition.vue'
@ -86,15 +87,18 @@ const currentNode = useWatchNode(props)
// //
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE) const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
const routerGroups = ref<RouterSetting[]>([]) const routerGroups = ref<RouterSetting[]>([])
const nodeOptions = ref<any>([]) const nodeOptions = ref<Array<{ label: string; value: string }>>([])
const conditionRef = ref([]) type ConditionRef = ComponentPublicInstance & {
validate?: () => Promise<boolean>
}
const conditionRef = ref<Array<ConditionRef | Element | null>>([])
/** 保存配置 */ /** 保存配置 */
const saveConfig = async () => { const saveConfig = async () => {
// //
let valid = true let valid = true
for (const item of conditionRef.value) { for (const item of conditionRef.value) {
if (item && !(await item.validate())) { if (item && 'validate' in item && item.validate && !(await item.validate())) {
valid = false valid = false
} }
} }
@ -173,7 +177,7 @@ const deleteRouterGroup = (index: number) => {
} }
// //
const getRouterNode = (node) => { const getRouterNode = (node?: SimpleFlowNode) => {
// TODO // TODO
// //
// //

View File

@ -70,7 +70,7 @@
<Icon <Icon
icon="ep:delete" icon="ep:delete"
:size="18" :size="18"
@click="deleteHttpResponseSetting(setting.response!, index)" @click="deleteHttpResponseSetting(setting.response!, Number(index))"
/> />
</div> </div>
</div> </div>

View File

@ -132,10 +132,13 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
// //
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => { const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功') message.success('上传成功')
const response = res as { data: string }
// //
const index = fileList.value.findIndex((item) => item.response?.data === res.data) const index = fileList.value.findIndex(
(item) => (item.response as { data?: string } | undefined)?.data === response.data
)
fileList.value.splice(index, 1) fileList.value.splice(index, 1)
uploadList.value.push({ name: res.data, url: res.data }) uploadList.value.push({ name: response.data, url: response.data })
if (uploadList.value.length == uploadNumber.value) { if (uploadList.value.length == uploadNumber.value) {
fileList.value.push(...uploadList.value) fileList.value.push(...uploadList.value)
uploadList.value = [] uploadList.value = []

View File

@ -135,10 +135,13 @@ interface UploadEmits {
const emit = defineEmits<UploadEmits>() const emit = defineEmits<UploadEmits>()
const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => { const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功') message.success('上传成功')
const response = res as { data: string }
// //
const index = fileList.value.findIndex((item) => item.response?.data === res.data) const index = fileList.value.findIndex(
(item) => (item.response as { data?: string } | undefined)?.data === response.data
)
fileList.value.splice(index, 1) fileList.value.splice(index, 1)
uploadList.value.push({ name: res.data, url: res.data }) uploadList.value.push({ name: response.data, url: response.data })
if (uploadList.value.length == uploadNumber.value) { if (uploadList.value.length == uploadNumber.value) {
fileList.value.push(...uploadList.value) fileList.value.push(...uploadList.value)
uploadList.value = [] uploadList.value = []

View File

@ -12,7 +12,7 @@ export const useNProgress = () => {
await nextTick() await nextTick()
const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef
if (bar) { if (bar) {
bar.style.background = unref(primaryColor.value) bar.style.background = unref(primaryColor.value) || ''
} }
} }

View File

@ -12,6 +12,6 @@ export const setupElementPlus = (app: App<Element>) => {
}) })
components.forEach((component) => { components.forEach((component) => {
app.component(component.name, component) app.component(component.name!, component)
}) })
} }

View File

@ -126,7 +126,7 @@ const components = [
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档 // 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
export const setupFormCreate = (app: App<Element>) => { export const setupFormCreate = (app: App<Element>) => {
components.forEach((component) => { components.forEach((component) => {
app.component(component.name, component) app.component(component.name!, component)
}) })
formCreate.use(install) formCreate.use(install)
app.use(formCreate) app.use(formCreate)

View File

@ -12,7 +12,7 @@ const HM_ID = import.meta.env.VITE_APP_BAIDU_CODE
const hm = document.createElement('script') const hm = document.createElement('script')
hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID
const s = document.getElementsByTagName('script')[0] const s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(hm, s) s.parentNode?.insertBefore(hm, s)
})() })()
router.afterEach(function (to) { router.afterEach(function (to) {

View File

@ -54,7 +54,7 @@ export const defaultShortcuts = [
/** /**
* *
* @param date new Date() * @param date new Date()dayjs
* @param format * @param format
* @description format `YYYY-MM、YYYY-MM-DD` * @description format `YYYY-MM、YYYY-MM-DD`
* @description format "YYYY-MM-DD HH:mm:ss QQQQ" * @description format "YYYY-MM-DD HH:mm:ss QQQQ"
@ -63,7 +63,7 @@ export const defaultShortcuts = [
* @description format + + "YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ" * @description format + + "YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ"
* @returns * @returns
*/ */
export function formatDate(date: Date | string, format?: string): string { export function formatDate(date: dayjs.ConfigType, format?: string): string {
// 日期不存在,则返回空 // 日期不存在,则返回空
if (!date) { if (!date) {
return '' return ''

View File

@ -48,7 +48,7 @@
<el-dropdown-item <el-dropdown-item
v-for="(file, index) in modelData.list" v-for="(file, index) in modelData.list"
:key="index" :key="index"
@click="selectFile(index)" @click="selectFile(Number(index))"
> >
{{ file.name }} {{ file.name }}
<span v-if="file.segments" class="ml-5px text-gray-500 text-12px"> <span v-if="file.segments" class="ml-5px text-gray-500 text-12px">
@ -141,7 +141,7 @@ const splitContent = async (file: any) => {
// Token // Token
file.segments = await KnowledgeSegmentApi.splitContent( file.segments = await KnowledgeSegmentApi.splitContent(
file.url, file.url,
modelData.value.segmentMaxTokens Number(modelData.value.segmentMaxTokens)
) )
} catch (error) { } catch (error) {
console.error('获取分段内容失败:', file, error) console.error('获取分段内容失败:', file, error)

View File

@ -48,7 +48,7 @@
<Icon icon="ep:document" class="mr-8px text-[#409eff]" /> <Icon icon="ep:document" class="mr-8px text-[#409eff]" />
<span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span> <span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span>
</div> </div>
<el-button type="danger" link @click="removeFile(index)" class="ml-2"> <el-button type="danger" link @click="removeFile(Number(index))" class="ml-2">
<Icon icon="ep:delete" /> <Icon icon="ep:delete" />
</el-button> </el-button>
</div> </div>

View File

@ -70,14 +70,28 @@ defineProps<{
provider: any provider: any
}>() }>()
type WorkflowParam = {
key: string
value: string
}
type StartNodeParameter = {
name: string
dataType: string
description?: string
disabled?: boolean
required?: boolean
defaultValue?: string
}
const tinyflowRef = ref() const tinyflowRef = ref()
const workflowData = inject('workflowData') as Ref const workflowData = inject('workflowData') as Ref
const showTestDrawer = ref(false) const showTestDrawer = ref(false)
const params4Test = ref([]) const params4Test = ref<WorkflowParam[]>([])
const paramsOfStartNode = ref({}) const paramsOfStartNode = ref<Record<string, StartNodeParameter>>({})
const testResult = ref(null) const testResult = ref(null)
const loading = ref(false) const loading = ref(false)
const error = ref(null) const error = ref<string | null>(null)
/** 展示工作流测试抽屉 */ /** 展示工作流测试抽屉 */
const testWorkflowModel = () => { const testWorkflowModel = () => {
@ -96,8 +110,8 @@ const goRun = async () => {
// //
const parameters = startNode.data?.parameters || [] const parameters = startNode.data?.parameters || []
const paramDefinitions = {} const paramDefinitions: Record<string, string> = {}
parameters.forEach((param) => { parameters.forEach((param: StartNodeParameter) => {
paramDefinitions[param.name] = param.dataType paramDefinitions[param.name] = param.dataType
}) })
@ -115,7 +129,8 @@ const goRun = async () => {
try { try {
convertedParams[paramKey] = convertParamValue(value, dataType) convertedParams[paramKey] = convertParamValue(value, dataType)
} catch (e) { } catch (e) {
throw new Error(`参数 ${paramKey} 转换失败: ${e.message}`) const message = e instanceof Error ? e.message : String(e)
throw new Error(`参数 ${paramKey} 转换失败: ${message}`)
} }
} }
@ -127,7 +142,11 @@ const goRun = async () => {
const response = await WorkflowApi.testWorkflow(data) const response = await WorkflowApi.testWorkflow(data)
testResult.value = response testResult.value = response
} catch (err) { } catch (err) {
error.value = err.response?.data?.message || '运行失败,请检查参数和网络连接' const responseMessage =
err && typeof err === 'object' && 'response' in err
? (err as any).response?.data?.message
: undefined
error.value = responseMessage || '运行失败,请检查参数和网络连接'
} finally { } finally {
loading.value = false loading.value = false
} }
@ -142,20 +161,20 @@ watch(showTestDrawer, (value) => {
// //
const parameters = startNode.data?.parameters || [] const parameters = startNode.data?.parameters || []
const paramDefinitions = {} const paramDefinitions: Record<string, StartNodeParameter> = {}
// 便 // 便
parameters.forEach((param) => { parameters.forEach((param: StartNodeParameter) => {
paramDefinitions[param.name] = param paramDefinitions[param.name] = param
}) })
function mergeIfRequiredButNotSet(target) { function mergeIfRequiredButNotSet(target: WorkflowParam[]) {
let needPushList = [] let needPushList: WorkflowParam[] = []
for (let key in paramDefinitions) { for (let key in paramDefinitions) {
let param = paramDefinitions[key] let param = paramDefinitions[key]
if (param.required) { if (param.required) {
let item = target.find((item) => item.key === key) let item = target.find((item: WorkflowParam) => item.key === key)
if (!item) { if (!item) {
needPushList.push({ key: param.name, value: param.defaultValue || '' }) needPushList.push({ key: param.name, value: param.defaultValue || '' })
@ -186,12 +205,12 @@ const addParam = () => {
} }
/** 删除参数项 */ /** 删除参数项 */
const removeParam = (index) => { const removeParam = (index: number) => {
params4Test.value.splice(index, 1) params4Test.value.splice(index, 1)
} }
/** 类型转换函数 */ /** 类型转换函数 */
const convertParamValue = (value, dataType) => { const convertParamValue = (value: string, dataType: string) => {
if (value === '') return null // if (value === '') return null //
switch (dataType) { switch (dataType) {
@ -210,7 +229,8 @@ const convertParamValue = (value, dataType) => {
try { try {
return JSON.parse(value) return JSON.parse(value)
} catch (e) { } catch (e) {
throw new Error(`JSON格式错误: ${e.message}`) const message = e instanceof Error ? e.message : String(e)
throw new Error(`JSON格式错误: ${message}`)
} }
default: default:
throw new Error(`不支持的类型: ${dataType}`) throw new Error(`不支持的类型: ${dataType}`)

View File

@ -97,10 +97,17 @@ const formRules = reactive({
const formRef = ref() // Ref const formRef = ref() // Ref
// //
type StartUserSelectTask = {
id: string
name: string
}
type LeaveCreateData = LeaveApi.LeaveVO & {
startUserSelectAssignees?: Record<string, number[]>
}
const processDefineKey = 'oa_leave' // Key const processDefineKey = 'oa_leave' // Key
const startUserSelectTasks = ref([]) // const startUserSelectTasks = ref<StartUserSelectTask[]>([]) //
const startUserSelectAssignees = ref({}) // const startUserSelectAssignees = ref<Record<string, number[]>>({}) //
const tempStartUserSelectAssignees = ref({}) // const tempStartUserSelectAssignees = ref<Record<string, number[]>>({}) //
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) //
const processDefinitionId = ref('') const processDefinitionId = ref('')
@ -125,7 +132,7 @@ const submitForm = async () => {
// 2. // 2.
formLoading.value = true formLoading.value = true
try { try {
const data = { ...formData.value } as unknown as LeaveApi.LeaveVO const data = { ...formData.value } as unknown as LeaveCreateData
// //
if (startUserSelectTasks.value?.length > 0) { if (startUserSelectTasks.value?.length > 0) {
data.startUserSelectAssignees = startUserSelectAssignees.value data.startUserSelectAssignees = startUserSelectAssignees.value

View File

@ -167,7 +167,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatPast2 } from '@/utils/formatTime' import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
// //
@ -179,7 +179,7 @@ const { t } = useI18n() // 国际化
const loading = ref(true) // const loading = ref(true) //
const total = ref(0) // const total = ref(0) //
const list = ref([]) // const list = ref<ProcessInstanceApi.ProcessInstanceVO[]>([]) //
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
@ -191,7 +191,7 @@ const queryParams = reactive({
createTime: [] createTime: []
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const categoryList = ref([]) // const categoryList = ref<CategoryVO[]>([]) //
const userList = ref<any[]>([]) // const userList = ref<any[]>([]) //
/** 查询列表 */ /** 查询列表 */
@ -219,7 +219,7 @@ const resetQuery = () => {
} }
/** 查看详情 */ /** 查看详情 */
const handleDetail = (row) => { const handleDetail = (row: ProcessInstanceApi.ProcessInstanceVO) => {
router.push({ router.push({
name: 'BpmProcessInstanceDetail', name: 'BpmProcessInstanceDetail',
query: { query: {
@ -229,7 +229,7 @@ const handleDetail = (row) => {
} }
/** 取消按钮操作 */ /** 取消按钮操作 */
const handleCancel = async (row) => { const handleCancel = async (row: ProcessInstanceApi.ProcessInstanceVO) => {
// //
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', { const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
confirmButtonText: t('common.ok'), confirmButtonText: t('common.ok'),

View File

@ -24,7 +24,7 @@ defineOptions({ name: 'CrmProductDetail' })
const route = useRoute() const route = useRoute()
const message = useMessage() const message = useMessage()
const id = route.params.id // const id = Number(route.params.id) //
const loading = ref(true) // const loading = ref(true) //
const product = ref<ProductApi.ProductVO>({} as ProductApi.ProductVO) // const product = ref<ProductApi.ProductVO>({} as ProductApi.ProductVO) //

View File

@ -28,7 +28,7 @@ const { currentRoute } = useRouter()
const route = useRoute() const route = useRoute()
const message = useMessage() const message = useMessage()
const id = route.params.id // const id = Number(route.params.id) //
const loading = ref(true) // const loading = ref(true) //
const product = ref<ProductVO>({} as ProductVO) // const product = ref<ProductVO>({} as ProductVO) //
const activeTab = ref('info') // info const activeTab = ref('info') // info

View File

@ -165,7 +165,7 @@
<el-descriptions-item v-if="formData.logisticsId" label="运单号: "> <el-descriptions-item v-if="formData.logisticsId" label="运单号: ">
{{ formData.logisticsNo }} {{ formData.logisticsNo }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item v-if="formatDate.deliveryTime" label="发货时间: "> <el-descriptions-item v-if="formData.deliveryTime" label="发货时间: ">
{{ formatDate(formData.deliveryTime) }} {{ formatDate(formData.deliveryTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item v-for="item in 2" :key="item" label-class-name="no-colon" /> <el-descriptions-item v-for="item in 2" :key="item" label-class-name="no-colon" />

View File

@ -198,7 +198,7 @@ const message = useMessage() // 消息弹窗
const loading = ref(false) // const loading = ref(false) //
const total = ref(0) // const total = ref(0) //
const list = ref([]) // const list = ref<SmsLogApi.SmsLogVO[]>([]) //
const queryFormRef = ref() // const queryFormRef = ref() //
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
@ -212,7 +212,7 @@ const queryParams = reactive({
receiveTime: [] receiveTime: []
}) })
const exportLoading = ref(false) // const exportLoading = ref(false) //
const channelList = ref([]) // const channelList = ref<SmsChannelApi.SmsChannelVO[]>([]) //
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {