批量更新功能

This commit is contained in:
hero920103 committed 2026-09-14 16:15:44 +08:00
1 parent 6f106e43af
commit a26c98b92b
79 files changed
+4529 -724

No files matched your search

@@ -41,9 +41,11 @@
<h4>联系方式</h4>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="手机号">
<span v-if="contact.phone">
<a :href="'tel:' + contact.phone" class="contact-link">{{ contact.phone }}</a>
</span>
<template v-if="contactMobiles.length">
<div v-for="(m, i) in contactMobiles" :key="i">
<a :href="'tel:' + m" class="contact-link">{{ m }}</a>
</div>
</template>
<span v-else class="empty-text">-</span>
</el-descriptions-item>
<el-descriptions-item label="办公电话">
@@ -124,6 +126,28 @@ const genderLabel = (gender) => {
return map[gender] || '未知'
}
/** 解析多手机号:JSON 数组字符串优先,缺省回退单个 phone */
const contactMobiles = computed(() => {
const c = props.contact
if (!c) return []
let arr = []
const raw = c.mobiles
if (Array.isArray(raw)) {
arr = raw
} else if (raw !== undefined && raw !== null && String(raw).trim()) {
const s = String(raw).trim()
try {
const parsed = JSON.parse(s)
arr = Array.isArray(parsed) ? parsed : [s]
} catch (error) {
arr = s.split(/[,,、;;\s]+/)
}
}
arr = arr.map((m) => String(m || '').trim()).filter(Boolean)
if (arr.length === 0 && c.phone) arr = [String(c.phone).trim()]
return arr
})
const handleEdit = () => {
emit('edit', props.contact)
}
@@ -84,15 +84,23 @@
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手机号" prop="phone">
<el-input
v-model="form.phone"
placeholder="请输入手机号"
maxlength="20"
/>
<el-col :span="24">
<!-- 手机号:支持登记多个(有的人有多个号码),默认一行,点加号增加,最多 5 个 -->
<el-form-item label="手机号" prop="mobiles">
<div class="mobile-list">
<div v-for="(m, idx) in form.mobiles" :key="idx" class="mobile-item">
<el-input v-model="form.mobiles[idx]" placeholder="请输入手机号" maxlength="20" clearable />
<el-button :icon="Delete" :disabled="form.mobiles.length <= 1" @click="removeMobile(idx)" />
</div>
<el-button v-if="form.mobiles.length < 5" type="primary" plain :icon="Plus" @click="addMobile">
添加手机号({{ form.mobiles.length }}/5)
</el-button>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="办公电话" prop="work_phone">
<el-input
@@ -223,6 +231,7 @@
<script setup>
import { computed, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus, Delete } from '@element-plus/icons-vue'
import { getOrganizationList, getPositionList, getPositionDetail } from '@/api/contactOA'
const props = defineProps({
@@ -248,6 +257,8 @@ const emptyForm = () => ({
contact_type: 2,
gender: 0,
phone: '',
// 手机号:动态列表,默认一行,点击加号增加(最多 5 个,与后端存储上限一致)
mobiles: [''],
work_phone: '',
email: '',
wechat: '',
@@ -273,8 +284,17 @@ const rules = {
contact_type: [
{ required: true, message: '请选择联系人类型', trigger: 'change' }
],
phone: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
mobiles: [
{
// 手机号非必填,填写时逐个校验格式
validator: (rule, value, callback) => {
const list = Array.isArray(value) ? value : [value]
const bad = list.map((m) => String(m || '').trim()).find((m) => m && !/^1[3-9]\d{9}$/.test(m))
if (bad) return callback(new Error(`手机号格式不正确:${bad}`))
callback()
},
trigger: 'blur'
}
],
email: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
@@ -367,9 +387,41 @@ const handlePositionChange = (val) => {
form.value.position_title = pos ? pos.position_name : ''
}
/** 增加一行手机号输入 */
const addMobile = () => {
if (form.value.mobiles.length < 5) form.value.mobiles.push('')
}
/** 删除一行手机号输入(至少保留一行) */
const removeMobile = (idx) => {
if (form.value.mobiles.length > 1) form.value.mobiles.splice(idx, 1)
}
/** 解析手机号集合:兼容 JSON 数组字符串 / 分隔符字符串 / 旧数据单个号码(最多 5 个) */
const parseMobilesArr = (row) => {
let arr = []
const raw = row?.mobiles
if (Array.isArray(raw)) {
arr = raw
} else if (raw !== undefined && raw !== null && String(raw).trim()) {
const s = String(raw).trim()
try {
const parsed = JSON.parse(s)
arr = Array.isArray(parsed) ? parsed : [s]
} catch (error) {
arr = s.split(/[,,、;;\s]+/)
}
}
arr = arr.map((m) => String(m || '').trim()).filter(Boolean).slice(0, 5)
if (arr.length === 0 && row?.phone) arr = [String(row.phone).trim()]
return arr.length ? arr : ['']
}
const initForm = () => {
if (props.isEdit && props.formData) {
form.value = { ...props.formData }
// 回显手机号:mobiles JSON 数组优先,缺省回退单个 phone
form.value.mobiles = parseMobilesArr(props.formData)
} else {
form.value = emptyForm()
}
@@ -380,7 +432,14 @@ const handleSubmit = async () => {
try {
await formRef.value.validate()
submitting.value = true
const payload = { ...form.value }
// 手机号:动态列表去空后存 JSON 字符串(后端 TEXT 存 JSON 数组,最多 5 个),
// phone 同步为第一个号码,保持新旧字段一致
const valid = form.value.mobiles.map((m) => String(m || '').trim()).filter(Boolean)
const payload = {
...form.value,
mobiles: valid.length ? JSON.stringify(valid) : '',
phone: valid[0] || ''
}
emit('save', payload)
} catch {
// validation failed
@@ -424,4 +483,16 @@ watch(() => form.value.contact_type, (type) => {
justify-content: flex-end;
gap: 8px;
}
/* 手机号:动态列表,每行输入框 + 删除按钮 */
.mobile-list {
width: 100%;
.mobile-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
}
</style>
+27 -1
View File
@@ -116,7 +116,9 @@
<span>{{ row.org_name || row.dept_name || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="手机号" width="140" />
<el-table-column label="手机号" width="150" show-overflow-tooltip>
<template #default="{ row }">{{ formatMobiles(row.mobiles) || row.phone || '-' }}</template>
</el-table-column>
<!-- <el-table-column prop="wechat" label="微信" width="180">
<template #default="{ row }">
<span>{{ row.wechat || '-' }}</span>
@@ -319,6 +321,8 @@ const editFormData = reactive({
contact_type: 2,
gender: 0,
phone: '',
// 多手机号原始 JSON 数组字符串,编辑弹窗内解析回显
mobiles: '',
work_phone: '',
email: '',
wechat: '',
@@ -342,6 +346,7 @@ const openCreateContact = () => {
contact_type: 2,
gender: 0,
phone: '',
mobiles: '',
work_phone: '',
email: '',
wechat: '',
@@ -366,6 +371,8 @@ const openEditContact = (row) => {
contact_type: row.contact_type,
gender: row.gender,
phone: row.phone,
// 多手机号原始 JSON 数组字符串,编辑弹窗内解析回显
mobiles: row.mobiles || '',
work_phone: row.work_phone,
email: row.email,
wechat: row.wechat,
@@ -399,6 +406,25 @@ const handleSaveContact = async (payload) => {
}
}
/** 多手机号格式化:JSON数组 / 分隔符字符串 / 单个号码 → "138…、139…"(无内容返回空串) */
function formatMobiles(val) {
if (!val) return ''
let arr = []
if (Array.isArray(val)) {
arr = val
} else {
const s = String(val).trim()
if (!s) return ''
try {
const parsed = JSON.parse(s)
arr = Array.isArray(parsed) ? parsed : [s]
} catch (e) {
arr = s.split(/[,,、;;\s]+/)
}
}
return arr.map((m) => String(m || '').trim()).filter(Boolean).join('、')
}
// ---- 详情 ----
const detailDialogVisible = ref(false)
const detailContact = ref(null)