diff --git a/package.json b/package.json index 2a76fbd9..68f3e363 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,10 @@ "pinia-plugin-persist-uni": "^1.2.0", "vue": "^3.4.21", "vue-i18n": "^9.1.9", - "weixin-js-sdk": "^1.6.0" + "weixin-js-sdk": "^1.6.0", + "pinyin-pro": "^3.23.1", + "quill": "^1.3.7", + "quill-image-resize-mp": "^3.0.1" }, "devDependencies": { "@dcloudio/types": "^3.4.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb150ab9..92ab08b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,15 @@ importers: pinia-plugin-persist-uni: specifier: ^1.2.0 version: 1.3.1(pinia@2.3.1(typescript@4.9.5)(vue@3.5.25(typescript@4.9.5)))(vue@3.5.25(typescript@4.9.5)) + pinyin-pro: + specifier: ^3.23.1 + version: 3.27.0 + quill: + specifier: ^1.3.7 + version: 1.3.7 + quill-image-resize-mp: + specifier: ^3.0.1 + version: 3.0.1 vue: specifier: ^3.4.21 version: 3.5.25(typescript@4.9.5) @@ -1926,6 +1935,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -1970,6 +1983,10 @@ packages: cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -2126,6 +2143,10 @@ packages: dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + deep-equal@1.1.2: + resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} + engines: {node: '>= 0.4'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -2134,6 +2155,14 @@ packages: resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} engines: {node: '>= 10'} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -2270,6 +2299,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + eventemitter3@2.0.3: + resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -2292,6 +2324,12 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-diff@1.1.2: + resolution: {integrity: sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -2369,6 +2407,9 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + generic-names@4.0.0: resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} @@ -2430,6 +2471,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -2515,6 +2559,10 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -2526,6 +2574,10 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2552,6 +2604,10 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2989,6 +3045,14 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} @@ -3028,6 +3092,9 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parchment@1.1.4: + resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} + parse-bmfont-ascii@1.0.6: resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} @@ -3123,6 +3190,9 @@ packages: typescript: optional: true + pinyin-pro@3.27.0: + resolution: {integrity: sha512-Osdgjwe7Rm17N2paDMM47yW+jUIUH3+0RGo8QP39ZTLpTaJVDK0T58hOLaMQJbcMmAebVuK2ePunTEVEx1clNQ==} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -3255,6 +3325,16 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quill-delta@3.6.3: + resolution: {integrity: sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==} + engines: {node: '>=0.10'} + + quill-image-resize-mp@3.0.1: + resolution: {integrity: sha512-etBtUbNH3XY21PVwfclMNGkifjWKSVXei4Dc4GWVeUyxOoympcNaV+Bk7Ocjc6Y8VlDKs2tcf3zyJ9syIFL/KA==} + + quill@1.3.7: + resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3286,6 +3366,10 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + regexpu-core@6.4.0: resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} engines: {node: '>=4'} @@ -3379,6 +3463,14 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -6640,6 +6732,13 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6688,6 +6787,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone@2.1.2: {} + co@4.6.0: {} collect-v8-coverage@1.0.3: {} @@ -6801,12 +6902,33 @@ snapshots: dedent@0.7.0: {} + deep-equal@1.1.2: + dependencies: + is-arguments: 1.2.0 + is-date-object: 1.1.0 + is-regex: 1.2.1 + object-is: 1.1.6 + object-keys: 1.1.1 + regexp.prototype.flags: 1.5.4 + deepmerge@4.3.1: {} default-gateway@6.0.3: dependencies: execa: 5.1.1 + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + defu@6.1.4: {} delayed-stream@1.0.0: {} @@ -6926,6 +7048,8 @@ snapshots: etag@1.8.1: {} + eventemitter3@2.0.3: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -6987,6 +7111,10 @@ snapshots: exsolve@1.0.8: {} + extend@3.0.2: {} + + fast-diff@1.1.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7061,6 +7189,8 @@ snapshots: function-bind@1.1.2: {} + functions-have-names@1.2.3: {} + generic-names@4.0.0: dependencies: loader-utils: 3.3.1 @@ -7123,6 +7253,10 @@ snapshots: has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -7200,6 +7334,11 @@ snapshots: ipaddr.js@1.9.1: {} + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-arrayish@0.2.1: {} is-binary-path@2.1.0: @@ -7210,6 +7349,11 @@ snapshots: dependencies: hasown: 2.0.2 + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -7226,6 +7370,13 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + is-stream@2.0.1: {} is-typedarray@1.0.0: {} @@ -7868,6 +8019,13 @@ snapshots: object-inspect@1.13.4: {} + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + ofetch@1.5.1: dependencies: destr: 2.0.5 @@ -7906,6 +8064,8 @@ snapshots: pako@1.0.11: {} + parchment@1.1.4: {} + parse-bmfont-ascii@1.0.6: {} parse-bmfont-binary@1.0.6: {} @@ -7986,6 +8146,8 @@ snapshots: transitivePeerDependencies: - '@vue/composition-api' + pinyin-pro@3.27.0: {} + pirates@4.0.7: {} pixelmatch@4.0.2: @@ -8115,6 +8277,25 @@ snapshots: queue-microtask@1.2.3: {} + quill-delta@3.6.3: + dependencies: + deep-equal: 1.1.2 + extend: 3.0.2 + fast-diff: 1.1.2 + + quill-image-resize-mp@3.0.1: + dependencies: + lodash: 4.17.21 + + quill@1.3.7: + dependencies: + clone: 2.1.2 + deep-equal: 1.1.2 + eventemitter3: 2.0.3 + extend: 3.0.2 + parchment: 1.1.4 + quill-delta: 3.6.3 + range-parser@1.2.1: {} raw-body@2.5.2: @@ -8144,6 +8325,15 @@ snapshots: regenerator-runtime@0.14.1: {} + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + regexpu-core@6.4.0: dependencies: regenerate: 1.4.2 @@ -8266,6 +8456,22 @@ snapshots: transitivePeerDependencies: - supports-color + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + setprototypeof@1.2.0: {} shebang-command@2.0.0: diff --git a/src/.env.js b/src/.env.js new file mode 100644 index 00000000..c9e42cfd --- /dev/null +++ b/src/.env.js @@ -0,0 +1,25 @@ +//设置环境(打包前修改此变量) +const ENV = "DEV"; +const UNI_APP = {}; +// 表情包路径 +UNI_APP.EMO_URL = "/static/emoji/"; +// #ifdef MP-WEIXIN +// 微信小程序的本地表情包经常莫名失效,建议将表情放到服务器中 +// UNI_APP.EMO_URL = "https://www.boxim.online/emoji/"; +// #endif + +if (ENV == "DEV") { + UNI_APP.BASE_URL = "http://chatsever.zianxin.com"; + // UNI_APP.BASE_URL = "https://www.boxim.online/api"; + UNI_APP.WS_URL = "ws://chatapp.zianxin.com/im"; + // UNI_APP.WS_URL = "wss://www.boxim.online/im"; + // H5 走本地代理解决跨域问题 + // #ifdef H5 + UNI_APP.BASE_URL = "/api"; + // #endif +} +if (ENV == "PROD") { + UNI_APP.BASE_URL = "https://www.boxim.online/api"; + UNI_APP.WS_URL = "wss://www.boxim.online/im"; +} +export default UNI_APP; diff --git a/src/App.vue b/src/App.vue index 1c8cb5d6..b10ac82c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,47 +1,615 @@ - diff --git a/src/common/date.js b/src/common/date.js new file mode 100644 index 00000000..cd4fc17f --- /dev/null +++ b/src/common/date.js @@ -0,0 +1,66 @@ +let toTimeText = (timeStamp, simple) => { + var dateTime = new Date(timeStamp) + var currentTime = Date.parse(new Date()); //当前时间 + var timeDiff = currentTime - dateTime; //与当前时间误差 + var timeText = ''; + if (timeDiff <= 60000) { //一分钟内 + timeText = '刚刚'; + } else if (timeDiff > 60000 && timeDiff < 3600000) { + //1小时内 + timeText = Math.floor(timeDiff / 60000) + '分钟前'; + } else if (timeDiff >= 3600000 && timeDiff < 86400000 && !isYestday(dateTime)) { + //今日 + timeText = formatDateTime(dateTime).substr(11, 5); + } else if (isYestday(dateTime)) { + //昨天 + timeText = '昨天' + formatDateTime(dateTime).substr(11, 5); + } else if (isYear(dateTime)) { + //今年 + timeText = formatDateTime(dateTime).substr(5, simple ? 5 : 14); + } else { + //不属于今年 + timeText = formatDateTime(dateTime); + if (simple) { + timeText = timeText.substr(2, 8); + } + } + return timeText; +} + +let isYestday = (date) => { + var yesterday = new Date(new Date() - 1000 * 60 * 60 * 24); + return yesterday.getYear() === date.getYear() && + yesterday.getMonth() === date.getMonth() && + yesterday.getDate() === date.getDate(); +} + +let isYear = (date) => { + return date.getYear() === new Date().getYear(); +} + +let formatDateTime = (date) => { + if (date === '' || !date) { + return '' + } + var dateObject = new Date(date) + var y = dateObject.getFullYear() + var m = dateObject.getMonth() + 1 + m = m < 10 ? ('0' + m) : m + var d = dateObject.getDate() + d = d < 10 ? ('0' + d) : d + var h = dateObject.getHours() + h = h < 10 ? ('0' + h) : h + var minute = dateObject.getMinutes() + minute = minute < 10 ? ('0' + minute) : minute + var second = dateObject.getSeconds() + second = second < 10 ? ('0' + second) : second + return y + '/' + m + '/' + d + ' ' + h + ':' + minute + ':' + second +} + + +export { + toTimeText, + isYestday, + isYear, + formatDateTime +} \ No newline at end of file diff --git a/src/common/emotion.js b/src/common/emotion.js new file mode 100644 index 00000000..f7dfe374 --- /dev/null +++ b/src/common/emotion.js @@ -0,0 +1,43 @@ +import UNI_APP from '@/.env.js' + + +const emoTextList = ['憨笑', '媚眼', '开心', '坏笑', '可怜', '爱心', '笑哭', '拍手', '惊喜', '打气', + '大哭', '流泪', '饥饿', '难受', '健身', '示爱', '色色', '眨眼', '暴怒', '惊恐', + '思考', '头晕', '大吐', '酷笑', '翻滚', '享受', '鼻涕', '快乐', '雀跃', '微笑', + '贪婪', '红心', '粉心', '星星', '大火', '眼睛', '音符', "叹号", "问号", "绿叶", + "燃烧", "喇叭", "警告", "信封", "房子", "礼物", "点赞", "举手", "拍手", "点头", + "摇头", "偷瞄", "庆祝", "疾跑", "打滚", "惊吓", "起跳" +]; + +const regex = /\#[\u4E00-\u9FA5]{1,3}\;/gi; + +let containEmoji = (content) => { + return regex.test(content) +} + +let transform = (content, extClass) => { + return content.replace(regex, (emoText)=>{ + // 将匹配结果替换表情图片 + let word = emoText.replace(/\#|\;/gi, ''); + let idx = emoTextList.indexOf(word); + if (idx == -1) { + return emoText; + } + let path = textToPath(emoText); + let img = ``; + return img; + }); +} + +let textToPath = (emoText) => { + let word = emoText.replace(/\#|\;/gi, ''); + let idx = emoTextList.indexOf(word); + return UNI_APP.EMO_URL + idx + ".gif"; +} + +export default { + containEmoji, + emoTextList, + transform, + textToPath +} \ No newline at end of file diff --git a/src/common/enums.js b/src/common/enums.js new file mode 100644 index 00000000..fa7ada08 --- /dev/null +++ b/src/common/enums.js @@ -0,0 +1,70 @@ + +const MESSAGE_TYPE = { + TEXT: 0, + IMAGE: 1, + FILE: 2, + AUDIO: 3, + VIDEO: 4, + RECALL: 10, + READED: 11, + RECEIPT: 12, + TIP_TIME: 20, + TIP_TEXT: 21, + LOADING: 30, + ACT_RT_VOICE: 40, + ACT_RT_VIDEO: 41, + USER_BANNED: 50, + FRIEND_NEW: 80, + FRIEND_DEL: 81, + FRIEND_DND: 82, + GROUP_NEW: 90, + GROUP_DEL: 91, + GROUP_DND: 92, + RTC_CALL_VOICE: 100, + RTC_CALL_VIDEO: 101, + RTC_ACCEPT: 102, + RTC_REJECT: 103, + RTC_CANCEL: 104, + RTC_FAILED: 105, + RTC_HANDUP: 106, + RTC_CANDIDATE: 107, + RTC_GROUP_SETUP: 200, + RTC_GROUP_ACCEPT: 201, + RTC_GROUP_REJECT: 202, + RTC_GROUP_FAILED: 203, + RTC_GROUP_CANCEL: 204, + RTC_GROUP_QUIT: 205, + RTC_GROUP_INVITE: 206, + RTC_GROUP_JOIN: 207, + RTC_GROUP_OFFER: 208, + RTC_GROUP_ANSWER: 209, + RTC_GROUP_CANDIDATE: 210, + RTC_GROUP_DEVICE: 211 +} + +const USER_STATE = { + OFFLINE: 0, + FREE: 1, + BUSY: 2 +} + +const TERMINAL_TYPE = { + WEB: 0, + APP: 1 +} + +const MESSAGE_STATUS = { + FAILED: -2, // 发送失败 + SENDING: -1, // 发送中(消息没到服务器) + PENDING: 0, // 未送达(消息已到服务器,但对方没收到) + DELIVERED: 1, // 已送达(对方已收到,但是未读消息) + RECALL: 2, // 已撤回 + READED: 3, // 消息已读 +} + +export { + MESSAGE_TYPE, + USER_STATE, + TERMINAL_TYPE, + MESSAGE_STATUS +} diff --git a/src/common/messageType.js b/src/common/messageType.js new file mode 100644 index 00000000..f6b1cafc --- /dev/null +++ b/src/common/messageType.js @@ -0,0 +1,40 @@ + +// 是否普通消息 +let isNormal = function (type) { + return type >= 0 && type < 10; +} + +// 是否状态消息 +let isStatus = function (type) { + return type >= 10 && type < 20; +} + +// 是否提示消息 +let isTip = function (type) { + return type >= 20 && type < 30; +} + +// 操作交互类消息 +let isAction = function (type) { + return type >= 40 && type < 50; +} + +// 单人通话信令 +let isRtcPrivate = function (type) { + return type >= 100 && type < 200; +} + +// 多人通话信令 +let isRtcGroup = function (type) { + return type >= 200 && type < 300; +} + + +export { + isNormal, + isStatus, + isTip, + isAction, + isRtcPrivate, + isRtcGroup +} \ No newline at end of file diff --git a/src/common/recorder-app.js b/src/common/recorder-app.js new file mode 100644 index 00000000..f114bbb3 --- /dev/null +++ b/src/common/recorder-app.js @@ -0,0 +1,67 @@ +import UNI_APP from '@/.env.js'; + +const rc = uni.getRecorderManager(); +// 录音开始时间 +let startTime = null; +let checkIsEnable = ()=>{ + return true; +} + +let start = () => { + return new Promise((resolve, reject) => { + rc.onStart(() => { + startTime = new Date(); + resolve() + }); + rc.onError((e) => { + console.log(e); + reject(e) + }) + rc.start({ + format: 'mp3' // 录音格式,可选值:aac/mp3 + }); + }) +} + +let close = () => { + rc.stop(); +} + +let upload = () => { + return new Promise((resolve, reject) => { + rc.onStop((wavFile, a, b) => { + uni.uploadFile({ + url: UNI_APP.BASE_URL + '/file/upload', + header: { + accessToken: uni.getStorageSync("loginInfo").accessToken + }, + filePath: wavFile.tempFilePath, + name: 'file', + success: (res) => { + let r = JSON.parse(res.data); + if (r.code != 200) { + reject(r.message); + } else { + const duration = (new Date().getTime() - startTime.getTime()) / + 1000 + const data = { + duration: Math.round(duration), + url: r.data + } + resolve(data); + } + }, + fail: (e) => { + reject(e); + } + }) + }); + }) +} + +export { + checkIsEnable, + start, + close, + upload +} \ No newline at end of file diff --git a/src/common/recorder-h5.js b/src/common/recorder-h5.js new file mode 100644 index 00000000..38559426 --- /dev/null +++ b/src/common/recorder-h5.js @@ -0,0 +1,92 @@ +import UNI_APP from '@/.env.js'; + +let rc = null; +let duration = 0; +let chunks = []; +let stream = null; +let startTime = null; +let checkIsEnable = () => { + if (origin.indexOf('https') === -1 && origin.indexOf('localhost') === -1 && + origin.indexOf('127.0.0.1') === -1) { + uni.showToast({ + title: '请在https环境中使用录音功能', + icon: 'error' + }) + return false; + } + if (!navigator.mediaDevices || !window.MediaRecorder) { + uni.showToast({ + title: '当前浏览器不支持录音', + icon: 'error' + }) + return false; + } + return true; +} + +let start = () => { + return navigator.mediaDevices.getUserMedia({ audio: true }).then(audioStream => { + startTime = new Date().getTime(); + chunks = []; + stream = audioStream; + rc = new MediaRecorder(stream) + rc.start() + }) +} + +let close = () => { + stream.getTracks().forEach((track) => { + track.stop() + }) + rc.stop() +} + + +let upload = () => { + return new Promise((resolve, reject) => { + rc.ondataavailable = (e) => { + chunks.push(e.data) + } + rc.onstop = () => { + if(!chunks[0].size){ + chunks = []; + return; + } + duration = (new Date().getTime() - startTime) / 1000; + const newbolb = new Blob(chunks, { 'type': 'audio/mpeg' }); + const name = new Date().getDate() + '.mp3'; + const file = new File([newbolb], name) + uni.uploadFile({ + url: UNI_APP.BASE_URL + '/file/upload', + header: { + accessToken: uni.getStorageSync("loginInfo").accessToken + }, + file: file, + name: 'file', + success: (res) => { + let r = JSON.parse(res.data); + if (r.code != 200) { + console.log(res) + reject(r.message); + } else { + const data = { + duration: parseInt(duration), + url: r.data + } + resolve(data); + } + }, + fail: (e) => { + reject(e); + } + }) + } + }) +} + +export { + checkIsEnable, + start, + close, + upload +} \ No newline at end of file diff --git a/src/common/request.js b/src/common/request.js new file mode 100644 index 00000000..83f8e429 --- /dev/null +++ b/src/common/request.js @@ -0,0 +1,92 @@ +import UNI_APP from '@/.env.js'; + +// 请求队列 +let requestList = []; +// 是否正在刷新中 +let isRefreshToken = false; + +const request = (options) => { + const header = options.header || {}; + const loginInfo = uni.getStorageSync('loginInfo'); + if (loginInfo) { + header.accessToken = loginInfo.accessToken; + } + return new Promise(function (resolve, reject) { + uni.request({ + url: UNI_APP.BASE_URL + options.url, + // url: UNI_APP.BASE_URL + options.url, + method: options.method || 'GET', + header: header, + data: options.data || {}, + timeout: options.timeout || 3000, + async success(res) { + if (res.data.code == 200) { + return resolve(res.data.data); + } else if (res.data.code == 400) { + getApp().$vm.exit(); + } else if (res.data.code == 401) { + console.log('token失效,尝试重新获取'); + if (isRefreshToken) { + // 正在刷新token,把其他请求存起来 + requestList.push(() => { + resolve(request(options)); + }); + return; + } + isRefreshToken = true; + // 发送请求, 进行刷新token操作, 获取新的token + const res = await reqRefreshToken(loginInfo); + if (!res || res.data.code != 200) { + requestList = []; + isRefreshToken = false; + console.log('刷新token失败'); + getApp().$vm.exit(); + return; + } + let newInfo = res.data.data; + uni.setStorageSync('loginInfo', newInfo); + requestList.forEach((cb) => cb()); + requestList = []; + isRefreshToken = false; + // 重新发送刚才的请求 + return resolve(request(options)); + } else { + uni.showToast({ + icon: 'none', + title: res.data.message, + duration: 1500, + }); + return reject(res.data); + } + }, + fail(error) { + uni.showToast({ + title: '网络似乎有点不给力哟', + icon: 'none', + duration: 1500, + }); + return reject(error); + }, + }); + }); +}; + +const reqRefreshToken = (loginInfo) => { + return new Promise(function (resolve, reject) { + uni.request({ + method: 'PUT', + url: UNI_APP.BASE_URL + '/refreshToken', + header: { + refreshToken: loginInfo.refreshToken, + }, + success: (res) => { + resolve(res); + }, + fail: (res) => { + reject(res); + }, + }); + }); +}; + +export default request; diff --git a/src/common/str.js b/src/common/str.js new file mode 100644 index 00000000..3f6de923 --- /dev/null +++ b/src/common/str.js @@ -0,0 +1,14 @@ +let html2Escape = (strText) => { + return strText.replace(/[<>&"]/g, function(c) { + return { + '<': '<', + '>': '>', + '&': '&', + '"': '"' + } [c]; + }); +} + +export default { + html2Escape +} \ No newline at end of file diff --git a/src/common/url.js b/src/common/url.js new file mode 100644 index 00000000..c95d8341 --- /dev/null +++ b/src/common/url.js @@ -0,0 +1,23 @@ + +// 使用正则表达式匹配更广泛的URL格式(此正则由deepseek生成) +const regex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]|\bwww\.[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + +let containUrl = (content) => { + return regex.test(content) +} + + +let replaceURLWithHTMLLinks = (content, color) => { + return content.replace(regex, (url) => { + // 如果URL不以http(s)://开头,则添加http://前缀 + if (!url.startsWith("http")) { + url = "http://" + url; + } + return `${url}`; + }); +} + +export default { + containUrl, + replaceURLWithHTMLLinks +} \ No newline at end of file diff --git a/src/common/wssocket.js b/src/common/wssocket.js new file mode 100644 index 00000000..5747dc55 --- /dev/null +++ b/src/common/wssocket.js @@ -0,0 +1,154 @@ +let accessToken = ""; +let messageCallBack = null; +let closeCallBack = null; +let connectCallBack = null; +let isConnect = false; //连接标识 避免重复连接 +let rec = null; +let lastConnectTime = new Date(); // 最后一次连接时间 +let socketTask = null; + +let connect = (wsurl, token) => { + accessToken = token; + if (isConnect) { + return; + } + lastConnectTime = new Date(); + socketTask = uni.connectSocket({ + url: wsurl, + success: (res) => {}, + fail: (e) => { + console.log(e); + console.log("WebSocket连接失败,10s后重连"); + setTimeout(() => { + connect(); + }, 10000) + } + }); + + socketTask.onOpen((res) => { + console.log("WebSocket连接成功"); + isConnect = true; + // 发送登录命令 + let loginInfo = { + cmd: 0, + data: { + accessToken: accessToken + } + }; + socketTask.send({ + data: JSON.stringify(loginInfo) + }); + }) + + socketTask.onMessage((res) => { + let sendInfo = JSON.parse(res.data) + if (sendInfo.cmd == 0) { + heartCheck.start() + connectCallBack && connectCallBack(); + console.log('WebSocket登录成功') + } else if (sendInfo.cmd == 1) { + // 重新开启心跳定时 + heartCheck.reset(); + } else { + // 其他消息转发出去 + console.log("接收到消息", sendInfo); + messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data) + } + }) + + socketTask.onClose((res) => { + console.log('WebSocket连接关闭') + isConnect = false; + closeCallBack && closeCallBack(res); + }) + + socketTask.onError(e => { + console.log("ws错误:", e) + close(1006); + isConnect = false; + closeCallBack && closeCallBack(e); + }) +} + +//定义重连函数 +let reconnect = (wsurl, accessToken) => { + console.log("尝试重新连接"); + if (isConnect) { + return; + } + // 延迟10秒重连 避免过多次过频繁请求重连 + let timeDiff = new Date().getTime() - lastConnectTime.getTime() + let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; + rec && clearTimeout(rec); + rec = setTimeout(function() { + connect(wsurl, accessToken); + }, delay); +}; + +//设置关闭连接 +let close = (code) => { + if (!isConnect) { + return; + } + socketTask.close({ + code: code, + complete: (res) => { + isConnect = false; + console.log("关闭websocket连接"); + }, + fail: (e) => { + console.log("关闭websocket连接失败", e); + } + }); +}; + + +// 心跳设置 +let heartCheck = { + timeout: 20000, //每段时间发送一次心跳包 这里设置为20s + timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) + start: function() { + if (isConnect) { + console.log('发送WebSocket心跳') + let heartBeat = { + cmd: 1, + data: {} + }; + sendMessage(JSON.stringify(heartBeat)) + } + }, + reset: function() { + clearTimeout(this.timeoutObj); + this.timeoutObj = setTimeout(() => heartCheck.start(), this.timeout); + } +}; + +let sendMessage = (message) => { + socketTask.send({ data: message }) +} + +let onConnect = (callback) => { + connectCallBack = callback; +} + + +let onMessage = (callback) => { + messageCallBack = callback; +} + + +let onClose = (callback) => { + closeCallBack = callback; +} + + +// 将方法暴露出去 +export { + connect, + reconnect, + close, + sendMessage, + onConnect, + onMessage, + onClose +} \ No newline at end of file diff --git a/src/components/bar/arrow-bar.vue b/src/components/bar/arrow-bar.vue new file mode 100644 index 00000000..6ecf1891 --- /dev/null +++ b/src/components/bar/arrow-bar.vue @@ -0,0 +1,64 @@ + + + + {{ title }} + + + + + + + \ No newline at end of file diff --git a/src/components/bar/bar-group.vue b/src/components/bar/bar-group.vue new file mode 100644 index 00000000..570965f6 --- /dev/null +++ b/src/components/bar/bar-group.vue @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/components/bar/btn-bar.vue b/src/components/bar/btn-bar.vue new file mode 100644 index 00000000..cc9408ce --- /dev/null +++ b/src/components/bar/btn-bar.vue @@ -0,0 +1,68 @@ + + + + {{ title }} + + + + + + \ No newline at end of file diff --git a/src/components/bar/switch-bar.vue b/src/components/bar/switch-bar.vue new file mode 100644 index 00000000..3616229d --- /dev/null +++ b/src/components/bar/switch-bar.vue @@ -0,0 +1,61 @@ + + + {{ title }} + + + + + + + \ No newline at end of file diff --git a/src/components/chat-at-box/chat-at-box.vue b/src/components/chat-at-box/chat-at-box.vue new file mode 100644 index 00000000..8f7c2578 --- /dev/null +++ b/src/components/chat-at-box/chat-at-box.vue @@ -0,0 +1,173 @@ + + + + + 选择要提醒的人 + 清空 + 确定({{ atUserIds.length }}) + + + + + + + + + + + + + + + + + + {{ item.showNickName }} + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/chat-group-readed/chat-group-readed.vue b/src/components/chat-group-readed/chat-group-readed.vue new file mode 100644 index 00000000..9e2d7d68 --- /dev/null +++ b/src/components/chat-group-readed/chat-group-readed.vue @@ -0,0 +1,133 @@ + + + + + + + + + + + + + {{ item.showNickName }} + + + + + + + + + + {{ item.showNickName }} + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/chat-item/chat-item.vue b/src/components/chat-item/chat-item.vue new file mode 100644 index 00000000..94777c44 --- /dev/null +++ b/src/components/chat-item/chat-item.vue @@ -0,0 +1,202 @@ + + + + + + + + + + + {{ chat.showName }} + + {{ $date.toTimeText(chat.lastSendTime, true) }} + + + {{ atText }} + {{ + chat.sendNickName + ': ' + }} + {{ chat.lastContent }} + + + + + + + + + + + diff --git a/src/components/chat-message-item/chat-message-item.vue b/src/components/chat-message-item/chat-message-item.vue new file mode 100644 index 00000000..dbcecc32 --- /dev/null +++ b/src/components/chat-message-item/chat-message-item.vue @@ -0,0 +1,546 @@ + + + + {{ msgInfo.content }} + + + {{ $date.toTimeText(msgInfo.sendTime) }} + + + + + + {{ showName }} + + + + + + + + + + + + + + + + + + + + + + + + + + {{ fileSize }} + + + + + + + + + + {{ contentData.duration + '"' }} + + + + + + + + + {{ msgInfo.content }} + + + + + + + + + 已读 + 未读 + + + + {{ msgInfo.readedCount }}人已读 + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/chat-record/chat-record.vue b/src/components/chat-record/chat-record.vue new file mode 100644 index 00000000..cb390d8e --- /dev/null +++ b/src/components/chat-record/chat-record.vue @@ -0,0 +1,243 @@ + + + + {{ recording ? '正在录音' : '长按 说话' }} + + + + + + + + + + + {{ recordTip }} + + + + {{ moveToCancel ? '松手取消' : '松手发送,上划取消' }} + + + + + + + + \ No newline at end of file diff --git a/src/components/file-upload/file-upload.vue b/src/components/file-upload/file-upload.vue new file mode 100644 index 00000000..c364ffd5 --- /dev/null +++ b/src/components/file-upload/file-upload.vue @@ -0,0 +1,122 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/friend-item/friend-item.vue b/src/components/friend-item/friend-item.vue new file mode 100644 index 00000000..543ef876 --- /dev/null +++ b/src/components/friend-item/friend-item.vue @@ -0,0 +1,82 @@ + + + + + {{ friend.nickName }} + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/group-item/group-item.vue b/src/components/group-item/group-item.vue new file mode 100644 index 00000000..48a74810 --- /dev/null +++ b/src/components/group-item/group-item.vue @@ -0,0 +1,58 @@ + + + + + {{ group.showGroupName }} + + + + + + + diff --git a/src/components/group-member-selector/group-member-selector.vue b/src/components/group-member-selector/group-member-selector.vue new file mode 100644 index 00000000..58d9cd8b --- /dev/null +++ b/src/components/group-member-selector/group-member-selector.vue @@ -0,0 +1,180 @@ + + + + + 选择成员 + 清空 + 确定({{ checkedIds.length }}) + + + + + + + + + + + + + + + + + + {{ item.showNickName }} + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/group-rtc-join/group-rtc-join.vue b/src/components/group-rtc-join/group-rtc-join.vue new file mode 100644 index 00000000..6ee2459c --- /dev/null +++ b/src/components/group-rtc-join/group-rtc-join.vue @@ -0,0 +1,88 @@ + + + + + + 发起人 + + + + {{ rtcInfo.userInfos.length + '人正在通话中' }} + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/head-image/head-image.vue b/src/components/head-image/head-image.vue new file mode 100644 index 00000000..9fa8bc28 --- /dev/null +++ b/src/components/head-image/head-image.vue @@ -0,0 +1,129 @@ + + + + + {{ name?.substring(0, 1).toUpperCase() }} + + + + + + + + + \ No newline at end of file diff --git a/src/components/image-upload/image-upload.vue b/src/components/image-upload/image-upload.vue new file mode 100644 index 00000000..74410da8 --- /dev/null +++ b/src/components/image-upload/image-upload.vue @@ -0,0 +1,100 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/components/loading/loading.vue b/src/components/loading/loading.vue new file mode 100644 index 00000000..b0f0c98f --- /dev/null +++ b/src/components/loading/loading.vue @@ -0,0 +1,71 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/components/long-press-menu/long-press-menu.vue b/src/components/long-press-menu/long-press-menu.vue new file mode 100644 index 00000000..22b7c8a2 --- /dev/null +++ b/src/components/long-press-menu/long-press-menu.vue @@ -0,0 +1,129 @@ + + + + + + + + + {{ item.name }} + + + + + + + + \ No newline at end of file diff --git a/src/components/nav-bar/nav-bar.vue b/src/components/nav-bar/nav-bar.vue new file mode 100644 index 00000000..5ec1b65f --- /dev/null +++ b/src/components/nav-bar/nav-bar.vue @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/virtual-scroller/virtual-scroller.vue b/src/components/virtual-scroller/virtual-scroller.vue new file mode 100644 index 00000000..48a99e49 --- /dev/null +++ b/src/components/virtual-scroller/virtual-scroller.vue @@ -0,0 +1,60 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/im-var.scss b/src/im-var.scss new file mode 100644 index 00000000..abd98786 --- /dev/null +++ b/src/im-var.scss @@ -0,0 +1,64 @@ +// 颜色 +$im-color-primary: #3e45d7; +$im-color-primary-light-1: mix(#fff, $im-color-primary, 10%); +$im-color-primary-light-2: mix(#fff, $im-color-primary, 20%); +$im-color-primary-light-3: mix(#fff, $im-color-primary, 30%); +$im-color-primary-light-4: mix(#fff, $im-color-primary, 40%); +$im-color-primary-light-5: mix(#fff, $im-color-primary, 50%); +$im-color-primary-light-6: mix(#fff, $im-color-primary, 60%); +$im-color-primary-light-7: mix(#fff, $im-color-primary, 70%); +$im-color-primary-light-8: mix(#fff, $im-color-primary, 80%); +$im-color-primary-light-9: mix(#fff, $im-color-primary, 90%); +$im-color-primary-dark-1: mix(#000, $im-color-primary, 10%); +$im-color-primary-dark-2: mix(#000, $im-color-primary, 20%); +$im-color-primary-dark-3: mix(#000, $im-color-primary, 30%); +$im-color-primary-dark-4: mix(#000, $im-color-primary, 40%); + +$im-color-success: #18bc37; +$im-color-warning: #f3a73f; +$im-color-danger: #e43d33; +$im-color-info: #8f939c; + +// 文字颜色 +$im-text-color: #000000; +$im-text-color-light: #6a6a6a; +$im-text-color-lighter: #909399; +$im-text-color-lighter-extra: #c7c7c7; + +// 边框颜色 +$im-border: #F0F0F0; +$im-border-light: #EDEDED; +$im-border-lighter: #DCDCDC; +$im-border-lighter-extra: #B9B9B9; + +// 文字大小 +$im-font-size: 30rpx; +$im-font-size-small: 28rpx; +$im-font-size-smaller: 26rpx; +$im-font-size-smaller-extra: 24rpx; +$im-font-size-large: 32rpx; +$im-font-size-larger: 34rpx; +$im-font-size-larger-extra: 36rpx; + +// 阴影 +$im-box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); +$im-box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1); +$im-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .12); +$im-box-shadow-dark: 0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 背景 +$im-bg: #f8f9ff; +$im-bg-linear: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%); +$im-bg-active: #f7f8fc; +$im-bg-active-dark: $im-color-primary-light-9; +$im-bg-input: #fafafa; + + +// 标题 +$im-title-size: 26px; +$im-title-size-1: 22px; +$im-title-size-2: 18px; + +$font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; + +$im-nav-bar-height: 50px; \ No newline at end of file diff --git a/src/im.scss b/src/im.scss new file mode 100644 index 00000000..f0634726 --- /dev/null +++ b/src/im.scss @@ -0,0 +1,258 @@ +/** 原生button样式 **/ +uni-button { + font-size: $im-font-size !important; + border-radius: 40rpx !important; +} +uni-button[type='primary'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-primary, lighten($im-color-primary, 15%)) !important; +} + +uni-button[type='primary'][plain] { + color: $im-color-primary !important; + border: 1px solid $im-color-primary; + background-color: transparent; +} + +uni-button[type='warn'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-danger, lighten($im-color-danger, 15%)) !important; +} + +uni-button[type='warn'][plain] { + color: $im-color-danger !important; + border: 1px solid $im-color-danger !important; + background: transparent !important; +} + +uni-button[size='mini'] { + font-size: $im-font-size-smaller !important; +} + +// #ifdef MP-WEIXIN +// wx小程序只有button,没有uni-botton +button { + font-size: $im-font-size !important; +} + +button[type='primary'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-primary, lighten($im-color-primary, 15%)) !important; +} + +button[type='primary'][plain] { + color: $im-color-primary !important; + border: 2px solid $im-color-primary; + background: transparent; +} + +button[type='warn'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-danger, lighten($im-color-danger, 15%)) !important; +} + +button[type='warn'][plain] { + color: $im-color-danger !important; + border: 1px solid $im-color-danger !important; + background: transparent !important; +} + +button[size='mini'] { + font-size: $im-font-size-smaller !important; +} +// #endif + +.button-hover[type='primary'] { + color: #fff !important; + background-color: $im-color-primary-dark-1 !important; +} + +/** uni-ui input激活后边框、图标颜色 **/ +.uni-easyinput__content.is-focused:not(.is-input-error-border) { + border-color: $im-color-primary-light-2 !important; + + .content-clear-icon { + color: $im-color-primary-light-2 !important; + } +} + +/** 底部导航 **/ +.uni-tabbar-bottom .uni-tabbar { + box-shadow: $im-box-shadow; +} + +.uni-tabbar-border { + display: none; +} + +.segmented-control { + border-color: $im-color-primary !important; + + .segmented-control__item--button { + border-color: $im-color-primary !important; + } + .segmented-control__item--button--active { + background-color: $im-color-primary !important; + + .segmented-control__text{ + color: #fff !important; + } + } + .segmented-control__text{ + color: $im-color-primary !important; + } +} + +.uni-radio-input svg{ + border-color: white !important; + background-color: $im-color-primary !important; +} + +.uni-radio-input svg { + background-color: $im-color-primary !important; + border-color: $im-color-primary !important; + background-clip: content-box !important; + box-sizing: border-box; + border-radius: 50%; + transform: translate(-50%, -50%) scale(0.7)!important; +} + +.uni-radio-input svg path{ + fill: $im-color-primary !important; +} + +.uni-radio-input { + background-color: white !important; + border-color: $im-color-primary !important; +} + +.uni-radio-input-disabled { + background-color: rgb(225, 225, 225) !important; + border-color: rgb(209, 209, 209) !important; + opacity: 0.5; +} + +.uni-section__content-title { + font-size: $im-font-size !important; + color: $im-text-color-light; +} + +.uni-forms-item__label { + color: $im-text-color; + font-size: $im-font-size !important; +} +.uni-forms-item { + //margin-bottom: 8px !important; +} +.uni-easyinput__content-input { + font-size: $im-font-size !important; +} +.uni-easyinput__placeholder-class { + color: $im-text-color-lighter; + font-size: $im-font-size !important;; +} +.uni-easyinput__content-textarea { + font-size: $im-font-size !important;; +} +.uni-input-input:disabled { + color: $im-text-color-light; +} + +.uni-forms-item.is-direction-top .uni-forms-item__label { + padding: 0 !important; +} +.uni-data-checklist .checklist-group .checklist-box .checklist-content .checklist-text { + font-size: $im-font-size !important; +} +.uni-card .uni-card__content { + color: unset !important; + padding: 10px 0 !important; +} + +.uni-tag{ + transform: scale(0.9); + margin-left: 5rpx; + border-radius: 12rpx !important; + font-weight: 600 !important; + white-space: nowrap; +} + +.uni-switch-input-checked { + background-color: $im-color-primary-light-1 !important; + border-color: $im-color-primary-light-1 !important; +} +.uni-modal__title { + font-size: $im-font-size-larger !important; +} +.uni-modal__bd { + font-size: $im-font-size; +} +.uni-modal__ft { + font-size: $im-font-size; + line-height: 90rpx !important; + + .uni-modal__btn_primary { + color: $im-color-primary !important; + } +} + + + + +.nav-bar { + height: 100rpx; + padding: 0 20rpx; + display: flex; + align-items: center; + border-bottom: 1rpx solid $im-border; + + .nav-search { + flex: 1; + } + + .nav-add { + cursor: pointer; + } +} + + +.bottom-btn { + margin: 40rpx 40rpx; + + uni-button + uni-button { + margin-top: 20rpx; + } +} + +.emoji-large { + width: 64rpx !important; + height: 64rpx !important; + vertical-align: bottom !important; +} + +.emoji-normal { + width: 54rpx !important; + height: 54rpx !important; + vertical-align: bottom !important; +} + +.emoji-small { + width: 36rpx !important; + height: 36rpx !important; + vertical-align: bottom !important; +} + +.none-pointer-events { + uni-image img { + // 阻止微信默认长按菜单 + pointer-events: none; + -webkit-pointer-events: none; + -ms-pointer-events: none; + -moz-pointer-events: none; + } +} + +p { + margin-block-start: 1em; + margin-block-end: 1em; +} diff --git a/src/main.ts b/src/main.ts index 244d5b56..5a57757f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,20 @@ import * as enums from './common/enums.js'; import * as date from './common/date'; import * as socketApi from './common/wssocket'; import * as messageType from './common/messageType'; +// #ifdef H5 +import * as recorder from './common/recorder-h5'; +import ImageResize from 'quill-image-resize-mp'; +import Quill from 'quill'; +// 以下组件用于兼容部分手机聊天边框无法输入的问题 +window.Quill = Quill; +window.ImageResize = { default: ImageResize }; +// 调试器 +// import VConsole from 'vconsole' +// new VConsole(); +// #endif +// #ifndef H5 +import * as recorder from './common/recorder-app'; +// #endif import App from './App.vue'; import { createSSRApp } from 'vue'; @@ -15,11 +29,46 @@ import { setupPinia } from './sheep/store'; import './main.scss'; import 'virtual:uno.css'; +import useChatStore from '@/store/chatStore.js'; +import useFriendStore from '@/store/friendStore.js'; +import useGroupStore from '@/store/groupStore.js'; +import useConfigStore from '@/store/configStore.js'; +import useUserStore from '@/store/userStore.js'; + +import barGroup from '@/components/bar/bar-group'; +import arrowBar from '@/components/bar/arrow-bar'; +import btnBar from '@/components/bar/btn-bar'; +import switchBar from '@/components/bar/switch-bar'; + export function createApp() { const app = createSSRApp(App); setupPinia(app); + app.config.globalProperties.$http = request; + app.config.globalProperties.$wsApi = socketApi; + app.config.globalProperties.$msgType = messageType; + app.config.globalProperties.$emo = emotion; + app.config.globalProperties.$url = url; + app.config.globalProperties.$str = str; + app.config.globalProperties.$enums = enums; + app.config.globalProperties.$date = date; + app.config.globalProperties.$rc = recorder; + + app.component('bar-group', barGroup); + app.component('arrow-bar', arrowBar); + app.component('btn-bar', btnBar); + app.component('switch-bar', switchBar); + + // 初始化时再挂载store对象 + app.config.globalProperties.$mountStore = () => { + app.config.globalProperties.chatStore = useChatStore(); + app.config.globalProperties.friendStore = useFriendStore(); + app.config.globalProperties.groupStore = useGroupStore(); + app.config.globalProperties.configStore = useConfigStore(); + app.config.globalProperties.userStore = useUserStore(); + }; + return { app, }; diff --git a/src/pages.json b/src/pages.json index d72ee072..c34df6da 100644 --- a/src/pages.json +++ b/src/pages.json @@ -101,7 +101,7 @@ } }, { - "path": "pages/messages/messages", + "path": "pages/message/message", "style": { "navigationBarTitleText": "消息", "navigationStyle": "custom" @@ -147,6 +147,30 @@ } ] }, + { + "root": "pages/message", + "pages": [ + { + "path": "chat-box", + "style": { + "mp-weixin": { + "disableScroll": true + }, + "app-plus": { + // adjustPan窗体高度不变,但窗体上推、adjustResize屏幕高度=webview窗体高度+软键盘高度 + "softinputMode": "adjustResize" + } + } + }, + { + "path": "user-info", + "style": { + "navigationBarTitleText": "用户信息", + "navigationStyle": "custom" + } + } + ] + }, { "root": "pages/profile", "pages": [ @@ -840,10 +864,10 @@ "selectedIconPath": "static/tabbar/moments-active.png" }, { - "pagePath": "pages/messages/messages", + "pagePath": "pages/message/message", "text": "消息", - "iconPath": "static/tabbar/messages.png", - "selectedIconPath": "static/tabbar/messages-active.png" + "iconPath": "static/tabbar/message.png", + "selectedIconPath": "static/tabbar/message-active.png" }, { "pagePath": "pages/profile/profile", diff --git a/src/pages/message/chat-box.vue b/src/pages/message/chat-box.vue new file mode 100644 index 00000000..b492622f --- /dev/null +++ b/src/pages/message/chat-box.vue @@ -0,0 +1,1452 @@ + + + {{ title }} + + + + + + + + + + + + {{ newMessageSize > 0 ? newMessageSize + '条新消息' : '回到底部' }} + + + + + + + + + + + + + + + + + + + + + + + + 发送 + + + + + + + + + + 文件 + + + + + + 相册 + + + + + + 拍摄 + + + + 语音消息 + + + + 回执消息 + + + + + + 视频通话 + + + + 语音通话 + + + + 语音通话 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/message/chat-group-video.vue b/src/pages/message/chat-group-video.vue new file mode 100644 index 00000000..434771fb --- /dev/null +++ b/src/pages/message/chat-group-video.vue @@ -0,0 +1,138 @@ + + + + + + + \ No newline at end of file diff --git a/src/pages/message/chat-private-video.vue b/src/pages/message/chat-private-video.vue new file mode 100644 index 00000000..0677af70 --- /dev/null +++ b/src/pages/message/chat-private-video.vue @@ -0,0 +1,100 @@ + + + + + + + \ No newline at end of file diff --git a/src/pages/message/message.vue b/src/pages/message/message.vue new file mode 100644 index 00000000..a8df83e6 --- /dev/null +++ b/src/pages/message/message.vue @@ -0,0 +1,207 @@ + + + 消息 + + + 消息接收中... + + + + + 正在初始化... + + + + + + + + + + + + 还没有聊天 + 添加好友或创建群聊,开始精彩的对话吧 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/message/user-info.vue b/src/pages/message/user-info.vue new file mode 100644 index 00000000..f7095537 --- /dev/null +++ b/src/pages/message/user-info.vue @@ -0,0 +1,252 @@ + + + 用户信息 + + + + + + + {{ userInfo.nickName }} + + + + + 用户名: + {{ userInfo.userName }} + + + + 签名: + {{ userInfo.signature }} + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/messages/1.vue b/src/pages/messages/1.vue deleted file mode 100644 index 23d96412..00000000 --- a/src/pages/messages/1.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - 消息 - 缘友 - - - - - - - 互动消息 - - - - - - - - - - - - - {{ message.username || '发放' }} - {{ message.time || 'ww' }} - - - - {{ message.lastMessage || '啊发发看来福娃' }} - - - - - - - - - - diff --git a/src/pages/messages/messages.vue b/src/pages/messages/messages.vue deleted file mode 100644 index 23d96412..00000000 --- a/src/pages/messages/messages.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - 消息 - 缘友 - - - - - - - 互动消息 - - - - - - - - - - - - - {{ message.username || '发放' }} - {{ message.time || 'ww' }} - - - - {{ message.lastMessage || '啊发发看来福娃' }} - - - - - - - - - - diff --git a/src/static/emoji/0.gif b/src/static/emoji/0.gif new file mode 100644 index 00000000..e7848ec7 Binary files /dev/null and b/src/static/emoji/0.gif differ diff --git a/src/static/emoji/1.gif b/src/static/emoji/1.gif new file mode 100644 index 00000000..cb5b26b3 Binary files /dev/null and b/src/static/emoji/1.gif differ diff --git a/src/static/emoji/10.gif b/src/static/emoji/10.gif new file mode 100644 index 00000000..53a6c672 Binary files /dev/null and b/src/static/emoji/10.gif differ diff --git a/src/static/emoji/11.gif b/src/static/emoji/11.gif new file mode 100644 index 00000000..d718e6d7 Binary files /dev/null and b/src/static/emoji/11.gif differ diff --git a/src/static/emoji/12.gif b/src/static/emoji/12.gif new file mode 100644 index 00000000..2362b454 Binary files /dev/null and b/src/static/emoji/12.gif differ diff --git a/src/static/emoji/13.gif b/src/static/emoji/13.gif new file mode 100644 index 00000000..4d8dc78a Binary files /dev/null and b/src/static/emoji/13.gif differ diff --git a/src/static/emoji/14.gif b/src/static/emoji/14.gif new file mode 100644 index 00000000..131c3d97 Binary files /dev/null and b/src/static/emoji/14.gif differ diff --git a/src/static/emoji/15.gif b/src/static/emoji/15.gif new file mode 100644 index 00000000..25c57aee Binary files /dev/null and b/src/static/emoji/15.gif differ diff --git a/src/static/emoji/16.gif b/src/static/emoji/16.gif new file mode 100644 index 00000000..27116567 Binary files /dev/null and b/src/static/emoji/16.gif differ diff --git a/src/static/emoji/17.gif b/src/static/emoji/17.gif new file mode 100644 index 00000000..4419ba28 Binary files /dev/null and b/src/static/emoji/17.gif differ diff --git a/src/static/emoji/18.gif b/src/static/emoji/18.gif new file mode 100644 index 00000000..478a4213 Binary files /dev/null and b/src/static/emoji/18.gif differ diff --git a/src/static/emoji/19.gif b/src/static/emoji/19.gif new file mode 100644 index 00000000..59261eaf Binary files /dev/null and b/src/static/emoji/19.gif differ diff --git a/src/static/emoji/2.gif b/src/static/emoji/2.gif new file mode 100644 index 00000000..b01cbcf3 Binary files /dev/null and b/src/static/emoji/2.gif differ diff --git a/src/static/emoji/20.gif b/src/static/emoji/20.gif new file mode 100644 index 00000000..45becb45 Binary files /dev/null and b/src/static/emoji/20.gif differ diff --git a/src/static/emoji/21.gif b/src/static/emoji/21.gif new file mode 100644 index 00000000..d3f1afee Binary files /dev/null and b/src/static/emoji/21.gif differ diff --git a/src/static/emoji/22.gif b/src/static/emoji/22.gif new file mode 100644 index 00000000..ee220d1c Binary files /dev/null and b/src/static/emoji/22.gif differ diff --git a/src/static/emoji/23.gif b/src/static/emoji/23.gif new file mode 100644 index 00000000..5dbd9f31 Binary files /dev/null and b/src/static/emoji/23.gif differ diff --git a/src/static/emoji/24.gif b/src/static/emoji/24.gif new file mode 100644 index 00000000..d3ae9465 Binary files /dev/null and b/src/static/emoji/24.gif differ diff --git a/src/static/emoji/25.gif b/src/static/emoji/25.gif new file mode 100644 index 00000000..5cf2a9e7 Binary files /dev/null and b/src/static/emoji/25.gif differ diff --git a/src/static/emoji/26.gif b/src/static/emoji/26.gif new file mode 100644 index 00000000..793b2ac0 Binary files /dev/null and b/src/static/emoji/26.gif differ diff --git a/src/static/emoji/27.gif b/src/static/emoji/27.gif new file mode 100644 index 00000000..b78a02bf Binary files /dev/null and b/src/static/emoji/27.gif differ diff --git a/src/static/emoji/28.gif b/src/static/emoji/28.gif new file mode 100644 index 00000000..4074630a Binary files /dev/null and b/src/static/emoji/28.gif differ diff --git a/src/static/emoji/29.gif b/src/static/emoji/29.gif new file mode 100644 index 00000000..166f2520 Binary files /dev/null and b/src/static/emoji/29.gif differ diff --git a/src/static/emoji/3.gif b/src/static/emoji/3.gif new file mode 100644 index 00000000..3b15c537 Binary files /dev/null and b/src/static/emoji/3.gif differ diff --git a/src/static/emoji/30.gif b/src/static/emoji/30.gif new file mode 100644 index 00000000..13499b90 Binary files /dev/null and b/src/static/emoji/30.gif differ diff --git a/src/static/emoji/31.gif b/src/static/emoji/31.gif new file mode 100644 index 00000000..b4ec1d8f Binary files /dev/null and b/src/static/emoji/31.gif differ diff --git a/src/static/emoji/32.gif b/src/static/emoji/32.gif new file mode 100644 index 00000000..ca863991 Binary files /dev/null and b/src/static/emoji/32.gif differ diff --git a/src/static/emoji/33.gif b/src/static/emoji/33.gif new file mode 100644 index 00000000..8ec811a8 Binary files /dev/null and b/src/static/emoji/33.gif differ diff --git a/src/static/emoji/34.gif b/src/static/emoji/34.gif new file mode 100644 index 00000000..2a3c006f Binary files /dev/null and b/src/static/emoji/34.gif differ diff --git a/src/static/emoji/35.gif b/src/static/emoji/35.gif new file mode 100644 index 00000000..7fc7f947 Binary files /dev/null and b/src/static/emoji/35.gif differ diff --git a/src/static/emoji/36.gif b/src/static/emoji/36.gif new file mode 100644 index 00000000..f0d46d0c Binary files /dev/null and b/src/static/emoji/36.gif differ diff --git a/src/static/emoji/37.gif b/src/static/emoji/37.gif new file mode 100644 index 00000000..2c9c084b Binary files /dev/null and b/src/static/emoji/37.gif differ diff --git a/src/static/emoji/38.gif b/src/static/emoji/38.gif new file mode 100644 index 00000000..b6b4e09c Binary files /dev/null and b/src/static/emoji/38.gif differ diff --git a/src/static/emoji/39.gif b/src/static/emoji/39.gif new file mode 100644 index 00000000..cfb75c68 Binary files /dev/null and b/src/static/emoji/39.gif differ diff --git a/src/static/emoji/4.gif b/src/static/emoji/4.gif new file mode 100644 index 00000000..fd60e4e3 Binary files /dev/null and b/src/static/emoji/4.gif differ diff --git a/src/static/emoji/40.gif b/src/static/emoji/40.gif new file mode 100644 index 00000000..c5d7a8b6 Binary files /dev/null and b/src/static/emoji/40.gif differ diff --git a/src/static/emoji/41.gif b/src/static/emoji/41.gif new file mode 100644 index 00000000..cf1c0a1c Binary files /dev/null and b/src/static/emoji/41.gif differ diff --git a/src/static/emoji/42.gif b/src/static/emoji/42.gif new file mode 100644 index 00000000..cc87f9e8 Binary files /dev/null and b/src/static/emoji/42.gif differ diff --git a/src/static/emoji/43.gif b/src/static/emoji/43.gif new file mode 100644 index 00000000..46153bc7 Binary files /dev/null and b/src/static/emoji/43.gif differ diff --git a/src/static/emoji/44.gif b/src/static/emoji/44.gif new file mode 100644 index 00000000..dd451188 Binary files /dev/null and b/src/static/emoji/44.gif differ diff --git a/src/static/emoji/45.gif b/src/static/emoji/45.gif new file mode 100644 index 00000000..a3e86d66 Binary files /dev/null and b/src/static/emoji/45.gif differ diff --git a/src/static/emoji/46.gif b/src/static/emoji/46.gif new file mode 100644 index 00000000..b3fb6814 Binary files /dev/null and b/src/static/emoji/46.gif differ diff --git a/src/static/emoji/47.gif b/src/static/emoji/47.gif new file mode 100644 index 00000000..d24bdbb1 Binary files /dev/null and b/src/static/emoji/47.gif differ diff --git a/src/static/emoji/48.gif b/src/static/emoji/48.gif new file mode 100644 index 00000000..ab98b2b6 Binary files /dev/null and b/src/static/emoji/48.gif differ diff --git a/src/static/emoji/49.gif b/src/static/emoji/49.gif new file mode 100644 index 00000000..19a7e72b Binary files /dev/null and b/src/static/emoji/49.gif differ diff --git a/src/static/emoji/5.gif b/src/static/emoji/5.gif new file mode 100644 index 00000000..85cd3738 Binary files /dev/null and b/src/static/emoji/5.gif differ diff --git a/src/static/emoji/50.gif b/src/static/emoji/50.gif new file mode 100644 index 00000000..51e40937 Binary files /dev/null and b/src/static/emoji/50.gif differ diff --git a/src/static/emoji/51.gif b/src/static/emoji/51.gif new file mode 100644 index 00000000..9bf82a8a Binary files /dev/null and b/src/static/emoji/51.gif differ diff --git a/src/static/emoji/52.gif b/src/static/emoji/52.gif new file mode 100644 index 00000000..18391ea3 Binary files /dev/null and b/src/static/emoji/52.gif differ diff --git a/src/static/emoji/53.gif b/src/static/emoji/53.gif new file mode 100644 index 00000000..0f584b5b Binary files /dev/null and b/src/static/emoji/53.gif differ diff --git a/src/static/emoji/54.gif b/src/static/emoji/54.gif new file mode 100644 index 00000000..5ab6d250 Binary files /dev/null and b/src/static/emoji/54.gif differ diff --git a/src/static/emoji/55.gif b/src/static/emoji/55.gif new file mode 100644 index 00000000..3d75ad7a Binary files /dev/null and b/src/static/emoji/55.gif differ diff --git a/src/static/emoji/56.gif b/src/static/emoji/56.gif new file mode 100644 index 00000000..25259709 Binary files /dev/null and b/src/static/emoji/56.gif differ diff --git a/src/static/emoji/6.gif b/src/static/emoji/6.gif new file mode 100644 index 00000000..a49c862a Binary files /dev/null and b/src/static/emoji/6.gif differ diff --git a/src/static/emoji/7.gif b/src/static/emoji/7.gif new file mode 100644 index 00000000..3a847b8f Binary files /dev/null and b/src/static/emoji/7.gif differ diff --git a/src/static/emoji/8.gif b/src/static/emoji/8.gif new file mode 100644 index 00000000..eb6db6a1 Binary files /dev/null and b/src/static/emoji/8.gif differ diff --git a/src/static/emoji/9.gif b/src/static/emoji/9.gif new file mode 100644 index 00000000..52562dc0 Binary files /dev/null and b/src/static/emoji/9.gif differ diff --git a/src/static/icon/iconfont.css b/src/static/icon/iconfont.css new file mode 100644 index 00000000..295c2a66 --- /dev/null +++ b/src/static/icon/iconfont.css @@ -0,0 +1,261 @@ +@font-face { + font-family: "iconfont"; /* Project id 4272106 */ + src: url('iconfont.ttf?t=1759053579007') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-chat:before { + content: "\e93e"; +} + +.icon-create-group:before { + content: "\e65b"; +} + +.icon-refresh:before { + content: "\e64c"; +} + +.icon-copy:before { + content: "\e604"; +} + +.icon-code:before { + content: "\e72c"; +} + +.icon-username:before { + content: "\e623"; +} + +.icon-pwd:before { + content: "\e60d"; +} + +.icon-pwd-hide:before { + content: "\e60c"; +} + +.icon-username2:before { + content: "\e647"; +} + +.icon-pwd-show:before { + content: "\e621"; +} + +.icon-teenager:before { + content: "\eba1"; +} + +.icon-warning-circle-empty:before { + content: "\e606"; +} + +.icon-loading2:before { + content: "\e6b6"; +} + +.icon-complaint:before { + content: "\e612"; +} + +.icon-about:before { + content: "\e637"; +} + +.icon-security:before { + content: "\e61d"; +} + +.icon-personal-info:before { + content: "\e62d"; +} + +.icon-success:before { + content: "\e649"; +} + +.icon-wait:before { + content: "\e701"; +} + +.icon-error:before { + content: "\e62b"; +} + +.icon-dnd:before { + content: "\e693"; +} + +.icon-create-group-2:before { + content: "\e616"; +} + +.icon-qrcode:before { + content: "\e642"; +} + +.icon-add-friend:before { + content: "\e64f"; +} + +.icon-scan:before { + content: "\e8b5"; +} + +.icon-remove:before { + content: "\e603"; +} + +.icon-doc:before { + content: "\e61c"; +} + +.icon-image:before { + content: "\e7f7"; +} + +.icon-top-message:before { + content: "\e6ff"; +} + +.icon-setting:before { + content: "\e851"; +} + +.icon-phone:before { + content: "\e692"; +} + +.icon-email:before { + content: "\e611"; +} + +.icon-chat-muted:before { + content: "\e634"; +} + +.icon-chat-unmuted:before { + content: "\ec44"; +} + +.icon-un-register:before { + content: "\e656"; +} + +.icon-modify-pwd:before { + content: "\e63c"; +} + +.icon-film:before { + content: "\e66b"; +} + +.icon-delete:before { + content: "\e605"; +} + +.icon-receipt:before { + content: "\e601"; +} + +.icon-pause:before { + content: "\e669"; +} + +.icon-play:before { + content: "\e620"; +} + +.icon-voice-play:before { + content: "\e675"; +} + +.icon-chat-video:before { + content: "\e73b"; +} + +.icon-chat-voice:before { + content: "\e633"; +} + +.icon-video:before { + content: "\e685"; +} + +.icon-ok:before { + content: "\e65a"; +} + +.icon-at:before { + content: "\e7de"; +} + +.icon-man:before { + content: "\e615"; +} + +.icon-girl:before { + content: "\e602"; +} + +.icon-file:before { + content: "\e671"; +} + +.icon-add:before { + content: "\e66c"; +} + +.icon-warning-circle-fill:before { + content: "\e848"; +} + +.icon-loading:before { + content: "\e93d"; +} + +.icon-camera:before { + content: "\e600"; +} + +.icon-folder:before { + content: "\e6a0"; +} + +.icon-microphone:before { + content: "\e63b"; +} + +.icon-icon_emoji:before { + content: "\e619"; +} + +.icon-call:before { + content: "\e610"; +} + +.icon-keyboard:before { + content: "\e679"; +} + +.icon-voice-circle:before { + content: "\e67f"; +} + +.icon-picture:before { + content: "\e653"; +} + +.icon-search:before { + content: "\e648"; +} + diff --git a/src/static/icon/iconfont.ttf b/src/static/icon/iconfont.ttf new file mode 100644 index 00000000..85b17d02 Binary files /dev/null and b/src/static/icon/iconfont.ttf differ diff --git a/src/static/tabbar/messages-active.png b/src/static/tabbar/message-active.png similarity index 100% rename from src/static/tabbar/messages-active.png rename to src/static/tabbar/message-active.png diff --git a/src/static/tabbar/messages.png b/src/static/tabbar/message.png similarity index 100% rename from src/static/tabbar/messages.png rename to src/static/tabbar/message.png diff --git a/src/store/chatStore.js b/src/store/chatStore.js new file mode 100644 index 00000000..56d8adcc --- /dev/null +++ b/src/store/chatStore.js @@ -0,0 +1,546 @@ +import { defineStore } from 'pinia'; +import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js'; +import useFriendStore from './friendStore.js'; +import useGroupStore from './groupStore.js'; +import useUserStore from './userStore'; + +let cacheChats = []; +export default defineStore('chatStore', { + state: () => { + return { + chats: [], + privateMsgMaxId: 0, + groupMsgMaxId: 0, + loading: false + } + }, + actions: { + initChats(chatsData) { + cacheChats = []; + this.chats = []; + for (let chat of chatsData.chats) { + chat.stored = false; + // 暂存至缓冲区 + cacheChats.push(JSON.parse(JSON.stringify(chat))); + // 加载期间显示只前15个会话做做样子,一切都为了加快初始化时间 + if (this.chats.length < 15) { + this.chats.push(chat); + } + } + this.privateMsgMaxId = chatsData.privateMsgMaxId || 0; + this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; + }, + openChat(chatInfo) { + let chats = this.curChats; + let chat = null; + for (let idx in chats) { + if (chats[idx].type == chatInfo.type && + chats[idx].targetId === chatInfo.targetId) { + chat = chats[idx]; + // 放置头部 + this.moveTop(idx) + break; + } + } + // 创建会话 + if (chat == null) { + chat = { + targetId: chatInfo.targetId, + type: chatInfo.type, + showName: chatInfo.showName, + headImage: chatInfo.headImage, + isDnd: chatInfo.isDnd, + lastContent: "", + lastSendTime: new Date().getTime(), + unreadCount: 0, + hotMinIdx: 0, + readedMessageIdx: 0, + messages: [], + atMe: false, + atAll: false, + stored: false + }; + chats.unshift(chat); + this.saveToStorage(); + } + }, + activeChat(idx) { + let chats = this.curChats; + if (idx >= 0) { + chats[idx].unreadCount = 0; + } + }, + resetUnreadCount(chatInfo) { + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == chatInfo.type && + chats[idx].targetId == chatInfo.targetId) { + chats[idx].unreadCount = 0; + chats[idx].atMe = false; + chats[idx].atAll = false; + chats[idx].stored = false; + this.saveToStorage(); + } + } + + }, + readedMessage(pos) { + let chat = this.findChatByFriend(pos.friendId); + if (!chat) return; + for (let idx = chat.readedMessageIdx; idx < chat.messages.length; idx++) { + let m = chat.messages[idx]; + if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) { + // pos.maxId为空表示整个会话已读 + if (!pos.maxId || m.id <= pos.maxId) { + m.status = MESSAGE_STATUS.READED + chat.readedMessageIdx = idx; + chat.stored = false; + } + } + } + if (!chat.stored) { + this.saveToStorage(); + } + }, + cleanMessage(idx) { + let chat = this.curChats[idx]; + chat.lastContent = ''; + chat.hotMinIdx = 0; + chat.unreadCount = 0; + chat.atMe = false; + chat.atAll = false; + chat.stored = false + chat.messages = []; + this.saveToStorage(true); + }, + removeChat(idx) { + let chats = this.curChats; + chats[idx].delete = true; + chats[idx].stored = false; + this.saveToStorage(); + }, + removePrivateChat(userId) { + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == 'PRIVATE' && + chats[idx].targetId == userId) { + this.removeChat(idx); + } + } + }, + removeGroupChat(groupId) { + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == 'GROUP' && + chats[idx].targetId == groupId) { + this.removeChat(idx); + } + } + }, + moveTop(idx) { + if (this.loading) { + return; + } + let chats = this.curChats; + if (idx > 0) { + let chat = chats[idx]; + chats.splice(idx, 1); + chats.unshift(chat); + chat.lastSendTime = new Date().getTime(); + chat.stored = false; + this.saveToStorage(); + } + }, + insertMessage(msgInfo, chatInfo) { + // 获取对方id或群id + let type = chatInfo.type; + // 记录消息的最大id + if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) { + this.privateMsgMaxId = msgInfo.id; + } + if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) { + this.groupMsgMaxId = msgInfo.id; + } + // 如果是已存在消息,则覆盖旧的消息数据 + let chat = this.findChat(chatInfo); + let message = this.findMessage(chat, msgInfo); + if (message) { + console.log("message:", message) + Object.assign(message, msgInfo); + chat.stored = false; + this.saveToStorage(); + return; + } + // 会话列表内容 + if (msgInfo.type == MESSAGE_TYPE.IMAGE) { + chat.lastContent = "[图片]"; + } else if (msgInfo.type == MESSAGE_TYPE.FILE) { + chat.lastContent = "[文件]"; + } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) { + chat.lastContent = "[语音]"; + } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) { + chat.lastContent = "[语音通话]"; + } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) { + chat.lastContent = "[视频通话]"; + } else if (msgInfo.type == MESSAGE_TYPE.TEXT || + msgInfo.type == MESSAGE_TYPE.RECALL || + msgInfo.type == MESSAGE_TYPE.TIP_TEXT) { + chat.lastContent = msgInfo.content; + } + chat.lastSendTime = msgInfo.sendTime; + chat.sendNickName = msgInfo.sendNickName; + // 未读加1 + if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED && + msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) { + chat.unreadCount++; + } + // 是否有人@我 + if (!msgInfo.selfSend && chat.type == "GROUP" && msgInfo.atUserIds && + msgInfo.status != MESSAGE_STATUS.READED) { + const userStore = useUserStore(); + let userId = userStore.userInfo.id; + if (msgInfo.atUserIds.indexOf(userId) >= 0) { + chat.atMe = true; + } + if (msgInfo.atUserIds.indexOf(-1) >= 0) { + chat.atAll = true; + } + } + // 间隔大于10分钟插入时间显示 + if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) { + chat.messages.push({ + sendTime: msgInfo.sendTime, + type: MESSAGE_TYPE.TIP_TIME, + }); + chat.lastTimeTip = msgInfo.sendTime; + } + // 插入消息 + chat.messages.push(msgInfo); + chat.stored = false; + this.saveToStorage(); + }, + updateMessage(msgInfo, chatInfo) { + // 获取对方id或群id + let chat = this.findChat(chatInfo); + let message = this.findMessage(chat, msgInfo); + if (message) { + // 属性拷贝 + Object.assign(message, msgInfo); + chat.stored = false; + this.saveToStorage(); + } + }, + deleteMessage(msgInfo, chatInfo) { + let isColdMessage = false; + let chat = this.findChat(chatInfo); + let delIdx = -1; + for (let idx in chat.messages) { + // 已经发送成功的,根据id删除 + if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) { + delIdx = idx; + break; + } + // 正在发送中的消息可能没有id,只有临时id + if (chat.messages[idx].tmpId && chat.messages[idx].tmpId == msgInfo.tmpId) { + delIdx = idx; + break; + } + } + if (delIdx >= 0) { + chat.messages.splice(delIdx, 1); + if (delIdx < chat.hotMinIdx) { + isColdMessage = true; + chat.hotMinIdx--; + } + if (delIdx < chat.readedMessageIdx) { + chat.readedMessageIdx--; + } + chat.stored = false; + this.saveToStorage(isColdMessage); + } + }, + recallMessage(msgInfo, chatInfo) { + let chat = this.findChat(chatInfo); + if (!chat) return; + let isColdMessage = false; + // 要撤回的消息id + let id = msgInfo.content; + let name = msgInfo.selfSend ? '你' : chat.type == 'PRIVATE' ? '对方' : msgInfo.sendNickName; + for (let idx in chat.messages) { + let m = chat.messages[idx]; + if (m.id && m.id == id) { + // 改造成一条提示消息 + m.status = MESSAGE_STATUS.RECALL; + m.content = name + "撤回了一条消息"; + m.type = MESSAGE_TYPE.TIP_TEXT + // 会话列表 + chat.lastContent = m.content; + chat.lastSendTime = msgInfo.sendTime; + chat.sendNickName = ''; + if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) { + chat.unreadCount++; + } + isColdMessage = idx < chat.hotMinIdx; + } + // 被引用的消息也要撤回 + if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) { + m.quoteMessage.content = "引用内容已撤回"; + m.quoteMessage.status = MESSAGE_STATUS.RECALL; + m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT + } + } + chat.stored = false; + this.saveToStorage(isColdMessage); + }, + updateChatFromFriend(friend) { + let chat = this.findChatByFriend(friend.id) + if (chat && (chat.headImage != friend.headImage || + chat.showName != friend.nickName)) { + // 更新会话中的群名和头像 + chat.headImage = friend.headImage; + chat.showName = friend.nickName; + chat.stored = false; + this.saveToStorage(); + } + }, + updateChatFromUser(user) { + let chat = this.findChatByFriend(user.id); + // 更新会话中的昵称和头像 + if (chat && (chat.headImage != user.headImageThumb || + chat.showName != user.nickName)) { + chat.headImage = user.headImageThumb; + chat.showName = user.nickName; + chat.stored = false; + this.saveToStorage(); + } + }, + updateChatFromGroup(group) { + let chat = this.findChatByGroup(group.id); + if (chat && (chat.headImage != group.headImageThumb || + chat.showName != group.showGroupName)) { + // 更新会话中的群名称和头像 + chat.headImage = group.headImageThumb; + chat.showName = group.showGroupName; + chat.stored = false; + this.saveToStorage(); + } + }, + setLoading(loading) { + this.loading = loading; + }, + setDnd(chatInfo, isDnd) { + let chat = this.findChat(chatInfo); + if (chat) { + chat.isDnd = isDnd; + } + }, + refreshChats() { + let chats = cacheChats || this.chats; + // 更新会话免打扰状态 + const friendStore = useFriendStore(); + const groupStore = useGroupStore(); + chats.forEach(chat => { + if (chat.type == 'PRIVATE') { + let friend = friendStore.findFriend(chat.targetId); + if (friend) { + chat.isDnd = friend.isDnd + } + } else if (chat.type == 'GROUP') { + let group = groupStore.findGroup(chat.targetId); + if (group) { + chat.isDnd = group.isDnd + } + } + }) + // 排序 + chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); + // #ifndef APP-PLUS + /** + * 由于h5和小程序的stroge只有5m,大约只能存储2w条消息,所以可能需要清理部分历史消息 + */ + this.fliterMessage(chats, 5000, 1000); + // #endif + // 记录热数据索引位置 + chats.forEach(chat => { + if(!chat.hotMinIdx || chat.hotMinIdx != chat.messages.length){ + chat.hotMinIdx = chat.messages.length; + chat.stored = false; + } + }); + // 将消息一次性装载回来 + this.chats = chats; + // 清空缓存,不再使用 + cacheChats = null; + // 消息持久化 + this.saveToStorage(true); + }, + fliterMessage(chats, maxTotalSize, maxPerChatSize) { + // 每个会话只保留maxPerChatSize条消息 + let remainTotalSize = 0; + chats.forEach(chat => { + if (chat.messages.length > maxPerChatSize) { + let idx = chat.messages.length - maxPerChatSize; + chat.messages = chat.messages.slice(idx); + } + remainTotalSize += chat.messages.length; + }) + // 保证消息总数不超过maxTotalSize条,否则继续清理 + if (remainTotalSize > maxTotalSize) { + this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); + } + }, + saveToStorage(withColdMessage) { + // 加载中不保存,防止卡顿 + if (this.loading) { + return; + } + const userStore = useUserStore(); + let userId = userStore.userInfo.id; + let key = "chats-app-" + userId; + let chatKeys = []; + // 按会话为单位存储,只存储有改动的会话 + this.chats.forEach((chat) => { + let chatKey = `${key}-${chat.type}-${chat.targetId}` + if (!chat.stored) { + if (chat.delete) { + uni.removeStorageSync(chatKey); + } else { + // 存储冷数据 + if (withColdMessage) { + let coldChat = Object.assign({}, chat); + coldChat.messages = chat.messages.slice(0, chat.hotMinIdx); + uni.setStorageSync(chatKey, coldChat) + } + // 存储热消息 + let hotKey = chatKey + '-hot'; + let hotChat = Object.assign({}, chat); + hotChat.messages = chat.messages.slice(chat.hotMinIdx) + uni.setStorageSync(hotKey, hotChat); + } + chat.stored = true; + } + if (!chat.delete) { + chatKeys.push(chatKey); + } + }) + // 会话核心信息 + let chatsData = { + privateMsgMaxId: this.privateMsgMaxId, + groupMsgMaxId: this.groupMsgMaxId, + chatKeys: chatKeys + } + uni.setStorageSync(key, chatsData) + // 清理已删除的会话 + this.chats = this.chats.filter(chat => !chat.delete) + }, + clear(state) { + cacheChats = []; + this.chats = []; + this.privateMsgMaxId = 0; + this.groupMsgMaxId = 0; + this.loadingPrivateMsg = false; + this.loadingGroupMsg = false; + }, + loadChat() { + return new Promise((resolve, reject) => { + let userStore = useUserStore(); + let userId = userStore.userInfo.id; + let chatsData = uni.getStorageSync("chats-app-" + userId) + if (chatsData) { + if (chatsData.chatKeys) { + chatsData.chats = []; + chatsData.chatKeys.forEach(key => { + let coldChat = uni.getStorageSync(key); + let hotChat = uni.getStorageSync(key + '-hot'); + if (!coldChat && !hotChat) { + return; + } + // 防止消息一直处在发送中状态 + hotChat && hotChat.messages.forEach(msg => { + if (msg.status == MESSAGE_STATUS.SENDING) { + msg.status = MESSAGE_STATUS.FAILED + } + }) + // 冷热消息合并 + let chat = Object.assign({}, coldChat, hotChat); + if (hotChat && coldChat) { + chat.messages = coldChat.messages.concat(hotChat.messages) + } + // 历史版本没有readedMessageIdx字段,做兼容一下 + chat.readedMessageIdx = chat.readedMessageIdx || 0; + chatsData.chats.push(chat); + }) + } + this.initChats(chatsData); + } + resolve() + }) + } + }, + getters: { + curChats: (state) => { + if (cacheChats && state.loading) { + return cacheChats; + } + return state.chats; + }, + findChatIdx: (state) => (chat) => { + let chats = state.curChats; + for (let idx in chats) { + if (chats[idx].type == chat.type && + chats[idx].targetId === chat.targetId) { + chat = state.chats[idx]; + return idx; + } + } + }, + findChat: (state) => (chat) => { + let chats = state.curChats; + let idx = state.findChatIdx(chat); + return chats[idx]; + }, + findChatByFriend: (state) => (fid) => { + return state.curChats.find(chat => chat.type == 'PRIVATE' && + chat.targetId == fid) + }, + findChatByGroup: (state) => (gid) => { + return state.curChats.find(chat => chat.type == 'GROUP' && + chat.targetId == gid) + }, + findMessage: (state) => (chat, msgInfo) => { + if (!chat) { + return null; + } + // 通过id判断 + if (msgInfo.id) { + for (let idx = chat.messages.length - 1; idx >= 0; idx--) { + let m = chat.messages[idx]; + if (m.id && msgInfo.id == m.id) { + return m; + } + // 如果id比要查询的消息小,说明没有这条消息 + if (m.id && m.id < msgInfo.id) { + break; + } + } + } + // 正在发送中的临时消息可能没有id,只有tmpId + if (msgInfo.selfSend && msgInfo.tmpId) { + for (let idx = chat.messages.length - 1; idx >= 0; idx--) { + let m = chat.messages[idx]; + if (!m.selfSend || !m.tmpId) { + continue; + } + if (msgInfo.tmpId == m.tmpId) { + return m; + } + // 如果id比要查询的消息小,说明没有这条消息 + if (m.tmpId && m.tmpId < msgInfo.tmpId) { + break; + } + } + } + return null; + } + } +}); \ No newline at end of file diff --git a/src/store/configStore.js b/src/store/configStore.js new file mode 100644 index 00000000..00488187 --- /dev/null +++ b/src/store/configStore.js @@ -0,0 +1,36 @@ +import { defineStore } from 'pinia'; +import http from '../common/request' + +export default defineStore('configStore', { + state: () => { + return { + appInit: false, + webrtc: {} + } + }, + actions: { + setConfig(config) { + this.webrtc = config.webrtc; + }, + setAppInit(appInit) { + this.appInit = appInit; + }, + clear() { + this.webrtc = {}; + }, + loadConfig() { + return new Promise((resolve, reject) => { + http({ + url: '/system/config', + method: 'GET' + }).then((config) => { + console.log("系统配置", config) + this.setConfig(config); + resolve(); + }).catch((res) => { + reject(res); + }); + }) + } + } +}) \ No newline at end of file diff --git a/src/store/friendStore.js b/src/store/friendStore.js new file mode 100644 index 00000000..c846e754 --- /dev/null +++ b/src/store/friendStore.js @@ -0,0 +1,96 @@ +import { defineStore } from 'pinia'; +import http from '../common/request' +import { TERMINAL_TYPE } from '../common/enums.js' + +export default defineStore('friendStore', { + state: () => { + return { + friendMap: new Map(), + timer: null + } + }, + actions: { + setFriends(friends) { + this.friendMap.clear(); + friends.forEach(f => this.friendMap.set(f.id, f)) + }, + updateFriend(friend) { + let f = this.findFriend(friend.id); + friend.online = f.online; + friend.onlineWeb = f.onlineWeb; + friend.onlineApp = f.onlineApp; + this.friendMap.set(friend.id, friend) + }, + removeFriend(id) { + this.friendMap.get(id).deleted = true; + }, + addFriend(friend) { + this.friendMap.set(friend.id, friend) + }, + setOnlineStatus(onlineTerminals) { + this.friends.forEach((f) => { + let userTerminal = onlineTerminals.find((o) => f.id == o.userId); + if (userTerminal) { + f.online = true; + f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB) >= 0 + f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP) >= 0 + } else { + f.online = false; + f.onlineWeb = false; + f.onlineApp = false; + } + }); + }, + refreshOnlineStatus() { + let userIds = this.friends.filter((f) => !f.deleted).map((f) => f.id); + if (userIds.length == 0) { + return; + } + http({ + url: '/user/terminal/online?userIds=' + userIds.join(','), + method: 'GET' + }).then((onlineTerminals) => { + this.setOnlineStatus(onlineTerminals); + }) + // 30s后重新拉取 + clearTimeout(this.timer); + this.timer = setTimeout(() => { + this.refreshOnlineStatus(); + }, 30000) + }, + setDnd(id, isDnd) { + this.friendMap.get(id).isDnd = isDnd; + }, + clear() { + this.friendMap.clear(); + clearTimeout(this.timer); + this.timer = null; + }, + loadFriend() { + return new Promise((resolve, reject) => { + http({ + url: '/friend/list', + method: 'GET' + }).then((friends) => { + this.setFriends(friends); + this.refreshOnlineStatus(); + resolve() + }).catch((res) => { + reject(); + }) + }); + } + }, + getters: { + friends: (state) => { + return Array.from(state.friendMap.values()); + }, + findFriend: (state) => (userId) => { + return state.friendMap.get(userId); + }, + isFriend: (state) => (userId) => { + let f = state.findFriend(userId) + return f && !f.deleted + } + } +}) \ No newline at end of file diff --git a/src/store/groupStore.js b/src/store/groupStore.js new file mode 100644 index 00000000..3409b10c --- /dev/null +++ b/src/store/groupStore.js @@ -0,0 +1,56 @@ +import { defineStore } from 'pinia'; +import http from '@/common/request'; + +export default defineStore('groupStore', { + state: () => { + return { + groupMap: new Map() + } + }, + actions: { + setGroups(groups) { + this.groupMap.clear(); + groups.forEach(g => this.groupMap.set(g.id, g)) + }, + addGroup(group) { + this.groupMap.set(group.id, group); + }, + removeGroup(id) { + this.groupMap.get(id).quit = true; + }, + updateGroup(group) { + this.groupMap.set(group.id, group); + }, + setDnd(id, isDnd) { + this.groupMap.get(id).isDnd = isDnd; + }, + clear() { + this.groupMap.clear(); + }, + loadGroup() { + return new Promise((resolve, reject) => { + http({ + url: '/group/list', + method: 'GET' + }).then((groups) => { + this.setGroups(groups); + resolve(); + }).catch((res) => { + reject(res); + }) + }); + } + }, + getters: { + groups: (state) => { + return Array.from(state.groupMap.values()); + }, + findGroup: (state) => (id) => { + return state.groupMap.get(id); + }, + isGroup: (state) => (id) => { + let group = state.findGroup(id); + return group && !group.quit + } + } +}) \ No newline at end of file diff --git a/src/store/userStore.js b/src/store/userStore.js new file mode 100644 index 00000000..a7163fb9 --- /dev/null +++ b/src/store/userStore.js @@ -0,0 +1,33 @@ +import { defineStore } from 'pinia'; +import http from '../common/request'; + +export default defineStore('userStore', { + state: () => { + return { + userInfo: {}, + }; + }, + actions: { + setUserInfo(userInfo) { + this.userInfo = userInfo; + }, + clear() { + this.userInfo = {}; + }, + loadUser() { + return new Promise((resolve, reject) => { + http({ + url: '/user/self', + method: 'GET', + }) + .then((userInfo) => { + this.setUserInfo(userInfo); + resolve(); + }) + .catch((res) => { + reject(res); + }); + }); + }, + }, +}); diff --git a/src/uni.scss b/src/uni.scss index 89c83dfb..4334ebbc 100644 --- a/src/uni.scss +++ b/src/uni.scss @@ -6,6 +6,8 @@ * */ @import '@/sheep/scss/_var.scss'; +@import '@/im-var.scss'; + /** * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 * diff --git a/vite.config.ts b/vite.config.ts index 0bfbb4b3..a0b87022 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -34,6 +34,12 @@ export default async (command: 'build' | 'serve', mode: string) => { target: env.SHOPRO_DEV_BASE_URL, changeOrigin: true, }, + '/api': { + rewrite: (path) => path.replace(/^\/api/, ''), + logLevel: 'debug', + target: 'http://chatsever.zianxin.com', + changeOrigin: true, + }, }, }, resolve: {