j-contacts.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <view v-if="showMask" class="contact-mask" @touchmove.stop.prevent="moveHandle">
  3. <view class="contact-box">
  4. <view class="page">
  5. <scroll-view class="scrollList" scroll-y :scroll-into-view="scrollViewId" :style="{height:winHeight + 'px', width: winWidth + 'px'}">
  6. <view v-for="(list, key) in lists" :key="key">
  7. <view v-if="list.data.length">
  8. <view class="contact-divider" :id="list.id">{{list.letter}}</view>
  9. <view class="contact-item" v-for="item in list.data" :key="item.key" @tap="chooseItem(item)">
  10. <uni-icons :type="item.choose ? 'checkbox-filled' : 'circle'" :color="item.choose ? '#007aff' : '#aaa'" size="24"/>
  11. <text>{{item.name}}&nbsp;&nbsp;{{item.phone}}</text>
  12. </view>
  13. </view>
  14. </view>
  15. </scroll-view>
  16. <view class="index-bar" :class="touchmove ? 'active' : ''" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd" @touchcancel="touchCancel">
  17. <text v-for="(list, key) in lists" :key="key" class="index-bar-text" :class="touchmoveIndex == key ? 'active' : ''" :style="{height:itemHeight + 'px', lineHeight:itemHeight + 'px'}">{{list.letter}}</text>
  18. </view>
  19. <view class="index-alert" v-if="indexAlert">
  20. {{lists[touchmoveIndex].letter}}
  21. </view>
  22. </view>
  23. <view class="contact-button-groups">
  24. <button class="contact-button" type="default" @click="cancel">取消</button>
  25. <button class="contact-button" type="primary" @click="confirm">确定</button>
  26. </view>
  27. </view>
  28. </view>
  29. </template>
  30. <script>
  31. var alphabet = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ]
  32. import uniIcons from '@/components/uni-icons/uni-icons.vue';
  33. import pinyin from './pinyin.js';
  34. export default {
  35. components: {
  36. uniIcons
  37. },
  38. props: {
  39. mode: { // 单选single或多选multi模式
  40. type: String,
  41. default: "single",
  42. },
  43. hashFirst: {
  44. type: Boolean,
  45. default: true,
  46. }
  47. },
  48. data() {
  49. return {
  50. showMask: false,
  51. lists: [], // 当前页面显示的列表
  52. chooseList: [],
  53. touchmove: false,
  54. indexAlert: false,
  55. touchmoveIndex: -1,
  56. itemHeight: 0,
  57. winHeight: 0,
  58. barHeight: 0,
  59. titleHeight: 0,
  60. scrollViewId: "A",
  61. }
  62. },
  63. methods: {
  64. moveHandle() {},
  65. show() {
  66. uni.hideKeyboard()
  67. this.showMask = true
  68. this.winHeight = uni.getSystemInfoSync().windowHeight - 46; // 通讯录列表高度 = 页面高度(不含导航条) - 按钮高度
  69. this.winWidth = uni.getSystemInfoSync().windowWidth;
  70. if(this.lists.length == 0) { // 只获取一次
  71. this.getContacts()
  72. }
  73. // 清除上次选中
  74. this.lists.forEach(group => {
  75. group.data.forEach(one => {
  76. one.choose = false
  77. })
  78. })
  79. this.chooseList = []
  80. },
  81. getContacts() {
  82. let _this = this
  83. if(this.hashFirst){
  84. alphabet.unshift("#")
  85. } else {
  86. alphabet.push("#")
  87. }
  88. // #ifdef APP-PLUS
  89. plus.contacts.getAddressBook(plus.contacts.ADDRESSBOOK_PHONE, function(addressbook) {
  90. addressbook.find(["displayName", "phoneNumbers"], function(contacts) {
  91. _this.getIndexList(contacts)
  92. }, function() {
  93. uni.showToast({
  94. title: '联系人获取失败',
  95. duration: 2000
  96. })
  97. }, {
  98. multiple: true
  99. });
  100. }, function(e) {
  101. uni.showToast({
  102. title: '通讯录对象获取失败:' + e.message,
  103. duration: 2000
  104. })
  105. });
  106. // #endif
  107. // #ifdef H5
  108. let contacts = JSON.parse(
  109. '[{"id":1,"rawId":null,"target":0,"displayName":"z朱海","name":null,"nickname":null,"phoneNumbers":[{"id":"1","pref":false,"value":"+86 138 1234 2222","type":"mobile"},{"id":"2","pref":false,"value":"136-1111-2222","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":3,"rawId":null,"target":0,"displayName":" ","name":null,"nickname":null,"phoneNumbers":[{"id":"6","pref":false,"value":"1-800-MY-APPLE","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":4,"rawId":null,"target":0,"displayName":"林二","name":null,"nickname":null,"phoneNumbers":[{"id":"8","pref":false,"value":"137 2222 7777","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":6,"rawId":null,"target":0,"displayName":null,"name":null,"nickname":null,"phoneNumbers":[{"id":"12","pref":false,"value":"186 4444 5555","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":7,"rawId":null,"target":0,"displayName":" 程紫衣","name":null,"nickname":null,"phoneNumbers":[{"id":"14","pref":false,"value":"180 9999 7777","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":8,"rawId":null,"target":0,"displayName":" ","name":null,"nickname":null,"phoneNumbers":[{"id":"16","pref":false,"value":"181 1115 6666","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":9,"rawId":null,"target":0,"displayName":"翁富翁","name":null,"nickname":null,"phoneNumbers":[{"id":"18","pref":false,"value":"134 6666 8888","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":10,"rawId":null,"target":0,"displayName":"我","name":null,"nickname":null,"phoneNumbers":[{"id":"20","pref":false,"value":"181 8888 8888","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":11,"rawId":null,"target":0,"displayName":"石更硬","name":null,"nickname":null,"phoneNumbers":[{"id":"22","pref":false,"value":"159 5555 5555","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":12,"rawId":null,"target":0,"displayName":"..私教","name":null,"nickname":null,"phoneNumbers":[{"id":"24","pref":false,"value":"131 5555 2222","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":13,"rawId":null,"target":0,"displayName":"!)陈靖南","name":null,"nickname":null,"phoneNumbers":[{"id":"27","pref":false,"value":"139 3333 2222","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null},{"id":14,"rawId":null,"target":0,"displayName":"翁啊啊啊啊","name":null,"nickname":null,"phoneNumbers":[{"id":"28","pref":false,"value":"138 3333 2222","type":"mobile"}],"emails":null,"addresses":null,"ims":null,"organizations":null,"birthday":null,"note":null,"photos":null,"categories":null,"urls":null}]'
  110. )
  111. this.getIndexList(contacts)
  112. // #endif
  113. },
  114. getIndexList(contacts) {
  115. let unsorted = this.getModel()
  116. let key = 0
  117. contacts.forEach(contact => {
  118. let firstChar = ''
  119. contact.displayName = contact.displayName ? contact.displayName.trim() : ""
  120. if(contact.displayName) { // 如传入空字符串,getCamelFistChar错误地返回Y
  121. firstChar = pinyin.getCamelFistChar(contact.displayName).toUpperCase() // 如字母开头,将返回字母且保留原大小写;一律改为大写
  122. }
  123. let hashIndex = alphabet.indexOf("#")
  124. let index = alphabet.indexOf(firstChar)
  125. index = index == -1 ? hashIndex : index
  126. contact.phoneNumbers.forEach(phoneNumber=>{ // 获取同一联系人下的多个电话号码
  127. unsorted[index].data.push({name: contact.displayName, phone: phoneNumber.value, choose: false, key: key})
  128. key ++
  129. })
  130. })
  131. // 只保留有数据的list
  132. let sorted = []
  133. unsorted.forEach(item=>{
  134. if(item.data.length > 0) {
  135. // 按姓名拼音排序
  136. item.data.sort((a, b)=> a.name.localeCompare(b.name, 'zh')) // name 不能为 null,否则localCompare 将报错
  137. sorted.push(item)
  138. }
  139. })
  140. this.lists = sorted
  141. // console.log(JSON.stringify(this.lists))
  142. this.itemHeight = this.winHeight / this.lists.length
  143. },
  144. getModel() {
  145. let model = []
  146. alphabet.forEach(letter => {
  147. model.push({"letter": letter, "data": [], id: letter == "#" ? "hash" : letter})
  148. })
  149. return model
  150. },
  151. confirm() {
  152. this.chooseList = []
  153. this.lists.forEach(group => {
  154. group.data.forEach(one => {
  155. if(one.choose){
  156. this.chooseList.push({name: one.name, phone: one.phone})
  157. }
  158. })
  159. })
  160. if(this.chooseList.length == 0) {
  161. uni.showToast({
  162. icon: "none",
  163. title: "未选中任何联系人"
  164. })
  165. }
  166. this.$emit('confirm', this.chooseList);
  167. this.showMask = false
  168. },
  169. cancel() {
  170. this.$emit('cancel')
  171. this.showMask = false
  172. },
  173. chooseItem(item) {
  174. if(this.mode == "single") { // 单选模式
  175. if(item.choose == false) { // 选中
  176. this.lists.forEach(group => {
  177. group.data.forEach(one => {
  178. one.choose = false // 全部设为false
  179. })
  180. })
  181. item.choose = true // 当前项设为true
  182. } else { // 撤销选中
  183. item.choose = false
  184. }
  185. } else if(this.mode == "multi") { // 多选模式
  186. item.choose = !item.choose // 改变当前点击项
  187. }
  188. },
  189. touchStart(e) {
  190. this.touchmove = true;
  191. let pageY = e.touches[0].pageY;
  192. let index = Math.floor((pageY - this.titleHeight) / this.itemHeight);
  193. let item = this.lists[index];
  194. if (item) {
  195. this.scrollViewId = item.id;
  196. this.touchmoveIndex = index;
  197. this.indexAlert = true
  198. }
  199. },
  200. touchMove(e) {
  201. this.touchmove = true;
  202. let pageY = e.touches[0].pageY;
  203. let index = Math.floor((pageY - this.titleHeight) / this.itemHeight);
  204. let item = this.lists[index];
  205. if (item) {
  206. this.scrollViewId = item.id;
  207. this.touchmoveIndex = index;
  208. this.indexAlert = true
  209. }
  210. },
  211. touchEnd() {
  212. this.touchmove = false;
  213. this.indexAlert = false
  214. this.touchmoveIndex = -1;
  215. },
  216. touchCancel() {
  217. this.touchmove = false;
  218. this.indexAlert = false
  219. this.touchmoveIndex = -1;
  220. }
  221. }
  222. }
  223. </script>
  224. <style>
  225. .contact-mask {
  226. display: block;
  227. position: fixed;
  228. right: 0;
  229. /* #ifdef H5 */
  230. top: 42px;
  231. /* #endif */
  232. /* #ifndef H5 */
  233. top: 0;
  234. /* #endif */
  235. bottom: 0;
  236. align-items: center;
  237. width: 100%;
  238. z-index: 100;
  239. }
  240. .contact-box {
  241. width: 100%;
  242. height: 100%;
  243. overflow: hidden;
  244. background: #fff;
  245. }
  246. .page {
  247. flex-direction: row;
  248. display: block;
  249. position: relative
  250. }
  251. .scrollList {
  252. flex: 1;
  253. height: 100vh;
  254. }
  255. .contact-divider {
  256. width: 100%;
  257. background-color: #F0F0F0;
  258. padding: 10upx 20upx;
  259. color: #666;
  260. }
  261. .contact-item {
  262. position: relative;
  263. display: flex;
  264. flex-direction: row;
  265. justify-content: flex-start;
  266. padding-right: 50upx;
  267. margin-left: 10upx;
  268. border-bottom: 1px solid #f0f0f0;
  269. }
  270. .contact-item text {
  271. padding-left: 20upx;
  272. font-size: 30upx;
  273. line-height: 80upx;
  274. }
  275. .index-bar {
  276. width: 50upx;
  277. background-color: #fff;
  278. display: flex;
  279. flex-direction: column;
  280. position: absolute;
  281. right: 0;
  282. top: 50%;
  283. transform: translateY(-50%);
  284. box-shadow: 0 0 20upx rgba(0, 0, 0, 0.1);
  285. border-radius: 10upx;
  286. z-index: 20;
  287. }
  288. .index-bar-text {
  289. color: #666;
  290. font-size: 24upx;
  291. text-align: center;
  292. }
  293. .index-bar-text.active,
  294. .index-bar.active .index-bar-text.active {
  295. color: #007AFF;
  296. }
  297. .index-alert {
  298. position: absolute;
  299. z-index: 20;
  300. width: 160upx;
  301. height: 160upx;
  302. left: 50%;
  303. top: 50%;
  304. margin-left: -80upx;
  305. margin-top: -80upx;
  306. border-radius: 80upx;
  307. text-align: center;
  308. line-height: 160upx;
  309. font-size: 70upx;
  310. color: #fff;
  311. background-color: rgba(0, 0, 0, 0.5);
  312. }
  313. .contact-button-groups {
  314. text-align: center;
  315. margin-top: 10upx;
  316. }
  317. .contact-button {
  318. display:inline-block ;
  319. width: 45%;
  320. margin: 0 10upx;
  321. font-size: 32upx;
  322. }
  323. </style>