Files
lucky_shop/pages_tool/card/contact-edit.vue
2026-10-19 16:44:02 +08:00

256 lines
7.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="contact-edit">
<view class="form-group">
<view class="group-title">{{ isEdit ? '编辑人脉' : '添加人脉' }}</view>
<view class="form-row">
<view class="row-label"><text class="req-star">*</text>姓名</view>
<input class="row-input" :value="form.name" placeholder="请输入姓名"
@input="e => form.name = e.detail.value" />
</view>
<view class="form-row">
<view class="row-label">手机</view>
<input class="row-input" type="number" :value="form.mobile" placeholder="手机号(选填)"
@input="e => form.mobile = e.detail.value" />
</view>
<view class="form-row">
<view class="row-label">公司</view>
<input class="row-input" :value="form.company" placeholder="公司名称(选填)"
@input="e => form.company = e.detail.value" />
</view>
<view class="form-row">
<view class="row-label">职位</view>
<input class="row-input" :value="form.job_title" placeholder="职位(选填)"
@input="e => form.job_title = e.detail.value" />
</view>
<view class="form-row">
<view class="row-label">微信号</view>
<input class="row-input" :value="form.wechat" placeholder="微信号(选填)"
@input="e => form.wechat = e.detail.value" />
</view>
<view class="form-row">
<view class="row-label">邮箱</view>
<input class="row-input" :value="form.email" placeholder="邮箱(选填)"
@input="e => form.email = e.detail.value" />
</view>
<view class="form-row">
<view class="row-label">网站</view>
<input class="row-input" :value="form.website" placeholder="个人主页 / 官网(选填)"
@input="e => form.website = e.detail.value" />
</view>
<view class="form-row">
<view class="row-label">地址</view>
<input class="row-input" :value="form.address" placeholder="地址(选填)"
@input="e => form.address = e.detail.value" />
</view>
<view class="form-row remark-row">
<view class="row-label">备注</view>
<textarea class="remark-input" :value="form.remark" placeholder="备注(选填)"
@input="e => form.remark = e.detail.value" />
</view>
</view>
<!-- 保存 -->
<view class="save-bar">
<button class="save-btn" :disabled="saving" @tap="save">{{ saving ? '保存中…' : '保存' }}</button>
</view>
</view>
</template>
<script>
// 与后端 Contact::EDITABLE_FIELDS 对齐(status 由列表归档操作管理, 编辑页不放)
const FIELDS = ['name', 'mobile', 'company', 'job_title', 'wechat', 'email', 'website', 'address', 'remark'];
export default {
data() {
return {
id: 0,
isEdit: false,
form: {
name: '', mobile: '', company: '', job_title: '',
wechat: '', email: '', website: '', address: '', remark: ''
},
saving: false,
fromAdmin: false
};
},
onLoad(options) {
this.id = parseInt(options.id || 0, 10) || 0;
this.isEdit = this.id > 0;
// from=admin: 商家端人脉管理页复用本编辑页, 保存后回商家端(缺省回员工端)
this.fromAdmin = options.from === 'admin';
if (this.isEdit) {
this.loadDetail();
}
},
onReady() {
// H5 uni-page-head 挂载早于路由 meta 就绪会回落默认标题 "title", onReady 再兜底一次
uni.setNavigationBarTitle({ title: '名片' });
// #ifdef H5
document.title = '名片';
// #endif
},
methods: {
loadDetail() {
this.$cardApi.contactInfo({
data: { id: this.id },
success: res => {
if (res.code == 0) {
const r = res.data || {};
FIELDS.forEach(k => {
if (r[k] !== undefined && r[k] !== null) {
this.form[k] = String(r[k]);
}
});
} else {
uni.showModal({
title: '提示',
content: res.message || '人脉不存在',
showCancel: false,
success: () => this.backToList()
});
}
},
fail: () => {
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
}
});
},
save() {
if (this.saving) return;
// 防御式取值: 任何非字符串/undefined 都不会让校验误判或抛错
const name = String(this.form.name || '').trim();
if (!name) {
uni.showToast({ title: '请填写姓名', icon: 'none' });
return;
}
this.form.name = name;
this.saving = true;
const data = { id: this.id, fields: {} };
FIELDS.forEach(k => { data.fields[k] = this.form[k]; });
const onOk = res => {
this.saving = false;
if (res.code == 0) {
uni.showToast({ title: '保存成功', icon: 'success' });
setTimeout(() => this.backToList(), 600);
} else {
// 后端错误文案有时放在 message, 有时放在 data(字符串), 两者都兜住
const msg = res.message || (typeof res.data === 'string' ? res.data : '') || '保存失败';
uni.showToast({ title: msg, icon: 'none' });
}
};
const onFail = () => {
this.saving = false;
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
};
// add/update 都走 JSON body,跨端序列化零歧义。
// ⚠️ 契约差异(见 docs/CARD_API_REFERENCE.md §1.4.2 / §1.4.4):
// contact/add → 入参是「顶层平铺字段」, name 必须在 body 顶层;
// contact/update→ 额外兼容 fields 对象。
// 历史 bug: 新增时也包成 { fields: {...} }, 后端在顶层读不到 name,
// 返回「请填写姓名」→ 表现为"明明填了姓名还提示填写姓名"。
// 这里新增按契约平铺, 同时保留一份 fields 兼容旧后端(多余键会被白名单忽略)。
if (this.isEdit) {
this.$cardApi.contactUpdate({ data: data, success: onOk, fail: onFail });
} else {
const addPayload = Object.assign({}, data.fields, { fields: data.fields });
this.$cardApi.contactAdd({ data: addPayload, success: onOk, fail: onFail });
}
},
// 鸿蒙安全回列表: $util.redirectTo(禁裸 uni.switchTab/uni.navigateBack 兜底)
// 商家端(from=admin)回商家端人脉管理, 员工端回「我的人脉」
backToList() {
const target = this.fromAdmin ? '/pages_tool/card/admin/contacts' : '/pages_tool/card/contacts';
this.$util.redirectTo(target);
}
}
};
</script>
<style lang="scss" scoped>
.contact-edit {
min-height: 100vh;
background-color: #f6f7f9;
padding: 24rpx 24rpx 60rpx;
box-sizing: border-box;
}
.form-group {
background-color: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
overflow: hidden;
.group-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
padding: 24rpx 24rpx 8rpx;
}
.form-row {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.row-label {
width: 160rpx;
font-size: 28rpx;
color: #666;
flex-shrink: 0;
}
/* 必填红星(与 card/edit.vue 同款) */
.req-star {
color: #f5222d;
margin-right: 4rpx;
font-weight: 600;
}
.row-input {
flex: 1;
font-size: 28rpx;
color: #333;
}
}
.remark-row {
align-items: flex-start;
.remark-input {
flex: 1;
min-height: 120rpx;
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
}
}
.save-bar {
padding: 20rpx 0 0;
.save-btn {
height: 88rpx;
line-height: 88rpx;
background-color: var(--main-color, #0054a5);
color: #ffffff;
font-size: 30rpx;
border-radius: 44rpx;
&[disabled] {
opacity: 0.6;
color: #ffffff;
background-color: var(--main-color, #0054a5);
}
}
}
</style>