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>
|