tel.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. <!--本代码主要是拨号盘的功能用户可以实现按键拨打并且在拨号盘的后方有与用户拨号相匹配的联系人号码点击即可快速拨打。
  2. 内容:
  3. 1.匹配的联系人和通话记录列表,点击即可播出
  4. 2.显示号码输入框和删除按钮
  5. 3.数字键盘和拨打按钮区域
  6. 4.点击切换按钮即可收起数字键盘展示与拨号相匹配的列表。
  7. 更新人:张甜甜
  8. 最后更新时间:2024.10.15
  9. 更新内容:样式-->
  10. <template>
  11. <view class="container">
  12. <!-- 匹配的联系人和通话记录列表 -->
  13. <view v-if="matchedItems.length > 0" class="matched-contacts">
  14. <view
  15. v-for="(item, index) in matchedItems"
  16. :key="index"
  17. class="contact-item"
  18. @touchstart="startPressTimer(item.number)"
  19. @touchend="clearPressTimer"
  20. @click="selectContact(item.number)"
  21. >
  22. <text class="contact-name">{{ item.name }}</text>
  23. <div class="contact-number" v-html="highlightMatch(item.number, displayPhoneNumber)"></div>
  24. </view>
  25. </view>
  26. <!-- 显示号码输入框和删除按钮 -->
  27. <view :class="['display', { 'display-bottom': !showDialPad }]">
  28. <text class="phone-number">
  29. {{ displayPhoneNumber }}
  30. </text>
  31. <image
  32. v-if="phoneNumber.length > 0"
  33. src="../../static/pics/delete.png"
  34. @touchstart="startDelete"
  35. @touchend="stopDelete"
  36. @click="deleteNumber"
  37. class="delete"
  38. ></image>
  39. </view>
  40. <!-- 数字键盘和拨打按钮区域 -->
  41. <!-- 数字键盘和拨打按钮区域 -->
  42. <view class="dial-pad" :style="{ zIndex: showDialPad ? '3' : '1' }">
  43. <view v-show="showDialPad" class="row" v-for="(row, index) in dialPad" :key="index">
  44. <button
  45. v-for="digit in row"
  46. :key="digit"
  47. @click="appendNumber(digit)"
  48. :class="{
  49. 'digit-button': true,
  50. 'hash-button': digit === '#'
  51. }"
  52. :style="digit === '#' ? hashStyle : {}"
  53. >
  54. <!-- 判断是否为星号,如果是则加载图片 -->
  55. <image v-if="digit === '*'" src="../../static/pics/star.png" class="star-icon" />
  56. <!-- 否则显示数字或其他字符 -->
  57. <span v-else>{{ digit }}</span>
  58. </button>
  59. </view>
  60. </view>
  61. <!-- 切换按钮和拨打按钮区域 -->
  62. <view class="bottom-buttons">
  63. <!-- 切换按钮 -->
  64. <view class="toggle-button">
  65. <button class="scale" @click="toggleDialPad">
  66. <image v-show="showDialPad" src="../../static/pics/down.png" class="ScaleIcon"></image>
  67. <image v-show="!showDialPad" src="../../static/pics/up.png" class="ScaleIcon"></image>
  68. </button>
  69. </view>
  70. <view class="action">
  71. <!-- 只显示一个拨打按钮 -->
  72. <button class="call-button single-sim" @click="callNumber(phoneNumber)">
  73. <image src="../../static/pics/call.png" class="CallIcon"></image>
  74. </button>
  75. </view>
  76. </view>
  77. </view>
  78. </template>
  79. <script>
  80. import pinyin from 'pinyin';
  81. export default {
  82. data() {
  83. return {
  84. simNumber: 0,
  85. phoneNumber: '', // 输入的电话号码
  86. showDialPad: true, // 控制是否显示数字键盘
  87. dialPad: [
  88. ['1', '2', '3'],
  89. ['4', '5', '6'],
  90. ['7', '8', '9'],
  91. ['*', '0', '#']
  92. ],
  93. contacts: [], // 存储联系人信息
  94. matchedItems: [], // 匹配的联系人和通话记录
  95. telephoneLog: [], // 存储通话记录
  96. pressTimer: null, // 计时器用于长按操作
  97. deleteInterval: null // 计时器用于删除操作
  98. };
  99. },
  100. mounted() {
  101. this.getContacts(); // 组件挂载时获取联系人数据
  102. this.handleGetCallLogs(); // 获取通话记录
  103. },
  104. computed: {
  105. displayPhoneNumber() {
  106. // 根据输入的电话号码长度显示部分号码或省略号
  107. if (this.phoneNumber.length <= 11) {
  108. const part1 = this.phoneNumber.slice(0, 3);
  109. const part2 = this.phoneNumber.slice(3, 7);
  110. const part3 = this.phoneNumber.slice(7, 11);
  111. return `${part1} ${part2} ${part3}`.trim(); // 按 "3 4 4" 格式显示号码
  112. } else {
  113. return `...${this.phoneNumber.slice(-11)}`; // 若号码过长,则显示最后11位,前面用省略号表示
  114. }
  115. }
  116. },
  117. watch: {
  118. phoneNumber(newValue) {
  119. this.matchContactsAndCallLogs(newValue); // 当电话号码变化时调用匹配方法
  120. }
  121. },
  122. onLoad() {
  123. // 初始化联系人列表
  124. this.getContacts();
  125. // 监听联系人更新事件
  126. uni.$on('contactsUpdated', () => {
  127. // 重新加载联系人
  128. this.getContacts();
  129. });
  130. },
  131. onUnload() {
  132. // 页面卸载时移除事件监听
  133. uni.$off('contactsUpdated');
  134. },
  135. methods: {
  136. startPressTimer(number) {
  137. this.pressTimer = setTimeout(() => {
  138. // 执行长按操作,例如显示操作菜单
  139. console.log('长按号码:', number);
  140. // 这里可以添加长按的具体操作
  141. }, 2000); // 设置长按时间
  142. },
  143. clearPressTimer() {
  144. if (this.pressTimer) {
  145. clearTimeout(this.pressTimer);
  146. this.pressTimer = null;
  147. }
  148. },
  149. startDelete(event) {
  150. if (!event) return;
  151. event.stopPropagation();
  152. if (this.phoneNumber.length === 0) {
  153. return;
  154. }
  155. if (this.deleteInterval) {
  156. clearInterval(this.deleteInterval);
  157. }
  158. this.deleteInterval = setInterval(() => {
  159. if (this.phoneNumber.length > 0) {
  160. this.deleteNumber();
  161. } else {
  162. clearInterval(this.deleteInterval);
  163. }
  164. }, 100);
  165. },
  166. stopDelete(event) {
  167. if (event) {
  168. event.stopPropagation();
  169. }
  170. if (this.deleteInterval) {
  171. clearInterval(this.deleteInterval);
  172. this.deleteInterval = null;
  173. }
  174. },
  175. deleteNumber() {
  176. if (this.phoneNumber.length > 0) {
  177. this.phoneNumber = this.phoneNumber.slice(0, -1);
  178. }
  179. if (this.phoneNumber.length === 0) {
  180. this.stopDelete();
  181. }
  182. console.log('Current phoneNumber:', this.phoneNumber);
  183. },
  184. // 高亮匹配的文本部分
  185. highlightMatch(text, query) {
  186. if (!query) return text;
  187. // 格式化后的文本,带有空格,如 '136 2341 3568'
  188. const formattedText = this.formatPhoneNumber(text);
  189. // 移除查询字符串中的空格
  190. const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '');
  191. // 构建正则表达式,允许匹配时中间有任意数量的空格
  192. const regexQuery = escapedQuery.split('').map(char => `${char}\\s*`).join('');
  193. const regex = new RegExp(`(${regexQuery})`, 'gi');
  194. // 执行替换,保持原始文本的空格并高亮匹配部分
  195. const result = formattedText.replace(regex, (match) => `<span class="highlight">${match}</span>`);
  196. return result;
  197. },
  198. // 获取联系人并格式化
  199. getContacts() {
  200. let self = this;
  201. plus.contacts.getAddressBook(plus.contacts.ADDRESSBOOK_PHONE, (addressbook) => {
  202. addressbook.find(['displayName', 'phoneNumbers'], (contacts) => {
  203. self.contacts = []; // 清空当前联系人列表
  204. contacts.forEach((contact) => {
  205. if (contact.phoneNumbers && contact.phoneNumbers.length > 0) {
  206. contact.phoneNumbers.forEach(phone => {
  207. let cleanNumber = phone.value.replace(/[^\d\s]/g, '');
  208. self.contacts.push({
  209. name: contact.displayName,
  210. number: cleanNumber
  211. });
  212. });
  213. }
  214. });
  215. // 确保事件在获取到联系人数据后触发
  216. uni.$emit('contactsUpdated');
  217. }, (e) => {
  218. console.log("获取联系人失败: " + e.message);
  219. });
  220. }, (e) => {
  221. console.log("打开通讯录失败: " + e.message);
  222. });
  223. },
  224. handleGetCallLogs() {
  225. //获取历史记录联系人
  226. if (plus.android) {
  227. try {
  228. const MainActivity = plus.android.runtimeMainActivity();
  229. const CallLogHelper = plus.android.importClass("com.example.mylibrary.CallLogHelper");
  230. if (CallLogHelper) {
  231. const logsJson = CallLogHelper.getCallLogs(MainActivity);
  232. console.log("Raw logsJson:", logsJson);
  233. const logsArray = JSON.parse(logsJson);
  234. console.log("Parsed logsArray:", logsArray);
  235. if (Array.isArray(logsArray)) {
  236. this.telephoneLog = logsArray;
  237. } else {
  238. console.error("解析后的数据不是数组");
  239. }
  240. } else {
  241. console.error("CallLogHelper 类未加载");
  242. }
  243. } catch (e) {
  244. console.error("获取通话记录失败:", e);
  245. }
  246. } else {
  247. console.error("Android 环境未准备好");
  248. }
  249. },
  250. appendNumber(digit) {
  251. if (this.phoneNumber.length >= 14) {
  252. return;
  253. }
  254. this.phoneNumber += digit;
  255. },
  256. getInitials(str) {
  257. const initialsArray = pinyin(str, {
  258. style: pinyin.STYLE_FIRST_LETTER
  259. });
  260. const firstLettersOnly = initialsArray.map(initial => initial[0]);
  261. return firstLettersOnly.join('');
  262. },
  263. // 匹配联系人
  264. matchContacts(query) {
  265. if (!this.contacts || this.contacts.length === 0) {
  266. return [];
  267. }
  268. const cleanQuery = query.replace(/\s+/g, '');
  269. let matchedContacts = [];
  270. this.contacts.forEach(contact => {
  271. const cleanNumber = contact.number.replace(/\s+/g, '');
  272. if (cleanNumber.includes(cleanQuery)) {
  273. matchedContacts.push({
  274. ...contact,
  275. number: cleanNumber
  276. });
  277. } else if (contact.notes) {
  278. contact.notes.forEach(note => {
  279. if (note.includes(cleanQuery)) {
  280. matchedContacts.push({
  281. ...contact,
  282. number: cleanNumber
  283. });
  284. }
  285. });
  286. }
  287. });
  288. // 按备注的第一个字的拼音首字母进行排序
  289. matchedContacts.sort((a, b) => {
  290. const noteA = a.notes ? a.notes[0] : '';
  291. const noteB = b.notes ? b.notes[0] : '';
  292. const initialsA = this.getInitials(noteA);
  293. const initialsB = this.getInitials(noteB);
  294. return initialsA.localeCompare(initialsB);
  295. });
  296. return matchedContacts;
  297. },
  298. // 匹配通话记录
  299. matchCallLogs(query) {
  300. // 如果 this.telephoneLog 未定义或为空,返回空数组
  301. if (!this.telephoneLog || this.telephoneLog.length === 0) {
  302. console.log('call logs is null');
  303. return [];
  304. }
  305. const cleanQuery = query.replace(/\s+/g, '');
  306. return this.telephoneLog.filter(log => {
  307. const cleanNumber = log.number.replace(/\s+/g, '');
  308. return cleanNumber.includes(cleanQuery);
  309. }).map(log => ({
  310. ...log,
  311. number: log.number.replace(/\s+/g, '')
  312. }));
  313. },
  314. // 匹配联系人和通话记录
  315. matchContactsAndCallLogs(query) {
  316. if (query.length === 0) {
  317. this.matchedItems = [];
  318. return;
  319. }
  320. const cleanQuery = query.replace(/\s+/g, '');
  321. const matchedContacts = this.matchContacts(cleanQuery);
  322. const matchedCallLogs = this.matchCallLogs(cleanQuery);
  323. console.log('Query:', cleanQuery);
  324. console.log('Matched Contacts:', matchedContacts);
  325. console.log('Matched Call Logs:', matchedCallLogs);
  326. const uniqueItems = new Map();
  327. // 先插入联系人信息
  328. matchedContacts.forEach(contact => {
  329. uniqueItems.set(contact.number, contact);
  330. });
  331. // 再插入通话记录,只有在号码未被匹配到联系人时才插入
  332. matchedCallLogs.forEach(log => {
  333. if (!uniqueItems.has(log.number)) {
  334. // 如果通话记录中的号码没有匹配到联系人,则标记为未知联系人
  335. uniqueItems.set(log.number, {
  336. name: '陌生号',
  337. number: log.number
  338. });
  339. }
  340. });
  341. // 获取所有条目并进行排序
  342. let items = Array.from(uniqueItems.values());
  343. // 将“陌生号”条目分开
  344. const unknownItems = items.filter(item => item.name === '陌生号');
  345. const knownItems = items.filter(item => item.name !== '陌生号');
  346. // 按拼音首字母排序已知条目
  347. knownItems.sort((a, b) => {
  348. const nameA = a.name || '';
  349. const nameB = b.name || '';
  350. const initialsA = this.getInitials(nameA);
  351. const initialsB = this.getInitials(nameB);
  352. return initialsA.localeCompare(initialsB);
  353. });
  354. // 合并已知条目和“陌生号”条目
  355. this.matchedItems = knownItems.concat(unknownItems);
  356. console.log('Final Matched Items:', this.matchedItems);
  357. },
  358. selectContact(number) {
  359. this.phoneNumber = number;
  360. this.matchedItems = [];
  361. // 显示号码后,延迟执行重置操作
  362. setTimeout(() => {
  363. this.callNumber(number);
  364. }, 500); // 延迟 500 毫秒后拨号并重置
  365. },
  366. callNumber(number) {
  367. if (!number || number.length === 0) {
  368. uni.showToast({
  369. title: '请输入电话号码',
  370. icon: 'none'
  371. });
  372. return;
  373. }
  374. // 在拨号前更新通话记录
  375. this.handleGetCallLogs();
  376. // this.getContacts();
  377. // 拨打电话
  378. plus.device.dial(number, false);
  379. // 拨打电话后重置拨号页面状态
  380. this.resetDialPad();
  381. },
  382. toggleDialPad() {
  383. this.showDialPad = !this.showDialPad;
  384. },
  385. // 重置拨号页面状态的方法
  386. resetDialPad() {
  387. this.phoneNumber = ''; // 清空输入的电话号码
  388. this.matchedItems = []; // 清空匹配的联系人和通话记录
  389. this.showDialPad = true; // 确保数字键盘显示
  390. },
  391. formatPhoneNumber(number) {
  392. const cleanedNumber = number.replace(/[^\d]/g, '');
  393. if (cleanedNumber.length === 11) {
  394. return cleanedNumber.replace(/^(\d{3})(\d{4})(\d{4})$/, '$1 $2 $3');
  395. } else if (cleanedNumber.length > 7) {
  396. return cleanedNumber.replace(/^(\d{3})(\d{4})(\d+)$/, '$1 $2 $3');
  397. } else {
  398. return cleanedNumber;
  399. }
  400. }
  401. }
  402. };
  403. </script>
  404. <style>
  405. .container {
  406. /* 整体容器,使用 flex 布局,垂直排列子元素并居中,容器高度占满整个视口,背景色为浅灰色 */
  407. display: flex;
  408. flex-direction: column;
  409. align-items: center;
  410. justify-content: flex-start;
  411. height: 100vh;
  412. background-color: #f7f7f7;
  413. padding-top: 50px;
  414. overflow: hidden;
  415. }
  416. .display, .display-bottom {
  417. /* 显示号码的输入框,宽度占满父容器,高度 80px,背景色为白色,带有灰色边框 */
  418. width: 100%;
  419. height: 80px;
  420. background-color: white;
  421. border: 1px solid #e0e0e0;
  422. display: flex;
  423. align-items: center;
  424. justify-content: space-between;
  425. padding:0 30px 0 10px; /* 调整左侧 padding,增加右侧 padding */
  426. margin-bottom: 0;
  427. transition: all 0.2s;
  428. position: fixed;
  429. z-index: 3;
  430. }
  431. .display {
  432. /* 控制输入框初始显示位置 */
  433. bottom: 420px;
  434. }
  435. .display-bottom {
  436. /* 切换后,输入框移动到更低位置 */
  437. bottom: 100px;
  438. z-index: 4;
  439. }
  440. .phone-number {
  441. /* 电话号码文本的样式,字体大小为 46px */
  442. font-size: 40px;
  443. transform: translateX(20px);
  444. }
  445. .delete {
  446. /* 删除按钮的样式,指定按钮的宽度和高度 */
  447. height: 100rpx;
  448. width: 80rpx;
  449. margin-left: -10rpx;
  450. }
  451. .dial-pad {
  452. /* 数字键盘区域的样式,宽度占满父容器,背景色为浅灰色,固定定位,覆盖在容器底部 */
  453. width: 100%;
  454. background-color: #f7f7f7;
  455. padding-bottom: 0;
  456. position: fixed;
  457. bottom: 100px;
  458. left: 0;
  459. right: 0;
  460. z-index: 3;
  461. }
  462. .row {
  463. /* 数字键盘每一行的样式,使用 flex 布局,使数字按钮在行内均匀分布 */
  464. display: flex;
  465. justify-content: space-around;
  466. transition: all 0.3s;
  467. }
  468. .digit-button {
  469. /* 数字按钮的样式 */
  470. width: 33%;
  471. height: 80px;
  472. background-color: #ecfff3;
  473. border-radius: 0; /* 去掉圆角 */
  474. color: black;
  475. font-size: 50px;
  476. border: 0px solid #d0dddd; /* 边框更细更浅 */
  477. display: flex;
  478. align-items: center;
  479. justify-content: center;
  480. z-index: 3;
  481. }
  482. /* "*"和"#"单独处理 */
  483. .star-icon {
  484. width: 50px; /* 根据需要调整图片大小 */
  485. height: 50px; /* 根据需要调整图片大小 */
  486. }
  487. .hash-button {
  488. font-size: 43px; /* 调整 "#" 的字体大小,使其比其他数字小 */
  489. }
  490. .toggle-button {
  491. /* 切换按钮的样式,宽度占 30%,固定在底部,高度为 100px,靠右对齐 */
  492. width: 33.2%;
  493. height: 100px;
  494. display: flex;
  495. justify-content: center;
  496. align-items: center;
  497. transition: all 0.2s;
  498. position: fixed;
  499. bottom: 0px;
  500. right: 0; /* 右对齐 */
  501. z-index: 4;
  502. background-color: #ecfff3;
  503. }
  504. .action {
  505. display: flex;
  506. position: fixed;
  507. bottom: 0;
  508. width: 66.7%; /* 确保整个区域占满屏幕宽度 */
  509. left: 0;
  510. padding: 0;
  511. z-index: 4;
  512. }
  513. .CallIcon {
  514. /* 拨打图标的样式,指定图标的宽度和高度 */
  515. width: 60px;
  516. height: 60px;
  517. z-index: 4;
  518. position: absolute; /* 使用绝对定位使图标在按钮内居中 */
  519. top: 50%; /* 垂直居中 */
  520. left: 50%; /* 水平居中 */
  521. transform: translate(-50%, -50%); /* 将图标移动到按钮的中心位置 */
  522. }
  523. .single-sim {
  524. width: 100%; /* 占满整个 .action 容器的宽度 */
  525. height: 100px; /* 设置按钮高度 */
  526. background-color: #ecfff3; /* 按钮背景颜色 */
  527. color: #A9A9A9;
  528. font-size: 36px;
  529. display: flex;
  530. align-items: center;
  531. justify-content: center;
  532. z-index: 4;
  533. position: relative;
  534. }
  535. .scale {
  536. /* 切换按钮的样式,背景色为浅绿色,内容居中对齐 */
  537. width: 100%;
  538. height: 100px;
  539. display: flex;
  540. background-color: #ecfff3;
  541. align-items: center;
  542. justify-content: center;
  543. }
  544. .ScaleIcon {
  545. /* 切换图标的样式,指定图标的宽度和高度 */
  546. width: 80px;
  547. height: 80px;
  548. }
  549. .matched-contacts {
  550. /* 匹配联系人列表的样式,宽度占满父容器,背景色为白色,带有浅灰色边框,高度占视口的 70%,支持垂直滚动 */
  551. width: 100%;
  552. background-color: #ffffff;
  553. border: 1px solid #ccc;
  554. height: 73vh; /* 设置为视口高度的 70% */
  555. overflow-y: auto;
  556. z-index: 3;
  557. position: fixed; /* 固定定位 */
  558. top: -10px; /* 距离顶部的距离,根据需要调整 */
  559. left: 0; /* 确保在左侧对齐 */
  560. }
  561. .contact-item {
  562. display: flex;
  563. justify-content: space-between; /* 使两个部分分别靠左和靠右 */
  564. align-items: center;
  565. height: 80px;
  566. padding: 10px;
  567. border-bottom: 1px solid #ccc;
  568. }
  569. /* 匹配部分用红色渲染 */
  570. .highlight {
  571. color: red;
  572. font-weight: bold; /* Optional: Make the highlighted text bold */
  573. }
  574. .contact-name {
  575. font-size: 36px;
  576. margin-right: 10px;
  577. max-width: 40%; /* 左侧区域占 40% 宽度 */
  578. white-space: normal;
  579. word-break: break-word;
  580. overflow: hidden;
  581. text-overflow: ellipsis;
  582. display: -webkit-box;
  583. -webkit-box-orient: vertical;
  584. -webkit-line-clamp: 2; /* 限制为两行显示 */
  585. }
  586. /* .contact-name > span {
  587. display: inline-block;
  588. width: 100%;
  589. text-align: center; /* 第二行居中对齐 */
  590. .contact-number {
  591. font-size: 26px;
  592. max-width: 80%;
  593. min-width: 0;
  594. white-space: nowrap; /* 禁止换行 */
  595. overflow: hidden;
  596. text-overflow: ellipsis;
  597. display: inline; /* 确保在一行内 */
  598. }
  599. .contact-number span {
  600. display: inline; /* 确保高亮部分在一行内 */
  601. }
  602. .contact-number .highlight {
  603. color: red; /* 高亮颜色 */
  604. font-weight: bold; /* Optional: Make the highlighted text bold */
  605. }
  606. .contact-number .line-1, .contact-number .line-2 {
  607. display: inline-block;
  608. width: 100%;
  609. text-align: right; /* 保持右对齐 */
  610. overflow: hidden;
  611. text-overflow: ellipsis;
  612. }
  613. </style>