Merge branch 'master' of https://git.ngsk.tech/linweidong/besure_app
commit
0bcd9bb579
@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<NavBar title="新增子模具" />
|
||||
|
||||
<scroll-view scroll-y class="detail-scroll">
|
||||
<view class="content-section">
|
||||
<view class="section-card">
|
||||
<view class="section-header">
|
||||
<view class="section-icon">
|
||||
<uni-icons type="compose" size="24" color="#1f7cff"></uni-icons>
|
||||
</view>
|
||||
<text class="section-title">子模具信息</text>
|
||||
</view>
|
||||
|
||||
<!-- 编码 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">编码</text>
|
||||
<view class="code-row">
|
||||
<view class="is-code-switch" @click="form.isCode = !form.isCode">
|
||||
<text :class="['switch-label', form.isCode ? 'active' : '']">自动生成</text>
|
||||
<view :class="['switch-track', form.isCode ? 'on' : 'off']">
|
||||
<view class="switch-thumb"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<input v-if="!form.isCode" v-model="form.code" class="form-input" placeholder="请输入编码" />
|
||||
</view>
|
||||
|
||||
<!-- 名称 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">名称<text class="required-star">*</text></text>
|
||||
<input v-model="form.name" class="form-input" placeholder="请输入名称" />
|
||||
</view>
|
||||
|
||||
<!-- 类型 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">类型</text>
|
||||
<view class="select-field" @click="openTypePicker">
|
||||
<text :class="['select-text', form.type ? '' : 'placeholder']">{{ form.typeLabel || '请选择' }}</text>
|
||||
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 安装位置 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">安装位置</text>
|
||||
<input v-model="form.installLocation" class="form-input" placeholder="请输入安装位置" />
|
||||
</view>
|
||||
|
||||
<!-- 材质 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">材质</text>
|
||||
<input v-model="form.material" class="form-input" placeholder="请输入材质" />
|
||||
</view>
|
||||
|
||||
<!-- 单位 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">单位<text class="required-star">*</text></text>
|
||||
<view class="select-field" @click="openUnitPicker">
|
||||
<text :class="['select-text', form.unitId ? '' : 'placeholder']">{{ form.unitLabel || '请选择' }}</text>
|
||||
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 入库时间 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">入库时间<text class="required-star">*</text></text>
|
||||
<picker mode="date" :value="form.inTimeDate" @change="handleInTimeChange">
|
||||
<view class="select-field">
|
||||
<text :class="['select-text', form.inTimeDate ? '' : 'placeholder']">{{ form.inTimeDate || '请选择' }}</text>
|
||||
<uni-icons type="calendar" size="18" color="#9ca3af"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 图片 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">图片</text>
|
||||
<view class="image-upload" @click="handleChooseImage">
|
||||
<view v-if="form.images" class="image-preview-wrap">
|
||||
<image :src="form.images" class="image-preview" mode="aspectFill" />
|
||||
<view class="image-delete" @click.stop="form.images = ''"><uni-icons type="closeempty" size="18" color="#fff"></uni-icons></view>
|
||||
</view>
|
||||
<view v-else class="image-placeholder">
|
||||
<uni-icons type="plusempty" size="28" color="#94a3b8"></uni-icons>
|
||||
<text>上传图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">备注</text>
|
||||
<textarea v-model="form.remark" class="form-textarea" placeholder="请输入备注" placeholder-class="placeholder-text" maxlength="200" />
|
||||
</view>
|
||||
|
||||
<!-- 是否启用 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">是否启用<text class="required-star">*</text></text>
|
||||
<view class="enable-radio">
|
||||
<view :class="['radio-option', form.isEnable === true ? 'active' : '']" @click="form.isEnable = true">启用</view>
|
||||
<view :class="['radio-option', form.isEnable === false ? 'active' : '']" @click="form.isEnable = false">禁用</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资料 -->
|
||||
<view class="form-field">
|
||||
<text class="form-label">资料</text>
|
||||
<view class="file-upload" @click="handleChooseFile">
|
||||
<view v-if="form.fileUrl" class="file-item">
|
||||
<text class="file-name">{{ form.fileName || '已选文件' }}</text>
|
||||
<view class="file-delete" @click.stop="form.fileUrl = ''; form.fileName = ''">
|
||||
<uni-icons type="closeempty" size="16" color="#ef4444"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="file-placeholder">
|
||||
<uni-icons type="paperclip" size="20" color="#1f7cff"></uni-icons>
|
||||
<text>选取文件</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="action-bar">
|
||||
<view class="action-btn back-btn" @click="handleCancel">取消</view>
|
||||
<view class="action-btn submit-btn" @click="handleSubmit">保存</view>
|
||||
</view>
|
||||
|
||||
<!-- 类型选择弹窗 -->
|
||||
<uni-popup ref="typePickerRef" type="bottom" background-color="#fff">
|
||||
<view class="picker-panel">
|
||||
<view class="picker-header">
|
||||
<text class="picker-title">选择类型</text>
|
||||
<text class="picker-clear" @click="form.type = ''; form.typeLabel = ''; typePickerRef.close()">清除</text>
|
||||
</view>
|
||||
<scroll-view scroll-y class="picker-list">
|
||||
<view v-for="item in typeOptions" :key="item.value" class="picker-item" @click="selectType(item)">
|
||||
<text class="picker-text">{{ item.label }}</text>
|
||||
<text v-if="form.type === item.value" class="picker-check">✓</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<!-- 单位选择弹窗 -->
|
||||
<uni-popup ref="unitPickerRef" type="bottom" background-color="#fff">
|
||||
<view class="picker-panel">
|
||||
<view class="picker-header">
|
||||
<text class="picker-title">选择单位</text>
|
||||
</view>
|
||||
<scroll-view scroll-y class="picker-list">
|
||||
<view v-for="item in unitOptions" :key="item.value" class="picker-item" @click="selectUnit(item)">
|
||||
<text class="picker-text">{{ item.label }}</text>
|
||||
<text v-if="form.unitId === item.value" class="picker-check">✓</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import NavBar from '@/components/common/NavBar.vue'
|
||||
import { createMold } from '@/api/mes/mold'
|
||||
import { getProductUnitSimpleList } from '@/api/mes/product'
|
||||
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
|
||||
|
||||
const brandId = ref('')
|
||||
const typePickerRef = ref(null)
|
||||
const unitPickerRef = ref(null)
|
||||
const typeOptions = ref([])
|
||||
const unitOptions = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
code: '',
|
||||
name: '',
|
||||
type: '',
|
||||
typeLabel: '',
|
||||
installLocation: '',
|
||||
material: '',
|
||||
unitId: '',
|
||||
unitLabel: '',
|
||||
inTimeDate: formatToday(),
|
||||
inTime: 0,
|
||||
images: '',
|
||||
remark: '',
|
||||
isEnable: true,
|
||||
isCode: true,
|
||||
fileUrl: '',
|
||||
fileName: ''
|
||||
})
|
||||
|
||||
function formatToday() {
|
||||
const d = new Date()
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
function handleInTimeChange(e) {
|
||||
form.inTimeDate = e.detail.value
|
||||
form.inTime = new Date(form.inTimeDate).getTime()
|
||||
}
|
||||
|
||||
async function loadDictAndUnits() {
|
||||
await initAllDict()
|
||||
// 子模具类型
|
||||
const types = []
|
||||
for (let i = 0; i <= 20; i++) {
|
||||
const label = getDictLabel(DICT_TYPE.SUBMOLD_TYPE, i, '')
|
||||
if (label && label !== String(i)) types.push({ label, value: String(i) })
|
||||
}
|
||||
typeOptions.value = types
|
||||
|
||||
// 单位列表
|
||||
try {
|
||||
const res = await getProductUnitSimpleList()
|
||||
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
|
||||
unitOptions.value = data.map(u => ({ value: String(u.id), label: u.name || String(u.id) }))
|
||||
} catch (e) { console.error('load units error', e) }
|
||||
}
|
||||
|
||||
function openTypePicker() { typePickerRef.value?.open() }
|
||||
function selectType(item) {
|
||||
form.type = item.value
|
||||
form.typeLabel = item.label
|
||||
typePickerRef.value?.close()
|
||||
}
|
||||
|
||||
function openUnitPicker() { unitPickerRef.value?.open() }
|
||||
function selectUnit(item) {
|
||||
form.unitId = item.value
|
||||
form.unitLabel = item.label
|
||||
unitPickerRef.value?.close()
|
||||
}
|
||||
|
||||
function handleChooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => { form.images = res.tempFilePaths?.[0] || '' }
|
||||
})
|
||||
}
|
||||
|
||||
function handleChooseFile() {
|
||||
// #ifdef APP-PLUS
|
||||
plus.gallery.pick((res) => {
|
||||
const f = res?.files?.[0]
|
||||
if (f) { form.fileUrl = f; form.fileName = f.name || '' }
|
||||
}, () => {}, { filter: 'all', multiple: false })
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
uni.chooseFile({
|
||||
count: 1,
|
||||
type: 'all',
|
||||
success: (res) => { form.fileUrl = res.tempFiles?.[0]?.path || ''; form.fileName = res.tempFiles?.[0]?.name || '' },
|
||||
fail: () => {}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.name.trim()) { uni.showToast({ title: '请输入名称', icon: 'none' }); return }
|
||||
if (!form.unitId) { uni.showToast({ title: '请选择单位', icon: 'none' }); return }
|
||||
if (!form.isCode && !form.code.trim()) { uni.showToast({ title: '请输入编码或启用自动生成', icon: 'none' }); return }
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const data = {
|
||||
brandId: Number(brandId.value),
|
||||
name: form.name.trim(),
|
||||
unitId: Number(form.unitId),
|
||||
inTime: form.inTime || Date.now(),
|
||||
isEnable: form.isEnable,
|
||||
isCode: form.isCode,
|
||||
code: form.isCode ? undefined : form.code.trim(),
|
||||
type: form.type || undefined,
|
||||
installLocation: form.installLocation.trim() || undefined,
|
||||
material: form.material.trim() || undefined,
|
||||
images: form.images || undefined,
|
||||
remark: form.remark.trim() || undefined,
|
||||
fileUrl: typeof form.fileUrl === 'string' ? form.fileUrl : undefined
|
||||
}
|
||||
await createMold(data)
|
||||
getApp().globalData._moldChildMoldNeedRefresh = true
|
||||
uni.showToast({ title: '新增成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 1500)
|
||||
} catch (e) {
|
||||
const msg = e?.message || e?.data?.msg || '新增失败'
|
||||
uni.showToast({ title: String(msg).substring(0, 50), icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() { uni.navigateBack() }
|
||||
|
||||
onLoad((options) => { brandId.value = options?.brandId || '' })
|
||||
onMounted(() => { loadDictAndUnits() })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container { min-height: 100vh; background: #f5f7fb; }
|
||||
.detail-scroll { height: calc(100vh - 172rpx); }
|
||||
.content-section { padding: 20rpx 24rpx 28rpx; }
|
||||
.section-card { background: #ffffff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; border: 1rpx solid #eef2f7; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
|
||||
.section-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 22rpx; padding-bottom: 18rpx; border-bottom: 1rpx solid #f1f5f9; }
|
||||
.section-icon { width: 40rpx; height: 40rpx; border-radius: 10rpx; background: #eff6ff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.section-title { font-size: 32rpx; font-weight: 600; color: #1f2937; }
|
||||
.form-field { display: flex; flex-direction: column; gap: 12rpx; }
|
||||
.form-field + .form-field { margin-top: 24rpx; }
|
||||
.form-label { font-size: 26rpx; color: #4b5563; font-weight: 500; }
|
||||
.required-star { color: #ef4444; font-size: 28rpx; margin-left: 4rpx; }
|
||||
.form-input { height: 76rpx; padding: 0 24rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; font-size: 28rpx; color: #374151; box-sizing: border-box; }
|
||||
.form-textarea { width: 100%; min-height: 120rpx; background: #f8fafc; border-radius: 14rpx; padding: 18rpx 24rpx; font-size: 28rpx; color: #374151; box-sizing: border-box; }
|
||||
.select-field { display: flex; align-items: center; justify-content: space-between; min-height: 76rpx; padding: 0 24rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; }
|
||||
.select-text { flex: 1; min-width: 0; font-size: 28rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.placeholder, .placeholder-text { color: #9ca3af; }
|
||||
|
||||
.code-row { display: flex; align-items: center; gap: 16rpx; }
|
||||
.is-code-switch { display: flex; align-items: center; gap: 10rpx; }
|
||||
.switch-label { font-size: 24rpx; color: #9ca3af; &.active { color: #1f4b79; font-weight: 600; } }
|
||||
.switch-track { width: 64rpx; height: 34rpx; border-radius: 17rpx; display: flex; align-items: center; padding: 3rpx; box-sizing: border-box; transition: background .2s; }
|
||||
.switch-track.on { background: #1f4b79; justify-content: flex-end; }
|
||||
.switch-track.off { background: #d1d5db; justify-content: flex-start; }
|
||||
.switch-thumb { width: 28rpx; height: 28rpx; border-radius: 14rpx; background: #fff; }
|
||||
|
||||
.enable-radio { display: flex; gap: 16rpx; }
|
||||
.radio-option { flex: 1; height: 76rpx; border-radius: 14rpx; border: 1rpx solid #e5e7eb; background: #f8fafc; display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: #64748b; }
|
||||
.radio-option.active { background: #1f4b79; color: #fff; border-color: #1f4b79; }
|
||||
|
||||
.image-upload { }
|
||||
.image-placeholder { height: 160rpx; border: 2rpx dashed #d7dde8; border-radius: 14rpx; background: #f8fafc; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8rpx; color: #94a3b8; font-size: 24rpx; }
|
||||
.image-preview-wrap { position: relative; width: 200rpx; height: 200rpx; border-radius: 14rpx; overflow: hidden; }
|
||||
.image-preview { width: 100%; height: 100%; }
|
||||
.image-delete { position: absolute; top: 6rpx; right: 6rpx; width: 40rpx; height: 40rpx; border-radius: 20rpx; background: rgba(0,0,0,.5); display: flex; align-items: center; justify-content: center; }
|
||||
|
||||
.file-upload { }
|
||||
.file-placeholder { height: 76rpx; border-radius: 14rpx; border: 1rpx dashed #bfdbfe; background: #eff6ff; color: #1f7cff; display: flex; align-items: center; justify-content: center; gap: 10rpx; font-size: 26rpx; font-weight: 600; }
|
||||
.file-item { display: flex; align-items: center; justify-content: space-between; padding: 16rpx 18rpx; background: #f8fafc; border-radius: 12rpx; }
|
||||
.file-name { flex: 1; font-size: 24rpx; color: #334155; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
.picker-panel { padding: 24rpx 24rpx 36rpx; border-radius: 28rpx 28rpx 0 0; }
|
||||
.picker-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20rpx; }
|
||||
.picker-title { color: #1f2d3d; font-size: 30rpx; font-weight: 700; }
|
||||
.picker-clear { color: #1f61ff; font-size: 26rpx; }
|
||||
.picker-list { max-height: 480rpx; }
|
||||
.picker-item { display: flex; align-items: center; justify-content: space-between; padding: 26rpx 6rpx; border-bottom: 1rpx solid #edf1f6; }
|
||||
.picker-text { color: #243447; font-size: 28rpx; }
|
||||
.picker-check { color: #1f61ff; font-size: 30rpx; }
|
||||
|
||||
.action-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx; padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom)); background: #ffffff; box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06); z-index: 99; }
|
||||
.action-btn { flex: 1; height: 84rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 600; }
|
||||
.back-btn { background: #eef2f7; color: #475569; }
|
||||
.submit-btn { background: #1f4b79; color: #ffffff; }
|
||||
</style>
|
||||
Loading…
Reference in New Issue