256 lines
7.3 KiB
Vue
256 lines
7.3 KiB
Vue
<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>
|