ContactImport.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <template>
  2. <view class="overlay" v-if="visible" @touchmove.stop.prevent>
  3. <view class="modal">
  4. <text class="modal-title">添加联系人</text>
  5. <!-- 从微信通讯录导入 -->
  6. <view class="wechat-import-btn" @click="chooseFromWeChat">
  7. <text class="wechat-import-text">从微信通讯录导入</text>
  8. </view>
  9. <view class="form">
  10. <view class="form-row">
  11. <text class="form-label">姓名</text>
  12. <input class="form-input" v-model="form.name" placeholder="请输入姓名" />
  13. </view>
  14. <view class="form-row">
  15. <text class="form-label">手机号</text>
  16. <input class="form-input" v-model="form.phone" placeholder="请输入手机号" type="number" />
  17. </view>
  18. <view class="form-row">
  19. <text class="form-label">关系类型</text>
  20. <picker mode="selector" :range="typeLabels" @change="onTypeChange">
  21. <view class="form-picker">{{ form.relationshipTypeLabel || '请选择关系类型' }}</view>
  22. </picker>
  23. </view>
  24. <view class="form-row">
  25. <text class="form-label">认识时间</text>
  26. <picker mode="date" @change="onDateChange">
  27. <view class="form-picker">{{ form.knownSince || '请选择认识时间' }}</view>
  28. </picker>
  29. </view>
  30. <view class="form-row">
  31. <text class="form-label">生日</text>
  32. <picker mode="date" @change="onBirthdayChange">
  33. <view class="form-picker">{{ form.birthday || '请选择生日' }}</view>
  34. </picker>
  35. </view>
  36. <view class="form-row">
  37. <text class="form-label">个人介绍</text>
  38. <textarea class="form-textarea" v-model="form.bio" placeholder="选填" />
  39. </view>
  40. </view>
  41. <view class="modal-actions">
  42. <button class="btn-cancel" @click="onCancel">取消</button>
  43. <button class="btn-confirm" @click="onConfirm">确认添加</button>
  44. </view>
  45. </view>
  46. </view>
  47. </template>
  48. <script>
  49. import { importPhoneContact, getRelationshipTypes } from '../utils/api.js'
  50. export default {
  51. props: {
  52. visible: { type: Boolean, default: false },
  53. name: { type: String, default: '' },
  54. phone: { type: String, default: '' }
  55. },
  56. data: function() {
  57. return {
  58. form: {
  59. name: '',
  60. phone: '',
  61. relationshipType: '',
  62. relationshipTypeLabel: '',
  63. knownSince: '',
  64. birthday: '',
  65. bio: ''
  66. },
  67. relationshipTypes: [],
  68. typeLabels: []
  69. }
  70. },
  71. watch: {
  72. visible: function(val) {
  73. if (val) {
  74. this.loadTypes()
  75. if (!this.form.name && this.name) {
  76. this.form.name = this.name
  77. }
  78. if (!this.form.phone && this.phone) {
  79. this.form.phone = this.phone
  80. }
  81. }
  82. },
  83. name: function(val) {
  84. if (val && !this.form.name) {
  85. this.form.name = val
  86. }
  87. },
  88. phone: function(val) {
  89. if (val && !this.form.phone) {
  90. this.form.phone = val
  91. }
  92. }
  93. },
  94. methods: {
  95. loadTypes: function() {
  96. var self = this
  97. getRelationshipTypes().then(function(res) {
  98. var labels
  99. if (res.code === 200 && Array.isArray(res.data)) {
  100. labels = res.data.map(function(item) {
  101. return typeof item === 'string' ? item : (item.typeName || item.name || item.label || item.value || '')
  102. })
  103. self.relationshipTypes = res.data
  104. self.typeLabels = labels
  105. }
  106. }).catch(function() {
  107. // fallback to default type options
  108. self.typeLabels = ['家人', '朋友', '伴侣', '同事', '其他']
  109. self.relationshipTypes = ['family', 'friend', 'partner', 'colleague', 'other']
  110. })
  111. },
  112. chooseFromWeChat: function() {
  113. var self = this
  114. if (typeof wx !== 'undefined' && wx.chooseContact) {
  115. wx.chooseContact({
  116. success: function(res) {
  117. self.form.name = res.name || ''
  118. self.form.phone = res.phone || ''
  119. },
  120. fail: function() {
  121. uni.showToast({ title: '获取通讯录失败', icon: 'none' })
  122. }
  123. })
  124. } else {
  125. uni.showToast({ title: '当前环境不支持', icon: 'none' })
  126. }
  127. },
  128. onTypeChange: function(e) {
  129. var idx = e.detail.value
  130. var typeObj
  131. this.form.relationshipTypeLabel = this.typeLabels[idx]
  132. typeObj = this.relationshipTypes && this.relationshipTypes[idx]
  133. if (typeObj) {
  134. this.form.relationshipType = typeof typeObj === 'string' ? typeObj : (typeObj.typeKey || typeObj.value || typeObj.code || typeObj.name || typeObj.label || '')
  135. } else {
  136. this.form.relationshipType = ''
  137. }
  138. },
  139. onDateChange: function(e) {
  140. this.form.knownSince = e.detail.value
  141. },
  142. onBirthdayChange: function(e) {
  143. this.form.birthday = e.detail.value
  144. },
  145. onConfirm: function() {
  146. var self = this
  147. if (!this.form.name || !this.form.phone) {
  148. uni.showToast({ title: '请填写姓名和手机号', icon: 'none' })
  149. return
  150. }
  151. var extra = {}
  152. if (this.form.relationshipType) extra.relationshipType = this.form.relationshipType
  153. if (this.form.knownSince) extra.knownSince = this.form.knownSince
  154. if (this.form.birthday) extra.birthday = this.form.birthday
  155. if (this.form.bio) extra.bio = this.form.bio
  156. importPhoneContact({
  157. name: this.form.name,
  158. phone: this.form.phone,
  159. extra: extra
  160. }).then(function(res) {
  161. if (res.code === 200) {
  162. uni.showToast({ title: '添加成功', icon: 'success' })
  163. self.$emit('success')
  164. self.reset()
  165. } else {
  166. uni.showToast({ title: res.message || '添加失败', icon: 'none' })
  167. }
  168. }).catch(function() {
  169. uni.showToast({ title: '网络错误', icon: 'none' })
  170. })
  171. },
  172. onCancel: function() {
  173. this.reset()
  174. this.$emit('close')
  175. },
  176. reset: function() {
  177. this.form = {
  178. name: '',
  179. phone: '',
  180. relationshipType: '',
  181. relationshipTypeLabel: '',
  182. knownSince: '',
  183. birthday: '',
  184. bio: ''
  185. }
  186. }
  187. }
  188. }
  189. </script>
  190. <style scoped>
  191. .overlay {
  192. position: fixed;
  193. top: 0; left: 0; right: 0; bottom: 0;
  194. background: rgba(0,0,0,0.5);
  195. z-index: 999;
  196. display: flex;
  197. align-items: flex-end;
  198. justify-content: center;
  199. }
  200. .modal {
  201. background: #fff;
  202. border-radius: 24rpx 24rpx 0 0;
  203. padding: 40rpx 30rpx 60rpx;
  204. width: 100%;
  205. box-sizing: border-box;
  206. max-height: 80vh;
  207. overflow-y: auto;
  208. }
  209. .modal-title {
  210. font-size: 32rpx;
  211. font-weight: bold;
  212. color: #333;
  213. margin-bottom: 24rpx;
  214. display: block;
  215. text-align: center;
  216. }
  217. .wechat-import-btn {
  218. background: #F0F9FF;
  219. border: 1rpx solid #BAE6FD;
  220. border-radius: 16rpx;
  221. padding: 24rpx 20rpx;
  222. display: flex;
  223. align-items: center;
  224. justify-content: center;
  225. margin-bottom: 24rpx;
  226. }
  227. .wechat-import-btn:active {
  228. opacity: 0.7;
  229. }
  230. .wechat-import-text {
  231. font-size: 28rpx;
  232. color: #0EA5E9;
  233. font-weight: 500;
  234. }
  235. .form-row {
  236. margin-bottom: 24rpx;
  237. }
  238. .form-label {
  239. font-size: 26rpx;
  240. color: #666;
  241. display: block;
  242. margin-bottom: 8rpx;
  243. }
  244. .form-input {
  245. border: 1rpx solid #ddd;
  246. border-radius: 12rpx;
  247. padding: 24rpx 20rpx;
  248. font-size: 28rpx;
  249. width: 100%;
  250. box-sizing: border-box;
  251. color: #333;
  252. min-height: 80rpx;
  253. background: #fff;
  254. }
  255. .form-textarea {
  256. border: 1rpx solid #ddd;
  257. border-radius: 12rpx;
  258. padding: 24rpx 20rpx;
  259. font-size: 28rpx;
  260. width: 100%;
  261. box-sizing: border-box;
  262. color: #333;
  263. min-height: 160rpx;
  264. }
  265. .form-picker {
  266. border: 1rpx solid #ddd;
  267. border-radius: 12rpx;
  268. padding: 24rpx 20rpx;
  269. font-size: 28rpx;
  270. color: #333;
  271. min-height: 80rpx;
  272. display: flex;
  273. align-items: center;
  274. }
  275. .modal-actions {
  276. display: flex;
  277. gap: 20rpx;
  278. margin-top: 40rpx;
  279. }
  280. .btn-cancel {
  281. flex: 1;
  282. padding: 20rpx;
  283. border-radius: 12rpx;
  284. border: 1rpx solid #ddd;
  285. background: #fff;
  286. font-size: 28rpx;
  287. color: #666;
  288. text-align: center;
  289. }
  290. .btn-confirm {
  291. flex: 2;
  292. padding: 20rpx;
  293. border-radius: 12rpx;
  294. background: linear-gradient(135deg, #FF6B35, #F97316);
  295. font-size: 28rpx;
  296. color: #fff;
  297. text-align: center;
  298. border: none;
  299. }
  300. </style>