commit 331167dd70e6d27d83f730a02e9f7a8174277fb0 Author: Your Name Date: Wed Sep 4 20:14:51 2024 +0800 first commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..54f01fe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM nginx:alpine + +RUN echo "https://mirrors.tuna.tsinghua.edu.cn/alpine/latest-stable/main" > /etc/apk/repositories && \ + echo "https://mirrors.tuna.tsinghua.edu.cn/alpine/latest-stable/community" >> /etc/apk/repositories + + +COPY nginx.conf /etc/nginx/nginx.conf + +ADD dist /app/www + + +RUN chown -R nginx:nginx /app/www + +CMD ["nginx", "-g", "daemon off;"] + diff --git a/Dockerfile-rc b/Dockerfile-rc new file mode 100644 index 0000000..9e14fc5 --- /dev/null +++ b/Dockerfile-rc @@ -0,0 +1,25 @@ +FROM node:20.12 as builder + + + +WORKDIR /app +COPY . . +RUN npm install +RUN npm install -g increase-memory-limit cross-env +ENV NODE_OPTIONS=--max_old_space_size=4096 +RUN npm run build + +FROM nginx:alpine + + + + +RUN mkdir -p /app/www + +COPY nginx.conf /etc/nginx/nginx.conf + +COPY --from=builder /app/dist /app/www/ +WORKDIR /app/www +RUN chown -R nginx:nginx /app/www + +CMD ["nginx", "-g", "daemon off;"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..809a518 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Charlie Wang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..54226b6 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +

目录结构

+ +``` +├── .husky +│ └── commit-msg # commit 信息校验 +| └── pre-commit # eslint 代码检验 +├── src +│ ├── api # Api ajax 等 +│ ├── assets # 本地静态资源 +│ ├── layouts # 封装布局组件 +│ ├── components # 业务通用组件和基础布局组件 +│ ├── router # Vue-Router +│ ├── store # Pinia +│ ├── utils # 工具库 +│ ├── views # 业务页面入口和常用模板 +│ ├── App.vue # Vue 模板入口 +│ └── main.js # Vue 入口 JS +│ └── app.less # 全局样式 +│ └── env.d.ts # 全局公用 TypeScript 类型 +├── build/mock # mock 服务 +├── mock # mock 数据 +├── public # 静态文件 +├── scripts # 公共执行脚本 +├── tests # 单元测试 +├── auto-imports.d.ts # Vue3 组合式API 类型声明文件 +├── components.d.ts # 组件自注册类型声明文件 +├── vite.config.js # Vite 配置文件 +├── tsconfig.json # TS 配置文件 +├── index.html # 浏览器渲染入口 +├── README.md # 简单介绍 +└── package.json # 项目的依赖 +``` + + +#### 有以下这几种情况 + +```js +// 使用Avatar组件的,并且传入url是从对象中取的可以使用事件回调 + +// after + +``` + +```js +// 使用Avatar组件,但是传入的url是单独的一个值的 + +// 使用cdnSource组件包裹 + + + +``` + +```js +// 使用van-image组件的 + + +// 增加error回调监听,调用全局的handleImgCdnError函数,弟23参数为需要更新的对象以及key值 + + +// 也可以用这种方式 + + + +``` \ No newline at end of file diff --git a/auto-imports.d.ts b/auto-imports.d.ts new file mode 100644 index 0000000..8859963 --- /dev/null +++ b/auto-imports.d.ts @@ -0,0 +1,85 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const afterAll: typeof import('vitest')['afterAll'] + const afterEach: typeof import('vitest')['afterEach'] + const assert: typeof import('vitest')['assert'] + const beforeAll: typeof import('vitest')['beforeAll'] + const beforeEach: typeof import('vitest')['beforeEach'] + const chai: typeof import('vitest')['chai'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const describe: typeof import('vitest')['describe'] + const effectScope: typeof import('vue')['effectScope'] + const expect: typeof import('vitest')['expect'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const it: typeof import('vitest')['it'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const resolveDirective: typeof import('vue')['resolveDirective'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const suite: typeof import('vitest')['suite'] + const test: typeof import('vitest')['test'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useLink: typeof import('vue-router')['useLink'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const vi: typeof import('vitest')['vi'] + const vitest: typeof import('vitest')['vitest'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/blank/blankTransfer.html b/blank/blankTransfer.html new file mode 100644 index 0000000..10f8849 --- /dev/null +++ b/blank/blankTransfer.html @@ -0,0 +1,17 @@ + + + + + + + + blankTransfer + + + + +
+ + + + \ No newline at end of file diff --git a/build/mock/createMockServer.js b/build/mock/createMockServer.js new file mode 100644 index 0000000..be82bdd --- /dev/null +++ b/build/mock/createMockServer.js @@ -0,0 +1,130 @@ +/* eslint-disable node/prefer-global/process */ +const { join } = require('node:path') +const url = require('node:url') +const chokidar = require('chokidar') +const signale = require('signale') +const { match } = require('path-to-regexp') +const { initMock, getMatchMock } = require('./getMockData') +const { winPath } = require('./utils') +const { configBabelRegister } = require('./registerBabel') + +let watcher +function getPaths(cwd) { + const winCwd = winPath(cwd) + const absMockPath = winPath(join(winCwd, 'mock')) + return { + absMockPath, + } +} + +function parseJson(req) { + return new Promise((resolve) => { + let body = '' + let jsonStr = '' + req.on('data', (chunk) => { + body += chunk + }) + req.on('end', () => { + try { + jsonStr = JSON.parse(body) + } + catch (err) { + jsonStr = '' + } + resolve(jsonStr) + }) + }) +} +const defaultOptions = { + watch: true, + cwd: process.cwd(), // 配置需要mock的url + mockUrlList: [], + // 是否开启mock + enable: true, +} +function initWatchMockFiles(paths) { + // chokidar 在 windows 下使用反斜杠组成的 glob 无法正确 watch 文件变动 + // ref: https://github.com/paulmillr/chokidar/issues/777 + const absPagesGlobPath = winPath(join('./mock', '**/*.[jt]s')) + watcher = chokidar.watch([...['./mock'], absPagesGlobPath], { + ignoreInitial: true, + }) + watcher.on('all', (event, file) => { + signale.info(`[${event}] ${file}, reload mock data`) + cleanRequireCache(paths) + }) + process.once('SIGINT', () => { + watcher.close() + }) +} +function cleanRequireCache(paths) { + Object.keys(require.cache).forEach((file) => { + if ( + paths.some((path) => { + return winPath(file).includes(path) + }) + ) + delete require.cache[file] + }) +} +module.exports = function (opts) { + const options = Object.assign({}, defaultOptions, opts) + const { absMockPath } = getPaths(options.cwd) + const paths = [absMockPath] + if (options.enable) { + // vite热更新时关闭上一个进程 + if (watcher) + watcher.close() + + configBabelRegister(paths, { + cwd: options.cwd, + }) + initMock(options) + if (options.watch) + initWatchMockFiles(paths) + } + + return { + name: 'vite:mock', + enforce: 'pre', + configureServer({ middlewares }) { + if (!options.enable) + return + + const middleware = async (req, res, next) => { + const matchMock = getMatchMock(req.url) + if (matchMock) { + let queryParams = {} + + if (req.url) + + // eslint-disable-next-line node/no-deprecated-api + queryParams = url.parse(req.url, true) + + const reqUrl = queryParams.pathname + let query = queryParams.query + if (reqUrl) { + const isGet = req.method && req.method.toUpperCase() === 'GET' + if ((isGet && JSON.stringify(query) === '{}') || !isGet) { + const urlMatch = match(url, { decode: decodeURIComponent }) + const params = urlMatch(reqUrl).params + if (JSON.stringify(params) !== '{}') + query = urlMatch(reqUrl).params || {} + + else + query = queryParams.query || {} + } + } + const body = await parseJson(req) + res.setHeader('Content-Type', 'application/json;charset=utf-8') + res.send = opt => res.end(JSON.stringify(opt)) + res.json = opt => res.end(JSON.stringify(opt)) + matchMock.handler({ url: req.url, body, query, headers: req.headers }, res) + return + } + next() + } + middlewares.use(middleware) + }, + } +} diff --git a/build/mock/getMockData.js b/build/mock/getMockData.js new file mode 100644 index 0000000..553304f --- /dev/null +++ b/build/mock/getMockData.js @@ -0,0 +1,110 @@ +const path = require('node:path') +const glob = require('glob') +const { pathToRegexp } = require('path-to-regexp') +const signale = require('signale') +const { winPath } = require('./utils') + +let _mockconfig = {} +// eslint-disable-next-line node/prefer-global/process +const cwd = process.cwd() +let mockList = [] +// 获取mock文件列表 +function getMockFile() { + let mockFiles = glob + .sync('mock/**/*.[jt]s', { + cwd, + ignore: [], + }) + .map(p => path.join(cwd, p)) + // windows下路径处理 + mockFiles = mockFiles.map(p => winPath(p)) + + return mockFiles +} + +// 获取mock文件数据 +function getMockData(files) { + let mockData = {} + files.forEach((mockFile) => { + try { + const m = require(mockFile) + mockData = Object.assign(mockData, m.default || m) + return mockData + } + catch (e) { + console.error(e) + throw e + } + }) + return mockData +} + +// mock数据格式化 +function parseMockData(config) { + mockList = Object.keys(config).reduce((list, key) => { + const handler = config[key] + const { method, path } = parseMockUrl(key) + const keys = [] + const re = pathToRegexp(path, keys) + list.push({ + method, + path, + re, + keys, + handler: mockHandle(handler), + }) + return list + }, []) +} +function getMatchMock(url) { + return ( + _mockconfig.mockUrlList.findIndex((whiteUrl) => { + if (Object.prototype.toString.call(whiteUrl) === '[object RegExp]') + return whiteUrl.test(url) + + else + return url.includes(whiteUrl) + }) !== -1 && mockList.filter(item => url.includes(item.path)).sort((a, b) => b.path.length - a.path.length)[0] + ) +} +function mockHandle(handler) { + if (Object.prototype.toString.call(handler) === '[object Function]') + return handler + + return (_, res) => { + return res.end(JSON.stringify(handler)) + } +} + +function parseMockUrl(key) { + let method = 'get' + let path = key + if (/\s+/.test(key)) { + const splited = key.split(/\s+/) + method = splited[0].toLowerCase() + path = splited[1] + } + return { + method, + path, + } +} + +function initMock(config) { + try { + _mockconfig = config + mockList = [] + const files = getMockFile() + const mockData = getMockData(files) + parseMockData(mockData) + } + catch (e) { + signale.warn('init mock failed') + signale.fatal(e) + } +} + +module.exports = { + getMatchMock, + initMock, +} diff --git a/build/mock/registerBabel.js b/build/mock/registerBabel.js new file mode 100644 index 0000000..47217ab --- /dev/null +++ b/build/mock/registerBabel.js @@ -0,0 +1,97 @@ +/* eslint-disable node/prefer-global/process */ +const { join, isAbsolute } = require('node:path') +const { existsSync } = require('node:fs') +const { winPath } = require('./utils') + +let files = null + +function initFiles(cwd) { + if (files) + return + files = getConfigPaths(cwd) +} +function getConfigPaths(cwd) { + const env = process.env.UMI_ENV + return [ + join(cwd, 'config/'), + join(cwd, '.umirc.js'), + join(cwd, '.umirc.ts'), + join(cwd, '.umirc.local.js'), + join(cwd, '.umirc.local.ts'), + ...(env ? [join(cwd, `.umirc.${env}.js`), join(cwd, `.umirc.${env}.ts`)] : []), + ] +} + +function addBabelRegisterFiles(extraFiles, { cwd }) { + initFiles(cwd) + files = files.concat(...extraFiles) +} +function addBabelRegister({ cwd }) { + initFiles(cwd) + const only = files.map((f) => { + const fullPath = isAbsolute(f) ? f : join(cwd, f) + return winPath(fullPath) + }) + + let absSrcPath = join(cwd, 'src') + if (!existsSync(absSrcPath)) + absSrcPath = cwd + + registerBabel({ + // only suport glob + // ref: https://babeljs.io/docs/en/next/babel-core.html#configitem-type + only, + babelPreset: [ + require.resolve('@babel/preset-env'), + { + targets: { browsers: ['last 2 versions'] }, + loose: false, + modules: 'commonjs', + exclude: [ + 'transform-typeof-symbol', + 'transform-unicode-regex', + 'transform-sticky-regex', + 'transform-new-target', + 'transform-modules-umd', + 'transform-modules-systemjs', + 'transform-modules-amd', + 'transform-literals', + ], + }, + ], + babelPlugins: [ + [ + require.resolve('babel-plugin-module-resolver'), + { + alias: { + '@': absSrcPath, + }, + }, + ], + ], + }) +} +function configBabelRegister(files, { cwd }) { + addBabelRegisterFiles(files, { + cwd, + }) + addBabelRegister({ + cwd, + }) +} +function registerBabel(opts = {}) { + const { only, ignore, babelPreset, babelPlugins } = opts + if (!process.env.IS_FROM_UMI_TEST) { + require('@babel/register')({ + presets: [require.resolve('@babel/preset-typescript'), babelPreset], + plugins: babelPlugins || [], + only, + ignore, + extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'], + babelrc: false, + cache: false, + }) + } +} + +module.exports = { addBabelRegisterFiles, addBabelRegister, configBabelRegister } diff --git a/build/mock/utils.js b/build/mock/utils.js new file mode 100644 index 0000000..be28273 --- /dev/null +++ b/build/mock/utils.js @@ -0,0 +1,9 @@ +const slash = require('slash2') + +const winPath = function (path) { + return slash(path) +} + +module.exports = { + winPath, +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..028c459 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,3 @@ +// import antfu from '@antfu/eslint-config' + +// export default antfu() diff --git a/index.html b/index.html new file mode 100644 index 0000000..09a4c4c --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + 球帝带 + + +
+ + + + + diff --git a/k8s.yaml b/k8s.yaml new file mode 100644 index 0000000..8a83779 --- /dev/null +++ b/k8s.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mobile +spec: + replicas: + selector: + matchLabels: + app: mobile + template: + metadata: + labels: + app: mobile + spec: + containers: + - name: mobile + image: {{image}} + ports: + - containerPort: 80 + resources: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "4000m" + memory: "4096Mi" + + volumeMounts: + - name: config-volume + mountPath: /app/www/web_config.js + subPath: web_config.js + readOnly: true + volumes: + - name: config-volume + configMap: + name: mobile-config + items: + - key: web_config.js + path: web_config.js + +--- +apiVersion: v1 +kind: Service +metadata: + name: mobile + +spec: + selector: + app: mobile + ports: + - protocol: TCP + name: mobile + port: 80 + targetPort: 80 + diff --git a/mock/data.ts b/mock/data.ts new file mode 100644 index 0000000..b933301 --- /dev/null +++ b/mock/data.ts @@ -0,0 +1,20 @@ +import { defineMockData } from 'vite-plugin-mock-dev-server' + +// defineMockData,用于在 mock 文件中使用 data.ts 作为共享数据源。 +export default defineMockData('proses', [ + { prose: '🔖 躲在某一时间,想念一段时光的掌纹;躲在某一地点,想念一个站在来路也站在去路的,让我牵挂的人。' }, + { prose: '🔖 天空一碧如洗,灿烂的阳光正从密密的松针的缝隙间射下来,形成一束束粗粗细细的光柱,把飘荡着轻纱般薄雾的林荫照得通亮。' }, + { prose: '🔖 这一次相遇,美得彻骨,美得震颤,美得孤绝,美得惊艳。' }, + { prose: '🔖 沉默的状态,能让我感觉到呼吸的自由和自己原来就处于的本色位置。' }, + { prose: '🔖 青春,是一包象征着阳光的向日葵种子,在现在洒下,就会在未来得到收获,那一株株饱含青春的花朵。' }, + { prose: '🔖 燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的日子为什么一去不复返呢?' }, + { prose: '🔖 毕业了,青春在无形之中离去,我们即将翻开人生的另一页。' }, + { prose: '🔖 成长,是每个孩子的权力,也是他们必经的征程,或平坦、或崎岖,有悲欢,有离合。' }, + { prose: '🔖 旧时光里的人和事,琐碎而零乱。我的记忆很模糊,好像大部分都成了一种温馨的符号,静静的沉在我心底。' }, + { prose: '🔖 生活是一部大百科全书,包罗万象;生活是一把六弦琴,弹奏出多重美妙的旋律:生活是一座飞马牌大钟,上紧发条,便会使人获得浓缩的生命。' }, + { prose: '🔖 毕业了,身边的朋友一个个各奔东西,开始学会自己撑起生命的暖色。' }, + { prose: '🔖 已经走到尽头的东西,重生也不过是再一次的消亡。就像所有的开始,其实都只是一个写好了的结局。' }, + { prose: '🔖 下午茶的芬香熏陶着房内的任何一个角落,午后的阳光透过窗帘的间隙洒在木制的桌面上,一份思念随着红茶顺滑至心中。' }, + { prose: '🔖 这里再不是我们的校园,当我们就此离开我们的青葱岁月。' }, + { prose: '🔖 很久找你,一直没有找到,微风吹过的时候,我深深的呼吸,才感觉到你也在陪伴着我呼吸。' }, +]) diff --git a/mock/index.ts b/mock/index.ts new file mode 100644 index 0000000..81d4952 --- /dev/null +++ b/mock/index.ts @@ -0,0 +1,5 @@ +import prose from './modules/prose.mock' + +export default { + ...prose, +} diff --git a/mock/modules/prose.mock.ts b/mock/modules/prose.mock.ts new file mode 100644 index 0000000..7af9bfe --- /dev/null +++ b/mock/modules/prose.mock.ts @@ -0,0 +1,16 @@ +// https://github.com/pengzhanbo/vite-plugin-mock-dev-server +import { defineMock } from 'vite-plugin-mock-dev-server' +import proses from '../data' + +export default defineMock({ + url: '/api/project/prose', + delay: 300, + body: () => { + const prose = proses.value[Math.floor(Math.random() * 8)] + return { + code: 0, + data: prose, + msg: 'success', + } + }, +}) diff --git a/mock/modules/prose.ts b/mock/modules/prose.ts new file mode 100644 index 0000000..8492b71 --- /dev/null +++ b/mock/modules/prose.ts @@ -0,0 +1,23 @@ +const list = [ + { prose: '🔖 躲在某一时间,想念一段时光的掌纹;躲在某一地点,想念一个站在来路也站在去路的,让我牵挂的人。' }, + { prose: '🔖 天空一碧如洗,灿烂的阳光正从密密的松针的缝隙间射下来,形成一束束粗粗细细的光柱,把飘荡着轻纱般薄雾的林荫照得通亮。' }, + { prose: '🔖 这一次相遇,美得彻骨,美得震颤,美得孤绝,美得惊艳。' }, + { prose: '🔖 沉默的状态,能让我感觉到呼吸的自由和自己原来就处于的本色位置。' }, + { prose: '🔖 青春,是一包象征着阳光的向日葵种子,在现在洒下,就会在未来得到收获,那一株株饱含青春的花朵。' }, + { prose: '🔖 燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的日子为什么一去不复返呢?' }, + { prose: '🔖 毕业了,青春在无形之中离去,我们即将翻开人生的另一页。' }, + { prose: '🔖 成长,是每个孩子的权力,也是他们必经的征程,或平坦、或崎岖,有悲欢,有离合。' }, + { prose: '🔖 旧时光里的人和事,琐碎而零乱。我的记忆很模糊,好像大部分都成了一种温馨的符号,静静的沉在我心底。' }, + { prose: '🔖 生活是一部大百科全书,包罗万象;生活是一把六弦琴,弹奏出多重美妙的旋律:生活是一座飞马牌大钟,上紧发条,便会使人获得浓缩的生命。' }, + { prose: '🔖 毕业了,身边的朋友一个个各奔东西,开始学会自己撑起生命的暖色。' }, + { prose: '🔖 已经走到尽头的东西,重生也不过是再一次的消亡。就像所有的开始,其实都只是一个写好了的结局。' }, + { prose: '🔖 下午茶的芬香熏陶着房内的任何一个角落,午后的阳光透过窗帘的间隙洒在木制的桌面上,一份思念随着红茶顺滑至心中。' }, + { prose: '🔖 这里再不是我们的校园,当我们就此离开我们的青葱岁月。' }, + { prose: '🔖 很久找你,一直没有找到,微风吹过的时候,我深深的呼吸,才感觉到你也在陪伴着我呼吸。' }, +] + +export default { + 'GET /api/project/prose': (req, res) => { + res.json(list[Math.floor(Math.random() * 8)]) + }, +} diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..16c3e09 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,49 @@ +user nginx; +worker_processes auto; +pcre_jit on; +error_log /dev/stderr; + + +events { + worker_connections 2000; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /dev/stdout main; + + sendfile on; + keepalive_timeout 65; + gzip on; # 开启gzip压缩 + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; # 压缩级别 + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/x-httpd-php image/jpeg image/gif image/png text/html; + + server { + listen 80; + server_name _; + + try_files $uri $uri/ /index.html; + root /app/www; + index index.html; + + location /apple-app-site-association { + add_header Content-Type application/json; + + } + + # 配置Nginx以提供预先压缩的.gz文件(如果存在) + location ~* \.(js|css|json|txt|html|ico|svg)(\.gz)?$ { + gzip_static on; # 优先提供.gz文件 + expires max; + add_header Cache-Control public; + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a7bb6a2 --- /dev/null +++ b/package.json @@ -0,0 +1,123 @@ +{ + "name": "vue3-vant-mobile", + "type": "module", + "version": "1.3.0", + "packageManager": "pnpm@7.14.0", + "description": "template for Vue3 Vant Mobile", + "license": "MIT", + "scripts": { + "dev": "cross-env MOCK_SERVER_PORT=8086 vite --mode=dev", + "local": "cross-env MOCK_SERVER_PORT=8086 vite --mode=localhost", + "sit": "cross-env MOCK_SERVER_PORT=8086 vite --mode=sit", + "uat": "cross-env MOCK_SERVER_PORT=8086 vite --mode=uat", + "prod": "cross-env MOCK_SERVER_PORT=8086 vite --mode=prod", + "build": "vite build --mode prod", + "build:dev": "vue-tsc --noEmit && vite build --mode dev", + "build:local": "vue-tsc --noEmit && vite build --mode localhost", + "build:sit": "vite build --mode sit", + "build:uat": "vite build --mode uat", + "preview": "npm run build && vite preview", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test": "vitest", + "prepare": "npx husky install" + }, + "dependencies": { + "@emoji-mart/data": "^1.1.2", + "@fingerprintjs/fingerprintjs": "^4.3.0", + "@vant/touch-emulator": "^1.4.0", + "@vant/use": "^1.6.0", + "@vueuse/core": "^10.7.2", + "ant-design-vue": "^4.2.1", + "axios": "^1.6.2", + "better-scroll": "^2.5.1", + "callapp-lib": "^3.5.3", + "crypto-js": "^4.2.0", + "dayjs": "^1.11.10", + "echarts": "^5.4.3", + "emoji-mart": "^5.5.2", + "html2canvas": "^1.4.1", + "js-base64": "^3.7.5", + "js-pinyin": "^0.2.5", + "jsencrypt": "^3.3.2", + "jsqr": "^1.4.0", + "llqrcode": "^1.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "mitt": "^3.0.1", + "nativeshare": "^2.1.5", + "nprogress": "^0.2.0", + "pinia": "^2.1.7", + "pinia-plugin-persistedstate": "^3.2.1", + "qrcode-vue3": "^1.6.8", + "recorder-core": "^1.3.23122400", + "resize-detector": "^0.3.0", + "store": "^2.0.12", + "vant": "^4.8.1", + "vconsole": "^3.15.1", + "video.js": "^8.9.0", + "vite-plugin-mock-dev-server": "^1.4.3", + "vue": "^3.2.37", + "vue-clipboard3": "^2.0.0", + "vue-i18n": "^9.9.0", + "vue-router": "^4.2.5", + "vue-router-better-scroller": "^0.0.0", + "vue-virtual-scroller": "^2.0.0-beta.8", + "vue3-google-login": "^2.0.26", + "vue3-lottie": "^3.3.0", + "xgplayer": "^3.0.16" + }, + "devDependencies": { + "@antfu/eslint-config": "2.4.6", + "@jlongster/sql.js": "^1.6.7", + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.10.5", + "@types/nprogress": "^0.2.3", + "@types/store": "^2.0.5", + "@unocss/preset-rem-to-px": "^0.58.0", + "@vitejs/plugin-legacy": "^5.2.0", + "@vitejs/plugin-vue": "^4.5.2", + "@vitejs/plugin-vue-jsx": "^3.1.0", + "absurd-sql-optimized": "^0.0.1", + "autoprefixer": "^10.4.16", + "consola": "^3.2.3", + "cross-env": "^7.0.3", + "eslint": "^8.56.0", + "husky": "^8.0.3", + "less": "^4.2.0", + "mockjs": "^1.1.0", + "open-im-sdk-wasm": "^3.5.2", + "pako": "^2.1.0", + "postcss-mobile-forever": "^4.0.0", + "rollup": "^4.9.1", + "rollup-plugin-visualizer": "^5.11.0", + "rpc-shooter": "^0.0.14", + "squel": "^5.13.0", + "terser": "^5.26.0", + "typescript": "^5.3.3", + "unocss": "^0.58.0", + "unplugin-auto-import": "^0.17.2", + "unplugin-vue-components": "^0.26.0", + "uuid": "^9.0.1", + "vite": "^5.0.10", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-image-optimizer": "^1.1.8", + "vite-plugin-mock-dev-server": "^1.4.3", + "vite-plugin-vconsole": "^2.0.1", + "vitest": "^1.1.0", + "vue-clipboard3": "^2.0.0", + "vue-tsc": "^1.8.25", + "worker-loader": "^3.0.8", + "worker-plugin": "^5.0.1" + }, + "pnpm": { + "peerDependencyRules": { + "ignoreMissing": [ + "postcss" + ], + "allowedVersions": { + "rollup": "^4.x" + } + } + } +} diff --git a/public/api_client.js b/public/api_client.js new file mode 100644 index 0000000..f2e8095 --- /dev/null +++ b/public/api_client.js @@ -0,0 +1,32 @@ +(function(){var aa="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a},ba=function(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b=e}});g("Object.is",function(a){return a?a:function(b,c){return b===c?0!==b||1/b===1/c:b!==b&&c!==c}}); +g("Array.prototype.includes",function(a){return a?a:function(b,c){var d=this;d instanceof String&&(d=String(d));var e=d.length;c=c||0;for(0>c&&(c=Math.max(c+e,0));c>>0),da=0,r=function(a){return a};var y=function(a){if(x!==x)throw Error("SafeUrl is not meant to be built directly");this.v=a};y.prototype.toString=function(){return this.v.toString()};var x={};new y("about:invalid#zClosurez");new y("about:blank");var A={},B=function(){if(A!==A)throw Error("SafeStyle is not meant to be built directly");this.s=""};B.prototype.toString=function(){return this.s.toString()};new B;var C={},ea=function(){if(C!==C)throw Error("SafeStyleSheet is not meant to be built directly");this.o=""};ea.prototype.toString=function(){return this.o.toString()};new ea;var fa={},ha=function(){var a=m.trustedTypes&&m.trustedTypes.emptyHTML||"";if(fa!==fa)throw Error("SafeHtml is not meant to be built directly");this.l=a};ha.prototype.toString=function(){return this.l.toString()};new ha;/* + + SPDX-License-Identifier: Apache-2.0 +*/ +new y("about:blank");new y("about:invalid#zClosurez");var ia=[],ja=function(a){console.warn("A URL with content '"+a+"' was sanitized away.")};-1===ia.indexOf(ja)&&ia.push(ja);/* + gapi.loader.OBJECT_CREATE_TEST_OVERRIDE &&*/ +var F=window,G=document,ka=F.location,la=function(){},ma=/\[native code\]/,H=function(a,b,c){return a[b]=a[b]||c},na=function(a){a=a.sort();for(var b=[],c=void 0,d=0;df}f&&c.push(e)}return c},Ja=function(){var a=K.nonce;return void 0!==a?a&&a===String(a)&&a.match(Ha)?a:K.nonce=null:G.querySelector?(a=G.querySelector("script[nonce]"))?(a=a.nonce||a.getAttribute("nonce")||"",a&&a===String(a)&&a.match(Ha)?K.nonce=a:K.nonce=null):null:null},La=function(a){if("loading"!=G.readyState)Ka(a); +else{var b=Ja(),c="";null!==b&&(c=' nonce="'+b+'"');a="<"+X+' src="'+encodeURI(a)+'"'+c+">";G.write(Y?Y.createHTML(a):a)}},Ka=function(a){var b=G.createElement(X);b.setAttribute("src",Y?Y.createScriptURL(a):a);a=Ja();null!==a&&b.setAttribute("nonce",a);b.async="true";(a=G.getElementsByTagName(X)[0])?a.parentNode.insertBefore(b,a):(G.head||G.body||G.documentElement).appendChild(b)},Na=function(a,b,c){Ma(function(){var d=b===oa()?H(J,"_",I()):I();d=H(N(b),"_",d);a(d)},c)},Pa=function(a,b){var c= +b||{};"function"==typeof b&&(c={},c.callback=b);var d=(b=c)&&b._c;if(d)for(var e=0;e=a.length||(a=a.substr(b).split(":").filter(function(d){return!["api","platform"].includes(d)}),c.features=a))}]);qa.bs0=window.gapi._bs||(new Date).getTime();P("bs0");qa.bs1=(new Date).getTime();P("bs1");delete window.gapi._bs;window.gapi.load("",{callback:window["gapi_onload"],_c:{url:"https://apis.google.com/js/api:client.js",jsl:{ci:{"oauth-flow":{authUrl:"https://accounts.google.com/o/oauth2/auth",proxyUrl:"https://accounts.google.com/o/oauth2/postmessageRelay",disableOpt:!0,idpIframeUrl:"https://accounts.google.com/o/oauth2/iframe",usegapi:!1},debug:{reportExceptionRate:1,forceIm:!1,rethrowException:!0,host:"https://apis.google.com"},enableMultilogin:!0,"googleapis.config":{auth:{useFirstPartyAuthV2:!0},root:"https://content.googleapis.com","root-1p":"https://clients6.google.com"},inline:{css:1}, +disableRealtimeCallback:!1,drive_share:{skipInitCommand:!0},csi:{rate:.01},client:{cors:!1},signInDeprecation:{rate:0},include_granted_scopes:!0,llang:"zh",iframes:{youtube:{params:{location:["search","hash"]},url:":socialhost:/:session_prefix:_/widget/render/youtube?usegapi=1",methods:["scroll","openwindow"]},ytsubscribe:{url:"https://www.youtube.com/subscribe_embed?usegapi=1"},plus_circle:{params:{url:""},url:":socialhost:/:session_prefix::se:_/widget/plus/circle?usegapi=1"},plus_share:{params:{url:""}, +url:":socialhost:/:session_prefix::se:_/+1/sharebutton?plusShare=true&usegapi=1"},rbr_s:{params:{url:""},url:":socialhost:/:session_prefix::se:_/widget/render/recobarsimplescroller"},":source:":"3p",playemm:{url:"https://play.google.com/work/embedded/search?usegapi=1&usegapi=1"},savetoandroidpay:{url:"https://pay.google.com/gp/v/widget/save"},blogger:{params:{location:["search","hash"]},url:":socialhost:/:session_prefix:_/widget/render/blogger?usegapi=1",methods:["scroll","openwindow"]},evwidget:{params:{url:""}, +url:":socialhost:/:session_prefix:_/events/widget?usegapi=1"},partnersbadge:{url:"https://www.gstatic.com/partners/badge/templates/badge.html?usegapi=1"},dataconnector:{url:"https://dataconnector.corp.google.com/:session_prefix:ui/widgetview?usegapi=1"},surveyoptin:{url:"https://www.google.com/shopping/customerreviews/optin?usegapi=1"},":socialhost:":"https://apis.google.com",shortlists:{url:""},hangout:{url:"https://talkgadget.google.com/:session_prefix:talkgadget/_/widget"},plus_followers:{params:{url:""}, +url:":socialhost:/_/im/_/widget/render/plus/followers?usegapi=1"},post:{params:{url:""},url:":socialhost:/:session_prefix::im_prefix:_/widget/render/post?usegapi=1"},signin:{params:{url:""},url:":socialhost:/:session_prefix:_/widget/render/signin?usegapi=1",methods:["onauth"]},rbr_i:{params:{url:""},url:":socialhost:/:session_prefix::se:_/widget/render/recobarinvitation"},share:{url:":socialhost:/:session_prefix::im_prefix:_/widget/render/share?usegapi=1"},plusone:{params:{count:"",size:"",url:""}, +url:":socialhost:/:session_prefix::se:_/+1/fastbutton?usegapi=1"},comments:{params:{location:["search","hash"]},url:":socialhost:/:session_prefix:_/widget/render/comments?usegapi=1",methods:["scroll","openwindow"]},":im_socialhost:":"https://plus.googleapis.com",backdrop:{url:"https://clients3.google.com/cast/chromecast/home/widget/backdrop?usegapi=1"},visibility:{params:{url:""},url:":socialhost:/:session_prefix:_/widget/render/visibility?usegapi=1"},autocomplete:{params:{url:""},url:":socialhost:/:session_prefix:_/widget/render/autocomplete"}, +":signuphost:":"https://plus.google.com",ratingbadge:{url:"https://www.google.com/shopping/customerreviews/badge?usegapi=1"},appcirclepicker:{url:":socialhost:/:session_prefix:_/widget/render/appcirclepicker"},follow:{url:":socialhost:/:session_prefix:_/widget/render/follow?usegapi=1"},community:{url:":ctx_socialhost:/:session_prefix::im_prefix:_/widget/render/community?usegapi=1"},sharetoclassroom:{url:"https://classroom.google.com/sharewidget?usegapi=1"},ytshare:{params:{url:""},url:":socialhost:/:session_prefix:_/widget/render/ytshare?usegapi=1"}, +plus:{url:":socialhost:/:session_prefix:_/widget/render/badge?usegapi=1"},family_creation:{params:{url:""},url:"https://families.google.com/webcreation?usegapi=1&usegapi=1"},commentcount:{url:":socialhost:/:session_prefix:_/widget/render/commentcount?usegapi=1"},configurator:{url:":socialhost:/:session_prefix:_/plusbuttonconfigurator?usegapi=1"},zoomableimage:{url:"https://ssl.gstatic.com/microscope/embed/"},appfinder:{url:"https://workspace.google.com/:session_prefix:marketplace/appfinder?usegapi=1"},savetowallet:{url:"https://pay.google.com/gp/v/widget/save"}, +person:{url:":socialhost:/:session_prefix:_/widget/render/person?usegapi=1"},savetodrive:{url:"https://drive.google.com/savetodrivebutton?usegapi=1",methods:["save"]},page:{url:":socialhost:/:session_prefix:_/widget/render/page?usegapi=1"},card:{url:":socialhost:/:session_prefix:_/hovercard/card"}}},h:"m;/_/scs/abc-static/_/js/k=gapi.lb.zh_CN.8PFwol1E9Vw.O/d=1/rs=AHpOoo-d42IG9Jt_xb6i4iVVzCZXFdBmaQ/m=__features__",u:"https://apis.google.com/js/api:client.js",hee:!0,dpo:!1,le:["scs"],glrp:false},platform:"backdrop blogger comments commentcount community donation family_creation follow hangout health page partnersbadge person playemm playreview plus plusone post ratingbadge savetoandroidpay savetodrive savetowallet sharetoclassroom shortlists signin2 surveyoptin visibility youtube ytsubscribe zoomableimage".split(" "), +annotation:["interactivepost","recobar","signin2","autocomplete"]}});}).call(this); diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..8ed3b2d Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/openIM.wasm.gzip b/public/openIM.wasm.gzip new file mode 100644 index 0000000..69616be Binary files /dev/null and b/public/openIM.wasm.gzip differ diff --git a/public/sql-wasm.wasm b/public/sql-wasm.wasm new file mode 100644 index 0000000..18b392e Binary files /dev/null and b/public/sql-wasm.wasm differ diff --git a/public/wasm_exec.js b/public/wasm_exec.js new file mode 100644 index 0000000..e9d82c8 --- /dev/null +++ b/public/wasm_exec.js @@ -0,0 +1,652 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// @ts-nocheck + +;(() => { + // Map multiple JavaScript environments to a single common API, + // preferring web standards over Node.js API. + // + // Environments considered: + // - Browsers + // - Node.js + // - Electron + // - Parcel + // - Webpack + + if (typeof window !== 'undefined') { + window.global = window + } else if (typeof self !== 'undefined') { + self.global = self + } else { + throw new Error('cannot export Go (neither global, window nor self is defined)') + } + + const enosys = () => { + const err = new Error('not implemented') + err.code = 'ENOSYS' + return err + } + + if (!global.fs) { + let outputBuf = '' + global.fs = { + constants: { + O_WRONLY: -1, + O_RDWR: -1, + O_CREAT: -1, + O_TRUNC: -1, + O_APPEND: -1, + O_EXCL: -1 + }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf) + const nl = outputBuf.lastIndexOf('\n') + if (nl != -1) { + // console.log(outputBuf.substr(0, nl)) + outputBuf = outputBuf.substr(nl + 1) + } + return buf.length + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()) + return + } + const n = this.writeSync(fd, buf) + callback(null, n) + }, + chmod(path, mode, callback) { + callback(enosys()) + }, + chown(path, uid, gid, callback) { + callback(enosys()) + }, + close(fd, callback) { + callback(enosys()) + }, + fchmod(fd, mode, callback) { + callback(enosys()) + }, + fchown(fd, uid, gid, callback) { + callback(enosys()) + }, + fstat(fd, callback) { + callback(enosys()) + }, + fsync(fd, callback) { + callback(null) + }, + ftruncate(fd, length, callback) { + callback(enosys()) + }, + lchown(path, uid, gid, callback) { + callback(enosys()) + }, + link(path, link, callback) { + callback(enosys()) + }, + lstat(path, callback) { + callback(enosys()) + }, + mkdir(path, perm, callback) { + callback(enosys()) + }, + open(path, flags, mode, callback) { + callback(enosys()) + }, + read(fd, buffer, offset, length, position, callback) { + callback(enosys()) + }, + readdir(path, callback) { + callback(enosys()) + }, + readlink(path, callback) { + callback(enosys()) + }, + rename(from, to, callback) { + callback(enosys()) + }, + rmdir(path, callback) { + callback(enosys()) + }, + stat(path, callback) { + callback(enosys()) + }, + symlink(path, link, callback) { + callback(enosys()) + }, + truncate(path, length, callback) { + callback(enosys()) + }, + unlink(path, callback) { + callback(enosys()) + }, + utimes(path, atime, mtime, callback) { + callback(enosys()) + } + } + } + + if (!global.process) { + global.process = { + getuid() { + return -1 + }, + getgid() { + return -1 + }, + geteuid() { + return -1 + }, + getegid() { + return -1 + }, + getgroups() { + throw enosys() + }, + pid: -1, + ppid: -1, + umask() { + throw enosys() + }, + cwd() { + throw enosys() + }, + chdir() { + throw enosys() + } + } + } + + if (!global.crypto) { + throw new Error('global.crypto is not available, polyfill required (getRandomValues only)') + } + + if (!global.performance) { + global.performance = { + now() { + const [sec, nsec] = process.hrtime() + return sec * 1000 + nsec / 1000000 + } + } + } + + if (!global.TextEncoder) { + throw new Error('global.TextEncoder is not available, polyfill required') + } + + if (!global.TextDecoder) { + throw new Error('global.TextDecoder is not available, polyfill required') + } + + // End of polyfills for common API. + + const encoder = new TextEncoder('utf-8') + const decoder = new TextDecoder('utf-8') + + global.Go = class { + constructor() { + this.argv = ['js'] + this.env = {} + this.exit = (code) => { + if (code !== 0) { + console.warn('exit code:', code) + } + } + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve + }) + this._pendingEvent = null + this._scheduledTimeouts = new Map() + this._nextCallbackTimeoutID = 1 + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true) + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true) + } + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true) + const high = this.mem.getInt32(addr + 4, true) + return low + high * 4294967296 + } + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true) + if (f === 0) { + return undefined + } + if (!isNaN(f)) { + return f + } + + const id = this.mem.getUint32(addr, true) + return this._values[id] + } + + const storeValue = (addr, v) => { + const nanHead = 0x7ff80000 + + if (typeof v === 'number' && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true) + this.mem.setUint32(addr, 0, true) + return + } + this.mem.setFloat64(addr, v, true) + return + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true) + return + } + + let id = this._ids.get(v) + if (id === undefined) { + id = this._idPool.pop() + if (id === undefined) { + id = this._values.length + } + this._values[id] = v + this._goRefCounts[id] = 0 + this._ids.set(v, id) + } + this._goRefCounts[id]++ + let typeFlag = 0 + switch (typeof v) { + case 'object': + if (v !== null) { + typeFlag = 1 + } + break + case 'string': + typeFlag = 2 + break + case 'symbol': + typeFlag = 3 + break + case 'function': + typeFlag = 4 + break + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true) + this.mem.setUint32(addr, id, true) + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0) + const len = getInt64(addr + 8) + return new Uint8Array(this._inst.exports.mem.buffer, array, len) + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0) + const len = getInt64(addr + 8) + const a = new Array(len) + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8) + } + return a + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0) + const len = getInt64(addr + 8) + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)) + } + + const timeOrigin = Date.now() - performance.now() + this.importObject = { + go: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + 'runtime.wasmExit': (sp) => { + sp >>>= 0 + const code = this.mem.getInt32(sp + 8, true) + this.exited = true + delete this._inst + delete this._values + delete this._goRefCounts + delete this._ids + delete this._idPool + this.exit(code) + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + 'runtime.wasmWrite': (sp) => { + sp >>>= 0 + const fd = getInt64(sp + 8) + const p = getInt64(sp + 16) + const n = this.mem.getInt32(sp + 24, true) + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)) + }, + + // func resetMemoryDataView() + 'runtime.resetMemoryDataView': (sp) => { + sp >>>= 0 + this.mem = new DataView(this._inst.exports.mem.buffer) + }, + + // func nanotime1() int64 + 'runtime.nanotime1': (sp) => { + sp >>>= 0 + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000) + }, + + // func walltime() (sec int64, nsec int32) + 'runtime.walltime': (sp) => { + sp >>>= 0 + const msec = new Date().getTime() + setInt64(sp + 8, msec / 1000) + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true) + }, + + // func scheduleTimeoutEvent(delay int64) int32 + 'runtime.scheduleTimeoutEvent': (sp) => { + sp >>>= 0 + const id = this._nextCallbackTimeoutID + this._nextCallbackTimeoutID++ + this._scheduledTimeouts.set( + id, + setTimeout( + () => { + this._resume() + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn('scheduleTimeoutEvent: missed timeout event') + this._resume() + } + }, + getInt64(sp + 8) + 1 // setTimeout has been seen to fire up to 1 millisecond early + ) + ) + this.mem.setInt32(sp + 16, id, true) + }, + + // func clearTimeoutEvent(id int32) + 'runtime.clearTimeoutEvent': (sp) => { + sp >>>= 0 + const id = this.mem.getInt32(sp + 8, true) + clearTimeout(this._scheduledTimeouts.get(id)) + this._scheduledTimeouts.delete(id) + }, + + // func getRandomData(r []byte) + 'runtime.getRandomData': (sp) => { + sp >>>= 0 + crypto.getRandomValues(loadSlice(sp + 8)) + }, + + // func finalizeRef(v ref) + 'syscall/js.finalizeRef': (sp) => { + sp >>>= 0 + const id = this.mem.getUint32(sp + 8, true) + this._goRefCounts[id]-- + if (this._goRefCounts[id] === 0) { + const v = this._values[id] + this._values[id] = null + this._ids.delete(v) + this._idPool.push(id) + } + }, + + // func stringVal(value string) ref + 'syscall/js.stringVal': (sp) => { + sp >>>= 0 + storeValue(sp + 24, loadString(sp + 8)) + }, + + // func valueGet(v ref, p string) ref + 'syscall/js.valueGet': (sp) => { + sp >>>= 0 + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 32, result) + }, + + // func valueSet(v ref, p string, x ref) + 'syscall/js.valueSet': (sp) => { + sp >>>= 0 + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)) + }, + + // func valueDelete(v ref, p string) + 'syscall/js.valueDelete': (sp) => { + sp >>>= 0 + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)) + }, + + // func valueIndex(v ref, i int) ref + 'syscall/js.valueIndex': (sp) => { + sp >>>= 0 + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))) + }, + + // valueSetIndex(v ref, i int, x ref) + 'syscall/js.valueSetIndex': (sp) => { + sp >>>= 0 + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)) + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + 'syscall/js.valueCall': (sp) => { + sp >>>= 0 + try { + const v = loadValue(sp + 8) + const m = Reflect.get(v, loadString(sp + 16)) + const args = loadSliceOfValues(sp + 32) + const result = Reflect.apply(m, v, args) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 56, result) + this.mem.setUint8(sp + 64, 1) + } catch (err) { + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 56, err) + this.mem.setUint8(sp + 64, 0) + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + 'syscall/js.valueInvoke': (sp) => { + sp >>>= 0 + try { + const v = loadValue(sp + 8) + const args = loadSliceOfValues(sp + 16) + const result = Reflect.apply(v, undefined, args) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, result) + this.mem.setUint8(sp + 48, 1) + } catch (err) { + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, err) + this.mem.setUint8(sp + 48, 0) + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + 'syscall/js.valueNew': (sp) => { + sp >>>= 0 + try { + const v = loadValue(sp + 8) + const args = loadSliceOfValues(sp + 16) + const result = Reflect.construct(v, args) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, result) + this.mem.setUint8(sp + 48, 1) + } catch (err) { + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, err) + this.mem.setUint8(sp + 48, 0) + } + }, + + // func valueLength(v ref) int + 'syscall/js.valueLength': (sp) => { + sp >>>= 0 + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)) + }, + + // valuePrepareString(v ref) (ref, int) + 'syscall/js.valuePrepareString': (sp) => { + sp >>>= 0 + const str = encoder.encode(String(loadValue(sp + 8))) + storeValue(sp + 16, str) + setInt64(sp + 24, str.length) + }, + + // valueLoadString(v ref, b []byte) + 'syscall/js.valueLoadString': (sp) => { + sp >>>= 0 + const str = loadValue(sp + 8) + loadSlice(sp + 16).set(str) + }, + + // func valueInstanceOf(v ref, t ref) bool + 'syscall/js.valueInstanceOf': (sp) => { + sp >>>= 0 + this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16) ? 1 : 0) + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + 'syscall/js.copyBytesToGo': (sp) => { + sp >>>= 0 + const dst = loadSlice(sp + 8) + const src = loadValue(sp + 32) + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0) + return + } + const toCopy = src.subarray(0, dst.length) + dst.set(toCopy) + setInt64(sp + 40, toCopy.length) + this.mem.setUint8(sp + 48, 1) + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + 'syscall/js.copyBytesToJS': (sp) => { + sp >>>= 0 + const dst = loadValue(sp + 8) + const src = loadSlice(sp + 16) + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0) + return + } + const toCopy = src.subarray(0, dst.length) + dst.set(toCopy) + setInt64(sp + 40, toCopy.length) + this.mem.setUint8(sp + 48, 1) + }, + + debug: (value) => { + console.log(value) + } + } + } + } + + async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error('Go.run: WebAssembly.Instance expected') + } + this._inst = instance + this.mem = new DataView(this._inst.exports.mem.buffer) + this._values = [ + // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + global, + this + ] + this._goRefCounts = new Array(this._values.length).fill(Infinity) // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ + // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [global, 5], + [this, 6] + ]) + this._idPool = [] // unused ids that have been garbage collected + this.exited = false // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096 + + const strPtr = (str) => { + const ptr = offset + const bytes = encoder.encode(str + '\0') + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes) + offset += bytes.length + if (offset % 8 !== 0) { + offset += 8 - (offset % 8) + } + return ptr + } + + const argc = this.argv.length + + const argvPtrs = [] + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)) + }) + argvPtrs.push(0) + + const keys = Object.keys(this.env).sort() + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)) + }) + argvPtrs.push(0) + + const argv = offset + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true) + this.mem.setUint32(offset + 4, 0, true) + offset += 8 + }) + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192 + if (offset >= wasmMinDataAddr) { + throw new Error('total length of command line and environment variables exceeds limit') + } + + this._inst.exports.run(argc, argv) + if (this.exited) { + this._resolveExitPromise() + } + await this._exitPromise + } + + _resume() { + if (this.exited) { + throw new Error('Go program has already exited') + } + this._inst.exports.resume() + if (this.exited) { + this._resolveExitPromise() + } + } + + _makeFuncWrapper(id) { + const go = this + return function () { + const event = { id: id, this: this, args: arguments } + go._pendingEvent = event + go._resume() + return event.result + } + } + } +})() diff --git a/public/webPushSdk.produce.min.2.1.6.js b/public/webPushSdk.produce.min.2.1.6.js new file mode 100644 index 0000000..9d65da0 --- /dev/null +++ b/public/webPushSdk.produce.min.2.1.6.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.MTpushInterface=e():t.MTpushInterface=e()}(self,(()=>(()=>{var t={452:function(t,e,i){var n;t.exports=(n=i(249),i(269),i(214),i(888),i(109),function(){var t=n,e=t.lib.BlockCipher,i=t.algo,s=[],o=[],r=[],a=[],c=[],u=[],d=[],h=[],l=[],f=[];!function(){for(var t=[],e=0;e<256;e++)t[e]=e<128?e<<1:e<<1^283;var i=0,n=0;for(e=0;e<256;e++){var p=n^n<<1^n<<2^n<<3^n<<4;p=p>>>8^255&p^99,s[i]=p,o[p]=i;var m=t[i],g=t[m],b=t[g],w=257*t[p]^16843008*p;r[i]=w<<24|w>>>8,a[i]=w<<16|w>>>16,c[i]=w<<8|w>>>24,u[i]=w,w=16843009*b^65537*g^257*m^16843008*i,d[p]=w<<24|w>>>8,h[p]=w<<16|w>>>16,l[p]=w<<8|w>>>24,f[p]=w,i?(i=m^t[t[t[b^m]]],n^=t[t[n]]):i=n=1}}();var p=[0,1,2,4,8,16,32,64,128,27,54],m=i.AES=e.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,e=t.words,i=t.sigBytes/4,n=4*((this._nRounds=i+6)+1),o=this._keySchedule=[],r=0;r6&&r%i==4&&(u=s[u>>>24]<<24|s[u>>>16&255]<<16|s[u>>>8&255]<<8|s[255&u]):(u=s[(u=u<<8|u>>>24)>>>24]<<24|s[u>>>16&255]<<16|s[u>>>8&255]<<8|s[255&u],u^=p[r/i|0]<<24),o[r]=o[r-i]^u);for(var a=this._invKeySchedule=[],c=0;c>>24]]^h[s[u>>>16&255]]^l[s[u>>>8&255]]^f[s[255&u]]}}},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._keySchedule,r,a,c,u,s)},decryptBlock:function(t,e){var i=t[e+1];t[e+1]=t[e+3],t[e+3]=i,this._doCryptBlock(t,e,this._invKeySchedule,d,h,l,f,o),i=t[e+1],t[e+1]=t[e+3],t[e+3]=i},_doCryptBlock:function(t,e,i,n,s,o,r,a){for(var c=this._nRounds,u=t[e]^i[0],d=t[e+1]^i[1],h=t[e+2]^i[2],l=t[e+3]^i[3],f=4,p=1;p>>24]^s[d>>>16&255]^o[h>>>8&255]^r[255&l]^i[f++],g=n[d>>>24]^s[h>>>16&255]^o[l>>>8&255]^r[255&u]^i[f++],b=n[h>>>24]^s[l>>>16&255]^o[u>>>8&255]^r[255&d]^i[f++],w=n[l>>>24]^s[u>>>16&255]^o[d>>>8&255]^r[255&h]^i[f++];u=m,d=g,h=b,l=w}m=(a[u>>>24]<<24|a[d>>>16&255]<<16|a[h>>>8&255]<<8|a[255&l])^i[f++],g=(a[d>>>24]<<24|a[h>>>16&255]<<16|a[l>>>8&255]<<8|a[255&u])^i[f++],b=(a[h>>>24]<<24|a[l>>>16&255]<<16|a[u>>>8&255]<<8|a[255&d])^i[f++],w=(a[l>>>24]<<24|a[u>>>16&255]<<16|a[d>>>8&255]<<8|a[255&h])^i[f++],t[e]=m,t[e+1]=g,t[e+2]=b,t[e+3]=w},keySize:8});t.AES=e._createHelper(m)}(),n.AES)},109:function(t,e,i){var n;t.exports=(n=i(249),i(888),void(n.lib.Cipher||function(t){var e=n,i=e.lib,s=i.Base,o=i.WordArray,r=i.BufferedBlockAlgorithm,a=e.enc,c=(a.Utf8,a.Base64),u=e.algo.EvpKDF,d=i.Cipher=r.extend({cfg:s.extend(),createEncryptor:function(t,e){return this.create(this._ENC_XFORM_MODE,t,e)},createDecryptor:function(t,e){return this.create(this._DEC_XFORM_MODE,t,e)},init:function(t,e,i){this.cfg=this.cfg.extend(i),this._xformMode=t,this._key=e,this.reset()},reset:function(){r.reset.call(this),this._doReset()},process:function(t){return this._append(t),this._process()},finalize:function(t){return t&&this._append(t),this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){function t(t){return"string"==typeof t?v:b}return function(e){return{encrypt:function(i,n,s){return t(n).encrypt(e,i,n,s)},decrypt:function(i,n,s){return t(n).decrypt(e,i,n,s)}}}}()}),h=(i.StreamCipher=d.extend({_doFinalize:function(){return this._process(!0)},blockSize:1}),e.mode={}),l=i.BlockCipherMode=s.extend({createEncryptor:function(t,e){return this.Encryptor.create(t,e)},createDecryptor:function(t,e){return this.Decryptor.create(t,e)},init:function(t,e){this._cipher=t,this._iv=e}}),f=h.CBC=function(){var e=l.extend();function i(e,i,n){var s,o=this._iv;o?(s=o,this._iv=t):s=this._prevBlock;for(var r=0;r>>2];t.sigBytes-=e}},m=(i.BlockCipher=d.extend({cfg:d.cfg.extend({mode:f,padding:p}),reset:function(){var t;d.reset.call(this);var e=this.cfg,i=e.iv,n=e.mode;this._xformMode==this._ENC_XFORM_MODE?t=n.createEncryptor:(t=n.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==t?this._mode.init(this,i&&i.words):(this._mode=t.call(n,this,i&&i.words),this._mode.__creator=t)},_doProcessBlock:function(t,e){this._mode.processBlock(t,e)},_doFinalize:function(){var t,e=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(e.pad(this._data,this.blockSize),t=this._process(!0)):(t=this._process(!0),e.unpad(t)),t},blockSize:4}),i.CipherParams=s.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}})),g=(e.format={}).OpenSSL={stringify:function(t){var e=t.ciphertext,i=t.salt;return(i?o.create([1398893684,1701076831]).concat(i).concat(e):e).toString(c)},parse:function(t){var e,i=c.parse(t),n=i.words;return 1398893684==n[0]&&1701076831==n[1]&&(e=o.create(n.slice(2,4)),n.splice(0,4),i.sigBytes-=16),m.create({ciphertext:i,salt:e})}},b=i.SerializableCipher=s.extend({cfg:s.extend({format:g}),encrypt:function(t,e,i,n){n=this.cfg.extend(n);var s=t.createEncryptor(i,n),o=s.finalize(e),r=s.cfg;return m.create({ciphertext:o,key:i,iv:r.iv,algorithm:t,mode:r.mode,padding:r.padding,blockSize:t.blockSize,formatter:n.format})},decrypt:function(t,e,i,n){return n=this.cfg.extend(n),e=this._parse(e,n.format),t.createDecryptor(i,n).finalize(e.ciphertext)},_parse:function(t,e){return"string"==typeof t?e.parse(t,this):t}}),w=(e.kdf={}).OpenSSL={execute:function(t,e,i,n){n||(n=o.random(8));var s=u.create({keySize:e+i}).compute(t,n),r=o.create(s.words.slice(e),4*i);return s.sigBytes=4*e,m.create({key:s,iv:r,salt:n})}},v=i.PasswordBasedCipher=b.extend({cfg:b.cfg.extend({kdf:w}),encrypt:function(t,e,i,n){var s=(n=this.cfg.extend(n)).kdf.execute(i,t.keySize,t.ivSize);n.iv=s.iv;var o=b.encrypt.call(this,t,e,s.key,n);return o.mixIn(s),o},decrypt:function(t,e,i,n){n=this.cfg.extend(n),e=this._parse(e,n.format);var s=n.kdf.execute(i,t.keySize,t.ivSize,e.salt);return n.iv=s.iv,b.decrypt.call(this,t,e,s.key,n)}})}()))},249:function(t,e,i){var n;t.exports=(n=n||function(t,e){var n;if("undefined"!=typeof window&&window.crypto&&(n=window.crypto),"undefined"!=typeof self&&self.crypto&&(n=self.crypto),"undefined"!=typeof globalThis&&globalThis.crypto&&(n=globalThis.crypto),!n&&"undefined"!=typeof window&&window.msCrypto&&(n=window.msCrypto),!n&&void 0!==i.g&&i.g.crypto&&(n=i.g.crypto),!n)try{n=i(480)}catch(t){}var s=function(){if(n){if("function"==typeof n.getRandomValues)try{return n.getRandomValues(new Uint32Array(1))[0]}catch(t){}if("function"==typeof n.randomBytes)try{return n.randomBytes(4).readInt32LE()}catch(t){}}throw new Error("Native crypto module could not be used to get secure random number.")},o=Object.create||function(){function t(){}return function(e){var i;return t.prototype=e,i=new t,t.prototype=null,i}}(),r={},a=r.lib={},c=a.Base={extend:function(t){var e=o(this);return t&&e.mixIn(t),e.hasOwnProperty("init")&&this.init!==e.init||(e.init=function(){e.$super.init.apply(this,arguments)}),e.init.prototype=e,e.$super=this,e},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var e in t)t.hasOwnProperty(e)&&(this[e]=t[e]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}},u=a.WordArray=c.extend({init:function(t,i){t=this.words=t||[],this.sigBytes=i!=e?i:4*t.length},toString:function(t){return(t||h).stringify(this)},concat:function(t){var e=this.words,i=t.words,n=this.sigBytes,s=t.sigBytes;if(this.clamp(),n%4)for(var o=0;o>>2]>>>24-o%4*8&255;e[n+o>>>2]|=r<<24-(n+o)%4*8}else for(var a=0;a>>2]=i[a>>>2];return this.sigBytes+=s,this},clamp:function(){var e=this.words,i=this.sigBytes;e[i>>>2]&=4294967295<<32-i%4*8,e.length=t.ceil(i/4)},clone:function(){var t=c.clone.call(this);return t.words=this.words.slice(0),t},random:function(t){for(var e=[],i=0;i>>2]>>>24-s%4*8&255;n.push((o>>>4).toString(16)),n.push((15&o).toString(16))}return n.join("")},parse:function(t){for(var e=t.length,i=[],n=0;n>>3]|=parseInt(t.substr(n,2),16)<<24-n%8*4;return new u.init(i,e/2)}},l=d.Latin1={stringify:function(t){for(var e=t.words,i=t.sigBytes,n=[],s=0;s>>2]>>>24-s%4*8&255;n.push(String.fromCharCode(o))}return n.join("")},parse:function(t){for(var e=t.length,i=[],n=0;n>>2]|=(255&t.charCodeAt(n))<<24-n%4*8;return new u.init(i,e)}},f=d.Utf8={stringify:function(t){try{return decodeURIComponent(escape(l.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return l.parse(unescape(encodeURIComponent(t)))}},p=a.BufferedBlockAlgorithm=c.extend({reset:function(){this._data=new u.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=f.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(e){var i,n=this._data,s=n.words,o=n.sigBytes,r=this.blockSize,a=o/(4*r),c=(a=e?t.ceil(a):t.max((0|a)-this._minBufferSize,0))*r,d=t.min(4*c,o);if(c){for(var h=0;h>>6-r%4*2;s[o>>>2]|=a<<24-o%4*8,o++}return e.create(s,o)}t.enc.Base64={stringify:function(t){var e=t.words,i=t.sigBytes,n=this._map;t.clamp();for(var s=[],o=0;o>>2]>>>24-o%4*8&255)<<16|(e[o+1>>>2]>>>24-(o+1)%4*8&255)<<8|e[o+2>>>2]>>>24-(o+2)%4*8&255,a=0;a<4&&o+.75*a>>6*(3-a)&63));var c=n.charAt(64);if(c)for(;s.length%4;)s.push(c);return s.join("")},parse:function(t){var e=t.length,n=this._map,s=this._reverseMap;if(!s){s=this._reverseMap=[];for(var o=0;on&&(e=t.finalize(e)),e.clamp();for(var s=this._oKey=e.clone(),o=this._iKey=e.clone(),a=s.words,c=o.words,u=0;u>>24)|4278255360&(s<<24|s>>>8)}var o=this._hash.words,r=t[e+0],c=t[e+1],f=t[e+2],p=t[e+3],m=t[e+4],g=t[e+5],b=t[e+6],w=t[e+7],v=t[e+8],_=t[e+9],y=t[e+10],C=t[e+11],S=t[e+12],k=t[e+13],I=t[e+14],T=t[e+15],E=o[0],x=o[1],N=o[2],A=o[3];E=u(E,x,N,A,r,7,a[0]),A=u(A,E,x,N,c,12,a[1]),N=u(N,A,E,x,f,17,a[2]),x=u(x,N,A,E,p,22,a[3]),E=u(E,x,N,A,m,7,a[4]),A=u(A,E,x,N,g,12,a[5]),N=u(N,A,E,x,b,17,a[6]),x=u(x,N,A,E,w,22,a[7]),E=u(E,x,N,A,v,7,a[8]),A=u(A,E,x,N,_,12,a[9]),N=u(N,A,E,x,y,17,a[10]),x=u(x,N,A,E,C,22,a[11]),E=u(E,x,N,A,S,7,a[12]),A=u(A,E,x,N,k,12,a[13]),N=u(N,A,E,x,I,17,a[14]),E=d(E,x=u(x,N,A,E,T,22,a[15]),N,A,c,5,a[16]),A=d(A,E,x,N,b,9,a[17]),N=d(N,A,E,x,C,14,a[18]),x=d(x,N,A,E,r,20,a[19]),E=d(E,x,N,A,g,5,a[20]),A=d(A,E,x,N,y,9,a[21]),N=d(N,A,E,x,T,14,a[22]),x=d(x,N,A,E,m,20,a[23]),E=d(E,x,N,A,_,5,a[24]),A=d(A,E,x,N,I,9,a[25]),N=d(N,A,E,x,p,14,a[26]),x=d(x,N,A,E,v,20,a[27]),E=d(E,x,N,A,k,5,a[28]),A=d(A,E,x,N,f,9,a[29]),N=d(N,A,E,x,w,14,a[30]),E=h(E,x=d(x,N,A,E,S,20,a[31]),N,A,g,4,a[32]),A=h(A,E,x,N,v,11,a[33]),N=h(N,A,E,x,C,16,a[34]),x=h(x,N,A,E,I,23,a[35]),E=h(E,x,N,A,c,4,a[36]),A=h(A,E,x,N,m,11,a[37]),N=h(N,A,E,x,w,16,a[38]),x=h(x,N,A,E,y,23,a[39]),E=h(E,x,N,A,k,4,a[40]),A=h(A,E,x,N,r,11,a[41]),N=h(N,A,E,x,p,16,a[42]),x=h(x,N,A,E,b,23,a[43]),E=h(E,x,N,A,_,4,a[44]),A=h(A,E,x,N,S,11,a[45]),N=h(N,A,E,x,T,16,a[46]),E=l(E,x=h(x,N,A,E,f,23,a[47]),N,A,r,6,a[48]),A=l(A,E,x,N,w,10,a[49]),N=l(N,A,E,x,I,15,a[50]),x=l(x,N,A,E,g,21,a[51]),E=l(E,x,N,A,S,6,a[52]),A=l(A,E,x,N,p,10,a[53]),N=l(N,A,E,x,y,15,a[54]),x=l(x,N,A,E,c,21,a[55]),E=l(E,x,N,A,v,6,a[56]),A=l(A,E,x,N,T,10,a[57]),N=l(N,A,E,x,b,15,a[58]),x=l(x,N,A,E,k,21,a[59]),E=l(E,x,N,A,m,6,a[60]),A=l(A,E,x,N,C,10,a[61]),N=l(N,A,E,x,f,15,a[62]),x=l(x,N,A,E,_,21,a[63]),o[0]=o[0]+E|0,o[1]=o[1]+x|0,o[2]=o[2]+N|0,o[3]=o[3]+A|0},_doFinalize:function(){var e=this._data,i=e.words,n=8*this._nDataBytes,s=8*e.sigBytes;i[s>>>5]|=128<<24-s%32;var o=t.floor(n/4294967296),r=n;i[15+(s+64>>>9<<4)]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),i[14+(s+64>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),e.sigBytes=4*(i.length+1),this._process();for(var a=this._hash,c=a.words,u=0;u<4;u++){var d=c[u];c[u]=16711935&(d<<8|d>>>24)|4278255360&(d<<24|d>>>8)}return a},clone:function(){var t=o.clone.call(this);return t._hash=this._hash.clone(),t}});function u(t,e,i,n,s,o,r){var a=t+(e&i|~e&n)+s+r;return(a<>>32-o)+e}function d(t,e,i,n,s,o,r){var a=t+(e&n|i&~n)+s+r;return(a<>>32-o)+e}function h(t,e,i,n,s,o,r){var a=t+(e^i^n)+s+r;return(a<>>32-o)+e}function l(t,e,i,n,s,o,r){var a=t+(i^(e|~n))+s+r;return(a<>>32-o)+e}e.MD5=o._createHelper(c),e.HmacMD5=o._createHmacHelper(c)}(Math),n.MD5)},783:function(t,e,i){var n,s,o,r,a,c,u,d;t.exports=(d=i(249),s=(n=d).lib,o=s.WordArray,r=s.Hasher,a=n.algo,c=[],u=a.SHA1=r.extend({_doReset:function(){this._hash=new o.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var i=this._hash.words,n=i[0],s=i[1],o=i[2],r=i[3],a=i[4],u=0;u<80;u++){if(u<16)c[u]=0|t[e+u];else{var d=c[u-3]^c[u-8]^c[u-14]^c[u-16];c[u]=d<<1|d>>>31}var h=(n<<5|n>>>27)+a+c[u];h+=u<20?1518500249+(s&o|~s&r):u<40?1859775393+(s^o^r):u<60?(s&o|s&r|o&r)-1894007588:(s^o^r)-899497514,a=r,r=o,o=s<<30|s>>>2,s=n,n=h}i[0]=i[0]+n|0,i[1]=i[1]+s|0,i[2]=i[2]+o|0,i[3]=i[3]+r|0,i[4]=i[4]+a|0},_doFinalize:function(){var t=this._data,e=t.words,i=8*this._nDataBytes,n=8*t.sigBytes;return e[n>>>5]|=128<<24-n%32,e[14+(n+64>>>9<<4)]=Math.floor(i/4294967296),e[15+(n+64>>>9<<4)]=i,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=r.clone.call(this);return t._hash=this._hash.clone(),t}}),n.SHA1=r._createHelper(u),n.HmacSHA1=r._createHmacHelper(u),d.SHA1)},238:function(t,e,i){var n;!function(s,o){"use strict";var r="function",a="undefined",c="object",u="string",d="model",h="name",l="type",f="vendor",p="version",m="architecture",g="console",b="mobile",w="tablet",v="smarttv",_="wearable",y="embedded",C="Amazon",S="Apple",k="ASUS",I="BlackBerry",T="Firefox",E="Google",x="Huawei",N="LG",A="Microsoft",O="Motorola",M="Opera",D="Samsung",R="Sharp",B="Sony",P="Xiaomi",U="Zebra",z="Facebook",F=function(t){for(var e={},i=0;i0?2===a.length?typeof a[1]==r?this[a[0]]=a[1].call(this,d):this[a[0]]=a[1]:3===a.length?typeof a[1]!==r||a[1].exec&&a[1].test?this[a[0]]=d?d.replace(a[1],a[2]):o:this[a[0]]=d?a[1].call(this,d,a[2]):o:4===a.length&&(this[a[0]]=d?a[3].call(this,d.replace(a[1],a[2])):o):this[a]=d||o;h+=2}},V=function(t,e){for(var i in e)if(typeof e[i]===c&&e[i].length>0){for(var n=0;n350?j(t,350):t,this},this.setUA(i),this};K.VERSION="1.0.32",K.BROWSER=F([h,p,"major"]),K.CPU=F([m]),K.DEVICE=F([d,f,l,g,b,v,w,_,y]),K.ENGINE=K.OS=F([h,p]),typeof e!==a?(t.exports&&(e=t.exports=K),e.UAParser=K):i.amdO?(n=function(){return K}.call(e,i,e,t))===o||(t.exports=n):typeof s!==a&&(s.UAParser=K);var J=typeof s!==a&&(s.jQuery||s.Zepto);if(J&&!J.ua){var $=new K;J.ua=$.getResult(),J.ua.get=function(){return $.getUA()},J.ua.set=function(t){$.setUA(t);var e=$.getResult();for(var i in e)J.ua[i]=e[i]}}}("object"==typeof window?window:this)},480:()=>{}},e={};function i(n){var s=e[n];if(void 0!==s)return s.exports;var o=e[n]={exports:{}};return t[n].call(o.exports,o,o.exports,i),o.exports}i.amdO={},i.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return i.d(e,{a:e}),e},i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var n={};return(()=>{"use strict";function t(t=(()=>{}),e){null!=window.PushManager||null!=navigator.serviceWorker?"serviceWorker"in navigator?navigator.serviceWorker.register(e||"/sw.produce.min.2.1.6.js").then((function(e){const i=e.installing;i?i.addEventListener("statechange",(function(i){var n;"activated"===(null===(n=i.target)||void 0===n?void 0:n.state)&&t(!0,e)})):t(!0,e)})).catch((function(e){t(!1,e)})):t(!1,"serviceWorker 不支持http"):t(!1,"浏览器不支持订阅推送")}i.d(n,{default:()=>Nt});var e=i(214),s=i.n(e),o=i(269),r=i.n(o),a=i(743),c=i.n(a),u=i(783),d=i.n(u),h=i(452),l=i.n(h);function f(t){return function(t){const e=t.length,i=new Uint8Array(e);for(let n=0,s=e;n0&&(a.headers=_.headers);const c=Object.assign(a,r);if(c.method=null===(o=c.method)||void 0===o?void 0:o.toUpperCase(),"GET"===c.method){if(i){let e="";const n=/([?&])_=[^&]*/,s=/\?/;"object"==typeof i&&(e=function(t){let e="";for(const i in t)t.hasOwnProperty(i)&&(e+=i+"="+encodeURIComponent(t[i])+"&");return e.slice(0,-1)}(i)),t=n.test(t)?t.replace(n,"$1"+e):t+(s.test(t)?"&":"?")+e}}else[FormData,ArrayBuffer,Blob].filter((t=>i instanceof t)).length>0?c.body=i:(c.headers=Object.assign({"Content-Type":"application/json"},c.headers),i&&(c.body="string"==typeof i?i:JSON.stringify(i)));return fetch(t,c).then((t=>{const{status:e}=t;if(e>=200&&e<400){let e;switch(s){case"json":e=new Promise((e=>{t.text().then((t=>{try{e(JSON.parse(t))}catch(i){e(t)}}))}));break;case"text":e=t.text();break;case"blob":e=t.blob();break;case"arraybuffer":e=t.arrayBuffer();break;default:e=t.json()}return e}return Promise.reject(t)})).catch((t=>Promise.reject(t)))}const C={get:(t,e,i,n)=>y(t,"GET",e,i,n),post:(t,e,i,n)=>y(t,"POST",e,i,n),put:(t,e,i,n)=>y(t,"PUT",e,i,n),delete:(t,e,i,n)=>y(t,"DELETE",e,i,n),fetch:y,defaults:_},S={uid:"",passwd:""};function k(t,e){return S.uid=t,S.passwd=e,S}function I(t,e,i){if(S.uid&&S.passwd){const n=v(S.uid,S.passwd,i);return C[e](t,n.body,{headers:{Authorization:"Basic "+n.authorization}})}return Promise.reject("uid 或 passwd 为空")}const T=(t,e)=>I(t,"post",e),E="https://conn.webpush.theengagelab.com/v2/",x={subscribeSuccess:E+"subscribe",auth:"https://conn.webpush.theengagelab.com/v1/auth",option:E+"notification_subscribe",tagalias:E+"webdevices",unsubscribe:E+"unsubscribe",safariSubscribe:E+"safari/subscribe"};function N(){var t;const e=(null===(t=mt.data)||void 0===t?void 0:t.website_push_id)||"web.cn.jiguang";return{data:window.safari.pushNotification.permission(e),id:e}}function A(t,e){T(t,{endpoint:e.endpoint,keys:{p256dh:p(e.getKey("p256dh")),auth:p(e.getKey("auth"))}})}const O={SDK_VERSION:"2.1.6",LOG_TAG_PREFIX:"[MTush-P]",REQUEST:{WS_URL:"ws://"+"https://conn.webpush.theengagelab.com".split("//")[1],WS_SUPPORT:!0,WS_HEART_BEAT:5e4,WS_RECONNECT_PERIOD:1e4,WS_RESP_TIMEOUT:3e4,WS_RETRY_TIMES:3,POLLING_URL:"",POLLING_SUPPORT:!1,POLLING_TIME_OUT:15e3},CMD:{INIT:"init",HEART_BEAT:"heartbeat",MESSAGE_RECIVER:"msgrecv",PUSH:"push"},EVENT:{OPEN:"open",MESSAGE:"message",SEND:"send",DISCONNECT:"disconnect",NOT_SUPPORT:"not_supoort"}};var M;function D(t){return"string"!=typeof t||(!t||0==t.length)}function R(){let t=!0;if(window.navigator){if(t=window.navigator.onLine,"boolean"==typeof t)return t;t=!0}return t}!function(t){let e=!1;const i=O.LOG_TAG_PREFIX;t.setDebugMode=function(n){t.d(i,`setDebugMode:${n}`),e=n},t.d=function(t,e){},t.dd=function(t,i){e&&window.console},t.ww=function(t,e){window.console},t.ee=function(t,e){window.console}}(M||(M={}));const B=0,P=1e3,U={INITING:1001,CONFIG_INVALID:1002,INIT_FAILED:1003,INIT_TIME_OUT:1004,NETWORK_ERROR:1005,NOT_SUPPORT:1006};function z(t){let e="";switch(t){case B:e="success";break;case P:e="unknown error";break;case U.INITING:e="initing , please try again later";break;case U.CONFIG_INVALID:e="invalid config";break;case U.INIT_FAILED:e="init failed";break;case U.INIT_TIME_OUT:e="init timeout";break;case U.NETWORK_ERROR:e="network error"}return e}function F(t,e){return{code:t,message:z(t),content:e}}class W{constructor(t){this.success=t.success,this.fail=t.fail}onSuccess(t){this.success&&"function"==typeof this.success&&this.success(t)}onFail(t){this.fail&&"function"==typeof this.fail&&this.fail(t)}}var H;!function(t){t[t.Initial=0]="Initial",t[t.Connecting=1]="Connecting",t[t.Connected=2]="Connected",t[t.Closing=3]="Closing",t[t.Closed=4]="Closed"}(H||(H={}));class j extends class{constructor(){this.sConnectionState=H.Initial}getConnectState(){return this.sConnectionState}}{constructor(t){super(),this.mUrl=t}connect(){M.d("WebSocketConnection",`${location.hostname} try to connect websocket:${this.mUrl}`),this.mWebSocket=new WebSocket(this.mUrl),this.mWebSocket.onopen=this.onOpen.bind(this),this.mWebSocket.onclose=this.onClose.bind(this),this.mWebSocket.onmessage=this.onMessage.bind(this)}disconnect(){this.mWebSocket.close()}onOpen(t){this.onopen(t)}onMessage(t){this.onmessage(t)}onClose(t){this.onclose(t)}sendMessage(t){this.mWebSocket.send(t)}getConnectState(){if(this.mWebSocket)switch(this.mWebSocket.readyState){case WebSocket.CLOSED:this.sConnectionState=H.Closed;break;case WebSocket.CLOSING:this.sConnectionState=H.Closing;break;case WebSocket.CONNECTING:this.sConnectionState=H.Connecting;break;case WebSocket.OPEN:this.sConnectionState=H.Connected}return this.sConnectionState}}const L="ConnectionController";class V{constructor(t=""){this.mAutoDiscon=!0,this.mDataCache="",this.url="",this._onOpen=this._onOpen.bind(this),this._onMessage=this._onMessage.bind(this),this._onClose=this._onClose.bind(this),this.sendMessage=this.sendMessage.bind(this),this.setupConnection=this.setupConnection.bind(this),this.url=t}setupConnection(){O.REQUEST.WS_SUPPORT&&"undefined"!=typeof WebSocket?(this.mConnection=new j(this.url||O.REQUEST.WS_URL),this.mConnection.onopen=this._onOpen,this.mConnection.onmessage=this._onMessage,this.mConnection.onclose=this._onClose,this.mAutoDiscon=!0,M.dd(L,"start connecting"),this.mConnection.connect()):this.onEvent({name:O.EVENT.NOT_SUPPORT})}_isConnect(){return this.mConnection&&(this.mConnection.getConnectState()===H.Connected||this.mConnection.getConnectState()===H.Connecting)}_onOpen(t){M.dd(L,"connection is opened"),this._sendEvent(O.EVENT.OPEN,t.event),this.sendMessage(this.mDataCache)}_onMessage(t){M.d(L,"connection is receive message"),this._sendEvent(O.EVENT.MESSAGE,t.data)}_onClose(t){M.dd(L,"connection close "+(this.mAutoDiscon?"auto":"manually")),this.mAutoDiscon&&this._scheduleReConnect(),this._sendEvent(O.EVENT.DISCONNECT,t)}_sendEvent(t,e){this.onEvent&&this.onEvent({name:t,data:e})}sendMessage(t){D(t)||(this.mConnection.getConnectState()===H.Connected?(this.mConnection.sendMessage(t),this.mDataCache="",this._sendEvent(O.EVENT.SEND,t)):this.mDataCache=t)}_scheduleReConnect(){var t;(t=O.REQUEST.WS_RECONNECT_PERIOD,new Promise((e=>setTimeout(e,t)))).then((()=>{this.mAutoDiscon&&(R()?(M.dd(L,"do reconnect"),this.setupConnection()):this._scheduleReConnect())}))}disconnect(){M.d(L,"disconnect"),this.mAutoDiscon=!1,this._isConnect()&&this.mConnection.disconnect()}reConnect(){M.d(L,"reconnect"),this.mAutoDiscon=!0,this._isConnect()?this.mConnection.disconnect():this.setupConnection()}}class G{constructor(t){this.mInitConfig=t}setCmd(t){return this.mCmd=t,this}setSid(t){return this.mSid=t,this}setUserInfo(t){return this.mUserInfo=t,this}setPushMsgArray(t){return this.mMsgArr=t,this}build(){let t;switch(this.mCmd){case O.CMD.INIT:t=this.mInitConfig.auth;break;case O.CMD.HEART_BEAT:t={r:this.mUserInfo.regid,u:this.mInitConfig.auth.is_temporary};break;case O.CMD.MESSAGE_RECIVER:t={r:this.mUserInfo.regid,m:this.mMsgArr.map((t=>t.msg_id))}}let e=0;this.mSid?e=this.mSid+2:(e=G.sid,G.sid>1e8?G.sid=0:G.sid=G.sid+4);return{cmd:this.mCmd,sid:e,time:Math.round(Date.now()/1e3),body:t?JSON.stringify(t):""}}}G.sid=0;const q="Channel";class K{constructor(){this.mDataCache={},this.mMsgChache={}}init(t){this.mInitConfig=t,this.mConnectionController||(this.mConnectionController=new V(t.webSocketUrl),this.mConnectionController.onEvent=this.onEvent.bind(this)),this.mConnectionController.setupConnection()}onEvent(t){if(M.d(q,"onEvent:"+t.name),t.name==O.EVENT.DISCONNECT){const t=this,e=Object.keys(t.mDataCache);e.length>0&&e.forEach((e=>{const i=t.mDataCache[e];i&&(i.cleanRespTimeout(),delete t.mDataCache[e])}))}if(t.name==O.EVENT.MESSAGE){const e=JSON.parse(t.data),i=e.cmd;M.dd(q,`---<- cmd:${i}, data:${t.data}`),i==O.CMD.HEART_BEAT&&(e.body=JSON.stringify({code:0}));const n=JSON.parse(e.body);if(i==O.CMD.PUSH){const t=this._filterPushArr(e.sid,n.d);if(!(t.length>0))return;e.body=t}const s=this.mDataCache[e.sid+""];s&&(s.cleanRespTimeout(),delete this.mDataCache[e.sid+""],0!==n.code?s.fail&&s.fail(n):s.hook?s.hook(e,s.success):s.success&&s.success(n)),t.data=e}this.onevent(t)}_filterPushArr(t,e){if(!e)return[];const i=e.map((t=>JSON.parse(t)));this.send(O.CMD.MESSAGE_RECIVER,new G(this.mInitConfig).setCmd(O.CMD.MESSAGE_RECIVER).setSid(t).setUserInfo(this.mUserInfo).setPushMsgArray(i).build());const n=i.map((t=>t.msg_id)),s=this.mMsgChache[this.mUserInfo.regid];let o=[];if(s){for(let t=0;t{this._heartBeat()}),O.REQUEST.WS_HEART_BEAT)}_heartBeat(){const t=this;new J(this.mChannel).setCmd(O.CMD.HEART_BEAT).setData(new G(this.mInitConfig).setCmd(O.CMD.HEART_BEAT).setUserInfo(this.mUserInfo).build()).onSuccess((function(){t._startHeartBeat()})).onFail((function(){t._reconnect()})).onTimeout((function(){t._reconnect()})).send()}_reconnect(){M.ww($,"wrong heartbeat,try reconnect"),this._stopHearBeat(),this.mChannel.reConnect()}_stopHearBeat(){this.mHeartTimeOut&&(clearTimeout(this.mHeartTimeOut),this.mHeartTimeOut=void 0)}_resetHeartBeat(){this.mHeartTimeOut&&(clearTimeout(this.mHeartTimeOut),this._startHeartBeat())}}X.INIT_SUCC="INITSUCC",X.INIT_ING="INITING",X.INIT_FAIL="INITFAIL";const Q=new X,Z={init:function(t){try{Q.init(t,new W(t))}catch(e){M.ww($,"init fail:"+e),t.fail(F(P))}},onMsgReceive:function(t){"function"==typeof t&&(M.dd($,"set onMsgReceive"),Q.onMsgReceive(t))},onDisconnect:function(t){"function"==typeof t&&(M.dd($,"set onDisconnect"),Q.onDisconnect(t))},stopPush:function(){M.dd($,"stopPush"),Q.stopPush()},getRegistrationID(){const t=Q.getRegistrationID();return M.dd($,"getRegistrationID:"+t),t},mtpush:Q};function Y(t){return t?` style="color:${t};"`:""}const tt={position:"left",title:"",titleColor:"",text:"",textColor:"",confirm:"",confirmColor:"",confirmBgc:"",cancel:"",cancelColor:"",src:""};function et(t,e=(()=>{}),i=(()=>{})){const n=Object.assign(tt,t);n.position={left:"left:0",center:"left:50%;transform:translate(-50%,0)",right:"right:0"}[t.position]||"left:0";let s=(n.confirmColor?`color:${n.confirmColor};`:"")+(n.confirmBgc?`background-color:${n.confirmBgc};`:"");s=s?` style="${s}"`:"";const o=`\n ${n.title}\n

${n.text}

\n
\n ${n.cancel}${n.confirm}\n
`;!function(t){const e=document.createElement("style");e.appendChild(document.createTextNode(t)),document.getElementsByTagName("head")[0].appendChild(e)}(".notic-user-tip-warp {\n width: 378px;\n min-height: 134px;\n background: #ffffff;\n box-shadow: 0px 24px 24px -6px rgba(37, 48, 68, 0.14), 0px 2px 6px 0px rgba(37, 48, 68, 0.12);\n border-radius: 4px;\n position: absolute;\n padding: 20px 20px 20px 72px;\n top: 0;\n}\n.notic-user-tip-warp.notic-user-tip-warp-hide{display:none}\n.notic-user-tip-warp > img {\n position: absolute;\n width: 40px;\n left: 16px;\n top: 20px;\n}\n.notic-user-tip-warp > h3 {\n font-size: 14px;\n color: #000;\n line-height: 18px;\n padding-bottom: 2px;\n font-weight: bolder;\n}\n.notic-user-tip-warp > .notic-user-tip-data {\n font-size: 14px;\n line-height: 22px;\n color: #666;\n min-height: 44px;\n overflow: hidden;\n padding-bottom: 16px;\n word-spacing: pre-wrap;\n}\n.notic-user-tip-warp > .button-warp {\n text-align: right;\n}\n.notic-user-tip-warp > .button-warp > span {\n display: inline-block;\n height: 32px;\n line-height: 32px;\n border-radius: 3px;\n padding: 0 16px;\n margin-left: 8px;\n color: #cccccc;\n cursor: pointer;\n}\n.notic-user-tip-warp > .button-warp > .confirm {\n color: #fff;\n background: #2c6bff;\n}");const r=document.createElement("div");function a(t=!1){t?r.classList.add("notic-user-tip-warp-hide"):r.classList.remove("notic-user-tip-warp-hide")}return r.className="notic-user-tip-warp notic-user-tip-warp-hide",r.style.cssText=n.position,r.innerHTML=o,document.body.appendChild(r),r.addEventListener("click",(t=>{const n=t.target.classList;n.contains("confirm")&&(e(),a(!0)),n.contains("web-push-tip-cancel")&&(i(),a(!0))})),a}const it=["active_launch","active_terminate"];function nt(t){const e=Number(localStorage.getItem(t)||"0");return isNaN(e)?0:e}function st(t,e="",i=!1){var n,s,o,r;if(mt.data&&mt.userOption){const a=mt;let c=!1;if(t.forEach((t=>{const e=(new Date).getTime()/1e3|0;t.type&&it.includes(t.type)&&(e-nt(t.type)<30&&(c=!0),localStorage.setItem(t.type,e.toString())),t.itime=e,t.msg_id&&(t.msg_id=t.msg_id.toString())})),c)return;const u={platform:"b",uid:null===(n=a.data)||void 0===n?void 0:n.uid,app_key:null===(s=a.userOption)||void 0===s?void 0:s.appkey,channel:e,content:t},d="Basic "+btoa((null===(o=a.data)||void 0===o?void 0:o.uid)+":"+(null===(r=a.data)||void 0===r?void 0:r.passwd));return i?{data:u,Authorization:d}:C.post("https://webpushstat.api.engagelab.cc/v4/web/report",u,{headers:{Authorization:d}})}}function ot(t){return(new Date).getTime()/1e3-nt(t)|0}var rt=i(238),at=i.n(rt);const ct="BCWYct994EyWepYA2QdTQrTz81NBfq5wOGJeqa_M4B-uLIaH5nOky58qfwLK0ou7XSDYjJDQGxAuPxRBM4KxPz4";function ut(){t(((t,e)=>{function i(t,i){1===t&&function(t,e=(()=>{})){t.pushManager?t.pushManager.getSubscription().then((i=>{i||t.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:f(ct)}).then((()=>{e(!0)})).catch((t=>{}))})).catch((t=>{e(!1,t)})):e(!1)}(e,(t=>{let e=-6,i="消息订阅失败!";t&&(e=0,i="消息订阅成功!")}))}t&&_t(((t,e)=>{var n;1===t?i(t):3===t&&(null===(n=Notification.requestPermission())||void 0===n||n.then((function(t){"granted"===t&&i(1)})))}))}))}const dt={mtPush:{code:0,msg:""},webPush:{code:0,msg:""}};function ht(){return dt.mtPush={INITSUCC:{code:1,msg:"极光推送通道初始化成功!"},INIT_ING:{code:-1,msg:"极光推送通道初始化中,请稍后再试!"},INIT_FAIL:{code:0,msg:"极光推送通道初始化失败或已停止!"}}[Z.mtpush.mInitStatu]||{code:0,msg:""},JSON.parse(JSON.stringify(dt))}function lt(t,e=(()=>{})){const i=t.visit||0,n=t.delay||0,s=Number(window.localStorage.getItem("subscriptionVisit")||0);s>i-1?n>0?setTimeout(e,1e3*n):e():window.localStorage.setItem("subscriptionVisit",(s+1).toString())}function ft(e=(()=>{}),i,n){function s(t,i,n){t<1&&(t=0,i="web推通知不可用"),dt.webPush.code=t,dt.webPush.msg=i,e(t,i,n)}t(((t,e)=>{function o(t,n){1===t?function(t,e,i,n=(()=>{}),s=!1,o={title:"我是测试"}){var r;let a=!1;if("safari"in window&&"pushNotification"in window.safari){a=!0;const t=N();t.data.deviceToken&&T(x.safariSubscribe,{appkey:null===(r=mt.userOption)||void 0===r?void 0:r.appkey,device_token:t.data.deviceToken})}t.pushManager?t.pushManager.getSubscription().then((r=>{r?(n(!0),s&&T(i,{pushInfo:{endpoint:r.endpoint,keys:{p256dh:p(r.getKey("p256dh")),auth:p(r.getKey("auth"))}},pushData:o}),A(i,r)):t.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:f(e)}).then((t=>{n(!0),A(i,t)})).catch((t=>{}))})).catch((t=>{n(!1,t)})):n(a)}(e,i.VAPIDPublicKey,x.subscribeSuccess,(e=>{let i=-6,o="消息订阅失败!";e&&(i=0,o="消息订阅成功!"),s(t+i,n+o)})):s(t,n)}function r(){!function(t=wt){var e;let i="web.cn.jiguang";const n=mt.userOption,s=((null==n?void 0:n.safari_url)||"https://conn.webpush.theengagelab.com")+"/webpush";function o(){const t=N(),e=t.data;i=t.id,r(e)}function r(e){var n,a;"default"===e.permission?(st([{type:"ntf_status",result:0,manufacturer:bt.browser.name,lang:navigator.language}]),window.safari.pushNotification.requestPermission(s,i,{appkey:null===(n=mt.userOption)||void 0===n?void 0:n.appkey,uid:null===(a=mt.data)||void 0===a?void 0:a.uid.toString(),origin:location.origin},r)):(document.body.removeEventListener("click",o),vt(e.permission,t),"denied"===e.permission?st([{type:"ntf_status",result:2,manufacturer:bt.browser.name,lang:navigator.language}]):st([{type:"ntf_status",result:1,manufacturer:bt.browser.name,lang:navigator.language}]))}if("safari"in window&&"pushNotification"in window.safari)try{o()}catch(t){document.body.addEventListener("click",o)}else st([{type:"ntf_status",result:0,manufacturer:bt.browser.name,lang:navigator.language}]),null===(e=Notification.requestPermission())||void 0===e||e.then((function(e){vt(e,t),"denied"===e?(st([{type:"ntf_status",result:2,manufacturer:bt.browser.name,lang:navigator.language}]),T(x.unsubscribe)):st([{type:"ntf_status",result:1,manufacturer:bt.browser.name,lang:navigator.language}])}))}(((t,e)=>{o(t,e)}))}t?_t(((t,e)=>{3!==t&&setTimeout((()=>{st([{type:"ntf_status",result:t,manufacturer:gt.browser.name,lang:navigator.language}])}),2e3),1===t?o(t,e):3===t?T(x.option,{appkey:n.appkey}).then((t=>{const e=t.subscribe_config||{};if(2===t.subscribe_type){const t=et(e,r);lt(e,(()=>{t()}))}else 3===t.subscribe_type?n.custom?n.custom(r):r():lt(e,r)})):s(t,e)})):s(-2,"ServiceWorker registration failed",e)}),i.swUrl)}function pt(){}const mt={data:null,userOption:null},gt=at()(navigator.userAgent);const bt=at()(navigator.userAgent),wt=(t,e)=>{};function vt(t,e=wt){switch(t){case"granted":e(1,"Notification 可用");break;case"denied":e(2,"Notification 权限已被禁用");break;default:e(3,"Notification 权限还没有确认")}}function _t(t=wt){if("undefined"==typeof Notification)return void t(0,"浏览器不支持 Notification API");vt(Notification.permission,t)}function yt(t,e,i={}){var n;"Notification"in window&&"granted"===Notification.permission&&(null===(n=navigator.serviceWorker)||void 0===n||n.getRegistration().then((function(n){if(null==n?void 0:n.showNotification)null==n||n.showNotification(t,e),i&&i.msg_id&&st([{type:"msg_status",msg_id:i.msg_id,result:3018}],"MTPush");else{const n=new Notification(t,e);i&&i.msg_id&&st([{type:"msg_status",msg_id:i.msg_id,result:3018}],"MTPush"),n.addEventListener("click",(()=>{i&&i.msg_id&&st([{type:"msg_status",msg_id:i.msg_id,result:3002}],"MTPush");const t=i.url||(null==i?void 0:i.engagelab_url);t&&window.open(t)}))}})))}window.addEventListener("unload",(()=>{if(ot("active_launch")>0){const t=st([{type:"active_terminate",duration:ot("active_launch")}],"",!0);t&&m(9999,t)}}));let Ct=()=>{};function St(t,e){"function"==typeof Ct&&Ct({data:t,type:e})}let kt=0;!function t(){setTimeout((()=>{kt<20&&(kt++,navigator.serviceWorker&&(kt=100,navigator.serviceWorker.addEventListener("message",(t=>{var e;t.data.data.engagelab_uid===(null===(e=mt.data)||void 0===e?void 0:e.uid)&&St(t.data,1)}))),t())}),1e3)}(),Z.onMsgReceive((t=>{if(St(t,0),t.messages&&t.messages.length>0){const e=[];t.messages.forEach((t=>{var i;e.push({type:"msg_status",msg_id:t.msg_id,result:3001}),!(i=t.ntf_or_msg)||1!==i&&3!==i||yt(t.title,function(t){var e,i,n;const s={engagelab_appkey:null===(e=mt.userOption)||void 0===e?void 0:e.appkey,engagelab_passwd:null===(i=mt.data)||void 0===i?void 0:i.passwd,engagelab_uid:null===(n=mt.data)||void 0===n?void 0:n.uid,engagelab_mesg_type:"MTPush"},o=Object.assign({},t),r=["content","extras","msg_id","ntf_or_msg","st_web_buttons"];if(void 0!==t.content){if(o.body=t.content,o.tag=t.msg_id,o.data=Object.assign({engagelab_ntf_or_msg:t.ntf_or_msg},s),t.url&&(o.data.engagelab_url=t.url),t.st_web_buttons){const e={},i=[];t.st_web_buttons.forEach((t=>{e[t.id]=t.url;const n={action:t.id,title:t.text};t.icon&&(n.icon=t.icon),i.push(n)})),o.actions=i,o.data.engagelab_action_urls=e}r.forEach((t=>{delete o[t]}))}return o}(t),t)})),st(e,"MTPush")}}));const It={customClickReport(t,e="MTPush"){st([{type:"msg_status",msg_id:t,result:3028}],e="W3Push"!==e?"MTPush":e)},init:function(t){if(t.localTest)return ut(),!1;1===ht().mtPush.code&&mt.data&&n(mt.data);const e="https://conn.webpush.theengagelab.com";mt.userOption=t;const i=t.is_temporary||"n";function n(e){k(e.uid.toString(),e.passwd),mt.data=e;const i=(new Date).getTimezoneOffset()/-60|0;st([{type:"oversea_info",time_zone:i>0?"+"+i:i.toString(),lang:navigator.language,contry:"",os:gt.os.name,os_version:gt.os.version,web_channel:gt.browser.name,web_channel_version:gt.browser.version}]),st([{type:"active_launch"}]),t.canGetInfo&&t.canGetInfo(e),"t"!==t.is_temporary&&function(t,e){ft(e.webPushcallback,{VAPIDPublicKey:t.vapid_pubkey,swUrl:e.swUrl},e)}(e,t)}(function(t,e,i,n="n"){return C.get(t,{user_str:e,appkey:i,is_temporary:n})})(x.auth,t.user_str,t.appkey,i).then((s=>{const o=e.split("//");let r="https:"===o[0]?"wss://":"ws://";r+=o[1];let a=!1;Z.init({webSocketUrl:t.webSocketUrl||r,auth:Object.assign(Object.assign({},s),{is_temporary:i}),success:t.success||pt,fail:t.fail||pt,getInitInfo(t){a||(n(t),a=!0)}})}))},getPushAuthority:ht,getRegistrationID:Z.getRegistrationID,getWebPermission:()=>Notification.permission,mtPush:Z,onMsgReceive:t=>{Ct=t},webPush:{displayNotification:yt,permission:_t,init:ft},setTagsAlias(t){function e(t,e){if(t)throw new Error(e)}if(mt.userOption){const i=mt.userOption;e(!t.tags,"缺少tags属性"),e(!t.alias,"缺少alias属性"),e(!!(t.alias&&t.alias.length>40),"alias最多40个字符"),e(t.alias.length>1e3,"alias一次最多设置1000个"),t.tags.forEach((t=>{e(t.length>40,"tag最多40个字符")})),T(x.tagalias,Object.assign({temporary:"t"===i.is_temporary,appkey:i.appkey},t))}},unSubscribe:()=>T(x.unsubscribe)},Tt={},Et=["webpush_safari_url","msg_id","engagelab_uid","engagelab_appkey","engagelab_passwd"];let xt=0;location.search.slice(1).split("&").forEach((t=>{const e=t.split("=");Et.forEach((t=>{2===e.length&&t===e[0]&&(xt+=1,Tt[t]=e[1])}))})),xt===Et.length&&(m(6666,Object.assign({},Tt)),window.location.href=Tt.webpush_safari_url);const Nt=It})(),n=n.default})())); \ No newline at end of file diff --git a/public/web_config.js b/public/web_config.js new file mode 100644 index 0000000..9bb60c6 --- /dev/null +++ b/public/web_config.js @@ -0,0 +1 @@ +window.WEB_CONFIG = {"api_1":"https://uat-im.exchangs.top/manage-api","api_2":"https://uat-im.exchangs.top/chat-api","api_3":"https://uat-im.exchangs.top/chat-server-api","ws_url":"wss://uat-im.exchangs.top","proxy_target":"https://uat-im.exchangs.top","backup_http_addrs":["https://uat-openim.exchangs.top/chat-server-api"]} \ No newline at end of file diff --git a/scripts/verifyCommit.js b/scripts/verifyCommit.js new file mode 100644 index 0000000..1382d39 --- /dev/null +++ b/scripts/verifyCommit.js @@ -0,0 +1,18 @@ +import fs from 'node:fs' + +const msg = fs.readFileSync('.git/COMMIT_EDITMSG', 'utf-8').trim() + +const commitRE = /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/ +const mergeRe = /^(Merge pull request|Merge branch)/ + +if (!commitRE.test(msg)) { + if (!mergeRe.test(msg)) { + console.log('git commit unpass') + console.error('git commit error, needs title(scope): desc') + // eslint-disable-next-line node/prefer-global/process + process.exit(1) + } +} +else { + console.log('git commit pass') +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..ad97d0a --- /dev/null +++ b/src/App.vue @@ -0,0 +1,316 @@ + + + + + + diff --git a/src/api/avatar.js b/src/api/avatar.js new file mode 100644 index 0000000..3b642ee --- /dev/null +++ b/src/api/avatar.js @@ -0,0 +1,18 @@ +import request from "./request"; + +const api = { + // 查询头像 + avatarList: (data) => { + return request.post('/v1/user/avatar/list', data, { urlTag: 'api_2' }) + }, + // 删除头像 + avatarDelete: (data) => { + return request.post('/v1/user/avatar/delete', data, { urlTag: 'api_2' }) + }, + // 添加头像 + avatarAdd: (data) => { + return request.post('/v1/user/avatar/add', data, { urlTag: 'api_2' }) + }, +} + +export default api \ No newline at end of file diff --git a/src/api/community.js b/src/api/community.js new file mode 100644 index 0000000..3187dcc --- /dev/null +++ b/src/api/community.js @@ -0,0 +1,22 @@ +import request from "./request"; + +const api = { + // 商户列表 + userSubAdminList: (data) => { + return request.post('/v1/user/sub_admin/list', data, { urlTag: 'api_2' }) + }, + // 商户活动列表 + communityActivityList: (data) => { + return request.post('/v1/community/activity/list', data, { urlTag: 'api_2' }) + }, + // 商户活动详情 + communityActivityGet: (data) => { + return request.post('/v1/community/activity/get', data, { urlTag: 'api_2' }) + }, + // 商户活动详情 + activityCheckOnlineStatus: (data) => { + return request.post('/v1/community/activity/check_online_status', data, { urlTag: 'api_2' }) + }, +} + +export default api \ No newline at end of file diff --git a/src/api/contact.js b/src/api/contact.js new file mode 100644 index 0000000..495dad5 --- /dev/null +++ b/src/api/contact.js @@ -0,0 +1,399 @@ +import request from "./request"; + +const api = { + //通过UserIDs查找用户 + queryInfoByuserId: data => { + return request.post("/v1/user/info/query", data, { urlTag: "api_2" }); + }, + //通过Uid查找用户,获取个人公开资料 + queryUserById: data => { + return request.post("/v1/user/public_info/query", data, { + urlTag: "api_2" + }); + }, + //获取个人全部资料 + getUserById: data => { + return request.post("/v1/user/info/query", data, { urlTag: "api_2" }); + }, + // 好友添加申请前校验 + applyAddBefore: data => { + return request.post("/v1/friend/check_apply_add_before", data, { + urlTag: "api_2" + }); + }, + // 好友数量校验 + checkFriendTotal: data => { + return request.post("/v1/friend/check_friend_total", data, { + urlTag: "api_2" + }); + }, + // 手机号后缀验证 + checkFriendPhoneSuffix: data => { + return request.post("/v1/friend/check_friend_phone_suffix", data, { + urlTag: "api_2" + }); + }, + // 群内发起好友申请前校验 + checkAddFriendApplyByGroup: data => { + return request.post("/v1/friend/check_add_friend_apply_by_group", data, { + urlTag: "api_2" + }); + }, + // 添加好友邮箱验证 + checkFriendEmailSuffix: data => { + return request.post("/v1/friend/check_friend_email", data, { + urlTag: "api_2" + }); + }, + // 删除面对面匹配输入值 + deleteMateCode: data => { + return request.post("/v1/friend/delete_mate_Code", data, { + urlTag: "api_2" + }); + }, + // 面对面匹配输入值 + userBindCode: data => { + return request.post("/v1/friend/user_bind_code", data, { urlTag: "api_2" }); + }, + // 面对面添加好友 + addFaceFriend: data => { + return request.post("/v1/friend/add_friend", data, { urlTag: "api_2" }); + }, + // 根据id添加好友 + addUidFriend: data => { + return request.post("/v1/friend/add_friend_by_uin", data, { + urlTag: "api_2" + }); + }, + // 群内根据id添加好友 + addGroupFriendByUin: data => { + return request.post("/v1/friend/add_group_friend_by_uin", data, { + urlTag: "api_2" + }); + }, + // 扫一扫添加好友 + addScanFriend: data => { + return request.post("/v1/friend/add_friend_by_scan", data, { + urlTag: "api_2" + }); + }, + // 面对面好友申请处理 + applyFriend: data => { + return request.post("/v1/friend/respond_friend_apply", data, { + urlTag: "api_2" + }); + }, + // 获取匹配好友信息 + getMateUser: params => { + return request.get("/v1/friend/get_mate_user", { params }); + }, + // 识别图片二维码 + getQrCode: data => { + return request.post("/v1/user/qrcode/get", data, { urlTag: "api_2" }); + }, + // 获取用户的最新的二维码 + getLastestQrCode: data => { + return request.post("/v1/user/qrcode/lastest", data, { urlTag: "api_2" }); + }, + // 生成的最新的二维码 + addQrCode: data => { + return request.post("/v1/user/qrcode/add", data, { urlTag: "api_2" }); + }, + // 获取好友分组列表 + getFriendsGroupList: () => { + return request.get("/v1/friends_group/list", { urlTag: "api_2" }); + }, + // 创建好友分组 + createFriendsGroup: data => { + return request.post("/v1/friends_group/create", data, { urlTag: "api_2" }); + }, + // 删除好友分组 + deleteFriendsGroup: data => { + return request.post("/v1/friends_group/delete", data, { urlTag: "api_2" }); + }, + // 保存分组好友 + saveFriendsGroup: data => { + return request.post("/v1/friends_group/friends_save", data, { + urlTag: "api_2" + }); + }, + // 编辑好友分组名 + updateFriendsGroup: data => { + return request.post("/v1/friends_group/update", data, { urlTag: "api_2" }); + }, + // 批量添加好友加入分组 + batchAddFriends: data => { + return request.post("/v1/friends_group/friends_batch_add", data, { + urlTag: "api_2" + }); + }, + // 批量移除分组内好友 + batchRemoveFriends: data => { + return request.post("/v1/friends_group/friends_batch_remove", data, { + urlTag: "api_2" + }); + }, + // 获取指定分组的好友列表 + getFriendsGroupListById: data => { + return request.post("/v1/friends_group/friends_list", data, { + urlTag: "api_2" + }); + }, + // 获取好友申请列表 + getFriendApplyList: data => { + return request.post("/v1/friend/get_friend_apply_list", data, { + urlTag: "api_2" + }); + }, + getRespondFriendApplyByTime: data => { + return request.post("/v1/friend/get_respond_friend_apply_by_time", data, { + urlTag: "api_2" + }); + }, + + // // 获取好友申请加自己 + // getFriendApplyList: (data) => { + // return request.post('/friend/get_friend_apply_list', data, { urlTag: 'api_3' }); + // }, + // // 获取自己申请加好友 + // getSelfFriendApplayList: (data) => { + // return request.post('/friend/get_self_friend_apply_list', data, { urlTag: 'api_3' }); + // }, + // 获取管理员或群主是否开启好友验证 + getVerifyStatus: data => { + return request.post("/v1/user/settings/get", data, { urlTag: "api_2" }); + }, + // 更改管理员或群主开启好友验证 + setVerifyStatus: data => { + return request.post("/v1/user/settings/update", data, { urlTag: "api_2" }); + }, + // 设置群成员信息 群管理员设置 + setGroupMemberInfoApi: (data) => { + return request.post('/v1/group/set_group_member_info', data, { urlTag: 'api_2' }); + }, + // 设置群扩展字段里面消息信息 群管理员设置 + setGroupInfoEx: data => { + return request.post("/v1/group/set_group_info_ex", data, { + urlTag: "api_2" + }); + }, + ossPutUrls: data => { + return request.post("/v1/user/oss_put_url", data, { urlTag: "api_2" }); + }, + awsPutUrls: data => { + return request.post("/v1/user/aws_put_url", data, { urlTag: "api_2" }); + }, + // 生成邀请链接 + groupGenerateLink: data => { + return request.post("/v1/group/group_generate_link", data, { + urlTag: "api_2" + }); + }, + // 删除链接 + groupDeleteLink: data => { + return request.post("/v1/group/group_delete_link", data, { + urlTag: "api_2" + }); + }, + // 设置链接时间 + groupLinkTime: data => { + return request.post("/v1/group/group_link_time", data, { urlTag: "api_2" }); + }, + // 管理邀请链接 + groupManageLink: data => { + return request.post("/v1/group/group_manage_link", data, { + urlTag: "api_2" + }); + }, + // 邀请用户进群 -- 获取群信息 + groupInviteInfo: data => { + return request.post("/v1/group/group_invite_info", data, { + urlTag: "api_2" + }); + }, + // 删除会话消息 + deleteconversationMsg: data => { + return request.post("/v1/msg/delete_conversation_msg", data, { + urlTag: "api_2" + }); + }, + // 获取申请的进群的待处理 已处理 列表 + getRecvGroupApplicationList: data => { + return request.post("/v1/group/get_recv_group_applicationList", data, { + urlTag: "api_2" + }); + }, + // 群主或管理员处理进群申请 或全部同意 + groupApplicationResponse: data => { + return request.post("/v1/group/group_application_response", data, { + urlTag: "api_2" + }); + }, + // 删除本地聊天记录 + deleteConversationMsgApi: data => { + return request.post("/v1/msg/delete_conversation_msg", data, { + urlTag: "api_2" + }); + }, + // 查询群详情 + getGroups: data => { + return request.post("/v1/group/get_groups", data, { urlTag: "api_2" }); + }, + // 根据UserID设置会话选项,包括置顶消息,免打扰,清空聊天记录,加入黑名单等(包括单聊和群聊) + setConversationsApi: data => { + return request.post("/v1/conversation/set_conversations", data, { + urlTag: "api_2" + }); + }, + // 设置置顶群信息 + setGroupTopMsg: data => { + return request.post("/v1/group/set_group_info_ex", data, { + urlTag: "api_2" + }); + }, + // 获取置顶群信息 + getGroupTopMsg: data => { + return request.post("/v1/msg/get_top_msg_list_by_msgId", data, { + urlTag: "api_2" + }); + }, + // 设置群聊面具-开关 + setGroupMask: data => { + return request.post("/v1/group/set_group_info_ex", data, { + urlTag: "api_2" + }); + }, + // 发送红包 + sendRedpkt: data => { + return request.post("/v1/envelope/send", data, { urlTag: "api_2" }); + }, + // 获取红包领取 + getAssetFlowDetail: params => { + return request.get("/v1/property/get_asset_flow_detail", { params }); + }, + // 获取红包领取详情 + getRedpktgetDetail: data => { + return request.post("/v1/envelope/get", data, { urlTag: "api_2" }); + }, + // 分页获取已领取所有用户 + getRedpktgetList: data => { + return request.post("/v1/envelope/get_receivers_by_envelope_id", data, { + urlTag: "api_2" + }); + }, + // 发送群公告 + setGroupNotification: data => { + return request.post("/v1/group/send_group_notification", data, { + urlTag: "api_2" + }); + }, + // 群成员信息查询 + getGroupMemberInfoEx: data => { + return request.post( + "/v1/group/get_group_members_info_ex", + data, + { urlTag: "api_2" } + ); + }, + // 领取红包 + claimRedpkt: data => { + return request.post("/v1/envelope/claim", data, { urlTag: "api_2" }); + }, + // 添加转发消息 + saveTransmitMessage: data => { + return request.post("/v1/msg/set_relay_message", data, { urlTag: "api_2" }); + }, + // 获取转发消息 + getTransmitMessage: params => { + return request.get("/v1/msg/get_relay_message", { params }); + }, + // 添加好友条件校验 + validateAddFriendByUser: data => { + return request.post("/v1/friend/validate_add_friend_by_user", data, { + urlTag: "api_2" + }); + }, + // 获取群发会话列表 + getBatchConversationList: data => { + return request.post("/v1/batch/get_batch_conversation_list", data, { + urlTag: "api_2" + }); + }, + // 删除群发会话 + batchDeleteConversation: data => { + return request.post("/v1/batch/delete_batch_conversation", data, { + urlTag: "api_2" + }); + }, + // 获取单个群发会话消息列表 + getMessageListByBatchConversation: data => { + return request.post( + "/v1/batch/get_message_list_by_batch_conversation", + data, + { urlTag: "api_2" } + ); + }, + // 单个群发会话发消息 + sendMessageByBatchConversation: data => { + return request.post("/v1/batch/send_message_by_batch_conversation", data, { + urlTag: "api_2" + }); + }, + // 创建群发会话 + createBatchConversation: data => { + return request.post("/v1/batch/create_batch_conversation", data, { + urlTag: "api_2" + }); + }, + // 设置阅后即焚 + setConversationBurnAfterReading: data => { + return request.post( + "/v1/conversation/set_conversation_burn_after_reading", + data, + { urlTag: "api_2" } + ); + }, + // 删除本地消息记录,清除远端焚烧消息 + burnMsgByClearMsg: data => { + return request.post( + "/msg/burn_msg_by_clear_msg", + data, + { urlTag: "api_3" } + ); + }, + // 删除该用户在本群发送的群消息 + delGroupMsgByUserId: data => { + return request.post( + "/msg/delete_msg_by_user_id", + data, + { urlTag: "api_3" } + ); + }, + // 获取服务器当前时间 + getCurrentTime: (data = {}) => { + return request.post( + "/v1/logger/get_current_time", + data, + { urlTag: "api_2" } + ); + }, + // 获取用户群聊数量 + countJoinedGroups: (data = {}) => { + return request.post( + "/group/count_joined_groups", + data, + { urlTag: "api_3" } + ); + }, + // 获取用户群聊数量 + friendIsFriend: (data = {}) => { + return request.post( + "friend/is_friend", + data, + { urlTag: "api_3" } + ); + }, +}; + + +export default api; diff --git a/src/api/file.js b/src/api/file.js new file mode 100644 index 0000000..814abca --- /dev/null +++ b/src/api/file.js @@ -0,0 +1,18 @@ +import request from "./request"; + +const api = { + //oss上传地址获取 + ossPutUrl: data => { + return request({ + method: "post", + url: '/v1/user/oss_put_url', + data, + urlTag: "api_2" + }); + }, + ossPutUrls: (data) => { + return request.post('/v1/user/oss_put_url', data, { urlTag: "api_2" }); + } +} +export default api + diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..e6fe6b2 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,19 @@ +import login from './login.js' +import user from './user.js' +import contact from './contact.js' +import setting from './setting.js' +import msg_privateChat from "@/api/msg_privateChat.js"; +import file from './file.js' +import avatar from './avatar.js' +import community from './community.js' +const api = { + login, + user, + contact, + setting, + msg_privateChat, + file, + avatar, + community, +} +export default api \ No newline at end of file diff --git a/src/api/line-request.js b/src/api/line-request.js new file mode 100644 index 0000000..4d2633d --- /dev/null +++ b/src/api/line-request.js @@ -0,0 +1,258 @@ +import axios from "axios"; +import router from "@/router"; +import { showToast, showDialog } from "vant"; +import { useUserStore } from "@/stores/user"; +import { removeAllLocalstorage } from "@/utils/tools"; +import { i18n } from "@/lang/index"; +import use from "@/use"; +const { useEmailLang } = use; +const { t } = i18n.global; +let appConfig = sessionStorage.getItem("appConfig") + ? JSON.parse(sessionStorage.getItem("appConfig")) + : {}; +const { DEV, VITE_APP_PUB_API, VITE_APP_MSG_API } = import.meta.env; +// @ts-ignore +const { api_2, api_3, api_1 } = window.WEB_CONFIG; +const service = axios.create({ + timeout: 60000, // 请求超时时间毫秒 + withCredentials: false, // 异步请求携带cookie + headers: { + // 设置后端需要的传参类型 + "Content-Type": "application/json" + } +}); +// 请求拦截器 +service.interceptors.request.use( + config => { + //给请求头设置token + const token = window.localStorage.getItem("token") || ""; + let globalRequestPrefix = sessionStorage.getItem("globalRequestPrefix") + if (config.urlTag === "api_1") { + config.headers.Token = token; + config.headers.Authorization = token; + // console.error('最后的1',window.WEB_CONFIG); + if (globalRequestPrefix) { + window.WEB_CONFIG.api_1 = globalRequestPrefix + '/manage-api' + config.baseURL = globalRequestPrefix + '/manage-api' + } else { + config.baseURL = DEV ? "/api_1" : api_1; + } + config.headers.language = useEmailLang(); + } + if ( + config.url === "/v1/friend/get_mate_user" || + config.url === "/v1/property/get_asset_flow_detail" || + config.url === "/v1/image/get_head_image_list_by_type" || + config.url === "/v1/msg/get_relay_message" || + config.url === "/v1/property/get_asset_flow_detail" + ) { + config.urlTag = "api_2"; + } + if (config.urlTag === "api_2") { + // console.error('最后的2',window.WEB_CONFIG); + if (globalRequestPrefix) { + window.WEB_CONFIG.api_2 = globalRequestPrefix +'/chat-api' + config.baseURL = globalRequestPrefix +'/chat-api' + } else { + config.baseURL = DEV ? "/api_2" : api_2; + } + config.headers.token = token; + if (config.url === "/v1/user/oss_put_url") { + config.headers.operationID = +new Date(); + } + config.headers.language = useEmailLang(); + } + if (config.urlTag === "api_3") { + config.headers.Operationid = +new Date(); + config.headers.operationID = localStorage.getItem("user_id"); + config.headers.token = localStorage.getItem("im_token"); + // console.error('最后的3',window.WEB_CONFIG); + if (globalRequestPrefix) { + window.WEB_CONFIG.api_3 = globalRequestPrefix +'/chat-server-api' + config.baseURL = globalRequestPrefix +'/chat-server-api' + } else { + config.baseURL = DEV ? "/api_3" : api_3; + } + config.headers.language = useEmailLang(); + } + console.error('线路检测的config',config); + return config; + }, + error => { + showToast({ position: "top", message: error.message }); + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + response => { + const { data, request } = response; + const { code, msg, detail } = data; + const { responseURL } = request; + const userStore = useUserStore(); + if (code === 511) { + if ( + responseURL.includes("/v1/login") || + responseURL.includes("/v1/user/phone_login") + ) { + showToast({ message: "系统正在维护中" }); + } + return data; + } + if (code === 401) { + removeAllLocalstorage(); + router.push({ path: "/firstScreen" }); + return data; + } + if (code === 1119) { + // 登录失效 + showDialog({ + title: t("dialog_tip1"), + message: "您的账户已在其他设备登录,如非本人操作,请立即修改登录密码!", + confirmButtonText: t("know") + }).then(() => { + removeAllLocalstorage(); + router.push({ path: "/firstScreen" }); + return data; + }); + return; + } + if (code === 200) { + // 将组件用的数据返回 + return data; + } else { + // 添加好友条件校验返回 + if ( + code === 6083 || + code === 6077 || + code === 6018 || + code === 6093 || + code === 6105 || + code === 6106 || + code === 6107 || + code === 6110 || + code === 6102 + ) { + return data; + } + // 处理业务错误。 + if (detail && code != 5050 && code !== 500) { + if (code === 100003) { + return data; + } + if (code === 100002) { + return data; + } + if (code === 6008 || code === 6005) { + showToast({ position: "top", message: t("error_6005") }); + return { + code: code + }; + } + if (code === 6011 || code === 6017) { + showToast({ position: "top", message: t("error_6011") }); + return { + code: code + }; + } + if (code === 6090) { + showToast({ position: "top", message: t("error_6090") }); + return { + code: code + }; + } + if (code === 6100) { + showToast({ + position: "top", + message: t("error_6100", { + day: appConfig?.modify_nickName_limit?.day + }) + }); + return { + code: code + }; + } + if (code === 6103) { + showToast({ + position: "top", + message: t("error_6103") + }); + return { + code: code + }; + } + if (code === 6104) { + showToast({ + position: "top", + message: t("error_6104") + }); + return { + code: code + }; + } + if (code === 6108) { + showToast({ + position: "top", + message: t("error_6108") + }); + return { + code: code + }; + } + if (code === 6034) { + showToast({ + position: "top", + message: t("friendNumberCatchTopLimit") + }); + return { + code: code + }; + } + if (code === 6109) { + showToast({ + position: "top", + message: t("error_6109") + }); + return { + code: code + }; + } + if (code === 6035) { + showToast({ + position: "top", + message: t("error_6035") + }); + return { + code: code + }; + } + if (code === 6007 || code === 6087 || code === 6088) { + showToast({ + position: "top", + message: t("error_6007") + }); + return { + code: code + }; + } + if (code === 6078) { + showToast({ + position: "top", + message: t("error_6078") + }); + return data; + } + showToast({ position: "top", message: detail }); + } + return data; + } + }, + error => { + // // HTTP 状态码 + // const status = error.response?.status; + console.error('处理 HTTP 网络错误',error); + return Promise.reject(error); + } +); +export default service; diff --git a/src/api/login.js b/src/api/login.js new file mode 100644 index 0000000..696f4ec --- /dev/null +++ b/src/api/login.js @@ -0,0 +1,189 @@ +// import request from "@/utils/request.js"; +import request from "./request"; + +const api = { + getPubKey: () => { + return request.post("/v1/system/auth/get_pub_key", { urlTag: "api_2" }); + }, + login: data => { + return request.post("/v1/account/login", data, { urlTag: "api_2" }); + }, + // 申请设备审核 + addVerify: data => { + return request.post("/v1/account/device/add_verify", data, { urlTag: "api_2" }); + }, + // 获取区号 + getAreaList: data => { + return request.post("/v1/area/list", data, { urlTag: "api_2" }); + }, + // 搜索区号 + searchAreaList: data => { + return request.post("/v1/area/search", data, { urlTag: "api_2" }); + }, + // 检查用户名接口 + checkAccount: data => { + return request.post("/v1/account/check_account", data, { urlTag: "api_2" }); + }, + // 发送验证码接口 + sendCode: data => { + return request.post("/v1/account/code/send", data, { urlTag: "api_2" }); + }, + // 检查验证码是否正确 + checkCode: data => { + return request.post("/v1/account/code/verify", data, { urlTag: "api_2" }); + }, + // 退出登录接口 + logout: data => { + return request.post("/v1/account/logout", data, { urlTag: "api_2" }); + }, + // 修改密码接口 + updatePas: data => { + return request.post("/v1/account/password/change", data, { + urlTag: "api_2" + }); + }, + // 第三方登录账号设置密码接口 + setThirdLoginPas: data => { + return request.post("/v1/account/password/add", data, { urlTag: "api_2" }); + }, + // // 重置密码接口 + // resetPas: data => { + // return request.post("/v1/account/password/reset", data, { + // urlTag: "api_2" + // }); + // }, + // 新重置密码接口 + resetPas: data => { + return request.post("v1/account/password/reset_with_verify_key", data, { + urlTag: "api_2" + }); + }, + // 注册 + register: data => { + return request.post("/v1/account/register", data, { urlTag: "api_2" }); + }, + // 查看用户信息 + userInfo: data => { + return request.post("/v1/user/get", data, { urlTag: "api_2" }); + }, + // 获取后台配置的登录注册方式 + getConfigInfo: data => { + return request.post("/v1/config/get", data, { urlTag: "api_2" }); + }, + // 获取后台群主配置(章云) + getGroupSettings: data => { + return request.post("/v1/config/group_settings", data, { urlTag: "api_2" }); + }, + // 修改个人资料 + updateUserInfo: data => { + return request.post("/v1/user/update", data, { urlTag: "api_2" }); + }, + // 修改个人昵称 + updateNickname: data => { + return request.post("/v1/user/nickname/update", data, { urlTag: "api_2" }); + }, + // 设备列表 + getDeviceList: data => { + return request.post("/v1/account/device/list", data, { urlTag: "api_2" }); + }, + // 移除设备 + deleteDevice: data => { + return request.post("/v1/account/device/delete", data, { urlTag: "api_2" }); + }, + // 设备登录日志列表 + deviceDetail: data => { + return request.post("/v1/account/device/login_log/list", data, { + urlTag: "api_2" + }); + }, + // 账号是否注册 + isRegister: data => { + return request.post("/v1/account/login/is_register", data, { + urlTag: "api_2" + }); + }, + // 绑定手机号 + bindPhone: data => { + return request.post("/v1/user/bind_phone", data, { urlTag: "api_2" }); + }, + // 换绑手机号 + changePhone: data => { + return request.post("/v1/user/change_phone", data, { urlTag: "api_2" }); + }, + // 绑定邮箱 + bindEmail: data => { + return request.post("/v1/user/bind_email", data, { urlTag: "api_2" }); + }, + // 换绑邮箱 + changeEmail: data => { + return request.post("/v1/user/change_email", data, { urlTag: "api_2" }); + }, + // 短信码校验 + verifyCode: data => { + return request.post("/v1/account/code/verify", data, { urlTag: "api_2" }); + }, + // 用户登录状态 + userLoginStatus: data => { + return request.post("/v1/user/login/status", data, { urlTag: "api_2" }); + }, + // 获取用户余额 + getUserBalance: () => { + return request.get("/v1/property/get_balance", { urlTag: "api_2" }); + }, + // 获取用户资产流水列表 + getUserAssetFlow: data => { + return request.post("/v1/property/get_asset_flow_list", data, { + urlTag: "api_2" + }); + }, + // 获取用户资产流水详情 + getUserAssetFlowDetail: params => { + return request.get("/v1/property/get_asset_flow_detail", { params }); + }, + // 获取红包详情 + getEnvelope: data => { + return request.post("/v1/envelope/get", data, { urlTag: "api_2" }); + }, + // 获取APP版本号 + getVersion: data => { + return request.post("/v1/version/get", data, { urlTag: "api_2" }); + }, + // 获取当前类别下所有头像信息 type:1群聊头像 2个人头像 3群头像 + getAllImageByType: params => { + return request.get("/v1/image/get_head_image_list_by_type", { params }); + }, + // 提出注销申请 + applicationApply: () => { + return request.post( + "/v1/account/application/apply", + {}, + { urlTag: "api_2" } + ); + }, + // 获取申请详情 + applicationGet: data => { + return request.post("/v1/account/application/get", data, { + urlTag: "api_2" + }); + }, + // 撤销注销申请 + applicationCancel: data => { + return request.post("/v1/account/application/cancel", data, { + urlTag: "api_2" + }); + }, + // 创建新的登录二维码 + generateQrcode: data => { + return request.post("/v1/account/auth/generate_qrcode", data, { urlTag: "api_2" }); + }, + // 获取二维码登录信息 + getLoginQrcode: data => { + return request.post("/v1/account/auth/get_login_qrcode", data, { urlTag: "api_2" }); + }, + // 获取ip 地址和归属地 + getAddressLocationIp: () => { + return request.get('v1/area/search_ip_info', { urlTag: 'api_2' }); + }, +}; + +export default api; diff --git a/src/api/msg_privateChat.js b/src/api/msg_privateChat.js new file mode 100644 index 0000000..033c0e1 --- /dev/null +++ b/src/api/msg_privateChat.js @@ -0,0 +1,15 @@ +import request from "./request"; + +const api = { + //查询公共用户信息 + query_public_userInfo:data=>{ + return request.post('/v1/user/public_info/query',data, { urlTag: 'api_2' }); + }, + //查询群在线人数 + queryGroupMemberOnlineCount:data=>{ + return request.post('/v1/group/get_group_member_online_count',data, { urlTag: 'api_2' }); + } + +} + +export default api \ No newline at end of file diff --git a/src/api/request.js b/src/api/request.js new file mode 100644 index 0000000..2d552c5 --- /dev/null +++ b/src/api/request.js @@ -0,0 +1,322 @@ +import axios from "axios"; +import router from "@/router"; +import { showToast, showDialog } from "vant"; +import { useUserStore } from "@/stores/user"; +import { removeAllLocalstorage } from "@/utils/tools"; +import { i18n } from "@/lang/index"; +import use from "@/use"; +const { useEmailLang } = use; +const { t } = i18n.global; +let appConfig = sessionStorage.getItem("appConfig") + ? JSON.parse(sessionStorage.getItem("appConfig")) + : {}; +const { DEV, VITE_APP_PUB_API, VITE_APP_MSG_API } = import.meta.env; +// @ts-ignore +const { api_2, api_3, api_1 } = window.WEB_CONFIG; +// fix 2024-8-15 保留日志打印 +// console.error('api_1',api_1); +// console.error('api_2',api_2); +// console.error('api_3',api_3); +console.error('DEV',DEV); + +// const maxSlowCount = 5; +// let slowCount = 0; +// const api_2 = 'https://dev-im-m.exchangs.top/chat-api' +const service = axios.create({ + // baseURL: DEV ? VITE_APP_PUB_API : api_2, // 所有的请求地址前缀部分 + timeout: 60000, // 请求超时时间毫秒 + withCredentials: false, // 异步请求携带cookie + headers: { + // 设置后端需要的传参类型 + "Content-Type": "application/json" + } +}); +// 请求拦截器 +service.interceptors.request.use( + config => { + // 记录请求开始时间 + // config.startTime = new Date().getTime(); + //给请求头设置token + const token = window.localStorage.getItem("token") || ""; + let globalRequestPrefix = sessionStorage.getItem("globalRequestPrefix") + if (config.urlTag === "api_1") { + config.headers.Token = token; + config.headers.Authorization = token; + // console.error('最后的1',window.WEB_CONFIG); + if (globalRequestPrefix) { + window.WEB_CONFIG.api_1 = globalRequestPrefix + '/manage-api' + config.baseURL = globalRequestPrefix + '/manage-api' + } else { + config.baseURL = DEV ? "/api_1" : api_1; + } + config.headers.language = useEmailLang(); + } + if ( + config.url === "/v1/friend/get_mate_user" || + config.url === "/v1/property/get_asset_flow_detail" || + config.url === "/v1/image/get_head_image_list_by_type" || + config.url === "/v1/msg/get_relay_message" || + config.url === "/v1/property/get_asset_flow_detail" + ) { + config.urlTag = "api_2"; + } + if (config.urlTag === "api_2") { + // console.error('最后的2',window.WEB_CONFIG); + if (globalRequestPrefix) { + window.WEB_CONFIG.api_2 = globalRequestPrefix +'/chat-api' + config.baseURL = globalRequestPrefix +'/chat-api' + } else { + config.baseURL = DEV ? "/api_2" : api_2; + } + config.headers.token = token; + if (config.url === "/v1/user/oss_put_url") { + config.headers.operationID = +new Date(); + } + config.headers.language = useEmailLang(); + } + if (config.urlTag === "api_3") { + config.headers.Operationid = +new Date(); + config.headers.operationID = localStorage.getItem("user_id"); + config.headers.token = localStorage.getItem("im_token"); + // console.error('最后的3',window.WEB_CONFIG); + if (globalRequestPrefix) { + window.WEB_CONFIG.api_3 = globalRequestPrefix +'/chat-server-api' + config.baseURL = globalRequestPrefix +'/chat-server-api' + } else { + config.baseURL = DEV ? "/api_3" : api_3; + } + config.headers.language = useEmailLang(); + } + // console.error('config',config); + return config; + }, + error => { + showToast({ position: "top", message: error.message }); + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + response => { + const { data, request } = response; + const { code, msg, detail } = data; + const { responseURL } = request; + const userStore = useUserStore(); + const { setToken } = userStore; + if (code === 511) { + if ( + responseURL.includes("/v1/login") || + responseURL.includes("/v1/user/phone_login") + ) { + showToast({ message: "系统正在维护中" }); + } + return data; + } + if (code === 401) { + // 登录失效 + // showDialog({ + // title: '温馨提示', + // message: '您的账户已在其他设备登录,如非本人操作,请立即修改登录密码!', + // }).then(() => { + removeAllLocalstorage(); + router.push({ path: "/firstScreen" }); + return data; + } + if (code === 1119) { + // 登录失效 + showDialog({ + title: t("dialog_tip1"), + message: "您的账户已在其他设备登录,如非本人操作,请立即修改登录密码!", + confirmButtonText: t("know") + }).then(() => { + removeAllLocalstorage(); + router.push({ path: "/firstScreen" }); + return data; + }); + return; + } + if (code === 200) { + // 将组件用的数据返回 + return data; + } else { + // 添加好友条件校验返回 + if ( + code === 6083 || + code === 6077 || + code === 6018 || + code === 6093 || + code === 6105 || + code === 6106 || + code === 6107 || + code === 6110 || + code === 6102 + ) { + return data; + } + // 处理业务错误。 + if (detail && code != 5050 && code !== 500) { + if (code === 100003) { + return data; + } + if (code === 100002) { + return data; + } + if (code === 6008 || code === 6005) { + showToast({ position: "top", message: t("error_6005") }); + return { + code: code + }; + } + if (code === 6011 || code === 6017) { + showToast({ position: "top", message: t("error_6011") }); + return { + code: code + }; + } + if (code === 6090) { + showToast({ position: "top", message: t("error_6090") }); + return { + code: code + }; + } + if (code === 6100) { + showToast({ + position: "top", + message: t("error_6100", { + day: appConfig?.modify_nickName_limit?.day + }) + }); + return { + code: code + }; + } + if (code === 6103) { + showToast({ + position: "top", + message: t("error_6103") + }); + return { + code: code + }; + } + if (code === 6104) { + showToast({ + position: "top", + message: t("error_6104") + }); + return { + code: code + }; + } + if (code === 6108) { + showToast({ + position: "top", + message: t("error_6108") + }); + return { + code: code + }; + } + if (code === 6034) { + showToast({ + position: "top", + message: t("friendNumberCatchTopLimit") + }); + return { + code: code + }; + } + if (code === 6109) { + showToast({ + position: "top", + message: t("error_6109") + }); + return { + code: code + }; + } + if (code === 6035) { + showToast({ + position: "top", + message: t("error_6035") + }); + return { + code: code + }; + } + if (code === 6007 || code === 6087 || code === 6088) { + showToast({ + position: "top", + message: t("error_6007") + }); + return { + code: code + }; + } + if (code === 6078) { + showToast({ + position: "top", + message: t("error_6078") + }); + return data; + } + if (code === 6001) { + showToast({ + position: "top", + message: t("error_6001") + }); + return data; + } + if (code === 6031) { + showToast({ + position: "top", + message: t("error_6031") + }); + return data; + } + if (code === 6032) { + showToast({ + position: "top", + message: t("error_6032") + }); + return data; + } + showToast({ position: "top", message: detail }); + } + return data; + } + }, + error => { + // 处理 HTTP 网络错误 + let message = ""; + // HTTP 状态码 + const status = error.response?.status; + switch (status) { + case 401: + message = t("dialog_tip_token_invaild"); + removeAllLocalstorage(); + router.push({ path: "/firstScreen" }); + // 这里可以触发退出的 action + break; + case 403: + message = t("noPermission"); + break; + case 404: + message = t("pageNotFound"); + break; + case 500: + message = t("serverError"); + break; + case 502: + message = t("databaseError"); + break; + default: + message = t("networkError"); + } + showToast({ position: "top", message: message }); + return Promise.reject(error); + } +); + +export default service; diff --git a/src/api/setting.js b/src/api/setting.js new file mode 100644 index 0000000..bd1c0bc --- /dev/null +++ b/src/api/setting.js @@ -0,0 +1,38 @@ +import request from "./request"; + +const api = { + // 设置语言 + setLanguage: (data) => { + return request.post('/v1/user/language_preference', data, { urlTag: 'api_2' }) + }, + // 解散群 + dismissGrounp: (data) => { + return request.post('/v1/group/dismiss_group', data, { urlTag: 'api_2' }) + }, + // 获取群成员列表 + getGroupMemberList: (data) => { + return request.post('/v1/group/get_group_member_list', data, { urlTag: 'api_2' }) + }, + // 获取系统聊天背景配置 + getSysBgSettings: (data) => { + return request.post('/v1/chat/get_sys_background_settings', data, { urlTag: 'api_2' }) + }, + // 获取用户聊天背景配置 + getUserBgSettings: (data) => { + return request.post('/v1/chat/get_user_background_settings', data, { urlTag: 'api_2' }) + }, + // 获取用户配置 + getUserInfoSettings: (data) => { + return request.post('/v1/user/settings/get', data, { urlTag: 'api_2' }) + }, + // 设置用户聊天背景 + setGlobalAppearance: (data) => { + return request.post('/v1/chat/set_user_background_settings', data, { urlTag: 'api_2' }) + }, + // 设置用户字体大小 + setGlobalFontSize: (data) => { + return request.post('/v1/user/settings/font_size/set', data, { urlTag: 'api_2' }) + }, +} + +export default api \ No newline at end of file diff --git a/src/api/typing.ts b/src/api/typing.ts new file mode 100644 index 0000000..a1936ef --- /dev/null +++ b/src/api/typing.ts @@ -0,0 +1,16 @@ +export interface ResponseBody { + message?: string + code?: number + data?: T + success: boolean +} + +/** 统一返回结构体 */ + +export interface PageResult { + data: T[] + current?: number + pageSize?: number + total?: number + success: boolean +} diff --git a/src/api/user.js b/src/api/user.js new file mode 100644 index 0000000..9d60aa9 --- /dev/null +++ b/src/api/user.js @@ -0,0 +1,124 @@ +import request from "./request"; + +let api = { + getPubKey: () => { + return request.post('/v1/cipher/get_pub_key'); + }, + configGet: () => { + return request.post('/v1/user/config/get', { urlTag: 'api_2' }); + }, + login: (data) => { + return request.post('/v1/user/login', data, { urlTag: 'api_2' }); + }, + logout: (data) => { + return request.post('/v1/user/logout', data, { urlTag: 'api_2' }); + }, + usernameRegister: (data) => { + return request.post('/v1/user/create', data, { urlTag: 'api_2' }); + }, + phoneRegister: (data) => { + return request.post('/v1/user/register', data, { urlTag: 'api_2' }); + }, + checkAccount: (data) => { + return request.post('/v1/user/check_account', data, { urlTag: 'api_2' }); + }, + resetPassword: (data) => { + return request.post('/v1/user/reset_password', data, { urlTag: 'api_2' }); + }, + resetPasswordByIdentity: (data) => { + return request.post('/v1/user/reset_password_by_identity', data, { urlTag: 'api_2' }); + }, + verifyCodeSend: (data) => { + return request.post('/v1/user/verify_code/send', data, { urlTag: 'api_2' }); + }, + verifyCodeCheck: (data) => { + return request.post('/v1/user/verify_code/check', data, { urlTag: 'api_2' }); + }, + userInfo: (data) => { + return request.post('/v1/user/get', data, { urlTag: 'api_2' }); + }, + userUpdate: (data) => { + return request.post('/v1/user/update', data, { urlTag: 'api_2' }); + }, + areaList: () => { + return request.post('/v1/user/area/list', { urlTag: 'api_2' }); + }, + needVerify: (data) => { + return request.post('/v1/user/need_verify', data, { urlTag: 'api_2' }); + }, + kycInit: (data) => { + return request.post('/v1/user/kyc/init', data, { urlTag: 'api_2' }); + }, + kycResult: (data) => { + return request.post('/v1/user/kyc/result', data, { urlTag: 'api_2' }); + }, + get_maintain: () => { + return request.get('/v1/get_maintain', { urlTag: 'api_2' }); + }, + // 增用户关系 + userRelationshipAdd: (data) => { + return request.post('/v1/user_relationship/add',data, { urlTag: 'api_2' }); + }, + // 收藏与拉黑列表 + userRelationshipList: (data) => { + return request.post('/v1/user_relationship/list',data, { urlTag: 'api_2' }); + }, + // 获取两者用户关系 + getByUserId: (data) => { + return request.post('/v1/user_relationship/get_by_userId',data, { urlTag: 'api_2' }); + }, + // 拼图验证码校验 + puzzle_verify: (data) => { + return request.post('/v1/user/puzzle_verify',data, { urlTag: 'api_2' }); + }, + // 获取用户登录状态 + getUserStatus: (data) => { + return request.post('/v1/user/login/status',data, { urlTag: 'api_2' }); + }, + //用户配置资产密码 + setPayPassword: (data) => { + return request.post('/v1/withdrawal/set_password',data, { urlTag: 'api_2' }); + }, + //用户校验资产密码 + checkPayPassword: (data) => { + return request.post('/v1/withdrawal/check_password',data, { urlTag: 'api_2' }); + }, + //提现申请 + withdrawApply: (data) => { + return request.post('/v1/withdrawal/apply',data, { urlTag: 'api_2' }); + }, + //申请记录列表 + withdrawApplyList: (data) => { + return request.post('/v1/withdrawal/apply/list',data, { urlTag: 'api_2' }); + }, + //撤销申请 + withdrawApplyCancel: (data) => { + return request.post('/v1/withdrawal/apply/cancel',data, { urlTag: 'api_2' }); + }, + //第三方sdk登录 + sdkLogin: (data) => { + return request.post('/v1/account/auth/sdk_login',data, { urlTag: 'api_2' }); + }, + //第三方sdk绑定 + sdkBind: (data) => { + return request.post('/v1/account/auth/sdk_bind',data, { urlTag: 'api_2' }); + }, + //第三方sdk解绑 + sdkUnBind: (data) => { + return request.post('/v1/account/auth/unbind',data, { urlTag: 'api_2' }); + }, + // 获取群主和群管理员标识 + getMemberIdentification: () => { + return request.get('/v1/group/get_group_owner_ids', { urlTag: 'api_2' }); + }, + // 账号活跃 + accountActive: (data) => { + return request.post('/v1/account/active',data, { urlTag: 'api_2' }); + }, + // 上报用户信息 + userReentrantRecord: (data) => { + return request.post('/v1/user_reentrant_record/insert',data, { urlTag: 'api_2' }); + }, +} + +export default api; diff --git a/src/app.less b/src/app.less new file mode 100644 index 0000000..b9db281 --- /dev/null +++ b/src/app.less @@ -0,0 +1,157 @@ +body { + // 这个可以屏蔽原生的下拉刷新 + overflow: hidden; +} + +html,body,#app,.app_main { + height: 100%; + font-size: 1rem; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +[data-theme='dark'] { + &, + * { + color-scheme: dark !important; + } +} + +[data-theme='light'] { + &, + * { + color-scheme: light !important; + } + + body { + background-color: #F8F8FB; + } +} +.contact-dialog { + border-radius: 12px; + background: #FFF; + box-shadow: 4px 4px 4px 0px rgba(118, 134, 149, 0.10); + .item { + display: flex; + align-items: center; + height: 48px; + padding: 0 24px 0 16px; + cursor: pointer; + img { + width: 24px; + height: 24px; + margin-right: 12px; + } + .txt { + color: #11121D; + font-family: PingFang SC; + font-size: 1rem; + font-weight: 400; + } + .txts { + font-size: 0.875rem; + } + .txt-red { + color: #FC5A50; + } + } + .item-small { + display: flex; + align-items: center; + height: 44px; + padding: 0 24px 0 16px; + cursor: pointer; + .txt { + color: #11121D; + font-family: PingFang SC; + font-size: 0.875rem; + font-weight: 400; + height: 22px; + } + .txt-red { + color: #FC5A50; + } + } + + .van-popover__content { + padding: 8px 0 !important; + } +} +// 去除掉全局导航栏的下划线 +.van-hairline--bottom:after { + border-bottom-width: 0; +} + +.wrap-content { + display: flex; + flex-direction: column; + margin-bottom: 12px; + + .title { + height: 38px; + font-size: 1rem; + color: #1B1B22; + padding: 8px 0 8px 24px; + font-weight: bold; + } + + .avatar-wrap { + height: 78px; + overflow-x: auto; + /* 横向溢出内容显示滚动条 */ + white-space: nowrap; + padding: 12px 0 12px 24px; + border-bottom: 0.5px solid #F2F4F5; + + .left-content { + display: flex; + align-items: center; + + .item-wrap { + margin-right: 10px; + + .avatar { + text-align: center; + + img { + width: 32px; + height: 32px; + border-radius: 50%; + margin-bottom: 4px; + } + + .name { + font-size: 0.75rem; + color: #1B1B22; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + .special-item-wrap { + display: flex; + flex-direction: row; + align-items: center; + + .name { + margin-left: 10px; + max-width: 80px; + } + } + + } + } + + .content-wrap { + padding: 12px 24px; + text-overflow: ellipsis; + overflow: auto; + max-height: 120px; + } +} \ No newline at end of file diff --git a/src/assets/Group 625883.png b/src/assets/Group 625883.png new file mode 100644 index 0000000..368df67 Binary files /dev/null and b/src/assets/Group 625883.png differ diff --git a/src/assets/Loading.png b/src/assets/Loading.png new file mode 100644 index 0000000..63ef49e Binary files /dev/null and b/src/assets/Loading.png differ diff --git a/src/assets/about.png b/src/assets/about.png new file mode 100644 index 0000000..6d803bf Binary files /dev/null and b/src/assets/about.png differ diff --git a/src/assets/activebg-icon.png b/src/assets/activebg-icon.png new file mode 100644 index 0000000..dee405f Binary files /dev/null and b/src/assets/activebg-icon.png differ diff --git a/src/assets/add.png b/src/assets/add.png new file mode 100644 index 0000000..6022c82 Binary files /dev/null and b/src/assets/add.png differ diff --git a/src/assets/animation/anim_guanliyuan.json b/src/assets/animation/anim_guanliyuan.json new file mode 100644 index 0000000..8eee045 --- /dev/null +++ b/src/assets/animation/anim_guanliyuan.json @@ -0,0 +1 @@ +{"nm":"guanliyuan3","ddd":0,"h":18,"w":120,"meta":{"g":"LottieFiles Figma v55"},"layers":[{"ty":0,"nm":"Frame 427321222","sr":1,"st":0,"op":361,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":240},{"s":[9,9],"t":360}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":240},{"s":[100,100],"t":360}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":240},{"s":[9,9],"t":360}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":240},{"s":[0],"t":360}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}},"w":120,"h":18,"refId":"1","ind":1},{"ty":4,"nm":"Rectangle 34624758","sr":1,"st":0,"op":361,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":240},{"s":[60,9],"t":360}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":240},{"s":[100,100],"t":360}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":240},{"s":[60,9],"t":360}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":240},{"s":[0],"t":360}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":240},{"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":360}]}},{"ty":"gf","bm":0,"hd":false,"nm":"","e":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[57.0890007019043,64.39510345458984],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[57.0890007019043,64.39510345458984],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[57.0890007019043,64.39510345458984],"t":240},{"s":[57.0890007019043,64.39510345458984],"t":360}]},"g":{"p":2,"k":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0,0.6991766053461561,0.8963808375620375,1,1,0.6664128857874403,1,0.9027315932535658,0,1,1,0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0,0.8644598084711561,1,0.9349612551950941,1,0.3581446963572035,0.7304432350420484,1,0,1,1,0],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0,0.9012240725779066,0.8765203315042982,1,1,0.44686537564034556,0.9004678684496412,1,0,1,1,0],"t":240},{"s":[0,0.6991766053461561,0.8963808375620375,1,1,0.6664128857874403,1,0.9027315932535658,0,1,1,0],"t":360}]}},"t":1,"a":{"a":0,"k":0},"h":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[5,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[5,9],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[5,9],"t":240},{"s":[5,9],"t":360}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}}],"ind":2}],"v":"5.7.0","fr":60,"op":360,"ip":0,"assets":[{"nm":"[Asset] Frame 427321222","id":"1","layers":[{"ty":4,"nm":"Union","sr":1,"st":0,"op":361,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.75,6],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.75,6],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.75,6],"t":240},{"s":[2.75,6],"t":360}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":240},{"s":[100,100],"t":360}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9.45],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9.45],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9.45],"t":240},{"s":[9,9.45],"t":360}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":240},{"s":[0],"t":360}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0.34,-0.29],[0.45,0],[0.34,0.29],[0,0.58],[-0.87,0],[0,-0.68]],"o":[[0,0.58],[-0.34,0.29],[-0.45,0],[-0.34,-0.29],[0,-0.68],[0.87,0],[0,0]],"v":[[4.99,0.78],[4.1,2.43],[2.75,2.73],[1.4,2.43],[0.51,0.78],[2.75,0],[4.99,0.78]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0.34,-0.29],[0.45,0],[0.34,0.29],[0,0.58],[-0.87,0],[0,-0.68]],"o":[[0,0.58],[-0.34,0.29],[-0.45,0],[-0.34,-0.29],[0,-0.68],[0.87,0],[0,0]],"v":[[4.99,0.78],[4.1,2.43],[2.75,2.73],[1.4,2.43],[0.51,0.78],[2.75,0],[4.99,0.78]]}],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0.34,-0.29],[0.45,0],[0.34,0.29],[0,0.58],[-0.87,0],[0,-0.68]],"o":[[0,0.58],[-0.34,0.29],[-0.45,0],[-0.34,-0.29],[0,-0.68],[0.87,0],[0,0]],"v":[[4.99,0.78],[4.1,2.43],[2.75,2.73],[1.4,2.43],[0.51,0.78],[2.75,0],[4.99,0.78]]}],"t":240},{"s":[{"c":true,"i":[[0,0],[0.34,-0.29],[0.45,0],[0.34,0.29],[0,0.58],[-0.87,0],[0,-0.68]],"o":[[0,0.58],[-0.34,0.29],[-0.45,0],[-0.34,-0.29],[0,-0.68],[0.87,0],[0,0]],"v":[[4.99,0.78],[4.1,2.43],[2.75,2.73],[1.4,2.43],[0.51,0.78],[2.75,0],[4.99,0.78]]}],"t":360}]}},{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0.19,0],[0,0],[0.07,-0.06],[0.02,-0.09],[0.31,-0.74],[-0.2,-0.58],[0,0],[-0.24,0.66],[0.3,0.68],[0.21,1.97]],"o":[[-0.04,-0.18],[0,0],[0,0],[-0.07,0.06],[-0.34,1.94],[-0.3,0.72],[0.23,0.67],[0,0],[0.2,-0.55],[-0.34,-0.78],[0,0]],"v":[[4.09,3.92],[3.7,3.61],[1.84,3.61],[1.59,3.7],[1.45,3.92],[0.36,7.68],[0.09,9.39],[2.75,11.99],[5.43,9.44],[5.13,7.78],[4.09,3.92]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0.19,0],[0,0],[0.07,-0.06],[0.02,-0.09],[0.31,-0.74],[-0.2,-0.58],[0,0],[-0.24,0.66],[0.3,0.68],[0.21,1.97]],"o":[[-0.04,-0.18],[0,0],[0,0],[-0.07,0.06],[-0.34,1.94],[-0.3,0.72],[0.23,0.67],[0,0],[0.2,-0.55],[-0.34,-0.78],[0,0]],"v":[[4.09,3.92],[3.7,3.61],[1.84,3.61],[1.59,3.7],[1.45,3.92],[0.36,7.68],[0.09,9.39],[2.75,11.99],[5.43,9.44],[5.13,7.78],[4.09,3.92]]}],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0.19,0],[0,0],[0.07,-0.06],[0.02,-0.09],[0.31,-0.74],[-0.2,-0.58],[0,0],[-0.24,0.66],[0.3,0.68],[0.21,1.97]],"o":[[-0.04,-0.18],[0,0],[0,0],[-0.07,0.06],[-0.34,1.94],[-0.3,0.72],[0.23,0.67],[0,0],[0.2,-0.55],[-0.34,-0.78],[0,0]],"v":[[4.09,3.92],[3.7,3.61],[1.84,3.61],[1.59,3.7],[1.45,3.92],[0.36,7.68],[0.09,9.39],[2.75,11.99],[5.43,9.44],[5.13,7.78],[4.09,3.92]]}],"t":240},{"s":[{"c":true,"i":[[0,0],[0.19,0],[0,0],[0.07,-0.06],[0.02,-0.09],[0.31,-0.74],[-0.2,-0.58],[0,0],[-0.24,0.66],[0.3,0.68],[0.21,1.97]],"o":[[-0.04,-0.18],[0,0],[0,0],[-0.07,0.06],[-0.34,1.94],[-0.3,0.72],[0.23,0.67],[0,0],[0.2,-0.55],[-0.34,-0.78],[0,0]],"v":[[4.09,3.92],[3.7,3.61],[1.84,3.61],[1.59,3.7],[1.45,3.92],[0.36,7.68],[0.09,9.39],[2.75,11.99],[5.43,9.44],[5.13,7.78],[4.09,3.92]]}],"t":360}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,1,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,1,1],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,1,1],"t":240},{"s":[1,1,1],"t":360}]},"r":2,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}}],"ind":1},{"ty":4,"nm":"路径 6","sr":1,"st":0,"op":361,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.4,2.74],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.4,2.74],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.4,2.74],"t":240},{"s":[2.4,2.74],"t":360}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,-100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,-100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,-100],"t":240},{"s":[100,-100],"t":360}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.82,2.04],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.82,2.04],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.82,2.04],"t":240},{"s":[2.82,2.04],"t":360}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[165.01],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[165.01],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[165.01],"t":240},{"s":[165.01],"t":360}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":240},{"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":360}]}},{"ty":"st","bm":0,"hd":false,"nm":"","lc":1,"lj":1,"ml":4,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":240},{"s":[0],"t":360}]},"w":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1],"t":240},{"s":[1],"t":360}]},"c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.592,0.592,0.592],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.592,0.592,0.592],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.592,0.592,0.592],"t":240},{"s":[0.592,0.592,0.592],"t":360}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.3239,0.5612,0.8833],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.3239,0.5612,0.8833],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.3239,0.5612,0.8833],"t":240},{"s":[0.3239,0.5612,0.8833],"t":360}]},"r":2,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}}],"ind":2},{"ty":4,"nm":"Frame 427321222 Bg","sr":1,"st":0,"op":361,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":240},{"s":[9,9],"t":360}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":240},{"s":[100,100],"t":360}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":240},{"s":[9,9],"t":360}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":240},{"s":[0],"t":360}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":240},{"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":360}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.3239,0.5612,0.8833],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.3239,0.5612,0.8833],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.3239,0.5612,0.8833],"t":240},{"s":[0.3239,0.5612,0.8833],"t":360}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":240},{"s":[100],"t":360}]}}],"ind":3}]}]} \ No newline at end of file diff --git a/src/assets/animation/anim_qunzhu.json b/src/assets/animation/anim_qunzhu.json new file mode 100644 index 0000000..b36b624 --- /dev/null +++ b/src/assets/animation/anim_qunzhu.json @@ -0,0 +1 @@ +{"nm":"qunzhu3","ddd":0,"h":18,"w":120,"meta":{"g":"LottieFiles Figma v55"},"layers":[{"ty":0,"nm":"Frame 427321221","sr":1,"st":0,"op":181,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"s":[9,9],"t":180}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"s":[100,100],"t":180}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"s":[9,9],"t":180}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"s":[0],"t":180}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}},"w":120,"h":18,"refId":"1","ind":1},{"ty":4,"nm":"Rectangle 34624758","sr":1,"st":0,"op":181,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":120},{"s":[60,9],"t":180}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"s":[100,100],"t":180}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[60,9],"t":120},{"s":[60,9],"t":180}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"s":[0],"t":180}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[80],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[80],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[80],"t":120},{"s":[80],"t":180}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":120},{"s":[{"c":true,"i":[[0,0],[-4.97,0],[0,0],[0,0],[0,0],[0,4.97],[0,0]],"o":[[0,-4.97],[0,0],[0,0],[0,0],[-4.97,0],[0,0],[0,0]],"v":[[0,9],[9,0],[120,0],[120,18],[9,18],[0,9],[0,9]]}],"t":180}]}},{"ty":"gf","bm":0,"hd":false,"nm":"","e":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[58.94660186767578,62.648399353027344],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[58.94660186767578,62.648399353027344],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[58.94660186767578,62.648399353027344],"t":120},{"s":[58.94660186767578,62.648399353027344],"t":180}]},"g":{"p":3,"k":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0,1,0.8798775035166273,0.6759669739031324,0.5046470165252686,1,0.6858302313112745,0.8555030781054029,1,1,0.6013575750612745,0.6767837959551344,0,1,0.5046470165252686,0.49535298347473145,1,0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0,1,0.7584835249208937,0.7054591137194166,0.5000041127204895,1,0.8573898035311232,0.7540919739031324,1,0.9960952955507765,0.8028549510263929,1,0,1,0.5000041127204895,0.4999958872795105,1,0],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0,1,0.7473211246752272,0.9596042591356764,0.4996807277202606,0.9946402865671644,0.7448764401697645,0.8317942577623854,1,1,0.7987045604013929,0.7987045604013929,0,1,0.4996807277202606,0.500319242477417,1,0],"t":120},{"s":[0,1,0.8798775035166273,0.6759669739031324,0.5046470165252686,1,0.6858302313112745,0.8555030781054029,1,1,0.6013575750612745,0.6767837959551344,0,1,0.5046470165252686,0.49535298347473145,1,0],"t":180}]}},"t":1,"a":{"a":0,"k":0},"h":{"a":0,"k":0},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8.5,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8.5,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8.5,9],"t":120},{"s":[8.5,9],"t":180}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}}],"ind":2}],"v":"5.7.0","fr":60,"op":180,"ip":0,"assets":[{"nm":"[Asset] Frame 427321221","id":"1","layers":[{"ty":4,"nm":"Union","sr":1,"st":0,"op":181,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[4.74,4.81],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[4.74,4.81],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[4.74,4.81],"t":120},{"s":[4.74,4.81],"t":180}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"s":[100,100],"t":180}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"s":[9,9],"t":180}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"s":[0],"t":180}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[-0.15,-0.02],[-0.12,-0.09],[-0.06,-0.14],[0.02,-0.15],[0,0],[0.15,-0.13],[0.2,0],[0,0],[0.15,0.13],[0.03,0.2],[0,0],[-0.05,0.14],[-0.12,0.09],[-0.15,0.02],[-0.14,-0.05],[0,0],[-0.17,0.04],[-0.11,0.13],[0,0],[-0.11,0.05],[-0.12,0],[-0.11,-0.05],[-0.08,-0.09],[0,0],[-0.17,-0.04],[-0.16,0.07]],"o":[[0,0],[0.14,-0.06],[0.15,0.02],[0.12,0.09],[0.06,0.14],[0,0],[-0.03,0.2],[-0.15,0.13],[0,0],[-0.2,0],[-0.15,-0.13],[0,0],[-0.02,-0.15],[0.05,-0.14],[0.12,-0.09],[0.15,-0.02],[0,0],[0.16,0.06],[0.17,-0.04],[0,0],[0.08,-0.09],[0.11,-0.05],[0.12,0],[0.11,0.05],[0,0],[0.12,0.13],[0.17,0.04],[0,0]],"v":[[7.53,1.81],[8.07,1.59],[8.51,1.53],[8.92,1.7],[9.18,2.06],[9.23,2.5],[8.56,6.13],[8.27,6.65],[7.72,6.85],[1.78,6.85],[1.22,6.65],[0.94,6.13],[0.25,2.45],[0.3,2.02],[0.56,1.66],[0.96,1.48],[1.4,1.53],[2.07,1.79],[2.58,1.82],[3.02,1.55],[4.12,0.29],[4.41,0.08],[4.76,0],[5.11,0.07],[5.4,0.28],[6.57,1.59],[7.01,1.86],[7.53,1.81]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[-0.15,-0.02],[-0.12,-0.09],[-0.06,-0.14],[0.02,-0.15],[0,0],[0.15,-0.13],[0.2,0],[0,0],[0.15,0.13],[0.03,0.2],[0,0],[-0.05,0.14],[-0.12,0.09],[-0.15,0.02],[-0.14,-0.05],[0,0],[-0.17,0.04],[-0.11,0.13],[0,0],[-0.11,0.05],[-0.12,0],[-0.11,-0.05],[-0.08,-0.09],[0,0],[-0.17,-0.04],[-0.16,0.07]],"o":[[0,0],[0.14,-0.06],[0.15,0.02],[0.12,0.09],[0.06,0.14],[0,0],[-0.03,0.2],[-0.15,0.13],[0,0],[-0.2,0],[-0.15,-0.13],[0,0],[-0.02,-0.15],[0.05,-0.14],[0.12,-0.09],[0.15,-0.02],[0,0],[0.16,0.06],[0.17,-0.04],[0,0],[0.08,-0.09],[0.11,-0.05],[0.12,0],[0.11,0.05],[0,0],[0.12,0.13],[0.17,0.04],[0,0]],"v":[[7.53,1.81],[8.07,1.59],[8.51,1.53],[8.92,1.7],[9.18,2.06],[9.23,2.5],[8.56,6.13],[8.27,6.65],[7.72,6.85],[1.78,6.85],[1.22,6.65],[0.94,6.13],[0.25,2.45],[0.3,2.02],[0.56,1.66],[0.96,1.48],[1.4,1.53],[2.07,1.79],[2.58,1.82],[3.02,1.55],[4.12,0.29],[4.41,0.08],[4.76,0],[5.11,0.07],[5.4,0.28],[6.57,1.59],[7.01,1.86],[7.53,1.81]]}],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[-0.15,-0.02],[-0.12,-0.09],[-0.06,-0.14],[0.02,-0.15],[0,0],[0.15,-0.13],[0.2,0],[0,0],[0.15,0.13],[0.03,0.2],[0,0],[-0.05,0.14],[-0.12,0.09],[-0.15,0.02],[-0.14,-0.05],[0,0],[-0.17,0.04],[-0.11,0.13],[0,0],[-0.11,0.05],[-0.12,0],[-0.11,-0.05],[-0.08,-0.09],[0,0],[-0.17,-0.04],[-0.16,0.07]],"o":[[0,0],[0.14,-0.06],[0.15,0.02],[0.12,0.09],[0.06,0.14],[0,0],[-0.03,0.2],[-0.15,0.13],[0,0],[-0.2,0],[-0.15,-0.13],[0,0],[-0.02,-0.15],[0.05,-0.14],[0.12,-0.09],[0.15,-0.02],[0,0],[0.16,0.06],[0.17,-0.04],[0,0],[0.08,-0.09],[0.11,-0.05],[0.12,0],[0.11,0.05],[0,0],[0.12,0.13],[0.17,0.04],[0,0]],"v":[[7.53,1.81],[8.07,1.59],[8.51,1.53],[8.92,1.7],[9.18,2.06],[9.23,2.5],[8.56,6.13],[8.27,6.65],[7.72,6.85],[1.78,6.85],[1.22,6.65],[0.94,6.13],[0.25,2.45],[0.3,2.02],[0.56,1.66],[0.96,1.48],[1.4,1.53],[2.07,1.79],[2.58,1.82],[3.02,1.55],[4.12,0.29],[4.41,0.08],[4.76,0],[5.11,0.07],[5.4,0.28],[6.57,1.59],[7.01,1.86],[7.53,1.81]]}],"t":120},{"s":[{"c":true,"i":[[0,0],[0,0],[-0.15,-0.02],[-0.12,-0.09],[-0.06,-0.14],[0.02,-0.15],[0,0],[0.15,-0.13],[0.2,0],[0,0],[0.15,0.13],[0.03,0.2],[0,0],[-0.05,0.14],[-0.12,0.09],[-0.15,0.02],[-0.14,-0.05],[0,0],[-0.17,0.04],[-0.11,0.13],[0,0],[-0.11,0.05],[-0.12,0],[-0.11,-0.05],[-0.08,-0.09],[0,0],[-0.17,-0.04],[-0.16,0.07]],"o":[[0,0],[0.14,-0.06],[0.15,0.02],[0.12,0.09],[0.06,0.14],[0,0],[-0.03,0.2],[-0.15,0.13],[0,0],[-0.2,0],[-0.15,-0.13],[0,0],[-0.02,-0.15],[0.05,-0.14],[0.12,-0.09],[0.15,-0.02],[0,0],[0.16,0.06],[0.17,-0.04],[0,0],[0.08,-0.09],[0.11,-0.05],[0.12,0],[0.11,0.05],[0,0],[0.12,0.13],[0.17,0.04],[0,0]],"v":[[7.53,1.81],[8.07,1.59],[8.51,1.53],[8.92,1.7],[9.18,2.06],[9.23,2.5],[8.56,6.13],[8.27,6.65],[7.72,6.85],[1.78,6.85],[1.22,6.65],[0.94,6.13],[0.25,2.45],[0.3,2.02],[0.56,1.66],[0.96,1.48],[1.4,1.53],[2.07,1.79],[2.58,1.82],[3.02,1.55],[4.12,0.29],[4.41,0.08],[4.76,0],[5.11,0.07],[5.4,0.28],[6.57,1.59],[7.01,1.86],[7.53,1.81]]}],"t":180}]}},{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,-0.44],[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0]],"o":[[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0],[0,0],[0,-0.44],[0,0]],"v":[[8.83,8.3],[0.66,8.3],[0,8.97],[0,8.97],[0.66,9.63],[8.83,9.63],[9.49,8.97],[9.49,8.97],[8.83,8.3]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,-0.44],[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0]],"o":[[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0],[0,0],[0,-0.44],[0,0]],"v":[[8.83,8.3],[0.66,8.3],[0,8.97],[0,8.97],[0.66,9.63],[8.83,9.63],[9.49,8.97],[9.49,8.97],[8.83,8.3]]}],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,-0.44],[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0]],"o":[[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0],[0,0],[0,-0.44],[0,0]],"v":[[8.83,8.3],[0.66,8.3],[0,8.97],[0,8.97],[0.66,9.63],[8.83,9.63],[9.49,8.97],[9.49,8.97],[8.83,8.3]]}],"t":120},{"s":[{"c":true,"i":[[0,0],[0,0],[0,-0.44],[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0]],"o":[[0,0],[-0.44,0],[0,0],[0,0.44],[0,0],[0.44,0],[0,0],[0,-0.44],[0,0]],"v":[[8.83,8.3],[0.66,8.3],[0,8.97],[0,8.97],[0.66,9.63],[8.83,9.63],[9.49,8.97],[9.49,8.97],[8.83,8.3]]}],"t":180}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,0.8627,0.3961],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,0.8627,0.3961],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,0.8627,0.3961],"t":120},{"s":[1,0.8627,0.3961],"t":180}]},"r":2,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}}],"ind":1},{"ty":4,"nm":"路径 6","sr":1,"st":0,"op":181,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.4,2.74],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.4,2.74],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.4,2.74],"t":120},{"s":[2.4,2.74],"t":180}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,-100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,-100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,-100],"t":120},{"s":[100,-100],"t":180}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.73,2.04],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.73,2.04],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.73,2.04],"t":120},{"s":[2.73,2.04],"t":180}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[165.68],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[165.68],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[165.68],"t":120},{"s":[165.68],"t":180}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":120},{"s":[{"c":true,"i":[[0,0],[-0.79,0.64],[-0.06,-0.43],[1.44,-1.7],[0,0]],"o":[[2.02,-0.4],[0.34,-0.27],[0.24,1.83],[0,0],[0,0]],"v":[[0,2.26],[3.77,0.66],[4.67,1.01],[3.49,5.48],[0,2.26]]}],"t":180}]}},{"ty":"st","bm":0,"hd":false,"nm":"","lc":1,"lj":1,"ml":4,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"s":[0],"t":180}]},"w":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1],"t":120},{"s":[1],"t":180}]},"c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.592,0.592,0.592],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.592,0.592,0.592],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.592,0.592,0.592],"t":120},{"s":[0.592,0.592,0.592],"t":180}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.1059,0.1059,0.1333],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.1059,0.1059,0.1333],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.1059,0.1059,0.1333],"t":120},{"s":[0.1059,0.1059,0.1333],"t":180}]},"r":2,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}}],"ind":2},{"ty":4,"nm":"Frame 427321221 Bg","sr":1,"st":0,"op":181,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"s":[9,9],"t":180}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":120},{"s":[100,100],"t":180}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":120},{"s":[9,9],"t":180}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":120},{"s":[0],"t":180}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":120},{"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":180}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.1059,0.1059,0.1333],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.1059,0.1059,0.1333],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.1059,0.1059,0.1333],"t":120},{"s":[0.1059,0.1059,0.1333],"t":180}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":120},{"s":[100],"t":180}]}}],"ind":3}]}]} \ No newline at end of file diff --git a/src/assets/animation/line-loading.json b/src/assets/animation/line-loading.json new file mode 100644 index 0000000..2f0873e --- /dev/null +++ b/src/assets/animation/line-loading.json @@ -0,0 +1 @@ +{"nm":"Frame 427324633","ddd":0,"h":16,"w":16,"meta":{"g":"LottieFiles Figma v64"},"layers":[{"ty":0,"nm":"xinhao-icon-yanchi3 1","sr":1,"st":0,"op":91,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":60},{"s":[8,8],"t":90}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"s":[100,100],"t":90}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":60},{"s":[8,8],"t":90}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"s":[0],"t":90}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"s":[100],"t":90}]}},"masksProperties":[{"nm":"","inv":false,"mode":"a","x":{"a":0,"k":0},"o":{"a":0,"k":100},"pt":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":60},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":90}]}}],"w":16,"h":16,"refId":"1","ind":1}],"v":"5.7.0","fr":60,"op":90,"ip":0,"assets":[{"nm":"[Asset] xinhao-icon-yanchi3 1","id":"1","layers":[{"ty":4,"nm":"Vector","sr":1,"st":0,"op":91,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,5.71],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,5.71],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,5.71],"t":60},{"s":[1.14,5.71],"t":90}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"s":[100,100],"t":90}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[13.71,8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[13.71,8],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[13.71,8],"t":60},{"s":[13.71,8],"t":90}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"s":[0],"t":90}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"s":[100],"t":90}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,10.29],[1.14,11.43],[0,10.29],[0,1.14],[1.14,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,10.29],[1.14,11.43],[0,10.29],[0,1.14],[1.14,0]]}],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,10.29],[1.14,11.43],[0,10.29],[0,1.14],[1.14,0]]}],"t":60},{"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,10.29],[1.14,11.43],[0,10.29],[0,1.14],[1.14,0]]}],"t":90}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":60},{"s":[0.5333,0.5333,0.5333],"t":90}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10],"t":60},{"s":[40],"t":90}]}}],"ind":1},{"ty":4,"nm":"Vector","sr":1,"st":0,"op":91,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,4],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,4],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,4],"t":60},{"s":[1.14,4],"t":90}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"s":[100,100],"t":90}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,9.71],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,9.71],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,9.71],"t":60},{"s":[8,9.71],"t":90}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"s":[0],"t":90}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"s":[100],"t":90}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,6.86],[1.14,8],[0,6.86],[0,1.14],[1.14,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,6.86],[1.14,8],[0,6.86],[0,1.14],[1.14,0]]}],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,6.86],[1.14,8],[0,6.86],[0,1.14],[1.14,0]]}],"t":60},{"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,6.86],[1.14,8],[0,6.86],[0,1.14],[1.14,0]]}],"t":90}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":60},{"s":[0.5333,0.5333,0.5333],"t":90}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[40],"t":60},{"s":[40],"t":90}]}}],"ind":2},{"ty":4,"nm":"Vector","sr":1,"st":0,"op":91,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,2.29],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,2.29],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1.14,2.29],"t":60},{"s":[1.14,2.29],"t":90}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"s":[100,100],"t":90}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.29,11.43],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.29,11.43],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[2.29,11.43],"t":60},{"s":[2.29,11.43],"t":90}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"s":[0],"t":90}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"s":[100],"t":90}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,3.43],[1.14,4.57],[0,3.43],[0,1.14],[1.14,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,3.43],[1.14,4.57],[0,3.43],[0,1.14],[1.14,0]]}],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,3.43],[1.14,4.57],[0,3.43],[0,1.14],[1.14,0]]}],"t":60},{"s":[{"c":true,"i":[[0,0],[0,-0.76],[0,0],[0.76,0],[0,0.76],[0,0],[-0.76,0]],"o":[[0.76,0],[0,0],[0,0.76],[-0.76,0],[0,0],[0,-0.76],[0,0]],"v":[[1.14,0],[2.29,1.14],[2.29,3.43],[1.14,4.57],[0,3.43],[0,1.14],[1.14,0]]}],"t":90}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.5333,0.5333,0.5333],"t":60},{"s":[0.5333,0.5333,0.5333],"t":90}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[40],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[40],"t":60},{"s":[40],"t":90}]}}],"ind":3},{"ty":4,"nm":"xinhao-icon-yanchi3 1 Bg","sr":1,"st":0,"op":91,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":60},{"s":[8,8],"t":90}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"s":[100,100],"t":90}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8,8],"t":60},{"s":[8,8],"t":90}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"s":[0],"t":90}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"s":[100],"t":90}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":60},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16,0],[16,16],[0,16],[0,0]]}],"t":90}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,1,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,1,1],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[1,1,1],"t":60},{"s":[1,1,1],"t":90}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":30},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"s":[0],"t":90}]}}],"ind":4}]}]} \ No newline at end of file diff --git a/src/assets/appearance-footer.png b/src/assets/appearance-footer.png new file mode 100644 index 0000000..7b7508b Binary files /dev/null and b/src/assets/appearance-footer.png differ diff --git a/src/assets/appearance.png b/src/assets/appearance.png new file mode 100644 index 0000000..c19e441 Binary files /dev/null and b/src/assets/appearance.png differ diff --git a/src/assets/apple.png b/src/assets/apple.png new file mode 100644 index 0000000..ed5581e Binary files /dev/null and b/src/assets/apple.png differ diff --git a/src/assets/arrow-down-black.svg b/src/assets/arrow-down-black.svg new file mode 100644 index 0000000..8cf629d --- /dev/null +++ b/src/assets/arrow-down-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow-down.svg b/src/assets/arrow-down.svg new file mode 100644 index 0000000..a5595d0 --- /dev/null +++ b/src/assets/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow-left-black.svg b/src/assets/arrow-left-black.svg new file mode 100644 index 0000000..c9a3c96 --- /dev/null +++ b/src/assets/arrow-left-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow-left.svg b/src/assets/arrow-left.svg new file mode 100644 index 0000000..b1d3328 --- /dev/null +++ b/src/assets/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow-right-black.svg b/src/assets/arrow-right-black.svg new file mode 100644 index 0000000..13c0559 --- /dev/null +++ b/src/assets/arrow-right-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow-right.svg b/src/assets/arrow-right.svg new file mode 100644 index 0000000..92b7624 --- /dev/null +++ b/src/assets/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow-up-black.svg b/src/assets/arrow-up-black.svg new file mode 100644 index 0000000..7e830ed --- /dev/null +++ b/src/assets/arrow-up-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow-up.svg b/src/assets/arrow-up.svg new file mode 100644 index 0000000..368682c --- /dev/null +++ b/src/assets/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/audio-close.svg b/src/assets/audio-close.svg new file mode 100644 index 0000000..f98b71b --- /dev/null +++ b/src/assets/audio-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/audio-open.svg b/src/assets/audio-open.svg new file mode 100644 index 0000000..479aa85 --- /dev/null +++ b/src/assets/audio-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/base.css b/src/assets/base.css new file mode 100644 index 0000000..dcdf0df --- /dev/null +++ b/src/assets/base.css @@ -0,0 +1,243 @@ +@import "./theme.css"; + +* { + padding: 0; + margin: 0; +} +.firstly_view { + background: white; + overflow: hidden; + height: 100vh; +} +.secondary_view { + height: 100vh; + position: fixed; + top: 0; + right: 0; + background: #a4ccf6; + z-index: 2; + overflow: hidden; +} +.thirdly_view { + height: 100vh; + position: fixed; + background: white; + z-index: 3; + top: 0; + right: 0; + overflow: hidden; + border-radius: 16px; +} +.fourth_view { + overflow: hidden; +} +.fifth_view { + overflow: hidden; +} +.thirdly_view_overlay { +} +@media screen and (max-width: 750px) { + .van_empty { + display: none; + } + .firstly_view { + } + .secondary_view { + width: 100vw; + } + .thirdly_view { + width: 100vw; + } + .fourth_view { + width: 100vw; + height: 100vh; + position: fixed; + background: red; + z-index: 4; + top: 0; + left: 0; + } + .fifth_view { + position: fixed; + top: 0; + left: 0; + z-index: 5; + width: 100vw; + height: 100vh; + /* background: #84e09f; */ + } +} +@media screen and (min-width: 750px) { + .firstly_view { + width: 375px !important; + } + + .firstly_view::after { + z-index: 1; + content: ''; + position: absolute; + top: 0; + right: 0; + height: 100vh; + width: var(--van-border-width); + background: var(--van-gray-3); + } + .secondary_view { + width: calc(100vw - 375px); + overflow: hidden; + & > .van-nav-bar__left { + background: red; + } + } + .secondary_view .custom-dialog { + left: auto; + right: calc(50vw - 350px) !important; + } + + .thirdly_view { + width: 70%; + height: 70%; + bottom: 0; + left: 0; + margin: auto; + display: flex; + /* background: #84e09f; */ + overflow: hidden; + } + .thirdly_view .custom-dialog { + right: calc(50vw - 170px) !important; + } + .fourth_view { + flex: 1; + z-index: 4; + background: red; + border-left: 1px solid #f2f4f5; + } + .fifth_view { + position: absolute; + top: 0; + right: 0; + width: 50%; + height: 100%; + z-index: 5; + /* background: #84e09f; */ + } + + .firstly_view_nav { + max-width: 375px; + } + .secondary_view_nav { + width: calc(100vw - 375px); + left: 375px; + } + .thirdly_view_nav { + position: absolute; + width: 100%; + top: 0; + left: 0; + } + .fourth_view_nav { + position: absolute; + width: 50%; + top: 0; + left: 50%; + } + .fifth_view_nav { + position: absolute; + top: 0; + left: 0; + } + .thirdly_view_overlay { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(0, 0, 0, 0.7); + z-index: 3; + } +} + +/* 全局公用样式 */ +.d-flex { + display: flex; +} +.flex1 { + flex: 1; +} +.flex-left { + flex: 1; + display: flex; +} +.flex-right { + flex: 1; + display: flex; + justify-content: flex-end; +} +.ml-auto { + margin-left: auto; +} +.al-center { + align-items: center; +} +.as-end { + align-self: end; +} +.tac { + text-align: center; +} + +/* 文字顏色 */ +.normal-text { + color: var(--c-text-color); +} +.light-text { + color: var(--c-light-text-color); +} +.blue-text { + color: var(--c-blue-color); +} +.yellow-text { + color: var(--c-yellow-color); +} +.red-text { + color: var(--c-red-color); +} +.green-text:hover, +.green-text { + color: var(--c-green-color); +} +.pink-text { + color: var(--c-pink-color); +} +.orange-text { + color: var(--c-orange-color); +} +.light-blue-text { + color: var(--c-light-blue-color); +} + +.text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.font-bold { + font-weight: bold; +} + +/** 滚动条整体部分 */ +::-webkit-scrollbar { + width: 4px; + height: 4px; +} + +/** 滚动条中的滑块 */ +::-webkit-scrollbar-thumb { + background: #d9d9d9; +} +:deep(.van-nav-bar__left), +:deep(.van-nav-bar__right) { + padding: 0 8px; +} diff --git a/src/assets/bind-email.png b/src/assets/bind-email.png new file mode 100644 index 0000000..de48c65 Binary files /dev/null and b/src/assets/bind-email.png differ diff --git a/src/assets/bind-phone.png b/src/assets/bind-phone.png new file mode 100644 index 0000000..cbcf535 Binary files /dev/null and b/src/assets/bind-phone.png differ diff --git a/src/assets/blank-logo.png b/src/assets/blank-logo.png new file mode 100644 index 0000000..a653244 Binary files /dev/null and b/src/assets/blank-logo.png differ diff --git a/src/assets/blank_bg.png b/src/assets/blank_bg.png new file mode 100644 index 0000000..4042303 Binary files /dev/null and b/src/assets/blank_bg.png differ diff --git a/src/assets/chanchu.svg b/src/assets/chanchu.svg new file mode 100644 index 0000000..1cd9fb3 --- /dev/null +++ b/src/assets/chanchu.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/chatbg.png b/src/assets/chatbg.png new file mode 100644 index 0000000..d7deac4 Binary files /dev/null and b/src/assets/chatbg.png differ diff --git a/src/assets/chazhao.png b/src/assets/chazhao.png new file mode 100644 index 0000000..c242b88 Binary files /dev/null and b/src/assets/chazhao.png differ diff --git a/src/assets/commonCSS/rootCss.css b/src/assets/commonCSS/rootCss.css new file mode 100644 index 0000000..4cd74d2 --- /dev/null +++ b/src/assets/commonCSS/rootCss.css @@ -0,0 +1,38 @@ +:root{ + --size-10px:0.625rem; + --size-11px:0.6875rem; + --size-12px: 0.75rem; + --size-13px: 0.8125rem; + --size-14px:0.875rem; + --size-15px: 0.9375rem; + --size-16px: 1rem; + --size-17px: 1.0625rem; + --size-18px: 1.125rem; + --size-19px: 1.1875rem; + --size-20px: 1.25rem; + --size-21px: 1.3125rem; + --size-22px: 1.375rem; + --size-23px: 1.4375rem; + --size-24px: 1.5rem; + --size-25px: 1.5625rem; + --size-26px:1.625rem; + --size-27px:1.6875rem; + --size-28px: 1.75rem; + --size-29px: 1.8125rem; + --size-30px: 1.875rem; + --size-31px: 1.9375rem; + --size-32px: 2rem; + --size-33px: 2.0625rem; + --size-34px: 2.125rem; + --size-35px: 2.1875rem; + --size-36px: 2.25rem; + --size-37px: 2.3125rem; + --size-38px: 2.375rem; + + --size-44px: 2.75rem; + --size-48px: 3rem; + --size-60px: 3.75rem; + --size-78px: 4.875rem; + --size-96px: 6rem; +} + diff --git a/src/assets/contact-add.svg b/src/assets/contact-add.svg new file mode 100644 index 0000000..b9a46d4 --- /dev/null +++ b/src/assets/contact-add.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/contact-chat.svg b/src/assets/contact-chat.svg new file mode 100644 index 0000000..8e76853 --- /dev/null +++ b/src/assets/contact-chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/contact-empty.png b/src/assets/contact-empty.png new file mode 100644 index 0000000..37f45fd Binary files /dev/null and b/src/assets/contact-empty.png differ diff --git a/src/assets/contact-frend.svg b/src/assets/contact-frend.svg new file mode 100644 index 0000000..c12c363 --- /dev/null +++ b/src/assets/contact-frend.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/contact-group.svg b/src/assets/contact-group.svg new file mode 100644 index 0000000..201647a --- /dev/null +++ b/src/assets/contact-group.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/contact-mass-texting.svg b/src/assets/contact-mass-texting.svg new file mode 100644 index 0000000..d098e56 --- /dev/null +++ b/src/assets/contact-mass-texting.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/contact-more.svg b/src/assets/contact-more.svg new file mode 100644 index 0000000..8299614 --- /dev/null +++ b/src/assets/contact-more.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/copy.svg b/src/assets/copy.svg new file mode 100644 index 0000000..84c134f --- /dev/null +++ b/src/assets/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/copy1.svg b/src/assets/copy1.svg new file mode 100644 index 0000000..77361c7 --- /dev/null +++ b/src/assets/copy1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/default_empty.svg b/src/assets/default_empty.svg new file mode 100644 index 0000000..81b0fb2 --- /dev/null +++ b/src/assets/default_empty.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/assets/delete-red.svg b/src/assets/delete-red.svg new file mode 100644 index 0000000..f4b14a2 --- /dev/null +++ b/src/assets/delete-red.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/deleteDia.svg b/src/assets/deleteDia.svg new file mode 100644 index 0000000..fcb33e0 --- /dev/null +++ b/src/assets/deleteDia.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/dialog/add.svg b/src/assets/dialog/add.svg new file mode 100644 index 0000000..dac5cb4 --- /dev/null +++ b/src/assets/dialog/add.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/dialog/audioPlay.png b/src/assets/dialog/audioPlay.png new file mode 100644 index 0000000..c335f40 Binary files /dev/null and b/src/assets/dialog/audioPlay.png differ diff --git a/src/assets/dialog/audioStop.png b/src/assets/dialog/audioStop.png new file mode 100644 index 0000000..4876aee Binary files /dev/null and b/src/assets/dialog/audioStop.png differ diff --git a/src/assets/dialog/chat.svg b/src/assets/dialog/chat.svg new file mode 100644 index 0000000..49b8e09 --- /dev/null +++ b/src/assets/dialog/chat.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/dialog/default1.png b/src/assets/dialog/default1.png new file mode 100644 index 0000000..a11579a Binary files /dev/null and b/src/assets/dialog/default1.png differ diff --git a/src/assets/dialog/down.svg b/src/assets/dialog/down.svg new file mode 100644 index 0000000..d70e0b8 --- /dev/null +++ b/src/assets/dialog/down.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/dialog/download.svg b/src/assets/dialog/download.svg new file mode 100644 index 0000000..c758e45 --- /dev/null +++ b/src/assets/dialog/download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/dialog/face.svg b/src/assets/dialog/face.svg new file mode 100644 index 0000000..94bfd8f --- /dev/null +++ b/src/assets/dialog/face.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/dialog/group.svg b/src/assets/dialog/group.svg new file mode 100644 index 0000000..cfe40b0 --- /dev/null +++ b/src/assets/dialog/group.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/dialog/groupChat.png b/src/assets/dialog/groupChat.png new file mode 100644 index 0000000..5daa48a Binary files /dev/null and b/src/assets/dialog/groupChat.png differ diff --git a/src/assets/dialog/mySend.svg b/src/assets/dialog/mySend.svg new file mode 100644 index 0000000..ad0c01a --- /dev/null +++ b/src/assets/dialog/mySend.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/dialog/no-data.png b/src/assets/dialog/no-data.png new file mode 100644 index 0000000..0d70226 Binary files /dev/null and b/src/assets/dialog/no-data.png differ diff --git a/src/assets/dialog/pic.svg b/src/assets/dialog/pic.svg new file mode 100644 index 0000000..5e4c8f9 --- /dev/null +++ b/src/assets/dialog/pic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/dialog/plus.svg b/src/assets/dialog/plus.svg new file mode 100644 index 0000000..a39dfae --- /dev/null +++ b/src/assets/dialog/plus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/dialog/qrcode.svg b/src/assets/dialog/qrcode.svg new file mode 100644 index 0000000..695eed6 --- /dev/null +++ b/src/assets/dialog/qrcode.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/dialog/recycle.svg b/src/assets/dialog/recycle.svg new file mode 100644 index 0000000..762e41c --- /dev/null +++ b/src/assets/dialog/recycle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/dialog/right-arrow.svg b/src/assets/dialog/right-arrow.svg new file mode 100644 index 0000000..ece7302 --- /dev/null +++ b/src/assets/dialog/right-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/dialog/scan.svg b/src/assets/dialog/scan.svg new file mode 100644 index 0000000..7f7abb7 --- /dev/null +++ b/src/assets/dialog/scan.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/dialog/scans.svg b/src/assets/dialog/scans.svg new file mode 100644 index 0000000..6cd2871 --- /dev/null +++ b/src/assets/dialog/scans.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/dialog/search.svg b/src/assets/dialog/search.svg new file mode 100644 index 0000000..f76f91d --- /dev/null +++ b/src/assets/dialog/search.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/dialog/share.svg b/src/assets/dialog/share.svg new file mode 100644 index 0000000..bdeea1e --- /dev/null +++ b/src/assets/dialog/share.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/download.png b/src/assets/download.png new file mode 100644 index 0000000..6f3839e Binary files /dev/null and b/src/assets/download.png differ diff --git a/src/assets/edit.svg b/src/assets/edit.svg new file mode 100644 index 0000000..c13f569 --- /dev/null +++ b/src/assets/edit.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/empety.svg b/src/assets/empety.svg new file mode 100644 index 0000000..1b675f9 --- /dev/null +++ b/src/assets/empety.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/expand-list-icon.svg b/src/assets/expand-list-icon.svg new file mode 100644 index 0000000..0e2882b --- /dev/null +++ b/src/assets/expand-list-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/filesvg/CloseShape.svg b/src/assets/filesvg/CloseShape.svg new file mode 100644 index 0000000..a38dd79 --- /dev/null +++ b/src/assets/filesvg/CloseShape.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/filesvg/apk.svg b/src/assets/filesvg/apk.svg new file mode 100644 index 0000000..e162507 --- /dev/null +++ b/src/assets/filesvg/apk.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/src/assets/filesvg/audio.svg b/src/assets/filesvg/audio.svg new file mode 100644 index 0000000..43b9ec9 --- /dev/null +++ b/src/assets/filesvg/audio.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/src/assets/filesvg/code.svg b/src/assets/filesvg/code.svg new file mode 100644 index 0000000..962d9c7 --- /dev/null +++ b/src/assets/filesvg/code.svg @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/src/assets/filesvg/deb.svg b/src/assets/filesvg/deb.svg new file mode 100644 index 0000000..6ed07aa --- /dev/null +++ b/src/assets/filesvg/deb.svg @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/src/assets/filesvg/dmg.svg b/src/assets/filesvg/dmg.svg new file mode 100644 index 0000000..b680376 --- /dev/null +++ b/src/assets/filesvg/dmg.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/src/assets/filesvg/doc.svg b/src/assets/filesvg/doc.svg new file mode 100644 index 0000000..d27653a --- /dev/null +++ b/src/assets/filesvg/doc.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/src/assets/filesvg/epub.svg b/src/assets/filesvg/epub.svg new file mode 100644 index 0000000..30fa1a4 --- /dev/null +++ b/src/assets/filesvg/epub.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/assets/filesvg/exe.svg b/src/assets/filesvg/exe.svg new file mode 100644 index 0000000..925d138 --- /dev/null +++ b/src/assets/filesvg/exe.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/filesvg/html.svg b/src/assets/filesvg/html.svg new file mode 100644 index 0000000..acff1d1 --- /dev/null +++ b/src/assets/filesvg/html.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/filesvg/image.svg b/src/assets/filesvg/image.svg new file mode 100644 index 0000000..4afe02a --- /dev/null +++ b/src/assets/filesvg/image.svg @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/src/assets/filesvg/ipa.svg b/src/assets/filesvg/ipa.svg new file mode 100644 index 0000000..6de5301 --- /dev/null +++ b/src/assets/filesvg/ipa.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/assets/filesvg/key.svg b/src/assets/filesvg/key.svg new file mode 100644 index 0000000..31ef940 --- /dev/null +++ b/src/assets/filesvg/key.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/src/assets/filesvg/mp3.svg b/src/assets/filesvg/mp3.svg new file mode 100644 index 0000000..77c6836 --- /dev/null +++ b/src/assets/filesvg/mp3.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/filesvg/numbers.svg b/src/assets/filesvg/numbers.svg new file mode 100644 index 0000000..1732e9e --- /dev/null +++ b/src/assets/filesvg/numbers.svg @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/src/assets/filesvg/pages.svg b/src/assets/filesvg/pages.svg new file mode 100644 index 0000000..a04add0 --- /dev/null +++ b/src/assets/filesvg/pages.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/src/assets/filesvg/pdf.svg b/src/assets/filesvg/pdf.svg new file mode 100644 index 0000000..99f76ac --- /dev/null +++ b/src/assets/filesvg/pdf.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/src/assets/filesvg/ppt.svg b/src/assets/filesvg/ppt.svg new file mode 100644 index 0000000..db3db12 --- /dev/null +++ b/src/assets/filesvg/ppt.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/src/assets/filesvg/rar.svg b/src/assets/filesvg/rar.svg new file mode 100644 index 0000000..c3e09e1 --- /dev/null +++ b/src/assets/filesvg/rar.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/assets/filesvg/txt.svg b/src/assets/filesvg/txt.svg new file mode 100644 index 0000000..572726f --- /dev/null +++ b/src/assets/filesvg/txt.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/assets/filesvg/unknown.svg b/src/assets/filesvg/unknown.svg new file mode 100644 index 0000000..840917a --- /dev/null +++ b/src/assets/filesvg/unknown.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/assets/filesvg/unknownd.svg b/src/assets/filesvg/unknownd.svg new file mode 100644 index 0000000..64fd680 --- /dev/null +++ b/src/assets/filesvg/unknownd.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/src/assets/filesvg/video.svg b/src/assets/filesvg/video.svg new file mode 100644 index 0000000..0842b39 --- /dev/null +++ b/src/assets/filesvg/video.svg @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/src/assets/filesvg/xls.svg b/src/assets/filesvg/xls.svg new file mode 100644 index 0000000..60e2a02 --- /dev/null +++ b/src/assets/filesvg/xls.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/src/assets/friend-chat.svg b/src/assets/friend-chat.svg new file mode 100644 index 0000000..8f1edb1 --- /dev/null +++ b/src/assets/friend-chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/group-check.svg b/src/assets/group-check.svg new file mode 100644 index 0000000..749538c --- /dev/null +++ b/src/assets/group-check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/hongbao.svg b/src/assets/hongbao.svg new file mode 100644 index 0000000..ee6d4a0 --- /dev/null +++ b/src/assets/hongbao.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/addFriend.png b/src/assets/img/addFriend.png new file mode 100644 index 0000000..d48975d Binary files /dev/null and b/src/assets/img/addFriend.png differ diff --git a/src/assets/img/banner-logo.png b/src/assets/img/banner-logo.png new file mode 100644 index 0000000..bb40739 Binary files /dev/null and b/src/assets/img/banner-logo.png differ diff --git a/src/assets/img/filed.svg b/src/assets/img/filed.svg new file mode 100644 index 0000000..90def14 --- /dev/null +++ b/src/assets/img/filed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_Vector.svg b/src/assets/img/msg_Vector.svg new file mode 100644 index 0000000..796d5c7 --- /dev/null +++ b/src/assets/img/msg_Vector.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_audio_start.svg b/src/assets/img/msg_audio_start.svg new file mode 100644 index 0000000..0822e3a --- /dev/null +++ b/src/assets/img/msg_audio_start.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/msg_audio_start1.svg b/src/assets/img/msg_audio_start1.svg new file mode 100644 index 0000000..f5fcf04 --- /dev/null +++ b/src/assets/img/msg_audio_start1.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/assets/img/msg_camera.svg b/src/assets/img/msg_camera.svg new file mode 100644 index 0000000..5529562 --- /dev/null +++ b/src/assets/img/msg_camera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_close.svg b/src/assets/img/msg_close.svg new file mode 100644 index 0000000..cd9bf11 --- /dev/null +++ b/src/assets/img/msg_close.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/msg_copy.svg b/src/assets/img/msg_copy.svg new file mode 100644 index 0000000..f6edbeb --- /dev/null +++ b/src/assets/img/msg_copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_delete.svg b/src/assets/img/msg_delete.svg new file mode 100644 index 0000000..29dd5b5 --- /dev/null +++ b/src/assets/img/msg_delete.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/img/msg_delete_blue.svg b/src/assets/img/msg_delete_blue.svg new file mode 100644 index 0000000..7ddaa15 --- /dev/null +++ b/src/assets/img/msg_delete_blue.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/img/msg_failed.svg b/src/assets/img/msg_failed.svg new file mode 100644 index 0000000..12637ec --- /dev/null +++ b/src/assets/img/msg_failed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/msg_forward.svg b/src/assets/img/msg_forward.svg new file mode 100644 index 0000000..2258ea5 --- /dev/null +++ b/src/assets/img/msg_forward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_pause.svg b/src/assets/img/msg_pause.svg new file mode 100644 index 0000000..f7ac5ce --- /dev/null +++ b/src/assets/img/msg_pause.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/msg_pause1.svg b/src/assets/img/msg_pause1.svg new file mode 100644 index 0000000..982708f --- /dev/null +++ b/src/assets/img/msg_pause1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/img/msg_pic.svg b/src/assets/img/msg_pic.svg new file mode 100644 index 0000000..0debc04 --- /dev/null +++ b/src/assets/img/msg_pic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_quote.svg b/src/assets/img/msg_quote.svg new file mode 100644 index 0000000..37c9204 --- /dev/null +++ b/src/assets/img/msg_quote.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/msg_read.svg b/src/assets/img/msg_read.svg new file mode 100644 index 0000000..a9f13ed --- /dev/null +++ b/src/assets/img/msg_read.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_recording.svg b/src/assets/img/msg_recording.svg new file mode 100644 index 0000000..cb3904c --- /dev/null +++ b/src/assets/img/msg_recording.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/img/msg_redPkt.svg b/src/assets/img/msg_redPkt.svg new file mode 100644 index 0000000..8a267de --- /dev/null +++ b/src/assets/img/msg_redPkt.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/msg_share.svg b/src/assets/img/msg_share.svg new file mode 100644 index 0000000..3e15328 --- /dev/null +++ b/src/assets/img/msg_share.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/msg_share_white.svg b/src/assets/img/msg_share_white.svg new file mode 100644 index 0000000..d12af3b --- /dev/null +++ b/src/assets/img/msg_share_white.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/img/msg_shares.svg b/src/assets/img/msg_shares.svg new file mode 100644 index 0000000..6f559aa --- /dev/null +++ b/src/assets/img/msg_shares.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/msg_unread.svg b/src/assets/img/msg_unread.svg new file mode 100644 index 0000000..cde22ff --- /dev/null +++ b/src/assets/img/msg_unread.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/open-en.png b/src/assets/img/open-en.png new file mode 100644 index 0000000..a5f1b33 Binary files /dev/null and b/src/assets/img/open-en.png differ diff --git a/src/assets/img/open.png b/src/assets/img/open.png new file mode 100644 index 0000000..e276b85 Binary files /dev/null and b/src/assets/img/open.png differ diff --git a/src/assets/img/receive.png b/src/assets/img/receive.png new file mode 100644 index 0000000..aef7dfc Binary files /dev/null and b/src/assets/img/receive.png differ diff --git a/src/assets/img/red-package.png b/src/assets/img/red-package.png new file mode 100644 index 0000000..4f77bd4 Binary files /dev/null and b/src/assets/img/red-package.png differ diff --git a/src/assets/img/unclaimed.png b/src/assets/img/unclaimed.png new file mode 100644 index 0000000..dea8437 Binary files /dev/null and b/src/assets/img/unclaimed.png differ diff --git a/src/assets/info.svg b/src/assets/info.svg new file mode 100644 index 0000000..777b644 --- /dev/null +++ b/src/assets/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/language.svg b/src/assets/language.svg new file mode 100644 index 0000000..e18bfab --- /dev/null +++ b/src/assets/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/login/avatar-hint.svg b/src/assets/login/avatar-hint.svg new file mode 100644 index 0000000..20cfcae --- /dev/null +++ b/src/assets/login/avatar-hint.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/login/bg-login-guge.svg b/src/assets/login/bg-login-guge.svg new file mode 100644 index 0000000..3e990b2 --- /dev/null +++ b/src/assets/login/bg-login-guge.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/login/email.svg b/src/assets/login/email.svg new file mode 100644 index 0000000..b6d1447 --- /dev/null +++ b/src/assets/login/email.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/login/head-portrait.svg b/src/assets/login/head-portrait.svg new file mode 100644 index 0000000..5c3fbcb --- /dev/null +++ b/src/assets/login/head-portrait.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/login/iphone.svg b/src/assets/login/iphone.svg new file mode 100644 index 0000000..1f9ed22 --- /dev/null +++ b/src/assets/login/iphone.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/login/return-icon.svg b/src/assets/login/return-icon.svg new file mode 100644 index 0000000..a32cca0 --- /dev/null +++ b/src/assets/login/return-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/login/sg-login.svg b/src/assets/login/sg-login.svg new file mode 100644 index 0000000..66468ea --- /dev/null +++ b/src/assets/login/sg-login.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/login/sg-wjmm.svg b/src/assets/login/sg-wjmm.svg new file mode 100644 index 0000000..31d2670 --- /dev/null +++ b/src/assets/login/sg-wjmm.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/login/sg-yxwjmm.svg b/src/assets/login/sg-yxwjmm.svg new file mode 100644 index 0000000..d9c8b69 --- /dev/null +++ b/src/assets/login/sg-yxwjmm.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/login/wj-password.svg b/src/assets/login/wj-password.svg new file mode 100644 index 0000000..55c40dc --- /dev/null +++ b/src/assets/login/wj-password.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/login/yx-login.svg b/src/assets/login/yx-login.svg new file mode 100644 index 0000000..e622bca --- /dev/null +++ b/src/assets/login/yx-login.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..428745f Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo1.png b/src/assets/logo1.png new file mode 100644 index 0000000..eee2dd7 Binary files /dev/null and b/src/assets/logo1.png differ diff --git a/src/assets/mine-avatar.png b/src/assets/mine-avatar.png new file mode 100644 index 0000000..a946ca1 Binary files /dev/null and b/src/assets/mine-avatar.png differ diff --git a/src/assets/mine-avatar.svg b/src/assets/mine-avatar.svg new file mode 100644 index 0000000..859d26d --- /dev/null +++ b/src/assets/mine-avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/mine-top.png b/src/assets/mine-top.png new file mode 100644 index 0000000..6cd92ba Binary files /dev/null and b/src/assets/mine-top.png differ diff --git a/src/assets/more.svg b/src/assets/more.svg new file mode 100644 index 0000000..6d504b4 --- /dev/null +++ b/src/assets/more.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/no-data.png b/src/assets/no-data.png new file mode 100644 index 0000000..e4597b1 Binary files /dev/null and b/src/assets/no-data.png differ diff --git a/src/assets/notDisturb.svg b/src/assets/notDisturb.svg new file mode 100644 index 0000000..7b9a2bc --- /dev/null +++ b/src/assets/notDisturb.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/notDisturb1.svg b/src/assets/notDisturb1.svg new file mode 100644 index 0000000..fad1254 --- /dev/null +++ b/src/assets/notDisturb1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/notice-logo.png b/src/assets/notice-logo.png new file mode 100644 index 0000000..ead65ef Binary files /dev/null and b/src/assets/notice-logo.png differ diff --git a/src/assets/notice.svg b/src/assets/notice.svg new file mode 100644 index 0000000..9e71ca3 --- /dev/null +++ b/src/assets/notice.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/pull-refresh-arrow-down.svg b/src/assets/pull-refresh-arrow-down.svg new file mode 100644 index 0000000..c077b54 --- /dev/null +++ b/src/assets/pull-refresh-arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/pull-refresh-arrow-up.svg b/src/assets/pull-refresh-arrow-up.svg new file mode 100644 index 0000000..93fe88f --- /dev/null +++ b/src/assets/pull-refresh-arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/qrcode.svg b/src/assets/qrcode.svg new file mode 100644 index 0000000..dafdbca --- /dev/null +++ b/src/assets/qrcode.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/qrcode1.svg b/src/assets/qrcode1.svg new file mode 100644 index 0000000..67c6a1d --- /dev/null +++ b/src/assets/qrcode1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/quill.snow.css b/src/assets/quill.snow.css new file mode 100644 index 0000000..86a553f --- /dev/null +++ b/src/assets/quill.snow.css @@ -0,0 +1,949 @@ +/*! + * Quill Editor v1.3.7 + * https://quilljs.com/ + * Copyright (c) 2014, Jason Chen + * Copyright (c) 2013, salesforce.com + */ +.ql-container { + box-sizing: border-box; + font-family: Helvetica, Arial, sans-serif; + font-size: 13px; + height: 100%; + margin: 0px; + position: relative; +} +.ql-container.ql-disabled .ql-tooltip { + visibility: hidden; +} +.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before { + pointer-events: none; +} +.ql-clipboard { + left: -100000px; + height: 1px; + overflow-y: hidden; + position: absolute; + top: 50%; +} +.ql-clipboard p { + margin: 0; + padding: 0; +} +.ql-editor { + box-sizing: border-box; + line-height: 1.42; + height: 100%; + outline: none; + overflow-y: auto; + padding: 12px 15px; + tab-size: 4; + -moz-tab-size: 4; + text-align: left; + white-space: pre-wrap; + word-wrap: break-word; +} +.ql-editor > * { + cursor: text; +} +.ql-editor p, +.ql-editor ol, +.ql-editor ul, +.ql-editor pre, +.ql-editor blockquote, +.ql-editor h1, +.ql-editor h2, +.ql-editor h3, +.ql-editor h4, +.ql-editor h5, +.ql-editor h6 { + margin: 0; + padding: 0; + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor ol, +.ql-editor ul { + padding-left: 1.5em; +} +.ql-editor ol > li, +.ql-editor ul > li { + list-style-type: none; +} +.ql-editor ul > li::before { + content: '\2022'; +} +.ql-editor ul[data-checked=true], +.ql-editor ul[data-checked=false] { + pointer-events: none; +} +.ql-editor ul[data-checked=true] > li *, +.ql-editor ul[data-checked=false] > li * { + pointer-events: all; +} +.ql-editor ul[data-checked=true] > li::before, +.ql-editor ul[data-checked=false] > li::before { + color: #777; + cursor: pointer; + pointer-events: all; +} +.ql-editor ul[data-checked=true] > li::before { + content: '\2611'; +} +.ql-editor ul[data-checked=false] > li::before { + content: '\2610'; +} +.ql-editor li::before { + display: inline-block; + white-space: nowrap; + width: 1.2em; +} +li:not(.ql-direction-rtl)::before { + margin-left: -1.5em; + margin-right: 0.3em; + text-align: right; +} +li.ql-direction-rtl::before { + margin-left: 0.3em; + margin-right: -1.5em; +} +ol li:not(.ql-direction-rtl), +ul li:not(.ql-direction-rtl) { + padding-left: 1.5em; +} +ol li.ql-direction-rtl, +ul li.ql-direction-rtl { + padding-right: 1.5em; +} +.ql-editor ol li { + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + counter-increment: list-0; +} +.ql-editor ol li:before { + content: counter(list-0, decimal) '. '; +} +ol li.ql-indent-1 { + counter-increment: list-1; +} +ol li.ql-indent-1:before { + content: counter(list-1, lower-alpha) '. '; +} +ol li.ql-indent-1 { + counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +ol li.ql-indent-2 { + counter-increment: list-2; +} +ol li.ql-indent-2:before { + content: counter(list-2, lower-roman) '. '; +} +ol li.ql-indent-2 { + counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +ol li.ql-indent-3 { + counter-increment: list-3; +} +ol li.ql-indent-3:before { + content: counter(list-3, decimal) '. '; +} +ol li.ql-indent-3 { + counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; +} +ol li.ql-indent-4 { + counter-increment: list-4; +} +ol li.ql-indent-4:before { + content: counter(list-4, lower-alpha) '. '; +} +ol li.ql-indent-4 { + counter-reset: list-5 list-6 list-7 list-8 list-9; +} +ol li.ql-indent-5 { + counter-increment: list-5; +} +ol li.ql-indent-5:before { + content: counter(list-5, lower-roman) '. '; +} +ol li.ql-indent-5 { + counter-reset: list-6 list-7 list-8 list-9; +} +ol li.ql-indent-6 { + counter-increment: list-6; +} +ol li.ql-indent-6:before { + content: counter(list-6, decimal) '. '; +} +ol li.ql-indent-6 { + counter-reset: list-7 list-8 list-9; +} +ol li.ql-indent-7 { + counter-increment: list-7; +} +ol li.ql-indent-7:before { + content: counter(list-7, lower-alpha) '. '; +} +ol li.ql-indent-7 { + counter-reset: list-8 list-9; +} +ol li.ql-indent-8 { + counter-increment: list-8; +} +ol li.ql-indent-8:before { + content: counter(list-8, lower-roman) '. '; +} +ol li.ql-indent-8 { + counter-reset: list-9; +} +ol li.ql-indent-9 { + counter-increment: list-9; +} +ol li.ql-indent-9:before { + content: counter(list-9, decimal) '. '; +} +.ql-indent-1:not(.ql-direction-rtl) { + padding-left: 3em; +} +li.ql-indent-1:not(.ql-direction-rtl) { + padding-left: 4.5em; +} +.ql-indent-1.ql-direction-rtl.ql-align-right { + padding-right: 3em; +} +li.ql-indent-1.ql-direction-rtl.ql-align-right { + padding-right: 4.5em; +} +.ql-indent-2:not(.ql-direction-rtl) { + padding-left: 6em; +} +li.ql-indent-2:not(.ql-direction-rtl) { + padding-left: 7.5em; +} +.ql-indent-2.ql-direction-rtl.ql-align-right { + padding-right: 6em; +} +li.ql-indent-2.ql-direction-rtl.ql-align-right { + padding-right: 7.5em; +} +.ql-indent-3:not(.ql-direction-rtl) { + padding-left: 9em; +} +li.ql-indent-3:not(.ql-direction-rtl) { + padding-left: 10.5em; +} +.ql-indent-3.ql-direction-rtl.ql-align-right { + padding-right: 9em; +} +li.ql-indent-3.ql-direction-rtl.ql-align-right { + padding-right: 10.5em; +} +.ql-indent-4:not(.ql-direction-rtl) { + padding-left: 12em; +} +li.ql-indent-4:not(.ql-direction-rtl) { + padding-left: 13.5em; +} +.ql-indent-4.ql-direction-rtl.ql-align-right { + padding-right: 12em; +} +li.ql-indent-4.ql-direction-rtl.ql-align-right { + padding-right: 13.5em; +} +.ql-indent-5:not(.ql-direction-rtl) { + padding-left: 15em; +} +li.ql-indent-5:not(.ql-direction-rtl) { + padding-left: 16.5em; +} +.ql-indent-5.ql-direction-rtl.ql-align-right { + padding-right: 15em; +} +li.ql-indent-5.ql-direction-rtl.ql-align-right { + padding-right: 16.5em; +} +.ql-indent-6:not(.ql-direction-rtl) { + padding-left: 18em; +} +li.ql-indent-6:not(.ql-direction-rtl) { + padding-left: 19.5em; +} +.ql-indent-6.ql-direction-rtl.ql-align-right { + padding-right: 18em; +} +li.ql-indent-6.ql-direction-rtl.ql-align-right { + padding-right: 19.5em; +} +.ql-indent-7:not(.ql-direction-rtl) { + padding-left: 21em; +} +li.ql-indent-7:not(.ql-direction-rtl) { + padding-left: 22.5em; +} +.ql-indent-7.ql-direction-rtl.ql-align-right { + padding-right: 21em; +} +li.ql-indent-7.ql-direction-rtl.ql-align-right { + padding-right: 22.5em; +} +.ql-indent-8:not(.ql-direction-rtl) { + padding-left: 24em; +} +li.ql-indent-8:not(.ql-direction-rtl) { + padding-left: 25.5em; +} +.ql-indent-8.ql-direction-rtl.ql-align-right { + padding-right: 24em; +} +li.ql-indent-8.ql-direction-rtl.ql-align-right { + padding-right: 25.5em; +} +.ql-indent-9:not(.ql-direction-rtl) { + padding-left: 27em; +} +li.ql-indent-9:not(.ql-direction-rtl) { + padding-left: 28.5em; +} +.ql-indent-9.ql-direction-rtl.ql-align-right { + padding-right: 27em; +} +li.ql-indent-9.ql-direction-rtl.ql-align-right { + padding-right: 28.5em; +} +.ql-image { + display: block; + max-width: 100%; +} +.ql-video { + display: block; + max-width: 100%; +} +.ql-video.ql-align-center { + margin: 0 auto; +} +.ql-video.ql-align-right { + margin: 0 0 0 auto; +} +.ql-bg-black { + background-color: #000; +} +.ql-bg-red { + background-color: #e60000; +} +.ql-bg-orange { + background-color: #f90; +} +.ql-bg-yellow { + background-color: #ff0; +} +.ql-bg-green { + background-color: #008a00; +} +.ql-bg-blue { + background-color: #06c; +} +.ql-bg-purple { + background-color: #93f; +} +.ql-color-white { + color: #fff; +} +.ql-color-red { + color: #e60000; +} +.ql-color-orange { + color: #f90; +} +.ql-color-yellow { + color: #ff0; +} +.ql-color-green { + color: #008a00; +} +.ql-color-blue { + color: #06c; +} +.ql-color-purple { + color: #93f; +} +.ql-font-serif { + font-family: Georgia, Times New Roman, serif; +} +.ql-font-monospace { + font-family: Monaco, Courier New, monospace; +} +.ql-size-small { + font-size: 0.75em; +} +.ql-size-large { + font-size: 1.5em; +} +.ql-size-huge { + font-size: 2.5em; +} +.ql-direction-rtl { + direction: rtl; + text-align: inherit; +} +.ql-align-center { + text-align: center; +} +.ql-align-justify { + text-align: justify; +} +.ql-align-right { + text-align: right; +} +.ql-editor.ql-blank::before { + color: rgba(0,0,0,0.6); + content: attr(data-placeholder); + font-style: italic; + left: 15px; + pointer-events: none; + position: absolute; + right: 15px; +} +.ql-snow.ql-toolbar:after, +.ql-snow .ql-toolbar:after { + clear: both; + content: ''; + display: table; +} +.ql-snow.ql-toolbar button, +.ql-snow .ql-toolbar button { + background: none; + border: none; + cursor: pointer; + display: inline-block; + float: left; + height: 24px; + padding: 3px 5px; + width: 28px; +} +.ql-snow.ql-toolbar button svg, +.ql-snow .ql-toolbar button svg { + float: left; + height: 100%; +} +.ql-snow.ql-toolbar button:active:hover, +.ql-snow .ql-toolbar button:active:hover { + outline: none; +} +.ql-snow.ql-toolbar input.ql-image[type=file], +.ql-snow .ql-toolbar input.ql-image[type=file] { + display: none; +} +.ql-snow.ql-toolbar button:hover, +.ql-snow .ql-toolbar button:hover, +.ql-snow.ql-toolbar button:focus, +.ql-snow .ql-toolbar button:focus, +.ql-snow.ql-toolbar button.ql-active, +.ql-snow .ql-toolbar button.ql-active, +.ql-snow.ql-toolbar .ql-picker-label:hover, +.ql-snow .ql-toolbar .ql-picker-label:hover, +.ql-snow.ql-toolbar .ql-picker-label.ql-active, +.ql-snow .ql-toolbar .ql-picker-label.ql-active, +.ql-snow.ql-toolbar .ql-picker-item:hover, +.ql-snow .ql-toolbar .ql-picker-item:hover, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected { + color: #06c; +} +.ql-snow.ql-toolbar button:hover .ql-fill, +.ql-snow .ql-toolbar button:hover .ql-fill, +.ql-snow.ql-toolbar button:focus .ql-fill, +.ql-snow .ql-toolbar button:focus .ql-fill, +.ql-snow.ql-toolbar button.ql-active .ql-fill, +.ql-snow .ql-toolbar button.ql-active .ql-fill, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill, +.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill, +.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill, +.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill, +.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill, +.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill, +.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill { + fill: #06c; +} +.ql-snow.ql-toolbar button:hover .ql-stroke, +.ql-snow .ql-toolbar button:hover .ql-stroke, +.ql-snow.ql-toolbar button:focus .ql-stroke, +.ql-snow .ql-toolbar button:focus .ql-stroke, +.ql-snow.ql-toolbar button.ql-active .ql-stroke, +.ql-snow .ql-toolbar button.ql-active .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, +.ql-snow.ql-toolbar button:hover .ql-stroke-miter, +.ql-snow .ql-toolbar button:hover .ql-stroke-miter, +.ql-snow.ql-toolbar button:focus .ql-stroke-miter, +.ql-snow .ql-toolbar button:focus .ql-stroke-miter, +.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter, +.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, +.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, +.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter { + stroke: #06c; +} +@media (pointer: coarse) { + .ql-snow.ql-toolbar button:hover:not(.ql-active), + .ql-snow .ql-toolbar button:hover:not(.ql-active) { + color: #444; + } + .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill, + .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill, + .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill, + .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill { + fill: #444; + } + .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke, + .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke, + .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter, + .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter { + stroke: #444; + } +} +.ql-snow { + box-sizing: border-box; +} +.ql-snow * { + box-sizing: border-box; +} +.ql-snow .ql-hidden { + display: none; +} +.ql-snow .ql-out-bottom, +.ql-snow .ql-out-top { + visibility: hidden; +} +.ql-snow .ql-tooltip { + position: absolute; + transform: translateY(10px); +} +.ql-snow .ql-tooltip a { + cursor: pointer; + text-decoration: none; +} +.ql-snow .ql-tooltip.ql-flip { + transform: translateY(-10px); +} +.ql-snow .ql-formats { + display: inline-block; + vertical-align: middle; +} +.ql-snow .ql-formats:after { + clear: both; + content: ''; + display: table; +} +.ql-snow .ql-stroke { + fill: none; + stroke: #444; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} +.ql-snow .ql-stroke-miter { + fill: none; + stroke: #444; + stroke-miterlimit: 10; + stroke-width: 2; +} +.ql-snow .ql-fill, +.ql-snow .ql-stroke.ql-fill { + fill: #444; +} +.ql-snow .ql-empty { + fill: none; +} +.ql-snow .ql-even { + fill-rule: evenodd; +} +.ql-snow .ql-thin, +.ql-snow .ql-stroke.ql-thin { + stroke-width: 1; +} +.ql-snow .ql-transparent { + opacity: 0.4; +} +.ql-snow .ql-direction svg:last-child { + display: none; +} +.ql-snow .ql-direction.ql-active svg:last-child { + display: inline; +} +.ql-snow .ql-direction.ql-active svg:first-child { + display: none; +} +.ql-snow h1 { + font-size: 2em; +} +.ql-snow h2 { + font-size: 1.5em; +} +.ql-snow h3 { + font-size: 1.17em; +} +.ql-snow h4 { + font-size: 1em; +} +.ql-snow h5 { + font-size: 0.83em; +} +.ql-snow h6 { + font-size: 0.67em; +} +.ql-snow a { + text-decoration: underline; +} +.ql-snow blockquote { + border-left: 4px solid #ccc; + margin-bottom: 5px; + margin-top: 5px; + padding-left: 16px; +} +.ql-snow code, +.ql-snow pre { + background-color: #f0f0f0; + border-radius: 3px; +} +.ql-snow pre { + white-space: pre-wrap; + margin-bottom: 5px; + margin-top: 5px; + padding: 5px 10px; +} +.ql-snow code { + font-size: 85%; + padding: 2px 4px; +} +.ql-snow pre.ql-syntax { + background-color: #23241f; + color: #f8f8f2; + overflow: visible; +} +.ql-snow img { + max-width: 100%; +} +.ql-snow .ql-picker { + color: #444; + display: inline-block; + float: left; + font-size: 14px; + font-weight: 500; + height: 24px; + position: relative; + vertical-align: middle; +} +.ql-snow .ql-picker-label { + cursor: pointer; + display: inline-block; + height: 100%; + padding-left: 8px; + padding-right: 2px; + position: relative; + width: 100%; +} +.ql-snow .ql-picker-label::before { + display: inline-block; + line-height: 22px; +} +.ql-snow .ql-picker-options { + background-color: #fff; + display: none; + min-width: 100%; + padding: 4px 8px; + position: absolute; + white-space: nowrap; +} +.ql-snow .ql-picker-options .ql-picker-item { + cursor: pointer; + display: block; + padding-bottom: 5px; + padding-top: 5px; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-label { + color: #ccc; + z-index: 2; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill { + fill: #ccc; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke { + stroke: #ccc; +} +.ql-snow .ql-picker.ql-expanded .ql-picker-options { + display: block; + margin-top: -1px; + top: 100%; + z-index: 1; +} +.ql-snow .ql-color-picker, +.ql-snow .ql-icon-picker { + width: 28px; +} +.ql-snow .ql-color-picker .ql-picker-label, +.ql-snow .ql-icon-picker .ql-picker-label { + padding: 2px 4px; +} +.ql-snow .ql-color-picker .ql-picker-label svg, +.ql-snow .ql-icon-picker .ql-picker-label svg { + right: 4px; +} +.ql-snow .ql-icon-picker .ql-picker-options { + padding: 4px 0px; +} +.ql-snow .ql-icon-picker .ql-picker-item { + height: 24px; + width: 24px; + padding: 2px 4px; +} +.ql-snow .ql-color-picker .ql-picker-options { + padding: 3px 5px; + width: 152px; +} +.ql-snow .ql-color-picker .ql-picker-item { + border: 1px solid transparent; + float: left; + height: 16px; + margin: 2px; + padding: 0px; + width: 16px; +} +.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg { + position: absolute; + margin-top: -9px; + right: 0; + top: 50%; + width: 18px; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before { + content: attr(data-label); +} +.ql-snow .ql-picker.ql-header { + width: 98px; +} +.ql-snow .ql-picker.ql-header .ql-picker-label::before, +.ql-snow .ql-picker.ql-header .ql-picker-item::before { + content: 'Normal'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { + content: 'Heading 1'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { + content: 'Heading 2'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { + content: 'Heading 3'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { + content: 'Heading 4'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { + content: 'Heading 5'; +} +.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { + content: 'Heading 6'; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { + font-size: 2em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { + font-size: 1.5em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { + font-size: 1.17em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { + font-size: 1em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { + font-size: 0.83em; +} +.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { + font-size: 0.67em; +} +.ql-snow .ql-picker.ql-font { + width: 108px; +} +.ql-snow .ql-picker.ql-font .ql-picker-label::before, +.ql-snow .ql-picker.ql-font .ql-picker-item::before { + content: 'Sans Serif'; +} +.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { + content: 'Serif'; +} +.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before, +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { + content: 'Monospace'; +} +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { + font-family: Georgia, Times New Roman, serif; +} +.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { + font-family: Monaco, Courier New, monospace; +} +.ql-snow .ql-picker.ql-size { + width: 98px; +} +.ql-snow .ql-picker.ql-size .ql-picker-label::before, +.ql-snow .ql-picker.ql-size .ql-picker-item::before { + content: 'Normal'; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before { + content: 'Small'; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before { + content: 'Large'; +} +.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before, +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { + content: 'Huge'; +} +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before { + font-size: 10px; +} +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before { + font-size: 18px; +} +.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { + font-size: 32px; +} +.ql-snow .ql-color-picker.ql-background .ql-picker-item { + background-color: #fff; +} +.ql-snow .ql-color-picker.ql-color .ql-picker-item { + background-color: #000; +} +.ql-toolbar.ql-snow { + border: 1px solid #ccc; + box-sizing: border-box; + font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + padding: 8px; +} +.ql-toolbar.ql-snow .ql-formats { + margin-right: 15px; +} +.ql-toolbar.ql-snow .ql-picker-label { + border: 1px solid transparent; +} +.ql-toolbar.ql-snow .ql-picker-options { + border: 1px solid transparent; + box-shadow: rgba(0,0,0,0.2) 0 2px 8px; +} +.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label { + border-color: #ccc; +} +.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options { + border-color: #ccc; +} +.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected, +.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover { + border-color: #000; +} +.ql-toolbar.ql-snow + .ql-container.ql-snow { + border-top: 0px; +} +.ql-snow .ql-tooltip { + background-color: #fff; + border: 1px solid #ccc; + box-shadow: 0px 0px 5px #ddd; + color: #444; + padding: 5px 12px; + white-space: nowrap; +} +.ql-snow .ql-tooltip::before { + content: "Visit URL:"; + line-height: 26px; + margin-right: 8px; +} +.ql-snow .ql-tooltip input[type=text] { + display: none; + border: 1px solid #ccc; + font-size: 13px; + height: 26px; + margin: 0px; + padding: 3px 5px; + width: 170px; +} +.ql-snow .ql-tooltip a.ql-preview { + display: inline-block; + max-width: 200px; + overflow-x: hidden; + text-overflow: ellipsis; + vertical-align: top; +} +.ql-snow .ql-tooltip a.ql-action::after { + border-right: 1px solid #ccc; + content: 'Edit'; + margin-left: 16px; + padding-right: 8px; +} +.ql-snow .ql-tooltip a.ql-remove::before { + content: 'Remove'; + margin-left: 8px; +} +.ql-snow .ql-tooltip a { + line-height: 26px; +} +.ql-snow .ql-tooltip.ql-editing a.ql-preview, +.ql-snow .ql-tooltip.ql-editing a.ql-remove { + display: none; +} +.ql-snow .ql-tooltip.ql-editing input[type=text] { + display: inline-block; +} +.ql-snow .ql-tooltip.ql-editing a.ql-action::after { + border-right: 0px; + content: 'Save'; + padding-right: 0px; +} +.ql-snow .ql-tooltip[data-mode=link]::before { + content: "Enter link:"; +} +.ql-snow .ql-tooltip[data-mode=formula]::before { + content: "Enter formula:"; +} +.ql-snow .ql-tooltip[data-mode=video]::before { + content: "Enter video:"; +} +.ql-snow a { + color: #06c; +} +.ql-container.ql-snow { + border: 1px solid #ccc; +} diff --git a/src/assets/qunzu-3.svg b/src/assets/qunzu-3.svg new file mode 100644 index 0000000..2f07882 --- /dev/null +++ b/src/assets/qunzu-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/readed.svg b/src/assets/readed.svg new file mode 100644 index 0000000..9f359fc --- /dev/null +++ b/src/assets/readed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/recvMsgOpt.svg b/src/assets/recvMsgOpt.svg new file mode 100644 index 0000000..0c18fea --- /dev/null +++ b/src/assets/recvMsgOpt.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/reset.css b/src/assets/reset.css new file mode 100644 index 0000000..7e0a6fe --- /dev/null +++ b/src/assets/reset.css @@ -0,0 +1,12 @@ +:root:root { + --van-nav-bar-height:56px; + --van-nav-bar-icon-color:#1B1B22; + --van-nav-bar-arrow-size:14px; + --van-nav-bar-text-color:#1B1B22; + --van-nav-bar-title-font-size:1rem; + --van-padding-md: 1rem; +} +.search { + display: none !important; +} + diff --git a/src/assets/right-small.svg b/src/assets/right-small.svg new file mode 100644 index 0000000..c8be030 --- /dev/null +++ b/src/assets/right-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/right-small1.svg b/src/assets/right-small1.svg new file mode 100644 index 0000000..2519fc8 --- /dev/null +++ b/src/assets/right-small1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/right-small2.svg b/src/assets/right-small2.svg new file mode 100644 index 0000000..b2038dc --- /dev/null +++ b/src/assets/right-small2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/right.svg b/src/assets/right.svg new file mode 100644 index 0000000..3b589b9 --- /dev/null +++ b/src/assets/right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/s_file.svg b/src/assets/s_file.svg new file mode 100644 index 0000000..efa8686 --- /dev/null +++ b/src/assets/s_file.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/s_image.svg b/src/assets/s_image.svg new file mode 100644 index 0000000..c1d86ab --- /dev/null +++ b/src/assets/s_image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/safe.svg b/src/assets/safe.svg new file mode 100644 index 0000000..1963a5d --- /dev/null +++ b/src/assets/safe.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/search-icon.svg b/src/assets/search-icon.svg new file mode 100644 index 0000000..e929d41 --- /dev/null +++ b/src/assets/search-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/shanchu.png b/src/assets/shanchu.png new file mode 100644 index 0000000..de35880 Binary files /dev/null and b/src/assets/shanchu.png differ diff --git a/src/assets/shouqi.svg b/src/assets/shouqi.svg new file mode 100644 index 0000000..3f23c40 --- /dev/null +++ b/src/assets/shouqi.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/shouyebeijing.png b/src/assets/shouyebeijing.png new file mode 100644 index 0000000..04cc712 Binary files /dev/null and b/src/assets/shouyebeijing.png differ diff --git a/src/assets/star.svg b/src/assets/star.svg new file mode 100644 index 0000000..0e34acf --- /dev/null +++ b/src/assets/star.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/svg/Frame 1500.svg b/src/assets/svg/Frame 1500.svg new file mode 100644 index 0000000..29864cd --- /dev/null +++ b/src/assets/svg/Frame 1500.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/svg/Group56.svg b/src/assets/svg/Group56.svg new file mode 100644 index 0000000..53e5a14 --- /dev/null +++ b/src/assets/svg/Group56.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/Group625882.svg b/src/assets/svg/Group625882.svg new file mode 100644 index 0000000..c8aff28 --- /dev/null +++ b/src/assets/svg/Group625882.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/svg/Union.svg b/src/assets/svg/Union.svg new file mode 100644 index 0000000..6fac039 --- /dev/null +++ b/src/assets/svg/Union.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/apply-withdraw.svg b/src/assets/svg/apply-withdraw.svg new file mode 100644 index 0000000..eb25387 --- /dev/null +++ b/src/assets/svg/apply-withdraw.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/arrow-r.svg b/src/assets/svg/arrow-r.svg new file mode 100644 index 0000000..cc6c60e --- /dev/null +++ b/src/assets/svg/arrow-r.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/avatar-bg.svg b/src/assets/svg/avatar-bg.svg new file mode 100644 index 0000000..2241c43 --- /dev/null +++ b/src/assets/svg/avatar-bg.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/svg/banner-close.svg b/src/assets/svg/banner-close.svg new file mode 100644 index 0000000..42d7184 --- /dev/null +++ b/src/assets/svg/banner-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/banner-logo.svg b/src/assets/svg/banner-logo.svg new file mode 100644 index 0000000..3c222aa --- /dev/null +++ b/src/assets/svg/banner-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/svg/bofang.svg b/src/assets/svg/bofang.svg new file mode 100644 index 0000000..6491b67 --- /dev/null +++ b/src/assets/svg/bofang.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/check-in.svg b/src/assets/svg/check-in.svg new file mode 100644 index 0000000..b4fe1e5 --- /dev/null +++ b/src/assets/svg/check-in.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/check_all.svg b/src/assets/svg/check_all.svg new file mode 100644 index 0000000..e3f545e --- /dev/null +++ b/src/assets/svg/check_all.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/check_all_active.svg b/src/assets/svg/check_all_active.svg new file mode 100644 index 0000000..a55ba87 --- /dev/null +++ b/src/assets/svg/check_all_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/close-down.svg b/src/assets/svg/close-down.svg new file mode 100644 index 0000000..9741711 --- /dev/null +++ b/src/assets/svg/close-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/close-gray.svg b/src/assets/svg/close-gray.svg new file mode 100644 index 0000000..b864088 --- /dev/null +++ b/src/assets/svg/close-gray.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/close-white.svg b/src/assets/svg/close-white.svg new file mode 100644 index 0000000..3c1fb80 --- /dev/null +++ b/src/assets/svg/close-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/community-active.svg b/src/assets/svg/community-active.svg new file mode 100644 index 0000000..22e614d --- /dev/null +++ b/src/assets/svg/community-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/community.svg b/src/assets/svg/community.svg new file mode 100644 index 0000000..302b2fb --- /dev/null +++ b/src/assets/svg/community.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/contact-active.svg b/src/assets/svg/contact-active.svg new file mode 100644 index 0000000..84c48f8 --- /dev/null +++ b/src/assets/svg/contact-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/contact.svg b/src/assets/svg/contact.svg new file mode 100644 index 0000000..4a8b5de --- /dev/null +++ b/src/assets/svg/contact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/delay-detection.svg b/src/assets/svg/delay-detection.svg new file mode 100644 index 0000000..ab1324f --- /dev/null +++ b/src/assets/svg/delay-detection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/delete.svg b/src/assets/svg/delete.svg new file mode 100644 index 0000000..9eb6b9b --- /dev/null +++ b/src/assets/svg/delete.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/svg/download.svg b/src/assets/svg/download.svg new file mode 100644 index 0000000..b613342 --- /dev/null +++ b/src/assets/svg/download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/duanxin.svg b/src/assets/svg/duanxin.svg new file mode 100644 index 0000000..4021978 --- /dev/null +++ b/src/assets/svg/duanxin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/edit-remark.svg b/src/assets/svg/edit-remark.svg new file mode 100644 index 0000000..ef79801 --- /dev/null +++ b/src/assets/svg/edit-remark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/error-img.svg b/src/assets/svg/error-img.svg new file mode 100644 index 0000000..a22b8e9 --- /dev/null +++ b/src/assets/svg/error-img.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/error-line.svg b/src/assets/svg/error-line.svg new file mode 100644 index 0000000..ce4db7d --- /dev/null +++ b/src/assets/svg/error-line.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/event-close.svg b/src/assets/svg/event-close.svg new file mode 100644 index 0000000..4496512 --- /dev/null +++ b/src/assets/svg/event-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/examine.svg b/src/assets/svg/examine.svg new file mode 100644 index 0000000..a8d6017 --- /dev/null +++ b/src/assets/svg/examine.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/svg/facebook-login.svg b/src/assets/svg/facebook-login.svg new file mode 100644 index 0000000..818ac60 --- /dev/null +++ b/src/assets/svg/facebook-login.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/form-login.svg b/src/assets/svg/form-login.svg new file mode 100644 index 0000000..d72cf75 --- /dev/null +++ b/src/assets/svg/form-login.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/fuzhi.svg b/src/assets/svg/fuzhi.svg new file mode 100644 index 0000000..3ef94be --- /dev/null +++ b/src/assets/svg/fuzhi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/google-login.svg b/src/assets/svg/google-login.svg new file mode 100644 index 0000000..0d216f0 --- /dev/null +++ b/src/assets/svg/google-login.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/svg/gou.svg b/src/assets/svg/gou.svg new file mode 100644 index 0000000..c22e465 --- /dev/null +++ b/src/assets/svg/gou.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/guanbi.svg b/src/assets/svg/guanbi.svg new file mode 100644 index 0000000..5bc2fbf --- /dev/null +++ b/src/assets/svg/guanbi.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/hi.svg b/src/assets/svg/hi.svg new file mode 100644 index 0000000..c78824f --- /dev/null +++ b/src/assets/svg/hi.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/hongbao-small.svg b/src/assets/svg/hongbao-small.svg new file mode 100644 index 0000000..21b4945 --- /dev/null +++ b/src/assets/svg/hongbao-small.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/svg/incineration-picture.svg b/src/assets/svg/incineration-picture.svg new file mode 100644 index 0000000..758eb01 --- /dev/null +++ b/src/assets/svg/incineration-picture.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/incineration-video.svg b/src/assets/svg/incineration-video.svg new file mode 100644 index 0000000..acffe88 --- /dev/null +++ b/src/assets/svg/incineration-video.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/incineration.svg b/src/assets/svg/incineration.svg new file mode 100644 index 0000000..77a2628 --- /dev/null +++ b/src/assets/svg/incineration.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/svg/list_hook.svg b/src/assets/svg/list_hook.svg new file mode 100644 index 0000000..57ab35c --- /dev/null +++ b/src/assets/svg/list_hook.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/loading.png b/src/assets/svg/loading.png new file mode 100644 index 0000000..61f4417 Binary files /dev/null and b/src/assets/svg/loading.png differ diff --git a/src/assets/svg/logo-active.svg b/src/assets/svg/logo-active.svg new file mode 100644 index 0000000..26e5b8d --- /dev/null +++ b/src/assets/svg/logo-active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/logo-black-cn.svg b/src/assets/svg/logo-black-cn.svg new file mode 100644 index 0000000..263d3df --- /dev/null +++ b/src/assets/svg/logo-black-cn.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svg/logo-black-us.svg b/src/assets/svg/logo-black-us.svg new file mode 100644 index 0000000..f8dd042 --- /dev/null +++ b/src/assets/svg/logo-black-us.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/svg/logo-cn.svg b/src/assets/svg/logo-cn.svg new file mode 100644 index 0000000..0d7855e --- /dev/null +++ b/src/assets/svg/logo-cn.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/svg/logo-us.svg b/src/assets/svg/logo-us.svg new file mode 100644 index 0000000..c8d80b9 --- /dev/null +++ b/src/assets/svg/logo-us.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svg/logo.svg b/src/assets/svg/logo.svg new file mode 100644 index 0000000..7823d43 --- /dev/null +++ b/src/assets/svg/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/merchant_default_avatar.svg b/src/assets/svg/merchant_default_avatar.svg new file mode 100644 index 0000000..846ea7e --- /dev/null +++ b/src/assets/svg/merchant_default_avatar.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/mine-active.svg b/src/assets/svg/mine-active.svg new file mode 100644 index 0000000..e38ba01 --- /dev/null +++ b/src/assets/svg/mine-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/mine.svg b/src/assets/svg/mine.svg new file mode 100644 index 0000000..0e73384 --- /dev/null +++ b/src/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/more-login.svg b/src/assets/svg/more-login.svg new file mode 100644 index 0000000..6d4a0b1 --- /dev/null +++ b/src/assets/svg/more-login.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/msg_user_default.svg b/src/assets/svg/msg_user_default.svg new file mode 100644 index 0000000..869a3e5 --- /dev/null +++ b/src/assets/svg/msg_user_default.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/svg/my-edit.svg b/src/assets/svg/my-edit.svg new file mode 100644 index 0000000..9bbccb5 --- /dev/null +++ b/src/assets/svg/my-edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/my-location.svg b/src/assets/svg/my-location.svg new file mode 100644 index 0000000..943353d --- /dev/null +++ b/src/assets/svg/my-location.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/my-pause-icon.svg b/src/assets/svg/my-pause-icon.svg new file mode 100644 index 0000000..bcd3e04 --- /dev/null +++ b/src/assets/svg/my-pause-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/my-play-icon.svg b/src/assets/svg/my-play-icon.svg new file mode 100644 index 0000000..2e06d39 --- /dev/null +++ b/src/assets/svg/my-play-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/network-check.svg b/src/assets/svg/network-check.svg new file mode 100644 index 0000000..ab836b5 --- /dev/null +++ b/src/assets/svg/network-check.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/svg/next.svg b/src/assets/svg/next.svg new file mode 100644 index 0000000..52e6c1b --- /dev/null +++ b/src/assets/svg/next.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/pack-up.svg b/src/assets/svg/pack-up.svg new file mode 100644 index 0000000..585d2f7 --- /dev/null +++ b/src/assets/svg/pack-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/pause-icon.svg b/src/assets/svg/pause-icon.svg new file mode 100644 index 0000000..79c82ce --- /dev/null +++ b/src/assets/svg/pause-icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/phone-call-black.svg b/src/assets/svg/phone-call-black.svg new file mode 100644 index 0000000..485e018 --- /dev/null +++ b/src/assets/svg/phone-call-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/phone-call.svg b/src/assets/svg/phone-call.svg new file mode 100644 index 0000000..a557926 --- /dev/null +++ b/src/assets/svg/phone-call.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/pre.svg b/src/assets/svg/pre.svg new file mode 100644 index 0000000..39776e7 --- /dev/null +++ b/src/assets/svg/pre.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/qrcode-login.svg b/src/assets/svg/qrcode-login.svg new file mode 100644 index 0000000..e775879 --- /dev/null +++ b/src/assets/svg/qrcode-login.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/qrcodereflesh.svg b/src/assets/svg/qrcodereflesh.svg new file mode 100644 index 0000000..d435cca --- /dev/null +++ b/src/assets/svg/qrcodereflesh.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/radio-check.svg b/src/assets/svg/radio-check.svg new file mode 100644 index 0000000..48d6624 --- /dev/null +++ b/src/assets/svg/radio-check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/redPackage.svg b/src/assets/svg/redPackage.svg new file mode 100644 index 0000000..6a44eb0 --- /dev/null +++ b/src/assets/svg/redPackage.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/retransmission.svg b/src/assets/svg/retransmission.svg new file mode 100644 index 0000000..5110ec3 --- /dev/null +++ b/src/assets/svg/retransmission.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/scan-success.svg b/src/assets/svg/scan-success.svg new file mode 100644 index 0000000..2f02f8c --- /dev/null +++ b/src/assets/svg/scan-success.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/shanchu2.svg b/src/assets/svg/shanchu2.svg new file mode 100644 index 0000000..f3d97cc --- /dev/null +++ b/src/assets/svg/shanchu2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/share.svg b/src/assets/svg/share.svg new file mode 100644 index 0000000..b95ad40 --- /dev/null +++ b/src/assets/svg/share.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/shouji.svg b/src/assets/svg/shouji.svg new file mode 100644 index 0000000..249910c --- /dev/null +++ b/src/assets/svg/shouji.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/svg/success.svg b/src/assets/svg/success.svg new file mode 100644 index 0000000..95442d7 --- /dev/null +++ b/src/assets/svg/success.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/transfer-page-logo.svg b/src/assets/svg/transfer-page-logo.svg new file mode 100644 index 0000000..58b4987 --- /dev/null +++ b/src/assets/svg/transfer-page-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/up-icon-straight.svg b/src/assets/svg/up-icon-straight.svg new file mode 100644 index 0000000..8ab1737 --- /dev/null +++ b/src/assets/svg/up-icon-straight.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/vector-icon.svg b/src/assets/svg/vector-icon.svg new file mode 100644 index 0000000..09dac33 --- /dev/null +++ b/src/assets/svg/vector-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/warn.svg b/src/assets/svg/warn.svg new file mode 100644 index 0000000..2fe8172 --- /dev/null +++ b/src/assets/svg/warn.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/xinhao-timeout.svg b/src/assets/svg/xinhao-timeout.svg new file mode 100644 index 0000000..e87a385 --- /dev/null +++ b/src/assets/svg/xinhao-timeout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/xinhao-yanchi1.svg b/src/assets/svg/xinhao-yanchi1.svg new file mode 100644 index 0000000..1e2738b --- /dev/null +++ b/src/assets/svg/xinhao-yanchi1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/xinhao-yanchi2.svg b/src/assets/svg/xinhao-yanchi2.svg new file mode 100644 index 0000000..1d899f4 --- /dev/null +++ b/src/assets/svg/xinhao-yanchi2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/xinhao-yanchi3.svg b/src/assets/svg/xinhao-yanchi3.svg new file mode 100644 index 0000000..a770363 --- /dev/null +++ b/src/assets/svg/xinhao-yanchi3.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/xinhao-yanchi4.svg b/src/assets/svg/xinhao-yanchi4.svg new file mode 100644 index 0000000..d69f955 --- /dev/null +++ b/src/assets/svg/xinhao-yanchi4.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/xinhao-yanchi5.svg b/src/assets/svg/xinhao-yanchi5.svg new file mode 100644 index 0000000..5fb38a5 --- /dev/null +++ b/src/assets/svg/xinhao-yanchi5.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/youjiantou.svg b/src/assets/svg/youjiantou.svg new file mode 100644 index 0000000..4087087 --- /dev/null +++ b/src/assets/svg/youjiantou.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/svg/zuanshi.svg b/src/assets/svg/zuanshi.svg new file mode 100644 index 0000000..08098b5 --- /dev/null +++ b/src/assets/svg/zuanshi.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/theme.css b/src/assets/theme.css new file mode 100644 index 0000000..ad09189 --- /dev/null +++ b/src/assets/theme.css @@ -0,0 +1,6 @@ +:root { + --c-text-color: #1B1B22; + --c-yellow-color: #FA9837; + --c-blue-color: #0095F6; + --c-red-color: #EA6558; +} diff --git a/src/assets/toTop.svg b/src/assets/toTop.svg new file mode 100644 index 0000000..3c70541 --- /dev/null +++ b/src/assets/toTop.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/toTop1.svg b/src/assets/toTop1.svg new file mode 100644 index 0000000..d1c562b --- /dev/null +++ b/src/assets/toTop1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/tupian.png b/src/assets/tupian.png new file mode 100644 index 0000000..c4ec114 Binary files /dev/null and b/src/assets/tupian.png differ diff --git a/src/assets/typeface.svg b/src/assets/typeface.svg new file mode 100644 index 0000000..10d6240 --- /dev/null +++ b/src/assets/typeface.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/zhiding.png b/src/assets/zhiding.png new file mode 100644 index 0000000..20b2d98 Binary files /dev/null and b/src/assets/zhiding.png differ diff --git a/src/blank/blankTransfer/App.vue b/src/blank/blankTransfer/App.vue new file mode 100644 index 0000000..ed78f24 --- /dev/null +++ b/src/blank/blankTransfer/App.vue @@ -0,0 +1,163 @@ + + + + diff --git a/src/blank/blankTransfer/api.js b/src/blank/blankTransfer/api.js new file mode 100644 index 0000000..15649cb --- /dev/null +++ b/src/blank/blankTransfer/api.js @@ -0,0 +1,11 @@ +import request from "./request" + + +let api = { + // 获取后台配置的登录注册方式 + getConfigInfo: data => { + return request.post("/v1/config/get", data, { urlTag: "api_2" }); + }, +} + +export default api diff --git a/src/blank/blankTransfer/main.js b/src/blank/blankTransfer/main.js new file mode 100644 index 0000000..dcac125 --- /dev/null +++ b/src/blank/blankTransfer/main.js @@ -0,0 +1,8 @@ + +import { createApp } from 'vue' +import App from './App.vue' +import api from './api' +// 导入pinia本地存储 +const app = createApp(App) +app.provide('$api', api) +app.mount('#app') diff --git a/src/blank/blankTransfer/request.js b/src/blank/blankTransfer/request.js new file mode 100644 index 0000000..7907a96 --- /dev/null +++ b/src/blank/blankTransfer/request.js @@ -0,0 +1,139 @@ +import axios from "axios"; +import { showToast } from "vant"; +import { i18n } from "@/lang/index"; + + +const useEmailLang = () => { + let navLang = 'us' + if (navigator.language === 'zh-CN') { + navLang = 'zh-cn' + } else if (navigator.language === 'en') { + navLang = 'us' + } else if (navigator.language === 'zh-TW') { + navLang = 'zh-hant-cn' + } else if (navigator.language === 'vi') { + navLang = 'vi' // 越南 + } else if (navigator.language === 'th') { + navLang = 'th' // 泰语 + } else if (navigator.language === 'id') { + navLang = 'id' // 印尼 + } else if (navigator.language === 'pt-BR') { + navLang = 'pt-br' // 巴西 + } + let configLang = '' + if (localStorage.getItem('language') === 'zh-cn') { + configLang = 'zh-cn' + } else if (localStorage.getItem('language') === 'us') { + configLang = 'us' + } else if (localStorage.getItem('language') === 'zh-hant-cn') { + configLang = 'zh-hant-cn' + } else if (localStorage.getItem('language') === 'vi-VN') { + configLang = 'vi' // 越南 + } else if (localStorage.getItem('language') === 'th') { + configLang = 'th' // 泰语 + } else if (localStorage.getItem('language') === 'id') { + configLang = 'id' // 印尼 + } else if (localStorage.getItem('language') === 'pt-br') { + configLang = 'pt-br' // 巴西 + } + const language = configLang || navLang; + console.log('navigator.language',language); + return language +} + +let appConfig = sessionStorage.getItem("appConfig") + ? JSON.parse(sessionStorage.getItem("appConfig")) + : {}; + +const { DEV, VITE_APP_PUB_API, VITE_APP_MSG_API } = import.meta.env; +// @ts-ignore +const { api_2, api_3, api_1 } = window.WEB_CONFIG; +// const api_2 = 'https://dev-im-m.exchangs.top/chat-api' +const service = axios.create({ + // baseURL: DEV ? VITE_APP_PUB_API : api_2, // 所有的请求地址前缀部分 + // baseURL: DEV ? '/chat-api' : api_2, + timeout: 60000, // 请求超时时间毫秒 + withCredentials: false, // 异步请求携带cookie + headers: { + // 设置后端需要的传参类型 + "Content-Type": "application/json" + // 'Origin': 'wp' + // 'X-Requested-With': 'XMLHttpRequest', + } +}); +// 请求拦截器 +service.interceptors.request.use( + config => { + //给请求头设置token + const token = window.localStorage.getItem("token") || ""; + if (config.urlTag === "api_2") { + config.baseURL = DEV ? "/api_2" : api_2; + config.headers.token = token; + config.headers.language = useEmailLang(); + } + return config; + }, + error => { + showToast({ position: "top", message: error.message }); + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + response => { + const { data, request } = response; + const { code, msg, detail } = data; + const { responseURL } = request; + if (code === 511) { + if ( + responseURL.includes("/v1/login") || + responseURL.includes("/v1/user/phone_login") + ) { + showToast({ message: "系统正在维护中" }); + } else { + } + return data; + } + if (code === 401) { + return data; + } + if (code === 0) { + // 将组件用的数据返回 + return data; + } else { + showToast({ position: "top", message: detail }); + return data; + } + }, + error => { + // 处理 HTTP 网络错误 + let message = ""; + // HTTP 状态码 + const status = error.response?.status; + switch (status) { + case 401: + message = t("dialog_tip_token_invaild"); + // 这里可以触发退出的 action + break; + case 403: + message = "没有权限,请获取权限后登录"; + break; + case 404: + message = "页面不存在"; + break; + case 500: + message = "服务器故障"; + break; + case 502: + message = "数据库查询错误"; + break; + default: + message = "网络连接错误"; + } + showToast({ position: "top", message: message }); + return Promise.reject(error); + } +); + +export default service; diff --git a/src/components/ABPPInput.vue b/src/components/ABPPInput.vue new file mode 100644 index 0000000..45dc30c --- /dev/null +++ b/src/components/ABPPInput.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/components/ABPPInput1.vue b/src/components/ABPPInput1.vue new file mode 100644 index 0000000..05b1b39 --- /dev/null +++ b/src/components/ABPPInput1.vue @@ -0,0 +1,161 @@ + + + + + + \ No newline at end of file diff --git a/src/components/ABPPInput2.vue b/src/components/ABPPInput2.vue new file mode 100644 index 0000000..9c0c8b2 --- /dev/null +++ b/src/components/ABPPInput2.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/src/components/CheckBox/index.vue b/src/components/CheckBox/index.vue new file mode 100644 index 0000000..bf99a75 --- /dev/null +++ b/src/components/CheckBox/index.vue @@ -0,0 +1,50 @@ + + + + + \ No newline at end of file diff --git a/src/components/CustomEdit/index.vue b/src/components/CustomEdit/index.vue new file mode 100644 index 0000000..b4961a2 --- /dev/null +++ b/src/components/CustomEdit/index.vue @@ -0,0 +1,284 @@ + + + + + \ No newline at end of file diff --git a/src/components/NavBar/index.vue b/src/components/NavBar/index.vue new file mode 100644 index 0000000..f59e2ff --- /dev/null +++ b/src/components/NavBar/index.vue @@ -0,0 +1,71 @@ + + + + + \ No newline at end of file diff --git a/src/components/applyDialog/index.vue b/src/components/applyDialog/index.vue new file mode 100644 index 0000000..8e438d3 --- /dev/null +++ b/src/components/applyDialog/index.vue @@ -0,0 +1,146 @@ + + + + \ No newline at end of file diff --git a/src/components/avatar/index.vue b/src/components/avatar/index.vue new file mode 100644 index 0000000..2acbdaf --- /dev/null +++ b/src/components/avatar/index.vue @@ -0,0 +1,85 @@ + + + \ No newline at end of file diff --git a/src/components/bindactionSheet/index.vue b/src/components/bindactionSheet/index.vue new file mode 100644 index 0000000..4876570 --- /dev/null +++ b/src/components/bindactionSheet/index.vue @@ -0,0 +1,49 @@ + + + + + \ No newline at end of file diff --git a/src/components/cdnSource/index.vue b/src/components/cdnSource/index.vue new file mode 100644 index 0000000..6c4a716 --- /dev/null +++ b/src/components/cdnSource/index.vue @@ -0,0 +1,108 @@ + + + \ No newline at end of file diff --git a/src/components/chart/dark.js b/src/components/chart/dark.js new file mode 100644 index 0000000..32d7b0d --- /dev/null +++ b/src/components/chart/dark.js @@ -0,0 +1,179 @@ +const contrastColor = 'rgba(255, 255, 255, 0.65)' +const backgroundColor = 'transparent' +const axisCommon = function () { + return { + axisLine: { + lineStyle: { + color: contrastColor, + }, + }, + splitLine: { + lineStyle: { + color: '#484753', + }, + }, + splitArea: { + areaStyle: { + color: ['rgba(255,255,255,0.02)', 'rgba(255,255,255,0.05)'], + }, + }, + minorSplitLine: { + lineStyle: { + color: '#20203B', + }, + }, + } +} + +const colorPalette = [ + '#4992ff', + '#7cffb2', + '#fddd60', + '#ff6e76', + '#58d9f9', + '#05c091', + '#ff8a45', + '#8d48e3', + '#dd79ff', +] +const theme = { + color: colorPalette, + backgroundColor, + axisPointer: { + lineStyle: { + color: '#817f91', + }, + crossStyle: { + color: '#817f91', + }, + label: { + // TODO Contrast of label backgorundColor + color: '#fff', + }, + }, + legend: { + textStyle: { + color: contrastColor, + }, + }, + textStyle: { + color: contrastColor, + }, + title: { + textStyle: { + color: 'red', + }, + subtextStyle: { + color: 'rgba(255, 255, 255, 0.65)', + }, + }, + toolbox: { + iconStyle: { + borderColor: contrastColor, + }, + }, + dataZoom: { + borderColor: '#71708A', + textStyle: { + color: contrastColor, + }, + brushStyle: { + color: 'rgba(135,163,206,0.3)', + }, + handleStyle: { + color: '#353450', + borderColor: '#C5CBE3', + }, + moveHandleStyle: { + color: '#B0B6C3', + opacity: 0.3, + }, + fillerColor: 'rgba(135,163,206,0.2)', + emphasis: { + handleStyle: { + borderColor: '#91B7F2', + color: '#4D587D', + }, + moveHandleStyle: { + color: '#636D9A', + opacity: 0.7, + }, + }, + dataBackground: { + lineStyle: { + color: '#71708A', + width: 1, + }, + areaStyle: { + color: '#71708A', + }, + }, + selectedDataBackground: { + lineStyle: { + color: '#87A3CE', + }, + areaStyle: { + color: '#87A3CE', + }, + }, + }, + visualMap: { + textStyle: { + color: contrastColor, + }, + }, + timeline: { + lineStyle: { + color: contrastColor, + }, + label: { + color: contrastColor, + }, + controlStyle: { + color: contrastColor, + borderColor: contrastColor, + }, + }, + calendar: { + itemStyle: { + color: backgroundColor, + }, + dayLabel: { + color: contrastColor, + }, + monthLabel: { + color: contrastColor, + }, + yearLabel: { + color: contrastColor, + }, + }, + timeAxis: axisCommon(), + logAxis: axisCommon(), + valueAxis: axisCommon(), + categoryAxis: axisCommon(), + + line: { + symbol: 'circle', + }, + graph: { + color: colorPalette, + }, + gauge: { + title: { + color: contrastColor, + }, + }, + candlestick: { + itemStyle: { + color: '#FD1050', + color0: '#0CF49B', + borderColor: '#FD1050', + borderColor0: '#0CF49B', + }, + }, +} + +theme.categoryAxis.splitLine.show = false + +export default theme diff --git a/src/components/chart/index.vue b/src/components/chart/index.vue new file mode 100644 index 0000000..c4c7a74 --- /dev/null +++ b/src/components/chart/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/components/chart/typing.ts b/src/components/chart/typing.ts new file mode 100644 index 0000000..08a54d9 --- /dev/null +++ b/src/components/chart/typing.ts @@ -0,0 +1,15 @@ +export interface SeriesDataItem { + x: any + y: any +} + +export interface RadarDataItem { + label: string + name: string + value: string | number +} + +export interface RadarIndicatorItem { + name: string + max: number +} diff --git a/src/components/codeInput/index.vue b/src/components/codeInput/index.vue new file mode 100644 index 0000000..cc41f0c --- /dev/null +++ b/src/components/codeInput/index.vue @@ -0,0 +1,156 @@ + + + + + + diff --git a/src/components/commonDialog/index.vue b/src/components/commonDialog/index.vue new file mode 100644 index 0000000..fb05eae --- /dev/null +++ b/src/components/commonDialog/index.vue @@ -0,0 +1,150 @@ + + + + \ No newline at end of file diff --git a/src/components/commonFooter/ChatFooterAction.vue b/src/components/commonFooter/ChatFooterAction.vue new file mode 100644 index 0000000..9cd439f --- /dev/null +++ b/src/components/commonFooter/ChatFooterAction.vue @@ -0,0 +1,79 @@ + + + + + \ No newline at end of file diff --git a/src/components/commonFooter/emoji.vue b/src/components/commonFooter/emoji.vue new file mode 100644 index 0000000..7f645b5 --- /dev/null +++ b/src/components/commonFooter/emoji.vue @@ -0,0 +1,49 @@ + + + + + \ No newline at end of file diff --git a/src/components/commonFooter/index.vue b/src/components/commonFooter/index.vue new file mode 100644 index 0000000..59a81c4 --- /dev/null +++ b/src/components/commonFooter/index.vue @@ -0,0 +1,141 @@ + + + + + \ No newline at end of file diff --git a/src/components/commonFooter/useCreateFileMessage.js b/src/components/commonFooter/useCreateFileMessage.js new file mode 100644 index 0000000..8648fc4 --- /dev/null +++ b/src/components/commonFooter/useCreateFileMessage.js @@ -0,0 +1,117 @@ +import { getPicInfo, getMediaDuration, getVideoSnshot } from "@/utils/common"; +import { getSDK } from '@/utils/openIM/sdk/index.js' +import { showFailToast } from "vant"; +import { useI18n } from "vue-i18n" + +export default function useCreateFileMessage() { + const IMSDK = getSDK() + const { t } = useI18n(); + + const getFileData = (data) => { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onload = function () { + resolve(reader.result); + }; + reader.readAsArrayBuffer(data); + }); + }; + + const getFileType = (name) => { + const idx = name.lastIndexOf("."); + return name.slice(idx + 1); + }; + + const getImageMessage = async (file) => { + const { width, height } = await getPicInfo(file); + const { data: { url } } = await IMSDK.uploadFile({ + name: file.name, + contentType: file.type, + uuid: IMSDK.createuuid(), + file, + size: file.size + }) + + const baseInfo = { + uuid: IMSDK.createuuid(), + type: getFileType(file.name), + size: file.size, + width, + height, + url: file.url, + }; + const options = { + sourcePicture: baseInfo, + bigPicture: baseInfo, + snapshotPicture: baseInfo, + sourcePath: "", + file, + }; + return (await IMSDK.createImageMessageByFile(options)).data; + }; + + const getVideoMessage = async ( + file, + snapShotFile + ) => { + const { width, height } = await getPicInfo(snapShotFile); + const options = { + videoFile: file, + snapshotFile: snapShotFile, + videoPath: "", + duration: await getMediaDuration(URL.createObjectURL(file)), + videoType: getFileType(file.name), + snapshotPath: "", + videoUUID: IMSDK.createuuid(), + videoUrl: "", + videoSize: file.size, + snapshotUUID: IMSDK.createuuid(), + snapshotSize: snapShotFile.size, + snapshotUrl: URL.createObjectURL(snapShotFile), + snapshotWidth: width, + snapshotHeight: height, + snapShotType: getFileType(file.name), + }; + return (await IMSDK.createVideoMessageByFile(options)).data; + }; + + const createFileMessage = async ( + file, + messageType, + duration + ) => { + let snapShotFile = undefined; + if (messageType === 104) { + try { + snapShotFile = await getVideoSnshot(URL.createObjectURL(file)); + } catch (error) { + showFailToast(t("messageTip.generateImageFailed")); + console.error("get video snapShotFile failed: " + error); + return { + error: "get video snapShotFile failed", + }; + } + } + switch (messageType) { + case 102: + return { + message: await getImageMessage(file), + buffer: await getFileData(file), + }; + case 104: + return { + message: await getVideoMessage(file, snapShotFile), + buffer: await getFileData(file), + snapBuffer: await getFileData(snapShotFile), + }; + default: + return { + error: "message type error", + }; + } + }; + + return { + createFileMessage, + }; +} diff --git a/src/components/commonFooter/useCreateNomalMessage.js b/src/components/commonFooter/useCreateNomalMessage.js new file mode 100644 index 0000000..0156f69 --- /dev/null +++ b/src/components/commonFooter/useCreateNomalMessage.js @@ -0,0 +1,39 @@ +import { feedbackToast } from "@/utils/common"; +import { getCleanText } from "@/utils/imCommon"; +import { getSDK } from "@/utils/openIM"; +const IMSDK = getSDK() + +export default function useCreateNomalMessage({ + messageContent, +}) { + + const getCleanTextWithBr = () => { + let text = messageContent.value; + text = text.replace(/
/g, "\n").replace(/<\/div>/g, ""); + return getCleanText(text); + }; + + const getTextMessage = async () => { + const formattedText = messageContent.value; + console.log(formattedText); + + return (await IMSDK.createTextMessage(formattedText)).data; + }; + + const switchNomalMessage = async () => { + let message = await getTextMessage(); + if (!message) { + feedbackToast({ + error: "create message failed", + message: "create message failed", + }); + return; + } + return message; + }; + + return { + switchNomalMessage, + getCleanText, + }; +} diff --git a/src/components/commonFooter/useSendMessage.js b/src/components/commonFooter/useSendMessage.js new file mode 100644 index 0000000..4d8b6e7 --- /dev/null +++ b/src/components/commonFooter/useSendMessage.js @@ -0,0 +1,58 @@ +// import useConversationStore from "@/stores/modules/conversation"; +import { useMessageStore } from "@/stores/modules/message"; +// import useUserStore from "@/store/modules/user"; +import emitter from "@/utils/events"; +import { getSDK } from "@/utils/openIM"; +const IMSDK = getSDK() +// const conversationStore = useConversationStore(); +// const userStore = useUserStore(); + +export default function useSendMessage() { + const messageStore = useMessageStore(); + const { pushNewMessage } = messageStore; + const sendMessage = async ({ + recvID, + groupID, + message, + // fileArrayBuffer, + // snpFileArrayBuffer, + needOpreateMessage + }) => { + needOpreateMessage = + needOpreateMessage ?? inCurrentConversation(recvID || groupID); + + if (needOpreateMessage) { + pushNewMessage(message); + emitter.emit("CHAT_MAIN_SCROLL_TO_BOTTOM", false); + // updateFrequentContacts(); + } + const options = { + recvID: recvID, + groupID: groupID, + message, + // fileArrayBuffer, + // snpFileArrayBuffer + }; + try { + const { data: successMessage } = await IMSDK.sendMessage(options); + // updateOneMessage(successMessage, true); + } catch (error) { + console.error(error); + + // updateOneMessage({ + // ...message, + // status: 3 + // }); + } + }; + + const inCurrentConversation = sourceID => + sourceID + ? conversationStore.storeCurrentConversation.userID === sourceID || + conversationStore.storeCurrentConversation.groupID === sourceID + : true; + + return { + sendMessage + }; +} diff --git a/src/components/commonInput/index.vue b/src/components/commonInput/index.vue new file mode 100644 index 0000000..b5bc88e --- /dev/null +++ b/src/components/commonInput/index.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/src/components/commonLottie/index.vue b/src/components/commonLottie/index.vue new file mode 100644 index 0000000..8027e00 --- /dev/null +++ b/src/components/commonLottie/index.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/src/components/commonPay/index.vue b/src/components/commonPay/index.vue new file mode 100644 index 0000000..afea725 --- /dev/null +++ b/src/components/commonPay/index.vue @@ -0,0 +1,135 @@ + + + \ No newline at end of file diff --git a/src/components/commonPayStep/index.vue b/src/components/commonPayStep/index.vue new file mode 100644 index 0000000..60e1d62 --- /dev/null +++ b/src/components/commonPayStep/index.vue @@ -0,0 +1,79 @@ + + + \ No newline at end of file diff --git a/src/components/commonPreview/index.vue b/src/components/commonPreview/index.vue new file mode 100644 index 0000000..5a30cd0 --- /dev/null +++ b/src/components/commonPreview/index.vue @@ -0,0 +1,515 @@ + + + + + diff --git a/src/components/customAvatar/index.vue b/src/components/customAvatar/index.vue new file mode 100644 index 0000000..2b0120d --- /dev/null +++ b/src/components/customAvatar/index.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/components/deleteDialog/index.vue b/src/components/deleteDialog/index.vue new file mode 100644 index 0000000..25fe431 --- /dev/null +++ b/src/components/deleteDialog/index.vue @@ -0,0 +1,110 @@ + + + + \ No newline at end of file diff --git a/src/components/emptyData/index.vue b/src/components/emptyData/index.vue new file mode 100644 index 0000000..563db9c --- /dev/null +++ b/src/components/emptyData/index.vue @@ -0,0 +1,43 @@ + + + \ No newline at end of file diff --git a/src/components/groupDialog/index.vue b/src/components/groupDialog/index.vue new file mode 100644 index 0000000..66f6900 --- /dev/null +++ b/src/components/groupDialog/index.vue @@ -0,0 +1,140 @@ + + + \ No newline at end of file diff --git a/src/components/header/headerNavbar.vue b/src/components/header/headerNavbar.vue new file mode 100644 index 0000000..95e1ee2 --- /dev/null +++ b/src/components/header/headerNavbar.vue @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/src/components/icons/IconEyeHide.vue b/src/components/icons/IconEyeHide.vue new file mode 100644 index 0000000..94dd4fb --- /dev/null +++ b/src/components/icons/IconEyeHide.vue @@ -0,0 +1,9 @@ + diff --git a/src/components/icons/IconEyeShow.vue b/src/components/icons/IconEyeShow.vue new file mode 100644 index 0000000..8986524 --- /dev/null +++ b/src/components/icons/IconEyeShow.vue @@ -0,0 +1,6 @@ + diff --git a/src/components/icons/IconRadioTick.vue b/src/components/icons/IconRadioTick.vue new file mode 100644 index 0000000..d1b524f --- /dev/null +++ b/src/components/icons/IconRadioTick.vue @@ -0,0 +1,6 @@ + diff --git a/src/components/icons/IconRadioTick2.vue b/src/components/icons/IconRadioTick2.vue new file mode 100644 index 0000000..f7e0040 --- /dev/null +++ b/src/components/icons/IconRadioTick2.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/icons/IconRemove.vue b/src/components/icons/IconRemove.vue new file mode 100644 index 0000000..10e55e7 --- /dev/null +++ b/src/components/icons/IconRemove.vue @@ -0,0 +1,6 @@ + diff --git a/src/components/icons/IconRemoveBG.vue b/src/components/icons/IconRemoveBG.vue new file mode 100644 index 0000000..44c5a49 --- /dev/null +++ b/src/components/icons/IconRemoveBG.vue @@ -0,0 +1,5 @@ + diff --git a/src/components/icons/IconSearchArea.vue b/src/components/icons/IconSearchArea.vue new file mode 100644 index 0000000..2c70e1c --- /dev/null +++ b/src/components/icons/IconSearchArea.vue @@ -0,0 +1,6 @@ + diff --git a/src/components/icons/IconTriangle.vue b/src/components/icons/IconTriangle.vue new file mode 100644 index 0000000..63df9e3 --- /dev/null +++ b/src/components/icons/IconTriangle.vue @@ -0,0 +1,5 @@ + diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..5c51dd1 --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,21 @@ +// charts +export { default as Chart } from './chart/index.vue' +export { default as AppNavBar } from './NavBar/index.vue' +export { default as AppPopup } from './popup/index.vue' +export { default as CodeInput } from './codeInput/index.vue' +export { default as TipDialog } from './tipDialog/index.vue' +export { default as DeleteDialog } from './deleteDialog/index.vue' +export { default as CommonDialog } from './commonDialog/index.vue' +export { default as ApplyDialog } from './applyDialog/index.vue' +export { default as GroupDialog } from './groupDialog/index.vue' +export { default as EmptyData } from './emptyData/index.vue' +export { default as CommonPreview } from './commonPreview/index.vue' +export { default as RedPacKDialog } from './redPacKDialog/index.vue' +export { default as LogJump } from './logJump/index.vue' +export { default as AppCheckBox } from './CheckBox/index.vue' +export { default as TransmitDataDialog } from './transmitDataDialog/index.vue' +export { default as CommonFooter } from './commonFooter/index.vue' +export { default as CommonPay } from './commonPay/index.vue' +export { default as CommonPayStep } from './commonPayStep/index.vue' +export { default as CommonLottie } from './commonLottie/index.vue' +export { default as CommonInput } from './commonInput/index.vue' \ No newline at end of file diff --git a/src/components/logJump/index.vue b/src/components/logJump/index.vue new file mode 100644 index 0000000..f5df758 --- /dev/null +++ b/src/components/logJump/index.vue @@ -0,0 +1,85 @@ + + + + + \ No newline at end of file diff --git a/src/components/pasteImgDialog/index.js b/src/components/pasteImgDialog/index.js new file mode 100644 index 0000000..34c60cb --- /dev/null +++ b/src/components/pasteImgDialog/index.js @@ -0,0 +1,39 @@ +import { createApp } from 'vue' +import PasteImgDialog from "./index.vue" + +let app = null; +let div = null; +const pasteImgDialog = { + show: function(props) { + if (div) { + pasteImgDialog.hide(div) + } + const { previewUrl, okText, cancelText, customClass, ok, cancel } = props + div = document.createElement('div') + document.body.appendChild(div) + app = createApp(PasteImgDialog, { + previewUrl, + okText, + cancelText, + customClass, + modelValue: true, + onOk() { + ok && ok() + app.unmount(div); + div.remove(); + }, + onCancel() { + cancel && cancel() + app.unmount(div); + div.remove(); + } + }) + app.mount(div) + }, + hide: function(props) { + app.unmount(div) + div.remove(div) + } +} + +export default pasteImgDialog diff --git a/src/components/pasteImgDialog/index.vue b/src/components/pasteImgDialog/index.vue new file mode 100644 index 0000000..2757853 --- /dev/null +++ b/src/components/pasteImgDialog/index.vue @@ -0,0 +1,123 @@ + + + + \ No newline at end of file diff --git a/src/components/popup/index.vue b/src/components/popup/index.vue new file mode 100644 index 0000000..3d8f479 --- /dev/null +++ b/src/components/popup/index.vue @@ -0,0 +1,43 @@ + + + + + \ No newline at end of file diff --git a/src/components/redPacKDialog/index.vue b/src/components/redPacKDialog/index.vue new file mode 100644 index 0000000..d447000 --- /dev/null +++ b/src/components/redPacKDialog/index.vue @@ -0,0 +1,237 @@ + + + + \ No newline at end of file diff --git a/src/components/selectAreaCode.vue b/src/components/selectAreaCode.vue new file mode 100644 index 0000000..241d610 --- /dev/null +++ b/src/components/selectAreaCode.vue @@ -0,0 +1,294 @@ + + + + + diff --git a/src/components/tipDialog/index.vue b/src/components/tipDialog/index.vue new file mode 100644 index 0000000..fd85071 --- /dev/null +++ b/src/components/tipDialog/index.vue @@ -0,0 +1,87 @@ + + + + \ No newline at end of file diff --git a/src/components/transmitDataDialog/index.vue b/src/components/transmitDataDialog/index.vue new file mode 100644 index 0000000..616e3b3 --- /dev/null +++ b/src/components/transmitDataDialog/index.vue @@ -0,0 +1,461 @@ + + + diff --git a/src/components/verifyButton.vue b/src/components/verifyButton.vue new file mode 100644 index 0000000..af698fd --- /dev/null +++ b/src/components/verifyButton.vue @@ -0,0 +1,27 @@ + + + \ No newline at end of file diff --git a/src/components/virtualList/index.js b/src/components/virtualList/index.js new file mode 100644 index 0000000..6fbb2d5 --- /dev/null +++ b/src/components/virtualList/index.js @@ -0,0 +1,3 @@ +import VirtualList from './virtual-list'; + +export default VirtualList; diff --git a/src/components/virtualList/item.jsx b/src/components/virtualList/item.jsx new file mode 100644 index 0000000..2d408bc --- /dev/null +++ b/src/components/virtualList/item.jsx @@ -0,0 +1,101 @@ +import { + computed, + defineComponent, + onMounted, + onUnmounted, + onUpdated, + ref, +} from "vue"; +import { ItemProps, SlotProps } from "./props"; + +const useResizeChange = ( + props, + rootRef, + emit +) => { + let resizeObserver = null; + const shapeKey = computed(() => + props.horizontal ? "offsetWidth" : "offsetHeight" + ); + + const getCurrentSize = () => { + return rootRef.value ? rootRef.value[shapeKey.value] : 0; + }; + + // tell parent current size identify by unqiue key + const dispatchSizeChange = () => { + const { event, uniqueKey, hasInitial } = props; + emit(event, uniqueKey, getCurrentSize(), hasInitial); + }; + + onMounted(() => { + if (typeof ResizeObserver !== "undefined") { + resizeObserver = new ResizeObserver(() => { + dispatchSizeChange(); + }); + rootRef.value && resizeObserver.observe(rootRef.value); + } + }); + + onUpdated(() => { + dispatchSizeChange(); + }); + + onUnmounted(() => { + if (resizeObserver) { + resizeObserver.disconnect(); + resizeObserver = null; + } + }); +}; + +export const Item = defineComponent({ + name: "VirtualListItem", + props: ItemProps, + emits: ["itemResize"], + setup(props, { emit }) { + const rootRef = ref(null); + useResizeChange(props, rootRef, emit); + return () => { + const { + tag: Tag, + component: Comp, + extraProps = {}, + index, + source, + scopedSlots = {}, + uniqueKey, + } = props; + const mergedProps = { + ...extraProps, + source, + index, + }; + return ( + + + + ); + }; + }, +}); + +export const Slot = defineComponent({ + name: "VirtualListSlot", + props: SlotProps, + emits: ["slotResize"], + setup(props, { slots, emit }) { + const rootRef = ref(null); + useResizeChange(props, rootRef, emit); + + return () => { + const { tag: Tag, uniqueKey } = props; + + return ( + + {slots.default?.()} + + ); + }; + }, +}); diff --git a/src/components/virtualList/props.js b/src/components/virtualList/props.js new file mode 100644 index 0000000..b49b7da --- /dev/null +++ b/src/components/virtualList/props.js @@ -0,0 +1,155 @@ +/** + * props declaration for default, item and slot component + */ +export const VirtualProps = { + dataKey: { + type: [String, Function], + required: true, + }, + dataSources: { + type: Array, + required: true, + default: () => [], + }, + dataComponent: { + type: [Object, Function], + required: true, + }, + + keeps: { + type: Number, + default: 30, + }, + extraProps: { + type: [Object,Function], + }, + estimateSize: { + type: Number, + default: 50, + }, + + direction: { + // 'vertical' | 'horizontal' + type: String, + default: 'vertical', // the other value is horizontal + }, + start: { + type: Number, + default: 0, + }, + offset: { + type: Number, + default: 0, + }, + topThreshold: { + type: Number, + default: 0, + }, + bottomThreshold: { + type: Number, + default: 0, + }, + pageMode: { + type: Boolean, + default: false, + }, + rootTag: { + type: String, + default: 'div', + }, + wrapTag: { + type: String, + default: 'div', + }, + wrapClass: { + type: String, + default: 'wrap', + }, + wrapStyle: { + type: Object, + }, + itemTag: { + type: String, + default: 'div', + }, + itemClass: { + type: String, + default: '', + }, + itemClassAdd: { + type: Function, + }, + itemStyle: { + type: Object, + }, + headerTag: { + type: String, + default: 'div', + }, + headerClass: { + type: String, + default: '', + }, + headerStyle: { + type: Object, + }, + footerTag: { + type: String, + default: 'div', + }, + footerClass: { + type: String, + default: '', + }, + footerStyle: { + type: Object, + }, + itemScopedSlots: { + type: Object, + }, +}; + +export const ItemProps = { + index: { + type: Number, + }, + event: { + type: String, + }, + tag: { + type: String, + }, + horizontal: { + type: Boolean, + }, + source: { + type: Object, + }, + component: { + type: [Object, Function], + }, + uniqueKey: { + type: [String, Number], + }, + extraProps: { + type: Object, + }, + scopedSlots: { + type: Object, + }, +}; + +export const SlotProps = { + event: { + type: String, + }, + uniqueKey: { + type: String, + }, + tag: { + type: String, + }, + horizontal: { + type: Boolean, + }, +}; diff --git a/src/components/virtualList/virtual-list.jsx b/src/components/virtualList/virtual-list.jsx new file mode 100644 index 0000000..48dfd25 --- /dev/null +++ b/src/components/virtualList/virtual-list.jsx @@ -0,0 +1,438 @@ +import { + defineComponent, + onActivated, + onBeforeMount, + onMounted, + onUnmounted, + ref, + watch, +} from "vue"; +import Virtual from "./virtual"; +import { Item, Slot } from "./item"; +import { VirtualProps } from "./props"; + +const EVENT_TYPE = { + ITEM: "itemResize", + SLOT: "slotResize" +} + +const SLOT_TYPE = { + HEADER: "thead", // string value also use for aria role attribute + FOOTER: "tfoot" +} + +// interface Range { +// start: number; +// end: number; +// padFront: number; +// padBehind: number; +// } + +export default defineComponent({ + name: "VirtualList", + props: VirtualProps, + setup(props, { emit, slots, expose }) { + const isHorizontal = props.direction === "horizontal"; + const directionKey = isHorizontal ? "scrollLeft" : "scrollTop"; + const range = ref(null); + const root = ref(null); + const shepherd = ref(null); + let virtual; + + /** + * watch + */ + watch( + () => props.dataSources.length, + () => { + virtual.updateParam("uniqueIds", getUniqueIdFromDataSources()); + virtual.handleDataSourcesChange(); + } + ); + watch( + () => props.keeps, + (newValue) => { + virtual.updateParam("keeps", newValue); + virtual.handleSlotSizeChange(); + } + ); + watch( + () => props.start, + (newValue) => { + scrollToIndex(newValue); + } + ); + watch( + () => props.offset, + (newValue) => scrollToOffset(newValue) + ); + + /** + * methods + */ + // get item size by id + const getSize = (id) => { + return virtual.sizes.get(id); + }; + const getOffset = () => { + if (props.pageMode) { + return ( + document.documentElement[directionKey] || document.body[directionKey] + ); + } else { + return root.value ? Math.ceil(root.value[directionKey]) : 0; + } + }; + // return client viewport size + const getClientSize = () => { + const key = isHorizontal ? "clientWidth" : "clientHeight"; + if (props.pageMode) { + return document.documentElement[key] || document.body[key]; + } else { + return root.value ? Math.ceil(root.value[key]) : 0; + } + }; + // return all scroll size + const getScrollSize = () => { + const key = isHorizontal ? "scrollWidth" : "scrollHeight"; + if (props.pageMode) { + return document.documentElement[key] || document.body[key]; + } else { + return root.value ? Math.ceil(root.value[key]) : 0; + } + }; + const emitEvent = (offset, clientSize, scrollSize, evt) => { + emit("scroll", evt, virtual.getRange()); + + if ( + virtual.isFront() && + !!props.dataSources.length && + offset - props.topThreshold <= 0 + ) { + emit("totop"); + } else if ( + virtual.isBehind() && + offset + clientSize + props.bottomThreshold >= scrollSize + ) { + emit("tobottom"); + } + }; + const onScroll = (evt) => { + const offset = getOffset(); + const clientSize = getClientSize(); + const scrollSize = getScrollSize(); + + // iOS scroll-spring-back behavior will make direction mistake + if (offset < 0 || offset + clientSize > scrollSize + 1 || !scrollSize) { + return; + } + + virtual.handleScroll(offset); + emitEvent(offset, clientSize, scrollSize, evt); + }; + + const getUniqueIdFromDataSources = () => { + const { dataKey, dataSources = [] } = props; + return dataSources.map((dataSource) => + typeof dataKey === "function" + ? dataKey(dataSource) + : dataSource[dataKey] + ); + }; + const onRangeChanged = (newRange) => { + range.value = newRange; + }; + const installVirtual = () => { + virtual = new Virtual( + { + slotHeaderSize: 0, + slotFooterSize: 0, + keeps: props.keeps, + estimateSize: props.estimateSize, + buffer: Math.round(props.keeps / 3), // recommend for a third of keeps + uniqueIds: getUniqueIdFromDataSources(), + }, + onRangeChanged + ); + + // sync initial range + range.value = virtual.getRange(); + }; + // set current scroll position to a expectant index + const scrollToIndex = (index) => { + // scroll to bottom + if (index >= props.dataSources.length - 1) { + scrollToBottom(); + } else { + const offset = virtual.getOffset(index); + scrollToOffset(offset); + } + }; + // set current scroll position to a expectant offset + const scrollToOffset = (offset) => { + if (props.pageMode) { + document.body[directionKey] = offset; + document.documentElement[directionKey] = offset; + } else { + if (root.value) { + root.value[directionKey] = offset; + } + } + }; + // get the real render slots based on range data + // in-place patch strategy will try to reuse components as possible + // so those components that are reused will not trigger lifecycle mounted + const getRenderSlots = () => { + const slots = []; + const { start, end } = range.value; + const { + dataSources, + dataKey, + itemClass, + itemTag, + itemStyle, + extraProps, + dataComponent, + itemScopedSlots, + } = props; + for (let index = start; index <= end; index++) { + const dataSource = dataSources[index]; + dataSource.showName = setRemark(dataSource) + if (dataSource) { + const uniqueKey = + typeof dataKey === "function" + ? dataKey(dataSource) + : dataSource[dataKey]; + const internalExtraProps = + typeof extraProps === "function" + ? extraProps(dataSource) + : extraProps; + const internalDataComponent = + typeof dataComponent === "function" + ? dataComponent(dataSource) + : dataComponent; + if (typeof uniqueKey === "string" || typeof uniqueKey === "number") { + slots.push( + + ); + } else { + console.warn( + `Cannot get the data-key '${dataKey}' from data-sources.` + ); + } + } else { + console.warn(`Cannot get the index '${index}' from data-sources.`); + } + } + return slots; + }; + /** + * 获取当前用是否有设置好友备注 + * @param {*} val + * @returns + */ + const setRemark = (val) =>{ + let remark = val.showName + //获取会话列表的时候判断是否可以获取到好友列表 + const contactList_json = JSON.parse(sessionStorage.getItem('contactList')||'[]')||[] + if(contactList_json&&contactList_json.length>0){ + const remarkList = contactList_json.filter(item=>val.userID===item.userID) + if(remarkList&&remarkList[0]){ + if(remarkList[0].remark&&remarkList[0].remark!=val.showName)remark = remarkList[0].remark + } + } + return remark + } + // event called when each item mounted or size changed + const onItemResized = (id, size) => { + virtual.saveSize(id, size); + emit("resized", id, size); + }; + + // event called when slot mounted or size changed type: SLOT_TYPE + const onSlotResized = (type, size, hasInit) => { + if (type === SLOT_TYPE.HEADER) { + virtual.updateParam("slotHeaderSize", size); + } else if (type === SLOT_TYPE.FOOTER) { + virtual.updateParam("slotFooterSize", size); + } + + if (hasInit) { + virtual.handleSlotSizeChange(); + } + }; + + // set current scroll position to bottom + const scrollToBottom = () => { + if (shepherd.value) { + const offset = + shepherd.value[isHorizontal ? "offsetLeft" : "offsetTop"]; + scrollToOffset(offset); + + // check if it's really scrolled to the bottom + // maybe list doesn't render and calculate to last range + // so we need retry in next event loop until it really at bottom + setTimeout(() => { + if (getOffset() + getClientSize() < getScrollSize()) { + scrollToBottom(); + } + }, 3); + } + }; + + // when using page mode we need update slot header size manually + // taking root offset relative to the browser as slot header size + const updatePageModeFront = () => { + if (root.value) { + const rect = root.value.getBoundingClientRect(); + const { defaultView } = root.value.ownerDocument; + const offsetFront = isHorizontal + ? rect.left + defaultView.pageXOffset + : rect.top + defaultView.pageYOffset; + virtual.updateParam("slotHeaderSize", offsetFront); + } + }; + + // get the total number of stored (rendered) items + const getSizes = () => { + return virtual.sizes.size; + }; + + /** + * life cycles + */ + onBeforeMount(() => { + installVirtual(); + }); + + // set back offset when awake from keep-alive + onActivated(() => { + scrollToOffset(virtual.offset); + }); + + onMounted(() => { + // set position + if (props.start) { + scrollToIndex(props.start); + } else if (props.offset) { + scrollToOffset(props.offset); + } + + // in page mode we bind scroll event to document + if (props.pageMode) { + updatePageModeFront(); + document.addEventListener("scroll", onScroll, { + passive: false, + }); + } + }); + + onUnmounted(() => { + virtual.destroy(); + if (props.pageMode) { + document.removeEventListener("scroll", onScroll); + } + }); + + /** + * public methods + */ + expose({ + scrollToBottom, + getSizes, + getSize, + getOffset, + getScrollSize, + getClientSize, + scrollToOffset, + scrollToIndex, + }); + + return () => { + const { + pageMode, + rootTag: RootTag, + wrapTag: WrapTag, + wrapClass, + wrapStyle, + headerTag, + headerClass, + headerStyle, + footerTag, + footerClass, + footerStyle, + } = props; + const { padFront, padBehind } = range.value; + const paddingStyle = { + padding: isHorizontal + ? `0px ${padBehind}px 0px ${padFront}px` + : `${padFront}px 0px ${padBehind}px`, + }; + const wrapperStyle = wrapStyle + ? Object.assign({}, wrapStyle, paddingStyle) + : paddingStyle; + const { header, footer } = slots; + + return ( + + {/* header slot */} + {header && ( + + {header()} + + )} + + {/* main list */} + + {getRenderSlots()} + + + {/* footer slot */} + {footer && ( + + {footer()} + + )} + + {/* an empty element use to scroll to bottom */} +
+ + ); + }; + }, +}); diff --git a/src/components/virtualList/virtual.js b/src/components/virtualList/virtual.js new file mode 100644 index 0000000..866e4e6 --- /dev/null +++ b/src/components/virtualList/virtual.js @@ -0,0 +1,333 @@ +/** + * virtual list core calculating center + */ +const DIRECTION_TYPE = { + FRONT: "FRONT", // scroll up or left + BEHIND: "BEHIND", // scroll down or right +}; +const CALC_TYPE = { + INIT: "INIT", + FIXED: "FIXED", + DYNAMIC: "DYNAMIC", +}; +const LEADING_BUFFER = 2; + +export default class Virtual { + constructor(param, callUpdate) { + this.init(param, callUpdate); + } + + init(param, callUpdate) { + // param data + this.param = param; + this.callUpdate = callUpdate; + + // size data + this.sizes = new Map(); + this.firstRangeTotalSize = 0; + this.firstRangeAverageSize = 0; + this.lastCalcIndex = 0; + this.fixedSizeValue = 0; + this.calcType = CALC_TYPE.INIT; + + // scroll data + this.offset = 0; + this.direction = ""; + + // range data + this.range = Object.create(null); + if (param) { + this.checkRange(0, param.keeps - 1); + } + + // benchmark test data + // this.__bsearchCalls = 0 + // this.__getIndexOffsetCalls = 0 + } + + destroy() { + this.init(null, null); + } + + // return current render range + getRange() { + const range = Object.create(null); + range.start = this.range.start; + range.end = this.range.end; + range.padFront = this.range.padFront; + range.padBehind = this.range.padBehind; + return range; + } + + isBehind() { + return this.direction === DIRECTION_TYPE.BEHIND; + } + + isFront() { + return this.direction === DIRECTION_TYPE.FRONT; + } + + // return start index offset + getOffset(start) { + return ( + (start < 1 ? 0 : this.getIndexOffset(start)) + this.param.slotHeaderSize + ); + } + + updateParam(key, value) { + if (this.param && key in this.param) { + // if uniqueIds change, find out deleted id and remove from size map + if (key === "uniqueIds") { + this.sizes.forEach((v, key) => { + if (!value.includes(key)) { + this.sizes.delete(key); + } + }); + } + this.param[key] = value; + } + } + + // save each size map by id + saveSize(id, size) { + this.sizes.set(id, size); + + // we assume size type is fixed at the beginning and remember first size value + // if there is no size value different from this at next comming saving + // we think it's a fixed size list, otherwise is dynamic size list + if (this.calcType === CALC_TYPE.INIT) { + this.fixedSizeValue = size; + this.calcType = CALC_TYPE.FIXED; + } else if ( + this.calcType === CALC_TYPE.FIXED && + this.fixedSizeValue !== size + ) { + this.calcType = CALC_TYPE.DYNAMIC; + // it's no use at all + delete this.fixedSizeValue; + } + + // calculate the average size only in the first range + if ( + this.calcType !== CALC_TYPE.FIXED && + typeof this.firstRangeTotalSize !== "undefined" + ) { + if ( + this.sizes.size < + Math.min(this.param.keeps, this.param.uniqueIds.length) + ) { + this.firstRangeTotalSize = [...this.sizes.values()].reduce( + (acc, val) => acc + val, + 0 + ); + this.firstRangeAverageSize = Math.round( + this.firstRangeTotalSize / this.sizes.size + ); + } else { + // it's done using + delete this.firstRangeTotalSize; + } + } + } + + // in some special situation (e.g. length change) we need to update in a row + // try goiong to render next range by a leading buffer according to current direction + handleDataSourcesChange() { + let start = this.range.start; + + if (this.isFront()) { + start = start - LEADING_BUFFER; + } else if (this.isBehind()) { + start = start + LEADING_BUFFER; + } + + start = Math.max(start, 0); + + this.updateRange(this.range.start, this.getEndByStart(start)); + } + + // when slot size change, we also need force update + handleSlotSizeChange() { + this.handleDataSourcesChange(); + } + + // calculating range on scroll + handleScroll(offset) { + this.direction = + offset < this.offset ? DIRECTION_TYPE.FRONT : DIRECTION_TYPE.BEHIND; + this.offset = offset; + + if (!this.param) { + return; + } + + if (this.direction === DIRECTION_TYPE.FRONT) { + this.handleFront(); + } else if (this.direction === DIRECTION_TYPE.BEHIND) { + this.handleBehind(); + } + } + + // ----------- public method end ----------- + + handleFront() { + const overs = this.getScrollOvers(); + // should not change range if start doesn't exceed overs + if (overs > this.range.start) { + return; + } + + // move up start by a buffer length, and make sure its safety + const start = Math.max(overs - this.param.buffer, 0); + this.checkRange(start, this.getEndByStart(start)); + } + + handleBehind() { + const overs = this.getScrollOvers(); + // range should not change if scroll overs within buffer + if (overs < this.range.start + this.param.buffer) { + return; + } + + this.checkRange(overs, this.getEndByStart(overs)); + } + + // return the pass overs according to current scroll offset + getScrollOvers() { + // if slot header exist, we need subtract its size + const offset = this.offset - this.param.slotHeaderSize; + if (offset <= 0) { + return 0; + } + + // if is fixed type, that can be easily + if (this.isFixedType()) { + return Math.floor(offset / this.fixedSizeValue); + } + + let low = 0; + let middle = 0; + let middleOffset = 0; + let high = this.param.uniqueIds.length; + + while (low <= high) { + // this.__bsearchCalls++ + middle = low + Math.floor((high - low) / 2); + middleOffset = this.getIndexOffset(middle); + + if (middleOffset === offset) { + return middle; + } else if (middleOffset < offset) { + low = middle + 1; + } else if (middleOffset > offset) { + high = middle - 1; + } + } + + return low > 0 ? --low : 0; + } + + // return a scroll offset from given index, can efficiency be improved more here? + // although the call frequency is very high, its only a superposition of numbers + getIndexOffset(givenIndex) { + if (!givenIndex) { + return 0; + } + + let offset = 0; + let indexSize = 0; + for (let index = 0; index < givenIndex; index++) { + // this.__getIndexOffsetCalls++ + indexSize = this.sizes.get(this.param.uniqueIds[index]); + offset = + offset + + (typeof indexSize === "number" ? indexSize : this.getEstimateSize()); + } + + // remember last calculate index + this.lastCalcIndex = Math.max(this.lastCalcIndex, givenIndex - 1); + this.lastCalcIndex = Math.min(this.lastCalcIndex, this.getLastIndex()); + + return offset; + } + + // is fixed size type + isFixedType() { + return this.calcType === CALC_TYPE.FIXED; + } + + // return the real last index + getLastIndex() { + return this.param.uniqueIds.length - 1; + } + + // in some conditions range is broke, we need correct it + // and then decide whether need update to next range + checkRange(start, end) { + const keeps = this.param.keeps; + const total = this.param.uniqueIds.length; + + // datas less than keeps, render all + if (total <= keeps) { + start = 0; + end = this.getLastIndex(); + } else if (end - start < keeps - 1) { + // if range length is less than keeps, corrent it base on end + start = end - keeps + 1; + } + + if (this.range.start !== start) { + this.updateRange(start, end); + } + } + + // setting to a new range and rerender + updateRange(start, end) { + this.range.start = start; + this.range.end = end; + this.range.padFront = this.getPadFront(); + this.range.padBehind = this.getPadBehind(); + this.callUpdate(this.getRange()); + } + + // return end base on start + getEndByStart(start) { + const theoryEnd = start + this.param.keeps - 1; + const truelyEnd = Math.min(theoryEnd, this.getLastIndex()); + return truelyEnd; + } + + // return total front offset + getPadFront() { + if (this.isFixedType()) { + return this.fixedSizeValue * this.range.start; + } else { + return this.getIndexOffset(this.range.start); + } + } + + // return total behind offset + getPadBehind() { + const end = this.range.end; + const lastIndex = this.getLastIndex(); + + if (this.isFixedType()) { + return (lastIndex - end) * this.fixedSizeValue; + } + + // if it's all calculated, return the exactly offset + if (this.lastCalcIndex === lastIndex) { + return this.getIndexOffset(lastIndex) - this.getIndexOffset(end); + } else { + // if not, use a estimated value + return (lastIndex - end) * this.getEstimateSize(); + } + } + + // get the item estimate size + getEstimateSize() { + return this.isFixedType() + ? this.fixedSizeValue + : this.firstRangeAverageSize || this.param.estimateSize; + } +} diff --git a/src/directive/click-outside.js b/src/directive/click-outside.js new file mode 100644 index 0000000..2ae6afe --- /dev/null +++ b/src/directive/click-outside.js @@ -0,0 +1,21 @@ +export default { + mounted(el, binding) { + el.handler = function (e) { + // 点击范围在绑定的元素范围内,不执行指令操作 + if (e.target.className === 'add-img') { + return + } + if (el.contains(e.target)) return + if (typeof binding.value === 'function') { + // 绑定给指令的如果是一个函数,那么将回调并指定this + binding.value.apply(this, arguments) + } + } + // 监听全局的点击事件 + document.addEventListener('click', el.handler) + }, + // 解除事件绑定 + beforeUnmount(el) { + document.removeEventListener('click', el.handler) + } +} diff --git a/src/directive/index.js b/src/directive/index.js new file mode 100644 index 0000000..4887b6c --- /dev/null +++ b/src/directive/index.js @@ -0,0 +1,9 @@ +import clickoutside from "./click-outside" +import longPress from "./long-press" + +const directive = { + longPress, + clickoutside +} + +export default directive diff --git a/src/directive/long-press.js b/src/directive/long-press.js new file mode 100644 index 0000000..4ce8b2a --- /dev/null +++ b/src/directive/long-press.js @@ -0,0 +1,41 @@ +const longPress = { + mounted(el, binding) { + let pressTimer = null + const start = (e) => { + if (e.type === 'click' && e.button !== 0) { + return + } + + if (pressTimer === null) { + pressTimer = setTimeout(() => { + binding.value() + }, binding.arg || 1000) // 设置默认长按时间为 1000 毫秒 + } + } + + const cancel = () => { + if (pressTimer !== null) { + clearTimeout(pressTimer) + pressTimer = null + } + } + + el.addEventListener('touchstart', start) + el.addEventListener('click', cancel) + el.addEventListener('touchend', cancel) + el.addEventListener('touchcancel', cancel) + el.addEventListener('touchmove', cancel) + + el._pressStart = start + el._pressCancel = cancel + }, + unmounted(el) { + el.removeEventListener('touchstart', el._pressStart) + el.removeEventListener('click', el._pressCancel) + el.removeEventListener('touchend', el._pressCancel) + el.removeEventListener('touchcancel', el._pressCancel) + el.removeEventListener('touchmove', el._pressCancel) + } +} + +export default longPress diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..058e692 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,9 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + + // eslint-disable-next-line ts/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/src/lang/id.js b/src/lang/id.js new file mode 100644 index 0000000..212e6c5 --- /dev/null +++ b/src/lang/id.js @@ -0,0 +1,1792 @@ +// 印尼语 +export default { + checkTopMessage:'Periksa pesan', + unsupportedVideoFormat: "Tidak mendukung format video ini", + welcome: "Selamat datang di OpenIM", + phoneNumber: "Nomor Telepon", + plsEnterPhoneNumber: "Silakan masukkan nomor telepon Anda", + password: "Kata Sandi", + plsEnterPassword: "Silakan masukkan kata sandi Anda", + plsEnterPassword6_15: "Kata sandi (6-15 karakter alfanumerik)", + account: "Akun", + plsEnterAccount: "Silakan masukkan akun Anda", + plsEnterAccount6_15: "Nama pengguna (6-15 karakter alfanumerik)", + forgetPassword: "Lupa kata sandi", + createAccount: "Buat akun", + verificationCodeLogin: "Login dengan kode verifikasi", + login: "Masuk", + noAccountYet: "Belum punya akun?", + loginNow: "Masuk sekarang", + registerNow: "Daftar sekarang", + lockPwdErrorHint: "Kesalahan %s kali", + newUserRegister: "Pendaftaran pengguna baru", + verificationCode: "Kode verifikasi", + verificationSuccessful: "Verifikasi berhasil", + sendVerificationCode: "Kirim kode verifikasi", + verification_code1: "Silakan masukkan kode verifikasi", + correct_email_address: "Silakan masukkan alamat email yang benar", + Resend: "Kirim ulang", + bind_email: "Ikatan email", + email_verification: "Verifikasi email", + login_details: "Detail login", + verification_code: "Silakan masukkan kode verifikasi email", + email_verification_code: "Kode verifikasi email", + currently_bound_email: "Email yang saat ini terikat:", + text_email: "Jika email Anda tidak lagi digunakan, harap segera ganti", + remove_this_device: "Apakah Anda yakin ingin menghapus perangkat ini?", + remove_this_device1: + "Apakah Anda yakin ingin menghapus perangkat ini? Setelah dihapus, Anda perlu memverifikasi login lagi saat menggunakan perangkat ini", + go_to_settings: "Buka pengaturan", + go_to_binding: "Buka ikatan", + date_of_birth: "Tanggal lahir", + confidential: "Rahasia", + year: "Tahun", + not_perfection: "Belum sempurna", + old_password: "Kata sandi lama", + myCreatedGroup: "Grup yang saya buat", + myManageGroup: "Grup yang saya kelola", + myJoinGroup: "Grup yang saya ikuti", + please_enter_old_password: "Silakan masukkan kata sandi lama", + trusted_device: "Perangkat terpercaya", + change_email_address: "Ganti alamat email", + current_device: "Perangkat saat ini", + remove_device: "Hapus perangkat", + current_device_records: "Berikut adalah semua catatan login perangkat Anda", + text_device: "Manajemen Perangkat Masuk", + verification_time: "Waktu verifikasi:", + text_device1: + "Berikut adalah semua perangkat masuk Anda. Klik untuk melihat detail dan catatan masuk perangkat ini.", + change_the_binding: "Ganti ikatan", + resendVerificationCode: "Kirim ulang kode verifikasi", + verificationCodeTimingReminder: + "Dapatkan ulang kode verifikasi dalam %s detik", + defaultVerificationCode: "(Kode verifikasi default: %s)", + plsEnterVerificationCode: "Silakan masukkan kode verifikasi Anda", + invitationCode: "Kode undangan", + device_list: "Daftar perangkat", + plsEnterInvitationCode: "Silakan masukkan kode undangan Anda %s", + optional: "Opsional", + nextStep: "Langkah berikutnya", + plsEnterRightPhone: "Silakan masukkan nomor telepon yang benar", + enterVerificationCode: "Masukkan kode verifikasi ponsel", + setPassword: "Atur kata sandi", + plsConfirmPasswordAgain: "Harap konfirmasi kata sandi Anda lagi", + text_password2: "Silakan konfirmasi kata sandi masuk lagi", + please_enter_new_password_again: "Silakan masukkan kata sandi baru Anda lagi", + confirmPassword: "Konfirmasi kata sandi", + wrongPasswordFormat: "Format kata sandi salah", + plsCompleteInfo: "Harap lengkapi informasi pribadi Anda", + plsEnterYourNickname: "Silakan masukkan nama panggilan Anda", + pleaseEnterYourUsername: "Silakan masukkan nama pengguna Anda", + enterYourUsernameToHelpUsIdentifyYourAccount: + "Masukkan nama pengguna Anda untuk membantu kami mengidentifikasi akun Anda", + text_1: + "Akun ini belum terikat email, jadi pemulihan kata sandi tidak didukung untuk sementara waktu", + setInfo: "Atur informasi", + loginPwdFormat: "6-20 karakter alfanumerik", + passwordLogin: "Masuk dengan kata sandi", + contacts: "Kontak", + workbench: "Bengkel", + mine: "Saya", + me: "Saya", + draftText: "Draf", + everyone: "Semua orang", + you: "Anda", + groupAc: "Pengumuman Grup", + createGroupNtf: "%s telah membuat obrolan grup ", + editGroupInfoNtf: "%s mengedit info grup", + quitGroupNtf: "%s keluar dari obrolan grup", + invitedJoinGroupNtf: "%s mengundang %s untuk bergabung dalam obrolan grup", + kickedGroupNtf: "%s ditendang dari obrolan grup oleh %s", + joinGroupNtf: "%s bergabung dengan obrolan grup", + dismissGroupNtf: "%s membubarkan obrolan grup", + transferredGroupNtf: "%s mentransfer hak administrasi grup kepada %s", + muteMemberNtf: "%s dibisukan oleh %s selama %s", + muteCancelMemberNtf: "%s pembisuan telah dibatalkan oleh %s", + muteGroupNtf: "%s mengaktifkan pembisuan grup", + muteCancelGroupNtf: "%s membatalkan pembisuan grup", + friendAddedNtf: + "Kamu sudah menjadi teman, sekarang kamu bisa mulai chatting.", + openPrivateChatNtf: "Mode penghapusan pesan sudah diaktifkan", + closePrivateChatNtf: "Mode penghapusan pesan sudah dinonaktifkan", + memberInfoChangedNtf: "%s telah mengedit informasi anggota grup sendiri", + unsupportedMessage: "Jenis pesan ini belum didukung", + picture: "Gambar", + video: "Video", + voice: "Pesan suara", + safety: "Keamanan", + location: "Lokasi", + file: "File", + carte: "Kartu nama", + emoji: "Emoji kustom", + chatRecord: "Riwayat obrolan", + revokeMsg: "Membatalkan pesan", + aRevokeBMsg: "%s Membatalkan pesan dari %s", + blockedByFriendHint: "Pesan telah dikirim tetapi ditolak oleh penerima", + sendFriendVerification: "Verifikasi tambahan", + removedFromGroupHint: "Anda telah dikeluarkan dari grup", + groupDisbanded: "Grup sudah dibubarkan", + createdGroup: "Anda belum membuat grup", + search: "Cari", + newGroups: "Buat grup baru", + groupNames: "Silakan masukkan nama grup", + synchronizing: "Menyinkronkan", + syncFailed: "Gagal menyinkronkan", + connecting: "Menghubungkan", + connectionFailed: "Gagal menghubungkan", + top: "Atas", + cancelTop: "Batalkan atas", + markHasRead: "Tandai sudah dibaca", + delete: "Hapus", + nPieces: "%s pesan", + online: "Online", + offline: "Offline", + phoneOnline: "Telepon", + pcOnline: "Komputer", + webOnline: "Web", + webMiniOnline: "Mini Web", + upgradeFind: "Ditemukan versi baru", + upgradeVersion: "Versi baru ditemukan %s, versi Anda saat ini adalah %s", + upgradeDescription: "Deskripsi pembaruan:", + upgradeIgnore: "Abaikan", + upgradeLater: "Nanti", + upgradeNow: "Sekarang", + upgradeNever: "Jangan pernah", + inviteYouCall: "%s mengundang Anda untuk melakukan %s", + rejectCall: "Tolak", + acceptCall: "Terima", + callVoice: "Panggilan suara", + callVideo: "Panggilan video", + sentSuccessfully: "Berhasil dikirim", + copySuccessfully: "Berhasil disalin", + binding_successful: "Berhasil terikat", + day: "hari", + hour: "jam", + hours: "jam", + minute: "menit", + seconds: "detik", + cancel: "Batal", + determine: "Tentukan", + failed_to_load: "Gagal memuat", + failed_to_load1: "Gagal memuat. Silakan coba lagi nanti", + removal_failed: "Gagal menghapus", + toolboxAlbum: "Album", + toolboxCall: "Panggilan video", + toolboxCamera: "Kamera", + toolboxCard: "Kartu", + toolboxFile: "Berkas", + toolboxLocation: "Lokasi", + send: "Kirim", + holdTalk: "Tahan untuk berbicara", + releaseToSend: "Lepaskan untuk mengirim", + releaseToSendSwipeUpToCancel: + "Lepaskan untuk mengirim, geser ke atas untuk membatalkan", + liftFingerToCancelSend: "Angkat jari untuk membatalkan pengiriman", + callDuration: "Durasi panggilan %s", + cancelled: "Dibatalkan", + cancelledByCaller: "Dibatalkan oleh penelepon", + rejectedByCaller: "Ditolak oleh penelepon", + callTimeout: "Waktu panggilan habis", + rejected: "Ditolak", + forwardMaxCountHint: + "Saat ini hanya mendukung paling banyak dua puluh pesan yang diteruskan~", + typing: "Sedang mengetik...", + addSuccessfully: "Berhasil ditambahkan", + addFailed: "Gagal menambahkan", + setSuccessfully: "Berhasil diatur", + callingBusy: + "Anda sudah dalam panggilan dan tidak dapat melakukan operasi ini!", + groupCallHint: + "Grup saat ini sedang dalam panggilan. Apakah Anda yakin ingin bergabung dengan panggilan saat ini?", + joinIn: "Bergabung", + menuCopy: "Salin", + menuDel: "Hapus", + menuForward: "Teruskan", + menuReply: "Balas", + menuMulti: "Multi-pilihan", + menuRevoke: "Tarik kembali", + menuAdd: "Tambahkan", + nMessage: "%s pesan baru", + plsSelectLocation: "Silakan pilih lokasi", + groupAudioCallHint: "%s orang sedang dalam panggilan suara", + groupVideoCallHint: "%s orang sedang dalam panggilan video", + reEdit: "Edit ulang", + playSpeed: "Kecepatan pemutaran", + download: "Unduh", + download1: "Unduh sekarang", + nicknames: "Nama panggilan pengguna", + downloadAPP: "Unduh aplikasi", + private_chat: "Obrolan pribadi", + googleMap: "Peta Google", + check_for_updates: "Periksa pembaruan", + appleMap: "Peta Apple", + baiduMap: "Peta Baidu", + amapMap: "Peta Amap", + signature: "Tanda tangan", + please_enter_user_nickname: "Silakan masukkan nama panggilan pengguna", + set_characters: "Harap atur 4-15 karakter", + personalized_signature: + "Silakan masukkan tanda tangan pribadi (batas karakter 6-50)", + tencentMap: "Peta Tencent", + offlineMeetingMessage: "Anda menerima pesan undangan rapat offline", + offlineMessage: "Anda memiliki pesan baru offline", + offlineCallMessage: "Anda menerima pesan undangan panggilan offline", + logoutHint: "Apakah Anda yakin ingin keluar?", + myInfo: "Informasi saya", + workingCircle: "Lingkaran kerja", + accountSetup: "Pengaturan akun", + aboutUs: "Tentang kami", + about: "Tentang", + + bgColor: "Warna latar belakang", + + logout: "Keluar", + qrcode: "Kode QR", + qrcodeHint: + "Pindai kode QR di bawah ini untuk menambahkan saya sebagai teman", + favoriteFace: "Wajah favorit", + favoriteManage: "Kelola", + favoriteCount: "Total %s emotikon favorit", + favoriteDel: "Hapus (%s)", + hasRead: "Telah dibaca", + unread: "Belum dibaca", + nPersonUnRead: "%s orang belum dibaca", + allRead: "Semua sudah dibaca", + messageRecipientList: "Daftar penerima pesan", + hasReadCount: "Dibaca (%s)", + unreadCount: "Belum dibaca (%s)", + newFriend: "Teman baru", + newFriendList: "Daftar permintaan pertemanan", + newGroup: "Obrolan grup baru", + myFriend: "Teman saya", + myGroup: "Grup saya", + addManager: "Tambahkan manajer", + privateSetting: "Pengaturan pribadi", + add: "Tambah", + scan: "Pindai", + scanHint: "Pindai kartu nama kode QR", + addFriend: "Tambah Teman", + addFriendHint: "Cari dan tambahkan dengan ID", + createGroup: "Buat Obrolan Grup", + createGroupHint: "Buat obrolan grup, gunakan OpenIM sepenuhnya", + addGroup: "Tambahkan Grup", + addGroupHint: "Tanyakan kepada administrator atau anggota grup untuk ID", + searchIDAddFriend: "Cari ID untuk Tambah Teman", + searchIDAddGroup: "Cari ID untuk Tambah Grup", + searchIDIs: "ID: %s", + searchPhoneIs: "Nomor telepon: %s", + searchEmailIs: "Surel: %s", + searchNicknameIs: "Nama panggilan: %s", + searchGroupNicknameIs: "Nama panggilan grup: %s", + noFoundUser: "Tidak dapat menemukan pengguna ini", + noFoundGroup: "Tidak dapat menemukan obrolan grup ini", + joinGroupDate: "Tanggal Bergabung", + joinGroupMethod: "Metode Bergabung", + byInviteJoinGroup: "%s mengundang masuk grup", + byIDJoinGroup: "Cari ID grup", + byQrcodeJoinGroup: "Kode QR grup", + groupID: "ID Grup", + setAsAdmin: "Atur sebagai Admin", + setMute: "Atur Senyap", + organizationInfo: "Informasi Organisasi", + organization: "Organisasi", + department: "Departemen", + position: "Posisi", + personalInfo: "Info Pribadi", + viewDynamics: "Lihat Dinamika", + audioAndVideoCall: "Panggilan Audio dan Video", + sendMessage: "Kirim Pesan", + avatar: "Avatar", + name: "Nama", + nickname: "Nama Panggilan", + gender: "Jenis Kelamin", + englishName: "Nama Inggris", + birthDay: "Tanggal Lahir", + tel: "Telepon Kantor", + mobile: "Nomor Ponsel", + email: "Surel", + man: "Pria", + woman: "Wanita", + friendSetup: "Pengaturan Teman", + setupRemark: "Atur Catatan", + recommendToFriend: "Rekomendasikan ke Teman", + addToBlacklist: "Tambahkan ke Daftar Hitam", + unfriend: "Membatalkan hubungan pertemanan", + areYouSureDelFriend: "Apakah Anda yakin ingin menghapus teman?", + areYouSureAddBlacklist: + "Apakah Anda yakin ingin menambahkan teman ke daftar hitam?", + remark: "Catatan", + save: "Simpan", + saveSuccessfully: "Berhasil disimpan", + saveFailed: "Gagal disimpan", + groupVerification: "Verifikasi obrolan grup", + friendVerification: "Verifikasi teman", + email_address: "Masukkan alamat email", + sendEnterGroupApplication: "Kirim permohonan bergabung ke grup", + sendToBeFriendApplication: "Kirim permohonan menjadi teman", + sendSuccessfully: "Berhasil dikirim", + reset_successful: "Berhasil direset", + bind_new_email: "Ikatan email baru", + Login_failed: "Gagal masuk", + successful_home: "Berhasil mendaftar", + sendFailed: "Gagal dikirim", + canNotAddFriends: "Pengguna ini telah mengatur tidak dapat ditambahkan!", + mutedAll: "Semua dibisukan", + tenMinutes: "10 menit", + oneHour: "1 jam", + twelveHours: "12 jam", + oneDay: "1 hari", + custom: "Kustom", + unmute: "Batalkan diam", + youMuted: "Anda telah dibisukan", + groupMuted: "Dibisukan di grup", + notDisturbMode: "Mode jangan ganggu", + allowRing: "Nada notifikasi pesan baru", + allowVibrate: "Getaran pesan baru", + forbidAddMeToFriend: "Larang tambahkan saya sebagai teman", + blacklist: "Daftar hitam buku alamat", + unlockSettings: "Buka pengaturan", + changePassword: "Ubah kata sandi", + clearChatHistory: "Hapus riwayat obrolan", + confirmClearChatHistory: + "Apakah Anda yakin ingin menghapus semua riwayat obrolan?", + languageSetup: "Pengaturan bahasa", + language: "Bahasa", + english: "Inggris", + setting_up: "Mengatur...", + chinese: "Cina Sederhana", + traditional_chinese: "Cina Tradisional", + followSystem: "Ikuti sistem", + blacklistEmpty: "Daftar hitam kosong", + remove: "Hapus", + removeFriend: "Hapus teman", + fingerprint: "Sidik jari", + gesture: "Gestur", + biometrics: "Pengenalan Biometrik", + plsEnterPwd: "Silakan masukkan kata sandi", + plsEnterOldPwd: "Silakan masukkan kata sandi lama", + plsEnterNewPwd: "Silakan masukkan kata sandi baru", + plsConfirmNewPwd: "Silakan konfirmasi kata sandi baru", + reset: "Reset", + oldPwd: "Kata sandi lama", + newPwd: "Kata sandi baru", + confirmNewPwd: "Konfirmasi kata sandi baru:", + plsEnterConfirmPwd: "Silakan masukkan kata sandi konfirmasi", + twicePwdNoSame: "Kata sandi yang dimasukkan dua kali tidak sama", + twicePwd: "Kata sandi baru tidak cocok, silakan setel ulang", + changedSuccessfully: "Berhasil diubah", + deleteSuccessfully: "Berhasil dihapus", + checkNewVersion: "Periksa versi baru", + noAddGround: "Belum ada teman yang ditambahkan ke grup", + chatContent: "Konten obrolan", + topContacts: "Kontak teratas", + messageNotDisturb: "Mode tidak mengganggu pesan", + messageNotDisturbHint: "Terima pesan tanpa gangguan", + burnAfterReading: "Membakar setelah dibaca", + timeSet: "Pengaturan waktu", + setChatBackground: "Atur latar belakang obrolan", + fontFace: "Font", + fontSize: "Ukuran font", + little: "Kecil", + standard: "Standar", + big: "Besar", + thirtySeconds: "30 detik", + fiveMinutes: "5 menit", + clearAll: "Bersihkan semua", + clearSuccessfully: "Berhasil membersihkan", + groupChatSetup: "Pengaturan obrolan grup", + viewAllGroupMembers: "Lihat semua anggota grup (%s)", + groupManage: "Kelola grup", + myGroupMemberNickname: "Nama saya dalam grup ini", + topChat: "Obrolan teratas", + muteAllMember: "Diamkan semua anggota", + exitGroup: "Keluar dari obrolan grup", + dismissGroup: "Bubar obrolan grup", + dismissGroupHint: + "Semua anggota grup akan keluar dari obrolan ini dan tidak bisa mengirim atau menerima pesan", + quitGroupHint: + "Anda tidak akan menerima pesan dari obrolan grup setelah keluar.", + joinGroupSet: "Pengaturan masuk ke grup", + allowAnyoneJoinGroup: "Izinkan siapa pun bergabung dengan grup", + inviteNotVerification: "Undangan anggota grup tidak perlu diverifikasi", + needVerification: "Diperlukan pengiriman pesan verifikasi", + addMember: "Tambah", + delMember: "Hapus", + groupOwner: "Pemilik grup", + groupAdmin: "Admin grup", + notAllowSeeMemberProfile: "Tidak diizinkan melihat profil anggota grup lain", + notAllAddMemberToBeFriend: + "Tidak diizinkan menambahkan anggota grup menjadi teman", + transferGroupOwnerRight: "Transfer hak kepemilikan grup", + groupName: "Nama grup", + groupAcPermissionTips: + "Hanya pemilik grup dan administrator yang dapat mengedit", + plsEnterGroupAc: "Silakan masukkan pengumuman grup", + edit: "Edit", + publish: "Terbitkan", + groupMember: "Anggota grup", + selectedPeopleCount: "Dipilih (%s)", + confirmSelectedPeople: "Konfirmasi (%s/%s)", + confirm: "Konfirmasi", + log_in_time: "Waktu login:", + log_in_time1: "Waktu login", + login_IP: "IP login:", + confirmTransferGroupToUser: + "Yakin ingin mentransfer kepemilikan grup ke: %s?", + removeGroupMember: "Hapus anggota grup", + searchNotResult: "Tidak ada hasil pencarian", + groupQrcode: "Kode QR grup", + groupQrcodeHint: + "Pindai kode QR di bawah untuk bergabung dengan obrolan grup", + approved: "Disetujui", + accept: "Terima", + reject: "Tolak", + friendGrouping: "Pengelompokan teman", + waitingForVerification: "Menunggu verifikasi dari pihak lain", + rejectSuccessfully: "Penolakan berhasil", + rejectFailed: "Penolakan gagal", + applyJoin: "Minta bergabung", + enterGroup: "Masuk ke obrolan grup", + applyReason: "Alasan permintaan: %s", + invite: "Undang", + sourceFrom: "Sumber: %s", + byMemberInvite: "Undangan", + bySearch: "Melalui pencarian", + byScanQrcode: "Pindai kode QR", + iCreatedGroup: "Saya yang membuat", + iJoinedGroup: "Saya yang bergabung", + nPerson: "{count} orang", + searchNotFound: "Tidak ditemukan hasil terkait", + searchNotFoundMember: "Tidak ditemukan pengguna terkait", + organizationStructure: "Struktur organisasi", + recentConversations: "Percakapan terbaru", + selectAll: "Pilih semua", + plsEnterGroupNameHint: + "Beri nama grup untuk memudahkan pencarian selanjutnya", + completeCreation: "Selesaikan pembuatan", + sendCarteConfirmHint: "Yakin ingin mengirimkan kontak ini ke obrolan ini?", + sentSeparatelyTo: "Kirimkan secara terpisah ke:", + sentTo: "Kirimkan ke:", + leaveMessage: "Pesan", + mergeForwardHint: "[Penggabungan Lanjutan] Total %s pesan", + mergeForward: "Gabungkan Lanjutan", + quicklyFindChatHistory: "Cari Riwayat Obrolan Cepat", + notFoundChatHistory: "Tidak ditemukan konten terkait %s", + globalSearchAll: "Semua", + globalSearchContacts: "Kontak", + globalSearchGroup: "Grup", + globalSearchChatHistory: "Riwayat Obrolan", + globalSearchChatFile: "Berkas", + relatedChatHistory: "%s catatan obrolan terkait", + seeMoreRelatedContacts: "Lihat lebih banyak kontak terkait", + seeMoreRelatedGroup: "Lihat lebih banyak grup terkait", + seeMoreRelatedChatHistory: "Lihat lebih banyak riwayat obrolan terkait", + seeMoreRelatedFile: "Lihat lebih banyak dokumen terkait", + publishPicture: "Terbitkan Gambar", + publishVideo: "Terbitkan Video", + mentioned: "Disebutkan: %s", + comment: "Komentar", + like: "Suka", + reply: "Balas", + rollUp: "Gulung", + fullText: "Teks Lengkap", + selectAssetsFromCamera: "Pilih dari Kamera", + selectAssetsFromAlbum: "Pilih dari Album", + whoCanWatch: "Siapa yang Bisa Melihat", + remindWhoToWatch: "Ingatkan Siapa yang Melihat", + public: "Publik", + everyoneCanSee: "Semua orang bisa melihat", + partiallyVisible: "Sebagian Terlihat", + visibleToTheSelected: "Terlihat bagi yang dipilih", + partiallyInvisible: "Sebagian Tak Terlihat", + invisibleToTheSelected: "Tak Terlihat bagi yang dipilih", + private: "Pribadi", + onlyVisibleToMe: "Hanya terlihat oleh saya", + selectVideoLimit: "Durasi tidak boleh lebih dari 15 detik", + selectContactsLimit: "Minimal pilih satu kontak", + message: "Pesan", + commentedYou: "Memberi komentar pada Anda: %s", + likedYou: "Menyukai Anda", + mentionedYou: "Mentioned Anda", + replied: "Membalas", + detail: "Detail", + totalNPicture: "Total %s gambar", + noDynamic: "Tidak ada dinamika", + callRecords: "Catatan Panggilan", + allCall: "Semua Panggilan", + missedCall: "Panggilan Tidak Terjawab", + incomingCall: "Panggilan Masuk", + outgoingCall: "Panggilan Keluar", + microphone: "Mikrofon", + speaker: "Pemancar suara", + hangUp: "Tutup", + pickUp: "Angkat", + waitingCallHint: "Sedang menelepon...", + waitingVoiceCallHint: "Menunggu lawan bicara menjawab...", + invitedVoiceCallHint: "Undang Anda untuk panggilan suara...", + waitingVideoCallHint: "Menunggu lawan bicara menerima undangan", + invitedVideoCallHint: "Undang Anda untuk panggilan video...", + waitingToAnswer: "Menunggu untuk menjawab", + invitedYouToCall: "Undang Anda untuk bicara", + calling: "Sedang menelepon", + nPeopleCalling: "%s orang sedang menelepon", + whoInvitedVoiceCallHint: "%s mengundang Anda untuk panggilan suara", + whoInvitedVideoCallHint: "%s mengundang Anda untuk panggilan video", + plsInputMeetingSubject: "Silakan masukkan topik pertemuan", + meetingStartTime: "Waktu mulai", + meetingDuration: "Durasi pertemuan", + enterMeeting: "Masuk ke pertemuan", + meetingNo: "Nomor pertemuan", + yourMeetingName: "Nama Anda", + plsInputMeetingNo: "Silakan masukkan nomor pertemuan", + plsInputYouMeetingName: "Silakan masukkan nama Anda", + meetingSubjectIs: "Topik pertemuan: %s", + meetingStartTimeIs: "Waktu mulai: %s", + meetingDurationIs: "Durasi pertemuan: %s", + meetingHostIs: "Pembawa acara: %s", + meetingNoIs: "Nomor pertemuan: %s", + meetingMessageClickHint: + "Klik pesan ini untuk langsung bergabung ke pertemuan", + meetingMessage: "Pesan pertemuan", + openMeeting: "Pertemuan belum selesai", + didNotStart: "Belum dimulai", + started: "Telah dimulai", + meetingInitiatorIs: "Pertemuan video yang diinisiasi oleh %s", + meetingDetail: "Detail pertemuan", + meetingOrganizerIs: "Penyelenggara: %s", + updateMeetingInfo: "Perbarui informasi pertemuan", + cancelMeeting: "Batal pertemuan", + videoMeeting: "Pertemuan video", + joinMeeting: "Gabung ke pertemuan", + bookAMeeting: "Jadwalkan pertemuan", + quickMeeting: "Pertemuan cepat", + confirmTheChanges: "Konfirmasi perubahan", + invitesYouToVideoConference: + "%s mengundang Anda untuk bergabung ke konferensi video", + over: "Selesai", + meetingMute: "Suarakan", + meetingUnmute: "Buka suara", + meetingCloseVideo: "Tutup video", + meetingOpenVideo: "Buka video", + meetingEndSharing: "Akhiri berbagi", + meetingShareScreen: "Berbagi Layar", + meetingMembers: "Anggota(%s)", + settings: "Pengaturan", + leaveMeeting: "Tinggalkan Rapat", + endMeeting: "Akhiri Rapat", + leaveMeetingConfirmHint: "Yakin ingin meninggalkan rapat?", + endMeetingConfirmHit: "Yakin ingin mengakhiri rapat?", + meetingSettings: "Pengaturan Rapat", + allowMembersOpenMic: "Izinkan anggota membuka mikrofon sendiri", + allowMembersOpenVideo: "Izinkan anggota membuka video", + onlyHostShareScreen: "Hanya tuan rumah yang dapat berbagi layar", + onlyHostInviteMember: "Hanya tuan rumah yang dapat mengundang anggota rapat", + defaultMuteMembers: "Anggota masuk dalam rapat dalam keadaan diam", + pinThisMember: "Pasang anggota ini di paling atas", + unpinThisMember: "Lepas pin", + allSeeHim: "Semua melihat dia", + cancelAllSeeHim: "Batalkan semua melihat dia", + muteAll: "Diamkan semua", + unmuteAll: "Batalkan diam semua", + members: "Anggota", + screenShare: "Berbagi Layar", + screenShareHint: "Sedang berbagi layar.", + meetingClosedHint: + "Rapat telah ditutup atau koneksi terputus, yakin ingin meninggalkan?", + meetingIsOver: "Rapat sudah selesai!", + networkError: "Kesalahan jaringan, silakan coba lagi nanti!", + shareSuccessfully: "Berbagi berhasil!", + notFoundMinP: "Belum menemukan aplikasi mini yang dipublikasikan", + notSendMessageNotInGroup: + "Tidak dapat mengirim pesan dalam obrolan grup yang sudah keluar", + whoModifyGroupName: "{name} telah mengubah nama obrolan grup menjadi ", + accountWarn: "Peringatan!", + accountException: + "Akun Anda telah masuk di perangkat lain, silakan segera ubah kata sandi Anda.", + tagGroup: "Tag", + issueNotice: "Berikan pemberitahuan", + createTagGroup: "Buat Grup Tag", + plsEnterTagGroupName: "Silakan masukkan nama tag", + tagGroupMember: "Anggota Tag", + completeEdit: "Selesai Mengedit", + finish: "Selesai", + emptyTagGroup: "Grup tag kosong", + confirmDelTagGroupHint: "Yakin ingin menghapus grup tag ini?", + editTagGroup: "Edit Tag", + newBuild: "Buat Baru", + receiveMember: "Terima Anggota", + emptyNotification: "Pemberitahuan kosong", + notificationReceiver: "%s penerima: %s", + sendAnother: "Kirim yang lain", + confirmDelTagNotificationHint: + "Yakin ingin menghapus catatan pemberitahuan ini?", + contentNotBlank: "Konten tidak boleh kosong", + plsEnterDescription: "Silakan masukkan teks deskripsi", + gifNotSupported: "Gambar gif tidak didukung", + register: "Daftar", + hk: "Tradisional", + RegisterSuccess: "Pendaftaran Berhasil", + LoginSuccess: "Login Berhasil", + init_validate_1: "Nama pengguna terdiri dari 6-15 huruf dan angka", + init_validate_10: "Nomor ponsel atau kata sandi salah, silakan coba lagi", + init_validate_5: "Kata sandi harus terdiri dari 6-15 huruf dan angka, silakan atur ulang kata sandi", + init_validate_2: "Kata sandi tidak cocok, silakan atur ulang", + init_validate_3: + "Kata sandi harus terdiri dari 6-15 huruf dan angka, silakan atur ulang kata sandi", + init_validate_6: "Kata sandi harus terdiri dari 6-15 huruf dan angka", + init_validate_4: "Pendaftaran berhasil, akan kembali ke halaman login", + init_hint_1: "6-15 digit atau huruf", + init_hint_2: "Konfirmasi kata sandi login", + init_hint_3: "Kata sandi login", + username: "Nama Pengguna", + to_modify: "Ubah", + confirm_password: "Konfirmasi Kata Sandi", + notification_settings: "Pengaturan Notifikasi", + init_text_1: "Pendaftaran dan login menunjukkan persetujuan Anda terhadap", + init_text_2: "《Perjanjian Layanan Pengguna》", + init_text_3: "dan", + init_text_4: "《Kebijakan Privasi》", + init_text_5: "Perjanjian Layanan dan Kebijakan Privasi", + init_text_6: + "Untuk melindungi hak-hak dan kepentingan hukum Anda, harap baca dan setujui perjanjian berikut", + refuse: "Menolak", + refuse_continue: "Setuju dan Lanjutkan", + forget_password: "Lupa Kata Sandi?", + register_user: "Daftar Pengguna", + gotoLogin: "Masuk", + gotoRegister: "Buat Akun Baru", + enterGroupCheck: "Masuk ke Pengajuan Grup", + openPhotoError_1: "Tidak dapat membaca file yang dipilih", + openPhotoError_2: "Gagal mengunggah file", + openPhotoError_3: "Tidak dapat membaca informasi file", + agreeAll: "Setuju Semua", + whatCanManagerDo: "Hak Pengelolaan Grup", + changeGroupName: "Ubah Nama Grup", + deleteMemberMessage: "Hapus Pesan Anggota Grup", + chatPrivate: "Obrolan Pribadi Terenkripsi dengan Anggota Grup", + memberMute: "Bisukan Anggota Grup", + deleteMember: "Hapus Anggota", + changeNotice: "Tambah/Ubah/Hapus Pengumuman Grup", + checkInGroup: "Periksa dalam Grup", + setAdmin: "Atur sebagai Admin", + deleteAdmin: "Hapus Admin", + deleteAdminTitle: "Anda yakin ingin menghapus administrator ini?", + deleteAdminDetail: + "Dengan menghapus anggota ini dari admin, dia tidak akan dapat mengelola pesan grup.", + deleteSuccess: "Berhasil dihapus", + mobile_web_page: "Akses halaman web ponsel", + deleteMemberTitle: "Anda yakin ingin menghapus anggota ini?", + deleteMemberDetail: + "Dengan menghapus anggota ini dari grup ini, dia tidak akan dapat mengirim atau menerima pesan.", + deleteSelf: "Tidak dapat menghapus diri sendiri", + handled: "Ditangani", + unhandle: "Belum Ditangani", + noCheck: "Tidak ada permintaan yang ditangani saat ini~", + success: "Berhasil", + nameLenth: "Silakan atur 1-15 karakter", + editNickname: "Edit nama panggilan grup", + text_password: "Silakan atur kata sandi login baru", + quit: "Keluar", + waitAMoment: "Saya akan mempertimbangkannya", + editWarningTitle: + "Apakah Anda yakin ingin keluar dari pengeditan nama panggilan grup?", + editWarningDetail: + "Perubahan nama panggilan grup Anda belum disimpan, apakah Anda ingin melanjutkan dan keluar?", + editGroupAvatar: "Edit avatar grup ini", + myGroupAvatar: "Avatar grup saya", + inviteLink: "Undangan tautan", + inviteTips: + "Semua pengguna yang telah menginstal aplikasi ini akan dapat bergabung dengan obrolan grup Anda melalui tautan ini", + inviteLinkCopy: "Tautan undangan telah disalin", + shareLink: "Bagikan tautan", + manageLink: "Kelola tautan undangan", + copyLink: "Salin tautan", + deleteLink: "Hapus tautan", + deleteLinkTipsTitle: "Anda yakin ingin mencabut tautan ini?", + deleteLinkTipsDetail: + "Anda yakin ingin mencabut tautan undangan ini? Setelah dicabut, orang lain tidak akan bisa bergabung dengan grup menggunakan tautan ini.", + linkManage: "Kelola tautan", + openManage: + "Setelah diaktifkan, persetujuan dari pemilik grup atau admin diperlukan untuk bergabung dengan grup", + timeValid: "Batas waktu yang valid", + linkInvalidTips: + "Tautan undangan akan kedaluwarsa setelah tanggal ini, dan tidak akan dapat diakses lagi", + valid: "Valid dalam", + dayValid: "hari", + forever: "Selamanya", + foreverValid: "Selamanya valid", + attendTips: + "Permintaan bergabung telah dikirim. Setelah diulas oleh admin, Anda akan dapat bergabung dengan grup.", + memberCountLimit: "Jumlah anggota grup telah mencapai batas", + linkIsOut: "Tautan undangan telah kadaluarsa", + requestAttend: "Minta bergabung dengan obrolan grup", + needCheck: "Grup ini memerlukan persetujuan admin untuk dapat bergabung", + countLimit: "Jumlah admin telah mencapai batas", + openLock: + "Setelah keluar dari aplikasi, waktu yang ditentukan harus diatasi dengan solusi pengunci", + closeLock: "Tidak memerlukan verifikasi saat aplikasi dimulai", + checkTips: + "Harap periksa apakah izin wajah atau sidik jari Anda telah diaktifkan~", + face: "Penguncian Wajah", + finger: "Penguncian Sidik Jari", + gestureLock: "Penguncian Pola", + closed: "Belum Dinyalakan", + opened: "Telah dibuka", + lanchProtect: "Perlindungan saat mulai", + noProtect: "Tidak perlu dilindungi", + lockSetting: "Pengaturan kunci", + drawLine: "Gambar pola kunci", + drawOldLine: "Gambar pola sandi gestur lama Anda", + drawNewLine: "Gambar pola sandi gestur baru Anda", + setGesturePassword: "Atur sandi gestur", + checkGesturePassword: "Periksa sandi gestur", + needJoinPoint: "Tolong sambungkan setidaknya 4 titik, silakan gambar ulang", + noFriendAdd: "Tidak ada teman yang bisa ditambahkan", + noFriendDelete: "Tidak ada teman yang bisa dihapus", + noVerify: "Verifikasi diperlukan saat menambahkan saya", + drawAgain: "Silakan gambar lagi", + checkSuccessful: "Verifikasi berhasil", + notInCommon: "Tidak konsisten dengan gambar sebelumnya, silakan gambar ulang", + changeGesturePassword: "Ubah sandi gestur", + toChange: "Untuk mengubah", + change: "Ubah", + openSuccess: "Berhasil membuka", + closeSuccess: "Berhasil menutup", + hasBeenMember: "Sudah menjadi anggota grup, tidak perlu bergabung lagi", + joinGroup: "Gabung dalam obrolan grup", + checking: "Sedang dicek oleh administrator", + noUnlock: "Tidak dapat membuka kunci?", + inputGesture: "Silakan masukkan sandi gestur", + faceDetect: "Harap lakukan pengenalan wajah", + fingerDetect: "Harap lakukan pengenalan sidik jari", + tryUnlocking: "Coba ini: Tidak bisa membuka kunci?", + countRemain: "Kesalahan gestur, Anda masih bisa memasukkan %s kali", + reLoginUnlock: "Masuk kembali untuk membuka kunci", + selectVerityType: "Silakan pilih jenis verifikasi ", + currentTypeNotSupport: "Jenis saat ini tidak mendukung", + //------------- + updateNewVersion: "Ditemukan versi baru", + updateOkUpdate: "Perbarui sekarang", + updateOpenDownloadFailed: "Gagal membuka unduhan", + updateCurrentIsNew: "Saat ini sudah versi terbaru", + newVersionNotFound: + "Tautan versi terbaru tidak ditemukan, Anda dapat mengunjungi halaman web untuk mengunduh", + openUrlFailed: "Tautan tidak valid", + qmvMsgNotExit: "[Konten yang dirujuk tidak ada]", + qmvMsgNotSupport: "[Konten yang dirujuk tidak mendukung tampilan]", + dataError: "Kesalahan parsing data", + userNotBindEmail: "Pengguna tidak terikat dengan surel", + userNotRegister: "Pengguna tidak terdaftar", + checkError: "Gagal memeriksa", + csvTitle: "Pengaturan Obrolan", + csvSetTopMsg: "Pin obrolan", + csvSetOptMsg: "Diamkan pesan", + csvSetDisableMsg: "Blokir pesan", + csvClearMsg: "Hapus riwayat obrolan", + csvClearLocal: "Hanya hapus riwayat obrolan lokal", + csvClearUser: "Hapus riwayat obrolan lokal dan %s", + csvSetErrorTip: "Gagal mengatur, silakan periksa koneksi internet", + gmiTitle: "Nama Grup:", + gmiSetTalk: "Pesan pribadi terenkripsi", + gmiSetMute: "Atur larangan percakapan grup", + gmiDeleteUser: "Keluar dari grup ini", + gmiMuteTo: "Dilarang bicara sampai ", + gmiError1: + "Informasi anggota tidak ditemukan, mungkin telah dikeluarkan dari obrolan grup", + gmiError2: + "Informasi anggota grup Anda tidak ditemukan, Anda mungkin telah dikeluarkan dari obrolan grup", + gmiError3: "Apakah Anda yakin ingin menghapus pengguna ini?", + gmiError4: + "Mengeluarkan anggota ini dari grup akan menghentikan kemampuan untuk mengirim dan menerima pesan", + gmiError5: "Anda tidak memiliki izin untuk menghapus administrator ini", + gmiError6: "Anda tidak memiliki izin untuk menghapus anggota ini", + gmiError7: "Larangan bicara telah dicabut", + gmiError8: + "Anda tidak memiliki izin untuk membatalkan larangan bicara administrator ini", + gmiError9: + "Anda tidak memiliki izin untuk membatalkan larangan bicara anggota ini", + gmiError10: + "Anda tidak memiliki izin untuk melarang bicara administrator ini", + gmiError11: "Anda tidak memiliki izin untuk melarang bicara anggota ini", + gmiError12: "Larangan bicara dicabut", + gmiGroupOwnerNotFound: "Gagal memuat informasi grup", + smmvTitle: "Durasi Larangan Bicara", + smmvMuteTime: "Pilih Durasi Larangan Bicara", + smmvMuteTime_10m: "10 menit", + smmvMuteTime_1hour: "1 jam", + smmvMuteTime_12hour: "12 jam", + smmvMuteTime_1day: "1 hari", + smmvMuteTime_30day: "30 hari", + smmvMuteTime_7day: "7 hari", + egnpTitle: "Edit Nama Obrolan Grup", + egnpSetGroupName: "Silakan atur nama grup", + egnpSetTextDesc: "Silakan atur 1-15 karakter", + egnpSetTitleNoPermission: + "Anda tidak memiliki izin untuk mengubah nama obrolan grup", + egnpSetFailed: "Gagal mengubah, silakan coba lagi nanti", + setGroupNameHint: "Silakan atur nama grup", + givTitle: "Edit Nama Obrolan Grup", + givGroupName: "Nama Grup", + givGroupFace: "Foto Grup", + gmsvTitle: "Kelola Grup", + gmsvSetManager: "Atur Administrator", + gmsvToSet: "Atur Sekarang", + gmsvFindMsg: "Cari Riwayat Obrolan", + gmsvFindMsgDesc: + "Dinyalakan, anggota yang baru masuk akan melihat pesan terbaru", + gmsvMute: "Bisukan Semua Anggota", + gmsvMuteDesc: + "Dinyalakan, hanya pemilik grup dan administrator yang dapat berbicara", + gmsvApply: "Konfirmasi Masuk Grup", + gmsvApplyDesc: + "Dinyalakan, masuk grup memerlukan konfirmasi dari pemilik grup atau administrator", + gmsvSetManagerTip: "Hanya pemilik grup yang bisa mengatur administrator", + gmsvSetMuteTipOn: "Semua anggota telah dibisukan", + gmsvSetNuteTipOff: "Pembisuan semua anggota telah dibatalkan", + gmsvApplyTipOn: "Konfirmasi masuk grup telah diaktifkan", + gmsvApplyTipOff: "Konfirmasi masuk grup telah dinonaktifkan", + gmsvFindGroupErr: "Gagal mendapatkan informasi grup", + gmsvFindMsgOn: "Pencarian riwayat obrolan telah diaktifkan", + gmsvFindMsgOff: "Pencarian riwayat obrolan telah dinonaktifkan", + gmsvSetFailed: "Gagal mengatur", + gsvTitle: "Detail Grup", + gsvGroupId: "ID Grup", + gsvGroupMember: "Anggota Grup", + gsvGroupMemberValue: "Total {num} orang", + gsvGroupNotification: "Pengumuman Grup", + gsvMyName: "Nama Panggilan Saya di Grup Ini", + gsvMyFace: "Foto Profil Saya di Grup Ini", + gsvGroupManager: "Manajemen Grup", + gsvGroupApply: "Konfirmasi Masuk Grup", + gsvFindMsg: "Cari Riwayat Obrolan", + gsvSetTop: "Pin ke Atas", + gsvSetOpt: "Mode Diam Pesan", + gsvDeleteMsg: "Hapus Riwayat Obrolan", + gsvDeleteLocal: "Hanya Hapus Riwayat Obrolan Lokal", + gsvDeleteAllMsg: "Hapus Riwayat Obrolan Lokal dan Semua Anggota", + gsvDeleteOtherMsg: 'Menghapus catatan obrolan lokal dan dari pihak lain', + gsvGroupDelete: "Anda yakin ingin membubarkan obrolan grup ini?", + gsvOutGroup: "Anda yakin ingin keluar dari obrolan grup ini?", + gsvNoNotification: "Belum ada pengumuman", + gsvClearSuccess: "Berhasil membersihkan", + gsvClearFailed: "Gagal membersihkan, silakan coba lagi nanti", + cvAllPeople: "Semua Orang", + cvVideo: "[Video]", + cvPicture: "[Gambar]", + cvFile: "[Berkas]", + cvVoice: "[Suara]", + cvNotSupportMsg: "Pesan tidak didukung saat ini", + cvSendMsgErrorNoPermission: + "Anda tidak dapat mengirim pesan kepada mereka karena pengaturan izin mereka", + cvSendMsgPnMute: "Dibisukan, tidak dapat mengirim pesan", + cvCopyMsg: "Tersalin ke papan klip", + cvCopyMsgNotSupport: "Pesan tidak didukung saat ini", + cvdeleteLocal: "Hapus dari lokal", + cvdeleteLocalAndServer: "Hapus dari lokal dan server", + cvdeleteLocalAndServerUser: "Hapus dari lokal dan {name}", + cvClearMsgTip: "Pesan percakapan telah dibersihkan", + cvGroupCreateSuccess: "Pembuatan obrolan grup berhasil", + cvGroupCreateSuccessTip1: + "Selamat! Anda berhasil membuat grup. Klik tombol di bawah ini untuk mengundang teman-teman ke dalam grup", + cvGroupCreateSuccessGo: "Undang Teman", + cvGroupNotification: "Pengumuman Grup:", + sendMsg: "Kirim Pesan", + cvOnlineCount: "{count} orang online", + cvUserCount: "Total {count} orang", + cvNotAtUser: + "Belum ada anggota kelompok yang tersedia. @ Oh, pergilah dan tambahkan anggota sekarang", + all: "Semua", + clCloseConversation: "Lipat Percakapan Terpasang", + clOpenConversation: "Percakapan Terpasang", + clNoConversation: "Belum ada percakapan baru~", + clTitle: "Percakapan", + clDelete: "Percakapan ini akan dihapus", + clDeleteDesc: "Menghapus percakapan ini akan menghapus riwayat obrolan lokal", + clDeleteSel: "Anda belum memilih percakapan", + clDeleteSel1: "Percakapan ini akan dihapus", + clDeleteSel2: "Percakapan ini akan dihapus", + clDeleteDesc2: + "Menghapus percakapan ini akan menghapus riwayat obrolan lokal", + homeItem0: "Percakapan", + homeItem1: "Kontak", + homeItem2: "Profil Saya", + exitTip: "Klik lagi tombol kembali untuk keluar", + mineSigEmpty: "Pengguna ini malas, belum ada tanda tangan~", + uaProtocol: "Perjanjian Layanan Pengguna", + savSearchTip: "Cari cepat isi percakapan", + savMedia: "Gambar/Video", + searchEmptyTip: "Tidak ada catatan percakapan terkait ditemukan", + savSearchMsg: "catatan percakapan terkait", + savConversation: "Percakapan", + savMsg: "Riwayat Obrolan", + savConversationNotFound: "Percakapan tidak ditemukan", + savMsgNotFound: "Informasi ini tidak ditemukan", + samTitle: "Media", + samTimeFormat: "MMM yyyy", + notSupportType: "Tipe tidak didukung", + media: "Media", + openUrl: "Buka Tautan", + lkAddGroupUrl: "Tambahkan ke Obrolan Grup", + lkAddOtherUrl: "Tautan Eksternal, bisa dibuka dengan browser default", + lkUnknowUrl: "Tautan tidak dikenali", + openUrlFailed1: "Gagal membuka tautan", + yszcTitle: "Kebijakan Privasi", + slsmTitle: "Deklarasi Hukum", + + ctmRemove: "Hapus", + ctmOkRemove: "Konfirmasi Hapus", + ctmTopMsgCount: "Total {count} pesan", + ctmEmptyText: "Tidak ada pesan terpasang", + recordTimeShort: "Waktu rekaman terlalu singkat", + sendCodeExceErr: "Gagal mengirim kode verifikasi, silakan coba lagi nanti", + bindEmailExecErr: "Gagal mengikat email, silakan coba lagi nanti", + ctmSuccess: "Berhasil dipasang", + ctmRemoveSuccess: "Berhasil menghapus pesan terpasang", + ctmMsgNotFound: "Pesan ini tidak ditemukan", + cvFirstTip: "Pesan dan panggilan dalam percakapan ini dienkripsi end-to-end", + clearGroupNotification: "Pengumuman grup telah dibersihkan", + publicGroupNotification: " Terbitkan Pengumuman", + dialog_tip1: " Tips Hangat", + deleteGroups: + " Setelah grup dihapus, tidak dapat dikembalikan lagi. Apakah Anda yakin ingin menghapus grup ini?", + dialog_tip_token_invaild: " Sesi login telah berakhir, harap login kembali", + notify_new_msg_title: "Anda menerima pesan baru", + notify_new_msg_msg: "Isi pesan: .....", + dataError1: "Kesalahan Data", + groupApply: " Permintaan bergabung grup", + muteLate: "Setelah ini, pembisuan akan dihapus", + groupMuteing: "Pembisuan grup sedang berlangsung", + modifyGroupName: "Ubah Nama Grup", + groupMemberNotFound: "Informasi anggota grup tidak ditemukan", + deleteGround: "Hapus Grup", + bindEmailTipTitle: "Ikatan Email", + bindEmailTipContent: "Untuk keamanan akun Anda, harap ikat email Anda", + bindEmailTipOk: "Ikatan Sekarang", + bindEmailTipCancle: "Tidak Sekarang", + alreadyBeOwner: + "Anda telah memilih sebagai pemilik grup, Anda memiliki hak pengelolaan tertinggi", + managerApplying: "Sedang dalam proses verifikasi sebagai administrator", + quote: "Kutip", + request_exception: "Permintaan bermasalah", + clearMsg: "Hapus Pesan", + pressToSay: "Tekan untuk Berbicara", + slideToCancelSend: "Geser ke atas untuk Membatalkan Kirim", + releaseToEnd: "Lepaskan untuk Selesai", + releaseToCancelSend: "Lepaskan jari untuk Membatalkan Kirim", + invalidVoiceMessage: "Pesan suara tidak valid", + hit: "Pukulan", + downloadingCache: "Mengunduh cache...", + unableToRecognize: "Tidak dapat dikenali", + scanResult: "Hasil Pemindaian", + register_fail: + " Registrasi gagal, silakan periksa koneksi internet Anda dan coba lagi", + skip: " Lewati", + toUse: " Gunakan Sekarang", + sideToSide: " Enkripsi End-to-End", + design: " Desain", + guide1: " Kirim pesan, transmisi terenkripsi, hanya untuk dibaca sendiri", + guide2: " Berkomunikasi dengan lebih bebas sesuai gaya Anda sendiri", + guide3: " Platform komunikasi aman yang dibangun di sekitar Anda", + inputGroupAc: " Masukkan pengumuman grup (0-500 karakter)", + clickToEnjoy: + " Klik tautan untuk menyimpan, atau salin teks ini, buka aplikasi [Nama Aplikasi] dan Anda dapat bergabung dengan obrolan grup.", + createOK: "Berhasil dibuat", + noPravcy: "Anda tidak memiliki izin untuk membuat obrolan grup", + groupNumberToTop: "Batas jumlah grup telah tercapai", + createFail: "Gagal membuat, silakan coba lagi nanti", + create: "Buat", + uploadGroupAvatar: "Unggah Foto Grup", + useNewPassword: + "Password berhasil diubah, segera kembali ke halaman login", + error_14: "Server bermasalah (14)", + error_500: "Server bermasalah (500)", + error_1000: "Token tanda tangan pengguna tidak valid", + error_1001: "Parameter salah", + error_1002: "Versi saat ini sudah yang terbaru", + error_6001: "Nama pengguna ini sudah terdaftar", + error_6003: "Password harus terdiri dari 6-15 karakter huruf dan angka", + error_6004: "Kesalahan verifikasi nama pengguna", + error_6005: "Kata sandi tidak benar, silakan coba lagi", + error_6006: "Jumlah percobaan password hari ini telah melebihi batas", + error_6007: "Pengguna tidak ditemukan", + error_6008: "Password lama salah, silakan masukkan kembali", + error_6009: + "Password lama sama dengan password baru, silakan masukkan kembali", + error_6010: "Pengambilan kode verifikasi terlalu sering", + error_6011: "Kode verifikasi salah, silakan coba lagi", + error_6012: "Kode verifikasi telah kedaluwarsa", + error_6013: "Jumlah percobaan gagal kode verifikasi terlalu banyak", + error_6014: "Kode verifikasi telah digunakan", + error_6015: "Email tidak ditemukan", + error_6016: "Email telah diikat oleh pengguna lain, silakan masukkan kembali", + error_6017: "Kode verifikasi tidak ada", + error_6018: "Akun ini telah diblokir", + error_http_11: "Koneksi jaringan time out", + error_http_12: "Koneksi jaringan time out", + error_http_13: "Koneksi jaringan time out", + error_http_14: "Kesalahan sertifikat jaringan", + error_http_15: "Permintaan data bermasalah", + error_http_16: "Permintaan dibatalkan", + error_http_17: "Koneksi jaringan time out", + error_http_18: "Kesalahan internal dalam permintaan jaringan", + error_sdk_err_1002: "Tidak memiliki izin untuk melakukan operasi ini", + error_sdk_err: "Operasi gagal", + search_hine_1: "Masukkan konten pencarian", + str_no_data: "Tidak ada data saat ini", + str_no_net: "Tidak ada koneksi internet", + download_not_support_type: "Tipe tidak didukung", + download_save_at: "File disimpan di: ", + download_not_url: "Tautan unduhan tidak ditemukan", + download_file_success: "Berhasil disimpan, Anda dapat melihatnya di galeri", + download_failed_file: "Gagal menyimpan file", + download_failed: "Unduhan gagal", + loading_failed_retry: "Gagal memuat, klik untuk mencoba lagi", + loading_ing: "Memuat...", + str_online_later_minute: "Menit yang lalu online", + str_online_later_hour: "Jam yang lalu online", + str_online_later_llg: "Belum online dalam waktu yang lama", + str_online_later_yesterday: "Online kemarin", + str_yesterday: "kemarin", + str_online_later_near: "Online baru-baru ini", + str_minute: "menit", + clean_notice: "Bersihkan pengumuman", + clean_notice_1: "Anda yakin ingin mengosongkan pengumuman grup?", + publish_notice: "Terbitkan pengumuman", + publish_notice_remind: + "Menerbitkan pengumuman ini akan memberi peringatan kepada semua anggota grup", + quit_edit: "Keluar dari pengeditan", + quit_the_edit: "Keluar dari pengeditan ini?", + continue_edit: "Lanjutkan pengeditan", + publish_success: "Berhasil diterbitkan", + face_success: "Pembukaan kunci wajah berhasil diaktifkan", + finger_success: "Pembukaan kunci sidik jari berhasil diaktifkan", + groupDismissed: "Grup ini telah dibubarkan", + hasOut: "Telah kadaluarsa", + otherLoginStyle: "Metode Login Lainnya", + otherRegisterStyle: "Metode Registrasi Lainnya", + hasAccount: "Sudah punya akun? Login disini", + mobileCode: "Kode Verifikasi Ponsel", + codeSendTo: "Kode verifikasi telah dikirim ke", + inputNumber: "Masukkan nomor ponsel", + bindPhone: "Ikatan Nomor Ponsel", + bindPhoneSuccess: "Nomor ponsel berhasil diikat", + bindPhoneTipChange: + "Jika nomor ponsel Anda sudah tidak digunakan, harap segera ganti", + please_input_email: "Masukkan alamat email", + findPassByEmailTip: "Reset password melalui email", + findPassByPhoneTip: "Reset password melalui nomor ponsel", + resetPass: "Reset password", + current_bind_phone: "Nomor ponsel terikat saat ini:", + reget: "Dapatkan ulang", + getVerifyCode: "Dapatkan Kode Verifikasi", + alreadyRegister: "Nomor ponsel ini sudah terdaftar", + alreadyRegister2: "Alamat email ini sudah terdaftar", + netErrorRetry: "Koneksi bermasalah, klik untuk memuat ulang halaman~", + newPhoneNumber: "Nomor ponsel baru", + addFriendText1: "Tambahkan sebagai teman", + qrSaveFail: "Gagal menyimpan QR Code", + qrSavePhoto: "QR Code telah disimpan di galeri", + userId: "ID Pengguna:", + addMeToFriend: "Tambahkan saya sebagai teman", + validTime: "Berlaku hingga", + clickRefresh: "Klik untuk memperbarui", + qrUseCount: "QR Code ini berlaku {count} kali", + share: "Bagikan", + savePhoto: "Simpan Foto", + iAm: "Saya adalah", + addFriendSuccessful: "Berhasil menambahkan teman", + addFriendFaceToFace: "Tambahkan teman secara langsung", + faceToFace: "Tambahkan teman secara langsung", + inputSameKey: + "Berikut adalah teman-teman yang sudah memasukkan nomor yang sama", + sameKeyAddFriend: + "Tambahkan teman dengan memasukkan nomor yang sama dengan teman", + self: "Diri sendiri", + age: "tahun", + confirmAddFriend: "Konfirmasi menambahkan sebagai teman", + inputUserId: "Masukkan ID pengguna", + myQr: "Kode QR saya", + back: "Kembali", + canNotAddSelf: "Anda tidak dapat menambahkan diri sendiri ke daftar kontak", + notExsit: "Pengguna ini tidak ada", + searchUserId: "Cari ID pengguna:", + remarkInfo: "Mohon tambahkan informasi verifikasi", + applyRemark: "Tambahkan Catatan", + last4Number: "Masukkan 4 digit terakhir nomor ponsel login pengguna lain", + checkLast4Number: "Verifikasi 4 digit terakhir nomor ponsel", + addFriendApply: "Permohonan Menambahkan Teman", + friendNumberCatchTopLimit: "Jumlah teman Anda telah mencapai batas atas", + friendNumberCatchTopLimitCurrentDay: + "Pembatasan penambahan teman telah tercapai untuk hari ini", + codeInvalid: "Kunci tidak valid atau sudah kedaluwarsa, silakan coba lagi", + shareFail: "Gagal berbagi", + error_1003: "Silakan coba lagi nanti", + error_6033: "Kunci tidak valid atau sudah kedaluwarsa, silakan coba lagi", + error_6034: "Jumlah teman telah mencapai batas atas", + error_6035: "Pembatasan penambahan teman telah tercapai untuk hari ini", + error_6036: "Nomor ponsel pengguna ini kosong", + error_6037: "Verifikasi nomor ponsel gagal", + error_6040: "Grup sudah ada", + error_6050: "Kode QR sudah kedaluwarsa", + error_6051: "Penggunaan kode QR pada hari ini telah mencapai batas atas", + error_6052: "Penggunaan kode QR telah mencapai batas atas", + error_6053: + "Penggunaan fitur Pindai Sekali Sudah mencapai batas atas hari ini", + error_6054: "Pengguna sudah menambahkan Anda sebagai teman", + error_6055: "Kunci tidak valid atau sudah kedaluwarsa, silakan coba lagi", + error_6066: "Saldo akun tidak mencukupi", + error_6067: "Angpau sudah habis", + error_6068: "Angpau sudah kedaluwarsa", + error_6069: "Tidak dapat menerima angpau eksklusif", + error_6070: "Angpau sudah diterima, tidak dapat menerima lagi", + deletedByFriendHint: "Anda belum menjadi teman mereka", + deletedByFriendHint1: "Anda belum menjadi teman mereka,", + deletedByFriendHint2: "Tambahkan teman", + deletedByFriendHint3: "lanjutkan percakapan", + gotoAddFriend: "Tambahkan teman", + addFriendGotoGroup: "Undang teman masuk grup", + notContacts: "Belum ada kontak", + notContactsFound: "Tidak ada teman yang ditemukan untuk {name}", + remarkStr: "Catatan", + remarkStrHint: "Masukkan catatan, 0-50 karakter", + deleteConfirm: "Apakah Anda yakin ingin menghapus teman?", + deleteFriend: "Hapus Teman", + searchContact: "Cari Kontak", + notGroupTip: "Belum bergabung dalam obrolan grup", + notAddFriend: "Belum menambahkan teman baru", + friendApplyAdd: "Permintaan Pertemanan", + addSuccess: "Berhasil ditambahkan", + hasSelectFriend: "Teman sudah dipilih", + myBalance: "Saldo Saya", + revokeMsgTip: "Batalkan Pesan", + error_10050: "Operasi gagal (rekaman tidak ditemukan)", + set_manager_tip_1: + "Anda telah memilih menjadi pemilik grup, Anda memiliki otoritas manajemen tertinggi", + envelope: "Amplop Merah", + uidStr: "ID Pengguna", + showGetDetail: "Lihat Detail Penerimaan", + waitOtherEnter: "Menunggu pihak lain untuk bergabung", + isFriend2SExit: "Telah menjadi teman, keluar otomatis dalam 2 detik", + sendRedPacket: "Kirim Angpau", + redPacketNumber: "Jumlah Angpau", + ge: "buah", + total: "Total", + eachNumber: "Jumlah Setiap", + selectUser: "Pilih Pengguna", + number: "Jumlah", + redPacketType: "Tipe Angpau", + checkVerity: "Verifikasi Keamanan", + balance: "Saldo Akun", + sendTo: "Kode Verifikasi SMS telah dikirim ke", + pin: "Angpau Lucky Draw", + putong: "Angpau Biasa", + zhuanshu: "Angpau Eksklusif", + dajidali: "Selamat menarik angpau, semoga beruntung besar!", + redPacketInfo: "Detail Angpau", + best: "Yang Paling Beruntung", + saveToAccount: "Disimpan ke Akun", + minuteOut: "Angpau Habis dalam Menit", + hourOut: "Angpau Habis dalam Jam", + showDetail: "Lihat Detail", + maxMemberNumber: + "Jumlah Angpau tidak boleh melebihi jumlah anggota grup saat ini", + sended: "Dikirim", + get: "Telah Diterima", + waiting: "Menunggu pengguna untuk mengambil", + gong: "Angpau sebanyak", + envelope_get_over: "Angpau telah diambil habis oleh", + envelope_get: "Telah diambil oleh", + de: "", + envelope_get_message: "Pesan Mengambil Angpau", + bind_phone_please: "Untuk keamanan akun Anda, silakan ikat nomor ponsel Anda", + delay_bind: "Tunda mengikat", + send_envelope_x: "Angpau Dikirim", + send_envelope_back_all: + "Angpau ini tidak diambil dalam 24 jam\n dan telah dikembalikan", + envelope_x_can_get: "Hanya {name} yang dapat diambil", + envelope_get_completed: "Sayangnya, angpau telah diambil habis", + send_envelope_back_some: + "Angpau ini tidak diambil dalam 24 jam\n dan sebagian telah dikembalikan", + envelope_x_personal: "Angpau Eksklusif untuk %s", + envelope_normal: "Angpau Biasa", + login_out_of_date: "Login telah kedaluwarsa, silakan login kembali", + get_envelope_completed: "Telah diambil habis", + x_is_personal: "Eksklusif", + error_6072: "Jumlah maksimum untuk satu angpau telah tercapai", + error_6073: "Jumlah untuk satu angpau tidak boleh kurang dari 0.01", + error_6074: "Fitur Angpau telah dinonaktifkan", + distroyAccount: "Menghapus Akun", + inComing: "Pemasukan", + outGoing: "Pengeluaran", + balance_order_detail: "Detail Saldo", + type: "Tipe", + payType: "Metode Pembayaran", + orderNo: "Nomor Pesanan", + time: "Waktu", + itemDataNotSupport: "Versi saat ini belum mendukung fitur ini", + manager_change_account: "Penyesuaian oleh Admin", + groupEnvelope: "Angpau Grup", + accountPay: "Pembayaran Saldo", + paySuccess: "Pembayaran Berhasil", + groupEnvelopePersonal: "Angpau Grup - Eksklusif", + groupEnvelopeNormal: "Angpau Grup - Biasa", + changeAccount: "Penyesuaian Akun", + groupEnvelopeRandom: "Angpau Grup - Acak", + groupEnvelopeAllBack: "Angpau Grup - Pengembalian Penuh", + groupEnvelopeSomeBack: "Angpau Grup - Pengembalian Sebagian", + distroyTips: + "Untuk memastikan keamanan akun Anda,\nkami akan melakukan verifikasi berikut sebelum Anda menonaktifkan akun.", + safeStatus: "Akun dalam keadaan aman", + safeTips: + "Pastikan akun Anda tidak mengalami perubahan kata sandi, pergantian nomor telepon/surel, atau risiko pencurian akun.", + strikeBalance: "Penyelesaian Saldo Akun", + strikeTips: + "Setelah menonaktifkan akun, semua data saldo angpau Anda akan dihapus dan tidak dapat dipulihkan.", + dataDelete: "Penghapusan Data Akun", + deleteTips: + "Setelah menonaktifkan akun, semua data di bawah akun tersebut akan dihapus dan tidak dapat dipulihkan. Data termasuk riwayat obrolan, grup yang diikuti, dan hubungan pertemanan.", + applyDistroy: "Ajukan Penghapusan Akun", + distroyConform: "Apakah Anda yakin ingin menonaktifkan akun?", + distroyConformTips: + "Setelah nonaktif, Anda akan keluar dari sesi login. Harap berhati-hati!", + distroy: "Konfirmasi Nonaktif", + submitSuccess: "Berhasil Mengirimkan", + submitted: "Permohonan penghapusan Anda telah diajukan!", + helpWord: + "Platform akan memproses permohonan Anda dalam {day} hari kerja dan menghapus semua data Anda. Jika Anda membutuhkan bantuan, silakan hubungi kami melalui situs web resmi.", + know: "Saya Mengerti", + revoke: "Batalkan Permohonan", + revokeSuccess: "Berhasil Membatalkan", + recover: "Apakah Anda yakin ingin membatalkan permohonan?", + toNormal: "Setelah dibatalkan, status akun akan kembali normal.", + beRecover: "Konfirmasi Pemulihan", + recoverSuccess: "Berhasil Dipulihkan", + redPackageCountTip: + "Jumlah total angpau perlu lebih dari atau sama dengan 0.01", + closeAccount: "Nonaktifkan Akun", + recoverAccount: "Pulihkan Akun", + recoverAccountTipTitle: "Apakah Anda yakin ingin memulihkan akun?", + recoverAccountTipDesc: "Setelah dipulihkan, status akun akan kembali normal.", + recoverAccountTipOk: "Konfirmasi Pemulihan", + closingAccount: "Akun dalam proses penonaktifan", + closingAccountEndTime: ", perkiraan selesai dalam {num} hari kerja", + error_6079: "Anda tidak lagi berada dalam obrolan grup, tidak dapat menerima", + error_6075: "Permohonan duplikat", + error_6076: "Tidak ada catatan permohonan penonaktifan ditemukan", + error_6077: "Penonaktifan dalam proses peninjauan", + error_6078: + "Pengguna tidak ditemukan atau telah dinonaktifkan, harap periksa kembali", + error_video_pre_err: "Kesalahan dalam pembuatan file, gagal memuat data.", + err_upload_file: "Upload gagal, silakan coba lagi nanti", + err_msg_send_failed: "Pengiriman pesan gagal, silakan coba lagi nanti", + error_red_package_tip1: "Angpau telah kedaluwarsa", + net_error_no_net: + "Jaringan tidak tersedia, harap periksa pengaturan jaringan Anda", + error_6081: "Pengiriman gagal, silakan coba lagi nanti", + atTip: "Ada yang menyebut Anda", + groupChatError: "Gagal memuat konten, klik untuk mencoba lagi", + legalNotice: "Pemberitahuan Hukum", + timeoutLock: "Kunci Waktu Habis", + reTry: "Coba Lagi", + sysNotify: "Pemberitahuan Sistem", + exit_group_tip: "Keluar dari obrolan grup", + group_mute_ing: "Obrolan grup ini sedang dibisukan", + w_edit_group_notification: "Mengedit pemberitahuan grup", + removeGroupNotice: "Hapus pemberitahuan grup", + messageNotify: "Pemberitahuan Pesan", + groupEnterLimit: + "Jumlah teman yang diundang untuk masuk ke grup telah mencapai batas maksimum, harap periksa", + tipAllGroupMember: "Beritahu semua anggota grup", + openPhotoPermission: "Buka izin akses 'Foto'", + gotoSettings: "Buka Pengaturan", + notSet: "Belum Diatur", + openCameraPermission: "Buka izin penggunaan 'Kamera'", + openMicrophonePermission: "Buka izin penggunaan 'Mikrofon'", + selectMaxCountTip: "Pilih maksimal %s orang setiap kali", + getVideoImageFailed: "Gagal mendapatkan gambar pratinjau video", + error_6086: "Nama pengguna telah digunakan", + error_6085: "Format nama pengguna salah", + error_6087: "Nomor telepon tidak ada", + error_6088: "Email tidak ada", + unRecard: "Tidak dapat merekam:", + hasCreateGroup: "Membuat percakapan kelompok", + joinGroupForLink: "Join group chat through link", + be: "disebut", + moveoutGroup: "Keluar dari obrolan grup", + groupOwnerChangeTo: "Pemilik grup telah berubah menjadi", + changeGroupNameTo: "Ubah nama grup menjadi", + disbandedGroup: "Membubarkan obrolan grup", + bannedFromSpeaking: "Dilarang berbicara", + gagWasLifted: "Pembungkaman telah dicabut", + initiatedGroupGag: "Memulai pembungkaman grup", + shutDownGroupGag: "Menutup pembungkaman grup", + target: "Target", + groupSizeUpperLimit: "Jumlah anggota grup mencapai batas atas", + topUpperLimit: "Puncak mencapai batas atas", + topMessageRepeate: "Pesan puncak sudah diulang", + userHasReadMessage: "Pengguna ini sudah membaca pesan ini", + placeholder: { + allMuted: + "Pemilik grup atau administrator telah mengaktifkan pembungkaman global", + groupDisbanded: "Grup ini telah dibubarkan", + groupBanned: "Grup ini telah dilarang", + singleBanned: "Anda telah dibungkam", + search: "Cari", + pleaseInput: "Silakan masukkan", + sec: "detik", + groupNoticePlaceholder: "Silakan masukkan pengumuman grup..", + inputPassword: "Silakan masukkan kata sandi", + confirmPassword: "Silakan konfirmasi kata sandi", + inputPhoneNumber: "Silakan masukkan nomor telepon", + select: "Pilih", + userOrGroupIDInput: "Silakan masukkan {type}", + writeMessage: "Masukkan pesan...", + inputGroupName: "Silakan masukkan nama grup", + typingMessage: "Ketik pesan di sini", + inputName: "Silakan masukkan nama panggilan Anda" + }, + withoutQualification: + "Pengguna ini tidak memenuhi syarat untuk menambahkan teman", + error_6084: "Verifikasi email gagal", + emailCheck: "Verifikasi dengan email", + phoneCheck: "Verifikasi dengan nomor telepon", + checkEmail: "Verifikasi alamat email", + inputOtherEmail: + "Silakan masukkan alamat email yang digunakan untuk login oleh pihak lain", + groupMask: "Topeng Obrolan Grup", + groupMaskDescription: "Atur nama dan avatar grup ini", + + pleaseBind: + "Untuk keamanan akun Anda dan untuk menghindari pembatasan fungsi menambahkan teman, silakan bind", + orStr: "atau", + andStr: "dan", + confirmExit: "Konfirmasi Keluar", + pleaseBindTip1: "Untuk keamanan akun Anda, silakan bind", + skin_item_user_name: "张娜娜", + skin_item_msg1: "Halo, mendapatkan balasan tepat waktu", + skin_item_msg2: "Baiklah, pesan diterima", + appearance: "Tampilan", + bubbleBgColor: "Warna Balon Percakapan", + chatBackground: "Latar Belakang Percakapan", + moreSettings: "Pengaturan Lainnya", + highPerformanceModel: "Mode Bersih", + highPerformanceModelDesc: "Aktifkan untuk tidak menampilkan efek tampilan", + skinPreview: "Pratinjau Tampilan", + defaultText: "Default", + fontSizePreview: "Pratinjau ukuran font, geser slider di bawah ini untuk menyesuaikan ukuran font. Setelah diatur, semua teks halaman akan berubah ukurannya.", + changedText: "Oke, sudah diubah", + imageAlpha: "Transparansi Gambar", + min: "Minimum", + max: "Maksimum", + backgroundColor: "Warna Latar Belakang", + changeSaveTip: + "Anda telah mengubah pengaturan, apakah ingin menyimpan dan kembali?", + saveAndBack: "Simpan dan Kembali", + changeAvatar: "Ganti Avatar", + appFontSize: "Ukuran Font Aplikasi", + appFontSizeTip: + "Pratinjau ukuran font, geser slider di bawah untuk mengatur ukuran font. Setelah diatur, akan mengubah ukuran font untuk semua teks di dalam aplikasi.", + maskDesc: + "Aktifkan untuk memungkinkan anggota grup mengatur nama dan avatar grup ini", + groupMaskOpenTip: " %s Topeng Obrolan Grup %s", + groupMaskOpenTipNew:" Topeng Obrolan Grup", + open: "Buka", + close: "Tutup", + groupMaskOpenMsgErrorTip: "Peringatan Pemutaran Fungsi Topeng Obrolan Grup", + maskOpen: "Topeng Obrolan Grup telah dibuka", + maskClose: "Topeng Obrolan Grup telah ditutup", + transmit: "Meneruskan", + multiSelect: "Pilih Banyak", + singleShareMsg: "Meneruskan Pesan Satu Per Satu", + groupShareMsg: "Gabung Meneruskan", + notGroupFound: "Tidak ditemukan obrolan grup terkait {name}", + rememberPassword: "Ingat Kata Sandi", + mobileLogin: "Masuk dengan Ponsel", + emailLogin: "Masuk dengan Email", + recentConversation: "Percakapan Terbaru", + selectContact: "Pilih Kontak", + selectGroup: "Pilih Obrolan Grup", + noGroup: "Tidak ada obrolan grup", + notSelectMessage: "Anda belum memilih pesan", + hiddenMsg: "Sembunyikan Pesan", + hiddenMsgTip: "Apakah Anda ingin menyembunyikan sumber pesan?", + yes: "Ya", + no: "Tidak", + mergeMessageList: "Gabungkan Daftar Pesan Meneruskan", + singleMlCount: "【Meneruskan Satu Per Satu】Total %s pesan", + notFoundUser: "Pengguna tidak ditemukan", + notFoundGroup: "Grup tidak ditemukan", + selectAt: "Pilih orang yang ingin di {'@'}", + welcomeToQDDChat: "Selamat datang di QDDChat", + choiceYourAvatar: "Pilih avatar Anda", + hasSelect: "Telah dipilih", + groupChat: "Obrolan Grup", + groupMember1: "Anggota Grup", + groupSendMsg: "Kirim Pesan Grup", + msgNotSupportTransmit: "Pesan tidak mendukung pengiriman", + bindGoogle: "Ikatan Google", + bindFacebook: "Ikatan Facebook", + unbind: "Lepas ikatan", + accountSettings: "Pengaturan Akun", + thirdPartyAccountBinding: "Pengikatan Akun Pihak Ketiga", + otherSettings: "Pengaturan Lainnya", + groupChatLoadFailed: "Gagal memuat informasi obrolan grup", + addGroupSend: "Mulai Mengirim ke Grup", + groupSendNoData: "Belum ada catatan pesan grup", + removeBinding: "Hapus Ikatan", + unBindToastPart1: + "Setelah menghapus ikatan, Anda tidak dapat lagi menggunakan", + unBindToastPart2: "masuk akun, ingin melanjutkan penghapusan?", + noOpPermission: "Tidak ada izin yang dapat dioperasikan", + bindingPhoneOrEmail: + "Untuk keamanan data akun Anda, harap ikat nomor telepon atau email terlebih dahulu sebelum melepaskan ikatan.", + unbindingSuccess: "Lepas ikatan berhasil", + unbindingFair: "Lepas ikatan gagal", + showAll: "Lihat Semua", + allCount: "Semua Total {num}", + bindingPhoneOrEmailSetPassword: + "Untuk keamanan data akun Anda, harap ikat nomor telepon atau email terlebih dahulu sebelum mengatur kata sandi.", + recoverSuccessLoginAgain: "Pemulihan berhasil, silakan login kembali", + createGroupSend: "Buat Kiriman Grup", + groupSend: "Kirim Grup", + notSelect: "Anda belum memilih", + confirmDeleteTitle: "Konfirmasi menghapus pengiriman grup yang dipilih?", + totalGe: "Total %s", + sendTotalGe: "Kirim ke ({num})", + uploadFailed: "Unggahan gagal", + selectMax: "Jumlah pilihan mencapai batas maksimum", + newFriendApply: "Anda memiliki teman baru, klik untuk melihat", + notSupportLoginStyle: + "Sementara tidak didukung, silakan gunakan cara login lain", + error_6091: "Jumlah pemilih melebihi batas maksimum", + error_6092: "ID pesan sudah ada", + pay_password: "Kata Sandi Pembayaran", + pleaseEnter: "Silakan masukkan", + loadingPage: "Halaman sedang dimuat", + payTip: + "Anda belum mengatur kata sandi pembayaran, harap selesaikan pengaturannya sebelum melanjutkan.", + deleteRedPackageErrTip: "Pesan redaksi tidak dapat dicabut", + applyIconTip: + "Permohonan telah diajukan, setelah disetujui Anda dapat mengganti avatar", + applyIconProgress: "Lihat Proses Permohonan", + applyRecord: "Catatan Permohonan", + repealWithdrawApply: "Apakah Anda yakin ingin mencabut permohonan penarikan?", + withdrawNotes: "Catatan Penarikan", + inReview: "Sedang Direview", + passTheAudit: "Lulus Audit", + rejectedPayment: "Pembayaran Ditolak", + revoked: "Dicabut", + undone: "Batal", + withdrawalQuantity: "Jumlah Penarikan", + withdrawal: "Penarikan", + withdrawalBack: "Penarikan Dikembalikan", + update_tip1: "Jika tidak dapat diperbarui,", + update_tip2: "Silakan unduh dari situs web resmi", + update_ing: "Memperbarui", + update_install: "Pasang Sekarang", + installFailed: "Instalasi Gagal, silakan coba lagi nanti", + installNotFoundFile: + "File instalasi tidak ditemukan, silakan coba lagi nanti", + error_6093: "Cara login tidak didukung", + error_6094: "Jumlah avatar telah mencapai batas maksimum", + deleteIcon: "Apakah Anda ingin menghapus avatar ini?", + balanceWithdraw: "Penarikan Saldo", + canBalanceWithdraw: "Saldo yang Dapat Ditarik", + inputPayPassword: "Masukkan Kata Sandi Pembayaran", + withdrawalSuccess: "Penarikan Berhasil", + personIcon: "Avatar Personal", + groupIcon: "Avatar Grup", + error_6096: + "Jangan atur kata sandi pembayaran dengan angka berulang atau berurutan", + forgetPayPassword: "Lupa Kata Sandi Pembayaran", + downloadFailedTip: "Pengunduhan Gagal, silakan coba lagi nanti", + error_6097: "Saldo tidak mencukupi", + notSet2: "Tidak diatur", + passed: "Diterima", + headRejected: "Ditolak", + readDeleteTime: "Waktu Penghapusan Pesan", + readDeleteTimeTitle: "Waktu Penghapusan", + error_6090: "Akun Telah Terikat", + retracted_h5: "Ditarik kembali", + message_h5: "pesan dari", + inputRemark: "Silakan masukkan catatan Anda", + authFailed: "Gagal otorisasi", + userNullError: "Jumlah pengguna tidak normal", + community: "Komunitas", + join: + "Bergabung dengan obrolan grup, menjelajah lebih banyak aktivitas komunitas~", + posteviews: "Belum ada aktivitas yang dipublikasikan~", + views: "kali dilihat", + ended: "Aktivitas telah berakhir", + configError: "Kesalahan konfigurasi, tidak dapat dibuka sementara", + burnMsgOpen: "{name} mengatur pesan dibaca {time} setelah dibakar", + burnMsgClose: + "{name} mematikan pembacaan dan pembakaran, pesan tidak lagi otomatis terbakar", + burnMsgNotify: "Notifikasi baca dan bakar", + homeItem3: "Komunitas", + messageTimeOut: "Pesan telah kedaluwarsa", + burnContentTip: + "Pesan sedang terbakar, apakah Anda ingin keluar dan membakar dengan sekali klik?", + cancelLater: "Saya akan cek nanti", + burnConfirm: "Bakar segera", + setNickNameTimes: ", nama panggilan hanya dapat diubah sekali dalam %s hari", + voiceSendErrTip1: "Hanya mendukung pengiriman suara dari %s-%s detik", + textSendErrTip1: "Input maksimum %s karakter", + pictureSendErrTip1: "Hanya mendukung pengiriman gambar dari %s-%sM", + videoSendErrTip1: "Hanya mendukung pengiriman video dari %s-%sM", + your: "Anda", + joinCommunity: + "Bergabung dengan obrolan grup, menjelajah lebih banyak aktivitas komunitas~", + noActivity: "Belum ada aktivitas yang dipublikasikan~", + views: "kali dilihat", + ended: "Aktivitas telah berakhir", + errorLink: "Tidak dapat temukan tautan", + set_payment_password: "Harap atur kata sandi pembayaran 6 digit", + unset_payment_password: + "Mohon jangan atur kata sandi pembayaran yang berulang atau berurutan", + Confirm_Payment_Password: "Konfirmasi Kata Sandi Pembayaran", + Please_confirm_payment_password: + "Silakan masukkan kembali untuk mengonfirmasi kata sandi pembayaran ", + error_6100: + "Maaf, Anda hanya bisa mengubah nama panggilan pribadi Anda sekali setiap {day}hari", + viewDetailed: "Lihat informasi detail anggota grup", + inviter: "Pengundang", + lastLoginTime: "Waktu login terakhir", + DeleteThisUsersgroup: "Hapus pesan obrolan grup pengguna ini dalam grup ini", + AddGroupMembers: "Tambahkan anggota grup tanpa persetujuan verifikasi", + error_6103: "Pengguna ini adalah anggota biasa dan tidak memiliki izin", + error_6104: "Orang yang ditambahkan tidak ada dalam grup", + error_6108: "Akun bermasalah", + group_remark: "Catatan Grup", + input_group_remark: "Mohon masukkan konten catatan", + await_accepting: "Permintaan otorisasi perangkat baru telah dikirim", + apply_tips: "Perangkat ini tidak dipercaya atau tidak lulus tinjauan, Apakah Anda ingin mengirim informasi perangkat untuk mengajukan permohonan otorisasi?", + apply_send: "Kirim permohonan", + apply_ttl: "Otorisasi Masuk Perangkat", + group_limit500: "Maksimum 500 orang per grup", + select_group: "Pilih grup teman", + cantfind: "Tidak dapat menemukan kontak terkait dengan {name}", + error_6109: "Jumlah teman pengguna telah mencapai batas", + error_6106: "Harap tunggu sampai perangkat diterima sebelum masuk", + selectGroups: 'Pilih Grup', + group_member_info: "Informasi Anggota Grup", + join_time: "Waktu Bergabung", + inviter_user: "Pengundang", + last_login_time: "Waktu Login Terakhir", + sync_group_avatar: 'Apakah Anda ingin mengubah avatar grup secara bersamaan?', + invite_fail: 'akun {usernames} bermasalah!', + enterAndCtrl:'Enter untuk mengirim / Enter+Ctrl untuk baris baru', + enterAndCtrlIOS:'Enter untuk mengirim / Enter+control untuk baris baru', + scanLogin: 'Masuk dengan Pindai Kode QR', + formLogin: 'Masuk dengan Sandi', + scanText: 'Masuk menggunakan aplikasi Qudi Dai dengan Pindai Kode QR', + scanSubText: 'Gunakan aplikasi Qudi Dai untuk masuk dengan Pindai Kode QR', + qrcodeExpired: 'Kode QR telah kedaluwarsa', + expiredTime10: 'Kode QR berlaku selama 10 menit', + reflesh: 'Klik untuk segarkan', + scanSuccess: 'Pindai berhasil', + confromLogin: 'Harap konfirmasi masuk', + onlineStatus: "Status Online:", + networkDiagnostic: "Diagnostik Jaringan", + "setPaymentPassword": "Harap atur kata sandi pembayaran 6 digit", + "unSetPaymentPassword": + "Mohon jangan atur kata sandi pembayaran yang berulang atau berurutan", + "verifyPassword": "Verifikasi kata sandi pembayaran", + "confirmPaymentPassword": "Konfirmasi kata sandi pembayaran", + "verifyCurrentPassword": "Silakan verifikasi kata sandi pembayaran saat ini", + "reInsetPaymentPassword": + "Silakan masukkan kembali kata sandi pembayaran untuk konfirmasi", + "paymentPasswordDifferent": + "Kata sandi tidak cocok, silakan masukkan kembali", + "getPaymentPasswordWay": "Silakan pilih cara mendapatkan kembali kata sandi", + "selectFriendGroup": "Pilih Grup Teman", + "selectGroups": "Pilih Grup", + "eachGroupLimitNumber": "Batas maksimum anggota per grup adalah {p} orang", + "selectChatLimitNumber": "Anda hanya bisa memilih hingga {p} percakapan", + "deviceID": "ID perangkat:", + "groupChatRemark": "Catatan obrolan grup", + "pleaseEnterRemarks": "Silakan masukkan konten catatan", + "groupMembershipInfo": "Informasi keanggotaan grup", + "error_6102": "Maksimum 500 orang per kelompok", + "distroyed": "Dinonaktifkan", + "forbiddened": "Diblokir", + "addGroupMemberWithoutApply": + "Tambah anggota grup tanpa persetujuan verifikasi", + "error_6109": "Jumlah teman pengguna ini telah mencapai batas maksimum.", + "apply_ttl": "Otorisasi Masuk Perangkat", + "apply_tips": + "Perangkat ini tidak dapat dipercaya atau tidak lulus audit, apakah Anda ingin mengirim informasi perangkat untuk meminta otorisasi?", + "apply_send": "Kirim permintaan", + "device_auth_send": "Permintaan otorisasi perangkat telah dikirim", + "using_trusted_device": + "Login menggunakan perangkat ini berisiko, silakan gunakan perangkat terpercaya untuk login", + "account_closed": "Akun telah ditutup", + "google_code": "Kode Verifikasi Google", + "input_google_code": "Silakan masukkan Kode Verifikasi Google", + "inviterUser": "Pengundang", + "lastLoginTime": "Waktu Login Terakhir", + "error_6105": "Perangkat tidak dipercaya, harap ajukan permohonan", + "error_6106": "Harap tunggu sampai perangkat diverifikasi sebelum masuk", + "error_6107": "Perangkat tidak lulus verifikasi", + "hasDetailInfoPermission": "Lihat informasi detail anggota grup", + "editUserHeaderWithGroupHeader": + "Apakah Anda ingin mengubah avatar obrolan grup sekaligus?", + "darkModel": "Mode Gelap", + "darkModelSetDesc": + "Ikuti sistem untuk mengaktifkan atau menonaktifkan mode gelap", + "whiteModel": "Mode Terang", + "fileNotFound": "Tidak dapat menemukan file", + "openFileInApp": "Pratinjau file", + "openFileOtherApp": "Buka dengan aplikasi lain", + "openByBrowser": "Unduh dengan browser", + "pauseDownload": "Jeda unduhan", + "resumeDownload": "Lanjutkan unduhan", + "downloadFailed": "Unduhan gagal", + "downloading": "Mengunduh", + "notdownloaded": "Belum diunduh", + "cancelDownloading": + "File sedang diunduh, apakah Anda ingin membatalkan unduhan?", + "failedToDownload": + "Gagal mengunduh file, apakah Anda ingin mengunduh ulang?", + "cancelDownload": "Batalkan unduhan", + "sending": "Mengirim", + "fileTypeNotSupported": "Tidak mendukung unggah format file ini", + "fileSizeOut": "Ukuran file melebihi batas", + "restrict": "Membatasi", + "getWithPhone": "Dapatkan kembali dengan nomor telepon", + "getWithEmail": "Dapatkan kembali dengan alamat email", + "fileSize": "Ukuran file", + "whetherDownloadFile": "Apakah ingin mengunduh file", + "downloadFailedTip1": "Download gagal, harap periksa jaringan dan coba lagi", + "downloadQueueTip": "Ditambahkan ke antrian download, menunggu untuk diunduh", + "notSupportTransmitTip": "Pesan ini saat ini tidak dapat diteruskan", + "voiceSize": "Ukuran file audio tidak boleh melebihi 100MB.", + "wordSize": "Ukuran file dokumen tidak boleh melebihi 100MB.", + "apkSize": "Ukuran file paket instalasi tidak boleh melebihi 300MB.", + "zipSize": "Ukuran file zip tidak boleh melebihi 300MB.", + "groupDeleteUserTip": + "Hapus pengguna ini bersama dengan pesan yang dikirim pengguna ini di grup", + "nPersonRead": "{p} orang telah membaca", + "darkModeEnabled": + "Mode gelap diaktifkan, restart aplikasi untuk menerapkan perubahan", + "darkModeOff": + "Mode gelap dinonaktifkan, restart aplikasi untuk menerapkan perubahan", + "otherUser": "Pengguna lain", + "selectMsgDeleteTime": "Pilih waktu penyimpanan", + "selectMsgDeleteTimeTip": + "Riwayat obrolan akan dihapus secara otomatis setelah melewati batas waktu penyimpanan. Lanjutkan?", + "msgSaveTime": "Waktu penyimpanan riwayat obrolan", + "msgSaveTimeDesc": + "Membersihkan otomatis catatan obrolan yang melebihi batas waktu", + "hasTimeDeleteMsgPermission": + "Konfigurasi waktu penyimpanan pesan obrolan grup", + "accountAbnormal": "Pengecualian akun, undangan gagal!", + "onlyBrowser": "Hanya mendukung tampilan browser", + "cancelSendSuccess": "Pengiriman dibatalkan berhasil", + "allStr1": "Global", + "modelStr1": "Mode siang dan malam", + "atMaxCountTip": "@ paling banyak {p} orang", + "transmitTo": "Kirim ke", + "moreActions": "Aksi lainnya", + "refreshing": "Segarkan", + "openInBrowser": "Buka di peramban", + "coverFlow": "Alur sampul", + "checkNet": "Pemeriksaan Jaringan", + "autoChangeServer": "Pindah ke Server Optimal dengan Sekali Klik", + "serverLine": "Garis Server", + "timeOut": "Waktu Habis", + "serverLineChange": "Ganti Garis Server", + "netCheck": "Diagnostik Jaringan", + "netDelayCheck": "Pengecekan Keterlambatan Jaringan", + "netDelay1Check": "Keterlambatan Internet", + "msgDelay1Check": "Keterlambatan Pesan", + "imgDelay1Check": "Keterlambatan Gambar", + "netLine": "Garis Jaringan", + "reCheck": "Cek Ulang", + "netChecking": "Sedang memeriksa jaringan", + "locationAddr": "Alamat Lokasi", + "selectPerfectLineTip": "Kami telah memilih garis terbaik untuk Anda", + "selectPerfectLineTip2": + "Saat ini sudah merupakan garis terbaik, tidak perlu pindah", + "selectPerfectLineTipNoNet": + "Tidak dapat mengidentifikasi garis terbaik, harap periksa koneksi internet dan coba lagi", + "changing": "Sedang mengganti", + "changeSuccess": "Berhasil mengganti", + "waitAnswer": "Menunggu untuk dijawab", + "callFinish": "Panggilan Berakhir", + "autoChanging": + "Sedang mengganti secara otomatis, harap tunggu sebentar lagi", + "fileSizeConnotZero": "Ukuran file tidak boleh nol", + "confirmLogin": "Konfirmasi masuk", + "toolboxAudioCall": "Panggilan suara", + "requestTimeOut": "Waktu permintaan habis, coba lagi nanti", + "error_6115": "Kode QR sudah kedaluwarsa", + "groupNotFound": "Grup tidak ditemukan", + "userNotFound": "Pengguna tidak ditemukan", + "microphoneOpen": "Mikrofon terbuka", + "speakerOpen": "Pembicara terbuka", + "voiceWithYou": "Undang Anda untuk panggilan suara", + "callInterceptor": "Panggilan terputus", + "startCallError": "Kesalahan memulai panggilan", + "callHungUp": "Panggilan ditutup", + "callRemoteHungUp": "Panggilan dari pihak lain ditutup", + "callReceiverError": "Kesalahan menerima panggilan", + "starting": "Memulai", + "startTimeOut": "Waktu habis saat memulai panggilan suara", + "callWaitReceiver": "Menunggu pihak lain menerima", + "callRemoteNotAccept": "Pihak lain tidak menerima panggilan", + "callStartSendError": "Gagal memulai panggilan suara", + "notAccept": "Tidak menerima", + "callAccepting": "Menerima panggilan", + "call": "Panggilan", + "callDoing": "Sibuk, dibatalkan", + "netBad": "Kualitas jaringan buruk saat ini", + "netDown": "Jaringan terputus", + "onCalling": "Sedang menelepon", + "error_1303": "Anda belum menjadi teman dari pihak lain", + "remoteAccountError": "Akun pihak lain bermasalah", + "userOnlineStatus": "Status online", + "error_100008": "Pengguna ini tidak memenuhi syarat untuk menjadi administrator", + "callTimeLimit": "Batas waktu panggilan {p} menit setiap kali", + "notLoginOnline": "Belum pernah login", + "callRemoteDoing": "Sedang sibuk", + "exitLoginIng": "Sedang keluar", + "usePhoneRecovery": "Gunakan nomor telepon untuk pemulihan", + "useEmailRecovery": "Gunakan email untuk pemulihan", + "otherRecoveryMethods": "Metode pemulihan lainnya", + "emailAddress": "Alamat email", + "phoneErrorOrNotExist": "Nomor telepon salah atau tidak ada", + "phoneNotExist": "Nomor telepon tidak ada", + "verificationCodeFailure": "Gagal mendapatkan kode verifikasi, silakan coba lagi", + verificationCodeSent: "Kode verifikasi telah dikirim", + hiWelcomeLogin: "Hai~ Selamat datang di login", + otherLoginMethods: "Metode login lainnya", + usernamePhoneEmail: "Nama Pengguna/Nomor Telepon/Email", + "error_1303": "Anda belum menjadi teman dari pihak lain", + "remoteAccountError": "Akun pihak lain bermasalah", + "userOnlineStatus": "Status online", + "error_100008": + "Pengguna ini tidak memenuhi syarat untuk menjadi administrator", + "callTimeLimit": "Batas waktu panggilan %s menit setiap kali", + "notLoginOnline": "Belum pernah login", + "callRemoteDoing": "Sedang sibuk", + "exitLoginIng": "Sedang keluar", + "searchAreaCode": "Cari negara/wilayah", + "otherFindStyle": "Metode pemulihan lainnya", + "welcome": "Selamat datang masuk", + "fileReadError": "Terjadi kesalahan saat membaca file", + "docPreviewError": "File tidak dapat dipratinjau", + "docPreviewUseOther": + "Jika karena alasan jaringan atau ketidakcocokan perangkat, file tidak dapat dipratinjau, Anda bisa menggunakan", + "netTimeOut": "Waktu koneksi jaringan habis", + "docReadError": "Tidak dapat membaca file, harap periksa apakah format file sudah benar", + "errorDetail": "Detail kesalahan", + ip: "IP", + imageLoadingRoute: "Rute pemuatan gambar", + networkChecking: "Memeriksa jaringan...", + noPermission: "Tidak memiliki izin, silakan masuk setelah mendapatkan izin", + pageNotFound: "Halaman tidak ditemukan", + serverError: "Kesalahan server", + databaseError: "Kesalahan kueri database", + networkError: "Kesalahan koneksi jaringan" +}; diff --git a/src/lang/index.js b/src/lang/index.js new file mode 100644 index 0000000..2eb3edd --- /dev/null +++ b/src/lang/index.js @@ -0,0 +1,140 @@ +// vue-i18n所需要引入的 +import { createI18n } from "vue-i18n"; +// 本地的文件夹 +import enLocale from "./us"; +import zhLocale from "./zh-cn"; +import zhhantLocale from "./zh-hant-cn"; +import viVnLocale from "./vi"; +import thTHLocale from "./th"; +import idIDLocale from "./id"; +import ptBRLocale from "./pt-br"; +// vant所需要引入的 +import { Locale } from "vant"; +//vant中的文件夹 需要的语言和本地的语言保持一致 +import enUS from "vant/lib/locale/lang/en-US"; +import zhCN from "vant/lib/locale/lang/zh-CN"; +import zhTW from "vant/lib/locale/lang/zh-TW"; +import viVn from "vant/lib/locale/lang/vi-VN"; +import thTH from "vant/lib/locale/lang/th-TH"; +import idID from "vant/lib/locale/lang/id-ID"; +import ptBR from "vant/lib/locale/lang/pt-BR"; +const messages = { + us: { + ...enUS, + ...enLocale, + }, + "zh-cn": { + ...zhCN, + ...zhLocale, + }, + "zh-hant-cn": { + ...zhTW, + ...zhhantLocale, + }, + "vi-VN": { + ...viVn, + ...viVnLocale, + }, + th: { + ...thTH, + ...thTHLocale, + }, + id: { + ...idID, + ...idIDLocale, + }, + "pt-br": { + ...ptBR, + ...ptBRLocale, + }, +}; +// 更新vant组件库本身的语言变化,支持国际化 +const vantLocales = (lang) => { + console.log(lang, "langlanglanglang", navigator.language); + if (lang === "us") { + Locale.use("en-US", enUS); + } else if (lang === "zh-cn") { + Locale.use("zh-CN", zhCN); + } else if (lang === "zh-hant-cn") { + Locale.use("zh-TW", zhTW); + } else if (lang === "vi-VN" || lang === "vi" || lang === "vi-BR") { + Locale.use("vi-VN", viVn); + } else if (lang === "th") { + Locale.use("th-TH", thTH); + } else if (lang === "id") { + Locale.use("id-ID", idID); + } else if (lang === "pt-br") { + Locale.use("pt-BR", ptBR); + } +}; +// 获取浏览器的语言 +let lang = "us"; +console.log("navigator.language", navigator.language); +if ( + navigator.language === "zh-CN" || + navigator.language === "zh-SG" || + navigator.language === "zh-Hans" || + navigator.language === "zh-BR" +) { + lang = "zh-cn"; + document.title = "球帝带"; +} else if ( + navigator.language === "en" || + navigator.language === "en-GB" || + navigator.language === "en-US" || + navigator.language === "en-CA" || + navigator.language === "en-AU" || + navigator.language === "en-BR" +) { + lang = "us"; + document.title = "QDDchat"; +} else if ( + navigator.language === "zh-TW" || + navigator.language === "zh-Hant" || + navigator.language === "zh-HK" || + navigator.language === "zh-MO" +) { + lang = "zh-hant-cn"; + document.title = "球帝帶"; +} else if ( + navigator.language === "vi" || + navigator.language === "vi-VN" || + navigator.language === "vi-BR" +) { + lang = "vi-VN"; + document.title = "QDDchat"; +} else if ( + navigator.language === "th" || + navigator.language === "th-TH" || + navigator.language === "th-TH" || + navigator.language === "th-TH-u-ca-buddhist" || + navigator.language === "th-BR" +) { + lang = "th"; + document.title = "QDDchat"; +} else if ( + navigator.language === "id" || + navigator.language === "id-ID" || + navigator.language === "id-BR" +) { + lang = "id"; + document.title = "QDDchat"; +} else if (navigator.language === "pt-BR" || navigator.language === "pt") { + lang = "pt-br"; + document.title = "QDDchat"; +} +localStorage.setItem("language", lang); +const language = localStorage.getItem("language") || lang; +const i18n = createI18n({ + //Not available in legacy mode 解决报错问题的配置项!!! + legacy: false, + // 全局注册 $t方法 + globalInjection: true, + //设置初始化语言 + locale: language, + // 设置备用语言 + fallbackLocale: "us", + messages, +}); + +export { i18n, vantLocales }; diff --git a/src/lang/pt-br.js b/src/lang/pt-br.js new file mode 100644 index 0000000..2945905 --- /dev/null +++ b/src/lang/pt-br.js @@ -0,0 +1,1831 @@ +// 巴西语 +export default { + checkTopMessage:'Verificar mensagens', + unsupportedVideoFormat: "Formato de vídeo não suportado", + welcome: "Bem-vindo ao OpenIM", + phoneNumber: "Número de telefone", + plsEnterPhoneNumber: "Por favor, insira seu número de telefone", + password: "Senha", + plsEnterPassword: "Por favor, insira sua senha", + plsEnterPassword6_15: "Senha (6-15 caracteres alfanuméricos)", + account: "Conta", + plsEnterAccount: "Por favor, insira sua conta", + plsEnterAccount6_15: "Nome de usuário (6-15 caracteres alfanuméricos)", + forgetPassword: "Esqueceu a senha", + createAccount: "Criar conta", + verificationCodeLogin: "Login com código de verificação", + login: "Login", + noAccountYet: "Ainda não tem uma conta?", + loginNow: "Entrar agora", + registerNow: "Registrar agora", + lockPwdErrorHint: "Erro %s vezes", + newUserRegister: "Registro de novo usuário", + verificationCode: "Código de verificação", + verificationSuccessful: "Verificação bem-sucedida", + sendVerificationCode: "Enviar código de verificação", + verification_code1: "Por favor, insira o código de verificação", + correct_email_address: "Por favor, insira um endereço de e-mail correto", + Resend: "Reenviar", + bind_email: "Vincular e-mail", + email_verification: "Verificação de e-mail", + login_details: "Detalhes de login", + verification_code: "Por favor, insira o código de verificação do e-mail", + email_verification_code: "Código de verificação do e-mail", + currently_bound_email: "E-mail atualmente vinculado:", + text_email: "Se o seu e-mail não estiver mais em uso, por favor, atualize-o", + remove_this_device: "Tem certeza de que deseja remover este dispositivo?", + remove_this_device1: + "Tem certeza de que deseja remover este dispositivo? Após a remoção, você precisará verificar o login novamente ao usar este dispositivo", + go_to_settings: "Ir para configurações", + go_to_binding: "Ir para vinculação", + date_of_birth: "Data de nascimento", + confidential: "Confidencial", + year: "Anos", + not_perfection: "Não perfeito", + old_password: "Senha antiga", + myCreatedGroup: "Grupos que criei", + myManageGroup: "Grupos que gerencio", + myJoinGroup: "Grupos que participei", + please_enter_old_password: "Por favor, insira a senha antiga", + trusted_device: "Dispositivo confiável", + change_email_address: "Alterar e-mail", + current_device: "Dispositivo atual", + remove_device: "Remover dispositivo", + current_device_records: + "Aqui estão todos os registros de login neste dispositivo", + text_device: "Gerenciamento de dispositivos de login", + verification_time: "Tempo de verificação:", + text_device1: + "Aqui estão todos os seus dispositivos de login. Clique para ver os detalhes e registros de login deste dispositivo.", + change_the_binding: "Alterar a ligação", + resendVerificationCode: "Reenviar código de verificação", + verificationCodeTimingReminder: + "Obter código de verificação novamente em %s segundos", + defaultVerificationCode: "(Código de verificação padrão: %s)", + plsEnterVerificationCode: "Por favor, insira seu código de verificação", + invitationCode: "Código de convite", + device_list: "Lista de dispositivos", + plsEnterInvitationCode: "Por favor, insira seu código de convite %s", + optional: "Opcional", + nextStep: "Próximo passo", + plsEnterRightPhone: "Por favor, insira um número de telefone correto", + enterVerificationCode: "Digite o código de verificação do celular", + setPassword: "Definir senha", + plsConfirmPasswordAgain: "Por favor, confirme sua senha novamente", + text_password2: "Por favor, confirme a senha de login novamente", + please_enter_new_password_again: "Por favor, insira a nova senha novamente", + confirmPassword: "Confirmar senha", + wrongPasswordFormat: "Formato de senha incorreto", + plsCompleteInfo: "Por favor, complete suas informações pessoais", + plsEnterYourNickname: "Por favor, insira seu apelido", + pleaseEnterYourUsername: "Por favor, insira seu nome de usuário", + enterYourUsernameToHelpUsIdentifyYourAccount: + "Insira seu nome de usuário para nos ajudar a identificar sua conta", + text_1: + "Esta conta não está vinculada a um e-mail, portanto, a recuperação de senha não é suportada temporariamente", + setInfo: "Configurar informações", + loginPwdFormat: "6-20 dígitos alfanuméricos", + passwordLogin: "Login de senha", + contacts: "Contatos", + workbench: "Bancada de trabalho", + mine: "Meu", + me: "Eu", + draftText: "Rascunho", + everyone: "Todos", + you: "Você", + groupAc: "Anúncio do grupo", + createGroupNtf: "%s criou um grupo de bate-papo", + editGroupInfoNtf: "%s editou as informações do grupo", + quitGroupNtf: "%s saiu do grupo de bate-papo", + invitedJoinGroupNtf: "%s convidou %s para o grupo de bate-papo", + kickedGroupNtf: "%s foi removido do grupo de bate-papo por %s", + joinGroupNtf: "%s entrou no grupo de bate-papo", + dismissGroupNtf: "%s dissolveu o grupo de bate-papo", + transferredGroupNtf: "%s transferiu a administração do grupo para %s", + muteMemberNtf: "%s foi silenciado por %s por %s", + muteCancelMemberNtf: "%s teve o silenciamento cancelado por %s", + muteGroupNtf: "%s ativou o silenciamento do grupo", + muteCancelGroupNtf: "%s desativou o silenciamento do grupo", + friendAddedNtf: "Vocês agora são amigos e podem começar a conversar.", + openPrivateChatNtf: "Chat privado destrutivo ativado", + closePrivateChatNtf: "Chat privado destrutivo desativado", + memberInfoChangedNtf: "%s editou as informações de membro do grupo", + unsupportedMessage: "Tipo de mensagem não suportado no momento", + picture: "Imagem", + video: "Vídeo", + voice: "Voz", + safety: "Segurança", + location: "Localização", + file: "Arquivo", + carte: "Cartão de visita", + emoji: "Emoji personalizado", + chatRecord: "Registro de chat", + revokeMsg: "Retirou uma mensagem", + aRevokeBMsg: "%s retirou uma mensagem de %s", + blockedByFriendHint: + "A mensagem foi enviada, mas foi recusada pelo destinatário", + sendFriendVerification: "Verificação de amigo enviada", + removedFromGroupHint: "Você foi removido do grupo", + groupDisbanded: "O grupo foi dissolvido", + createdGroup: "Você ainda não criou grupos", + search: "Buscar", + newGroups: "Novo grupo", + groupNames: "Por favor, insira o nome do grupo", + synchronizing: "Sincronizando", + syncFailed: "Falha na sincronização", + connecting: "Conectando", + connectionFailed: "Falha na conexão", + top: "Fixar no topo", + cancelTop: "Cancelar fixação", + markHasRead: "Marcar como lido", + delete: "Excluir", + nPieces: "%s mensagens", + online: "Online", + offline: "Offline", + phoneOnline: "Celular", + pcOnline: "Computador", + webOnline: "Web", + webMiniOnline: "Mini Web", + upgradeFind: "Nova versão encontrada", + upgradeVersion: + "Detectada uma nova versão disponível %s, sua versão atual é %s", + upgradeDescription: "Descrição da atualização:", + upgradeIgnore: "Ignorar", + upgradeLater: "Atualizar mais tarde", + upgradeNow: "Atualizar agora", + upgradeNever: "Não atualizar", + inviteYouCall: "%s convida você para %s", + rejectCall: "Rejeitar", + acceptCall: "Aceitar", + callVoice: "Chamada de voz", + callVideo: "Chamada de vídeo", + sentSuccessfully: "Enviado com sucesso", + copySuccessfully: "Copiado com sucesso", + binding_successful: "Vinculado com sucesso", + day: "dia", + hour: "hora", + hours: "horas", + minute: "minuto", + seconds: "segundos", + cancel: "Cancelar", + determine: "Determinar", + failed_to_load: "Falha ao carregar", + failed_to_load1: "Falha ao carregar. Por favor, tente novamente mais tarde", + removal_failed: "Falha ao remover", + toolboxAlbum: "Álbum", + toolboxCall: "Chamada de vídeo", + toolboxCamera: "Câmera", + toolboxCard: "Cartão", + toolboxFile: "Arquivo", + toolboxLocation: "Localização", + send: "Enviar", + holdTalk: "Segure para falar", + releaseToSend: "Solte para enviar", + releaseToSendSwipeUpToCancel: + "Solte para enviar, deslize para cima para cancelar", + liftFingerToCancelSend: "Levante o dedo para cancelar o envio", + callDuration: "Duração da chamada %s", + cancelled: "Cancelado", + cancelledByCaller: "Cancelado pelo chamador", + rejectedByCaller: "Rejeitado pelo chamador", + callTimeout: "Tempo limite de chamada", + rejected: "Rejeitado", + forwardMaxCountHint: + "Atualmente, só é possível encaminhar até vinte mensagens~", + typing: "Digitando...", + addSuccessfully: "Adicionado com sucesso", + addFailed: "Falha ao adicionar", + setSuccessfully: "Configurado com sucesso", + callingBusy: "Você já está em uma chamada e não pode realizar esta operação!", + groupCallHint: + "O grupo atual está em uma chamada. Você tem certeza de que deseja participar desta chamada?", + joinIn: "Participar", + menuCopy: "Copiar", + menuDel: "Excluir", + menuForward: "Encaminhar", + menuReply: "Responder", + menuMulti: "Seleção múltipla", + menuRevoke: "Revogar", + menuAdd: "Adicionar", + nMessage: "%s novas mensagens", + plsSelectLocation: "Por favor, selecione uma localização", + groupAudioCallHint: "%s pessoas estão em uma chamada de áudio", + groupVideoCallHint: "%s pessoas estão em uma chamada de vídeo", + reEdit: "Reeditar", + playSpeed: "Velocidade de reprodução", + download: "Baixar", + download1: "Baixar agora", + nicknames: "Apelidos de usuário", + downloadAPP: "Baixar aplicativo", + private_chat: "Chat privado", + googleMap: "Mapa do Google", + check_for_updates: "Verificar atualizações", + appleMap: "Mapa da Apple", + baiduMap: "Mapa Baidu", + amapMap: "Mapa Amap", + signature: "Assinatura", + please_enter_user_nickname: "Por favor, insira o apelido do usuário", + set_characters: "Por favor, defina de 4 a 15 caracteres", + personalized_signature: + "Por favor, insira uma assinatura personalizada (limite de 6 a 50 caracteres)", + tencentMap: "Mapa Tencent", + offlineMeetingMessage: "Você recebeu um convite de reunião offline", + offlineMessage: "Você tem uma nova mensagem offline", + offlineCallMessage: "Você recebeu um convite de chamada offline", + logoutHint: "Tem certeza de que deseja fazer logout?", + myInfo: "Minhas informações", + workingCircle: "Círculo de trabalho", + accountSetup: "Configuração da conta", + aboutUs: "Sobre nós", + about: "Sobre", + bgColor: "Cor do fundo", + + logout: "Sair", + qrcode: "Código QR", + qrcodeHint: "Escaneie o código QR abaixo para me adicionar como amigo", + favoriteFace: "Rosto favorito", + favoriteManage: "Gerenciar", + favoriteCount: "%s emojis favoritos", + favoriteDel: "Excluir (%s)", + hasRead: "Lido", + unread: "Não lido", + nPersonUnRead: "%s pessoas não leram", + allRead: "Tudo lido", + messageRecipientList: "Lista de destinatários da mensagem", + hasReadCount: "Lido (%s)", + unreadCount: "Não lido (%s)", + newFriend: "Novo amigo", + newFriendList: "Lista de solicitações de amigos", + newGroup: "Novo grupo", + myFriend: "Meus amigos", + myGroup: "Meus grupos", + addManager: "Adicionar gerente", + privateSetting: "Configuração privada", + add: "Adicionar", + scan: "Escanear", + scanHint: "Escanear cartão de visita QR", + addFriend: "Adicionar amigo", + addFriendHint: "Adicionar por ID de usuário", + createGroup: "Criar grupo", + createGroupHint: "Criar grupo para usar o OpenIM", + addGroup: "Adicionar grupo", + addGroupHint: "Perguntar ao administrador ou membros do grupo pelo ID", + searchIDAddFriend: "Buscar ID para adicionar amigo", + searchIDAddGroup: "Buscar ID para adicionar grupo", + searchIDIs: "ID: %s", + searchPhoneIs: "Número de telefone: %s", + searchEmailIs: "E-mail: %s", + searchNicknameIs: "Apelido: %s", + searchGroupNicknameIs: "Apelido do grupo: %s", + noFoundUser: "Não foi possível encontrar este usuário", + noFoundGroup: "Não foi possível encontrar este grupo", + joinGroupDate: "Data de entrada no grupo", + joinGroupMethod: "Método de entrada no grupo", + byInviteJoinGroup: "Convidado por %s", + byIDJoinGroup: "Pesquisar ID do grupo", + byQrcodeJoinGroup: "QR do grupo", + groupID: "ID do grupo", + setAsAdmin: "Definir como administrador", + setMute: "Definir como silenciado", + organizationInfo: "Informações da organização", + organization: "Organização", + department: "Departamento", + position: "Cargo", + personalInfo: "Informações pessoais", + viewDynamics: "Ver dinâmicas", + audioAndVideoCall: "Chamada de áudio e vídeo", + sendMessage: "Enviar mensagem", + avatar: "Avatar", + name: "Nome", + nickname: "Apelido", + gender: "Gênero", + englishName: "Nome em inglês", + birthDay: "Data de nascimento", + tel: "Telefone", + mobile: "Celular", + email: "E-mail", + man: "Masculino", + woman: "Feminino", + friendSetup: "Configurações de amigo", + setupRemark: "Configurar observação", + recommendToFriend: "Recomendar a um amigo", + addToBlacklist: "Adicionar à lista negra", + unfriend: "Terminar amizade", + areYouSureDelFriend: "Tem certeza de que deseja excluir o amigo?", + areYouSureAddBlacklist: + "Tem certeza de que deseja adicionar o amigo à lista negra?", + remark: "Observação", + save: "Salvar", + saveSuccessfully: "Salvo com sucesso", + saveFailed: "Falha ao salvar", + groupVerification: "Verificação do grupo", + friendVerification: "Verificação do amigo", + email_address: "Por favor, insira o endereço de e-mail", + sendEnterGroupApplication: "Enviar solicitação de entrada no grupo", + sendToBeFriendApplication: "Enviar solicitação para ser amigo", + sendSuccessfully: "Enviado com sucesso", + reset_successful: "Redefinido com sucesso", + bind_new_email: "Vincular novo e-mail", + Login_failed: "Falha no login", + successful_home: "Registrado com sucesso", + sendFailed: "Falha ao enviar", + canNotAddFriends: "Este usuário está configurado para não ser adicionado!", + mutedAll: "Mudo para todos", + tenMinutes: "10 minutos", + oneHour: "1 hora", + twelveHours: "12 horas", + oneDay: "1 dia", + custom: "Personalizado", + unmute: "Desativar mudo", + youMuted: "Você foi silenciado", + groupMuted: "Mudo de grupo ativado", + notDisturbMode: "Modo Não Perturbe", + allowRing: "Som de notificação de nova mensagem", + allowVibrate: "Vibração de nova mensagem", + forbidAddMeToFriend: "Proibir adição como amigo", + blacklist: "Lista negra do catálogo de endereços", + unlockSettings: "Desbloquear configurações", + changePassword: "Alterar senha", + clearChatHistory: "Limpar histórico de bate-papo", + confirmClearChatHistory: + "Tem certeza de que deseja limpar todo o histórico de bate-papo?", + languageSetup: "Configurações de idioma", + language: "Idioma", + english: "Inglês", + setting_up: "Configurando...", + chinese: "Chinês Simplificado", + traditional_chinese: "Chinês Tradicional", + followSystem: "Seguir sistema", + blacklistEmpty: "Lista negra vazia", + remove: "Remover", + removeFriend: "Remover amigo", + fingerprint: "Impressão digital", + gesture: "Gesto", + biometrics: "Biometria", + plsEnterPwd: "Por favor, insira a senha", + plsEnterOldPwd: "Por favor, insira a senha antiga", + plsEnterNewPwd: "Por favor, insira a nova senha", + plsConfirmNewPwd: "Por favor, confirme a nova senha", + reset: "Resetar", + oldPwd: "Senha antiga", + newPwd: "Nova senha", + confirmNewPwd: "Confirmar nova senha:", + plsEnterConfirmPwd: "Por favor, insira a senha de confirmação", + twicePwdNoSame: "As senhas inseridas não são iguais", + twicePwd: "As novas senhas não correspondem, por favor, redefina", + changedSuccessfully: "Alterado com sucesso", + deleteSuccessfully: "Excluído com sucesso", + checkNewVersion: "Verificar nova versão", + noAddGround: "Ainda não foram adicionados amigos ao grupo", + chatContent: "Conteúdo do chat", + topContacts: "Contatos principais", + messageNotDisturb: "Não perturbar mensagens", + messageNotDisturbHint: "Receber mensagens sem ser notificado", + burnAfterReading: "Autodestruição", + timeSet: "Configuração de tempo", + setChatBackground: "Definir fundo do chat", + fontFace: "Tipo de letra", + fontSize: "Tamanho da fonte", + little: "Pequeno", + standard: "Padrão", + big: "Grande", + thirtySeconds: "30 segundos", + fiveMinutes: "5 minutos", + clearAll: "Limpar tudo", + clearSuccessfully: "Limpeza concluída com sucesso", + groupChatSetup: "Configuração do chat em grupo", + viewAllGroupMembers: "Ver todos os membros do grupo (%s)", + groupManage: "Gerenciar grupo", + myGroupMemberNickname: "Meu apelido no grupo", + topChat: "Chat fixado", + muteAllMember: "Silenciar todos os membros", + exitGroup: "Sair do grupo", + dismissGroup: "Encerrar o grupo", + dismissGroupHint: + "Todos os membros serão removidos deste grupo e não poderão mais enviar ou receber mensagens", + quitGroupHint: "Você não receberá mais mensagens deste grupo depois de sair.", + joinGroupSet: "Configuração de entrada no grupo", + allowAnyoneJoinGroup: "Permitir que qualquer pessoa entre no grupo", + inviteNotVerification: + "Convites de membros do grupo não precisam de verificação", + needVerification: "É necessário enviar uma mensagem de verificação", + addMember: "Adicionar", + delMember: "Remover", + groupOwner: "Proprietário do grupo", + groupAdmin: "Administrador do grupo", + notAllowSeeMemberProfile: + "Não permitir ver o perfil de outros membros do grupo", + notAllAddMemberToBeFriend: + "Não permitir adicionar membros do grupo como amigos", + transferGroupOwnerRight: "Transferir direitos de proprietário do grupo", + groupName: "Nome do grupo", + groupAcPermissionTips: + "Apenas o proprietário do grupo e administradores podem editar", + plsEnterGroupAc: "Por favor, insira o anúncio do grupo", + edit: "Editar", + publish: "Publicar", + groupMember: "Membros do grupo", + selectedPeopleCount: "Selecionado (%s)", + confirmSelectedPeople: "Confirmar (%s/%s)", + confirm: "Confirmar", + log_in_time: "Hora do login:", + log_in_time1: "Hora do login", + login_IP: "IP do login:", + confirmTransferGroupToUser: + "Confirmar a transferência da propriedade do grupo para: %s?", + removeGroupMember: "Remover membro do grupo", + searchNotResult: "Nenhum resultado encontrado na pesquisa", + groupQrcode: "QR Code do grupo", + groupQrcodeHint: + "Escanear o QR code abaixo para entrar imediatamente no chat do grupo", + approved: "Aprovado", + accept: "Aceitar", + reject: "Rejeitar", + friendGrouping: "Agrupamento de amigos", + waitingForVerification: "Aguardando verificação do outro lado", + rejectSuccessfully: "Rejeição bem sucedida", + rejectFailed: "Falha ao rejeitar", + applyJoin: "Aplicar para entrar", + enterGroup: "Entrar no chat do grupo", + applyReason: "Motivo da aplicação: %s", + invite: "Convidar", + sourceFrom: "Fonte: %s", + byMemberInvite: "Convite de membro", + bySearch: "Por busca", + byScanQrcode: "Escanear QR code", + iCreatedGroup: "Eu criei", + iJoinedGroup: "Eu entrei", + nPerson: "{count} pessoa(s)", + searchNotFound: "Nenhum resultado encontrado na pesquisa", + searchNotFoundMember: "Nenhum usuário relacionado encontrado", + organizationStructure: "Estrutura da organização", + recentConversations: "Conversas recentes", + selectAll: "Selecionar todos", + plsEnterGroupNameHint: + "Escolha um nome para o grupo para facilitar buscas futuras", + completeCreation: "Concluir criação", + sendCarteConfirmHint: "Confirmar o envio deste contato para este chat?", + sentSeparatelyTo: "Enviar separadamente para:", + sentTo: "Enviar para:", + leaveMessage: "Deixar mensagem", + mergeForwardHint: "[Encaminhar em conjunto] %s mensagens no total", + mergeForward: "Encaminhar em conjunto", + quicklyFindChatHistory: "Encontrar histórico de conversas rapidamente", + notFoundChatHistory: "Não foi encontrado nenhum conteúdo relacionado a %s", + globalSearchAll: "Todos", + globalSearchContacts: "Contatos", + globalSearchGroup: "Grupos", + globalSearchChatHistory: "Histórico de conversas", + globalSearchChatFile: "Arquivos", + relatedChatHistory: "%s conversas relacionadas", + seeMoreRelatedContacts: "Ver mais contatos relacionados", + seeMoreRelatedGroup: "Ver mais grupos relacionados", + seeMoreRelatedChatHistory: "Ver mais histórico de conversas relacionadas", + seeMoreRelatedFile: "Ver mais arquivos relacionados", + publishPicture: "Publicar imagem", + publishVideo: "Publicar vídeo", + mentioned: "Mencionado: %s", + comment: "Comentário", + like: "Curtir", + reply: "Responder", + rollUp: "Recolher", + fullText: "Texto completo", + selectAssetsFromCamera: "Selecionar da câmera", + selectAssetsFromAlbum: "Selecionar do álbum", + whoCanWatch: "Quem pode ver", + remindWhoToWatch: "Lembre quem assistir", + public: "Público", + everyoneCanSee: "Todos podem ver", + partiallyVisible: "Parcialmente visível", + visibleToTheSelected: "Visível para os selecionados", + partiallyInvisible: "Parcialmente invisível", + invisibleToTheSelected: "Invisível para os selecionados", + private: "Privado", + onlyVisibleToMe: "Apenas visível para mim", + selectVideoLimit: "A duração não pode exceder 15 segundos", + selectContactsLimit: "Selecione pelo menos um contato", + message: "Mensagem", + commentedYou: "Comentou em você: %s", + likedYou: "Gostou de você", + mentionedYou: "Mencionou você", + replied: "Respondeu", + detail: "Detalhes", + totalNPicture: "Total de %s imagens", + noDynamic: "Nenhuma dinâmica", + callRecords: "Registro de chamadas", + allCall: "Todas as chamadas", + missedCall: "Chamadas perdidas", + incomingCall: "Recebidas", + outgoingCall: "Realizadas", + microfone: "microfone", + "alto-falante": "alto-falante", + desligar: "desligar", + atender: "atender", + esperandoChamada: "esperando chamada...", + esperandoChamadaVoz: "à espera da resposta do outro lado...", + conviteChamadaVoz: "convidou você para uma chamada de voz...", + esperandoChamadaVideo: "à espera da resposta do outro lado...", + conviteChamadaVideo: "convidou você para uma chamada de vídeo...", + esperandoResposta: "à espera da resposta", + convidouParaChamada: "convidou você para uma chamada", + chamando: "em chamada", + nPessoasChamando: "%s pessoas em chamada", + quemConvidouChamadaVoz: "%s convidou você para uma chamada de voz", + quemConvidouChamadaVideo: "%s convidou você para uma chamada de vídeo", + plsInputMeetingSubject: "Por favor, insira o assunto da reunião", + meetingStartTime: "Hora de início", + meetingDuration: "Duração da reunião", + enterMeeting: "Entrar na reunião", + meetingNo: "Número da reunião", + yourMeetingName: "Seu nome", + plsInputMeetingNo: "Por favor, insira o número da reunião", + plsInputYouMeetingName: "Por favor, insira seu nome", + meetingSubjectIs: "Assunto da reunião: %s", + meetingStartTimeIs: "Hora de início: %s", + meetingDurationIs: "Duração da reunião: %s", + meetingHostIs: "Anfitrião: %s", + meetingNoIs: "Número da reunião: %s", + meetingMessageClickHint: + "Clique nesta mensagem para entrar na reunião diretamente", + meetingMessage: "Mensagem da reunião", + openMeeting: "Reunião em andamento", + didNotStart: "Não iniciada", + started: "Iniciada", + meetingInitiatorIs: "Reunião em vídeo iniciada por %s", + meetingDetail: "Detalhes da reunião", + meetingOrganizerIs: "Organizador: %s", + updateMeetingInfo: "Atualizar informações da reunião", + cancelMeeting: "Cancelar reunião", + videoMeeting: "Reunião de vídeo", + joinMeeting: "Entrar na reunião", + bookAMeeting: "Agendar reunião", + quickMeeting: "Reunião rápida", + confirmTheChanges: "Confirmar alterações", + invitesYouToVideoConference: "%s convida você para uma conferência de vídeo", + over: "Encerrar", + meetingMute: "Mudo", + meetingUnmute: "Desmutar", + meetingCloseVideo: "Fechar vídeo", + meetingOpenVideo: "Abrir vídeo", + meetingEndSharing: "Encerrar compartilhamento", + meetingShareScreen: "Compartilhar tela", + meetingMembers: "Membros(%s)", + settings: "Configurações", + leaveMeeting: "Sair da reunião", + endMeeting: "Encerrar reunião", + leaveMeetingConfirmHint: "Tem certeza de que deseja sair da reunião?", + endMeetingConfirmHit: "Tem certeza de que deseja encerrar a reunião?", + meetingSettings: "Configurações da reunião", + allowMembersOpenMic: "Permitir que os membros desmutem-se", + allowMembersOpenVideo: "Permitir que os membros ativem o vídeo", + onlyHostShareScreen: "Somente o host pode compartilhar a tela", + onlyHostInviteMember: "Somente o host pode convidar membros", + defaultMuteMembers: "Membros entram na reunião em modo mudo", + pinThisMember: "Fixar este membro", + unpinThisMember: "Desafixar este membro", + allSeeHim: "Todos veem ele", + cancelAllSeeHim: "Cancelar todos veem ele", + muteAll: "Silenciar todos", + unmuteAll: "Desmutar todos", + members: "Membros", + screenShare: "Compartilhamento de tela", + screenShareHint: "Compartilhando tela.", + meetingClosedHint: + "A reunião foi encerrada ou a conexão foi perdida. Tem certeza de que deseja sair?", + meetingIsOver: "A reunião já terminou!", + networkError: "Erro de rede. Por favor, tente novamente mais tarde!", + shareSuccessfully: "Compartilhamento realizado com sucesso!", + notFoundMinP: "Aplicativo Mini Programa não encontrado", + notSendMessageNotInGroup: + "Não é possível enviar mensagens em grupos dos quais você saiu", + whoModifyGroupName: "{name} modificou o nome do grupo para ", + accountWarn: "Aviso!", + accountException: + "Sua conta foi acessada em outro dispositivo. Por favor, altere sua senha imediatamente.", + tagGroup: "Tag", + issueNotice: "Emitir Aviso", + createTagGroup: "Criar Grupo de Tag", + plsEnterTagGroupName: "Por favor, insira o nome da tag", + tagGroupMember: "Membro do Grupo de Tag", + completeEdit: "Concluir edição", + finish: "Finalizar", + emptyTagGroup: "Grupo de tag vazio", + confirmDelTagGroupHint: + "Tem certeza de que deseja excluir este grupo de tag?", + editTagGroup: "Editar Tag", + newBuild: "Novo", + receiveMember: "Membro Receptor", + emptyNotification: "Nenhuma notificação", + notificationReceiver: "%s destinatários: %s", + sendAnother: "Enviar outro", + confirmDelTagNotificationHint: + "Tem certeza de que deseja excluir este registro de notificação?", + contentNotBlank: "O conteúdo não pode estar em branco", + plsEnterDescription: "Por favor, insira o texto da descrição", + gifNotSupported: "GIF não é suportado", + register: "Registrar", + hk: "Tradicional", + RegisterSuccess: "Registro realizado com sucesso", + LoginSuccess: "Login realizado com sucesso", + init_validate_1: + "O nome de usuário deve ter entre 6 e 15 caracteres alfanuméricos", + init_validate_10: + "Número de telefone ou senha incorretos, por favor, tente novamente", + init_validate_5: "A senha deve ter de 6 a 15 caracteres, incluindo letras e números. Por favor, redefina a senha.", + init_validate_2: "As senhas não coincidem, por favor, insira novamente", + init_validate_3: + "A senha deve conter entre 6 e 15 caracteres alfanuméricos. Por favor, defina uma nova senha", + init_validate_6: "A senha deve conter entre 6 e 15 caracteres alfanuméricos", + init_validate_4: + "Registro realizado com sucesso, você será redirecionado para a página de login", + init_hint_1: "6-15 dígitos ou letras", + init_hint_2: "Confirmar senha de login", + init_hint_3: "Senha de login", + username: "Nome de usuário", + to_modify: "Modificar", + confirm_password: "Confirmar senha", + notification_settings: "Configurações de notificação", + init_text_1: "Ao registrar e fazer login, você concorda com", + init_text_2: "o Contrato de Serviço do Usuário", + init_text_3: " e ", + init_text_4: "a Política de Privacidade", + init_text_5: "do Contrato de Serviço do Usuário e da Política de Privacidade", + init_text_6: + "Para proteger seus direitos legais, por favor, leia e concorde com os seguintes termos", + refuse: "Recusar", + refuse_continue: "Concordar e continuar", + forget_password: "Esqueceu a senha?", + register_user: "Registrar conta", + gotoLogin: "Entrar", + gotoRegister: "Criar uma nova conta", + enterGroupCheck: "Solicitação de entrada no grupo", + openPhotoError_1: "Falha ao carregar o arquivo selecionado", + openPhotoError_2: "Falha ao fazer upload do arquivo", + openPhotoError_3: "Falha ao ler as informações do arquivo", + agreeAll: "Concordar com tudo", + whatCanManagerDo: "Permissões de gerenciamento de grupo", + changeGroupName: "Alterar nome do grupo", + deleteMemberMessage: "Excluir mensagem de membro do grupo", + chatPrivate: "Conversa privada criptografada com membros do grupo", + memberMute: "Silenciar membro do grupo", + deleteMember: "Remover membro", + changeNotice: "Publicar/Alterar/Excluir aviso do grupo", + checkInGroup: "Verificar entrada no grupo", + setAdmin: "Definir como administrador", + deleteAdmin: "Remover administração", + deleteAdminTitle: "Tem certeza de que deseja remover este administrador?", + deleteAdminDetail: + "Ao remover este membro como administrador, ele não poderá gerenciar as mensagens do grupo", + deleteSuccess: "Remoção bem-sucedida", + mobile_web_page: "Acessar página da web móvel", + deleteMemberTitle: "Tem certeza de que deseja remover este membro?", + deleteMemberDetail: + "Ao remover este membro do grupo, ele não poderá enviar nem receber mensagens", + deleteSelf: "Você não pode remover a si mesmo", + handled: "Processado", + unhandle: "Aguardando", + noCheck: "Nenhuma solicitação para processar no momento", + success: "Operação bem-sucedida", + nameLenth: "Por favor, defina entre 1 e 15 caracteres", + editNickname: "Editar apelido do grupo", + text_password: "Por favor, defina uma nova senha de login", + quit: "Sair", + waitAMoment: "Aguarde um momento", + editWarningTitle: + "Tem certeza de que deseja sair da edição do apelido do grupo?", + editWarningDetail: + "As alterações no apelido do grupo não foram salvas. Deseja continuar e sair?", + editGroupAvatar: "Editar avatar do grupo", + myGroupAvatar: "Meu avatar de grupo", + inviteLink: "Link de convite", + inviteTips: + "Qualquer usuário que tenha este aplicativo instalado poderá ingressar em seu grupo através deste link de convite", + inviteLinkCopy: "Link de convite copiado", + shareLink: "Compartilhar link", + manageLink: "Gerenciar link de convite", + copyLink: "Copiar link", + deleteLink: "Excluir link", + deleteLinkTipsTitle: "Tem certeza de que deseja revogar este link?", + deleteLinkTipsDetail: + "Tem certeza de que deseja revogar este link de convite? Uma vez revogado, outros não poderão ingressar no grupo através deste link.", + linkManage: "Gerenciamento de link", + openManage: + "Depois de ativado, é necessário a confirmação do administrador ou proprietário do grupo para ingressar no grupo", + timeValid: "Limite de tempo válido", + linkInvalidTips: + "O link de convite expirará após esta data e não poderá mais ser acessado", + valid: "Válido internamente", + dayValid: "Válido por dias", + forever: "Para sempre", + foreverValid: "Válido permanentemente", + attendTips: + "A solicitação de entrada foi enviada. Após a aprovação do administrador, você poderá ingressar no grupo", + memberCountLimit: "O limite de membros deste grupo foi alcançado", + linkIsOut: "O link de convite expirou", + requestAttend: "Solicitar entrada no grupo", + needCheck: "A entrada neste grupo requer aprovação do administrador", + countLimit: "O número máximo de administradores foi atingido", + openLock: + "Após sair do aplicativo, será necessário usar uma solução de desbloqueio após o tempo limite", + closeLock: "Não é necessário autenticação ao iniciar o aplicativo", + checkTips: + "Verifique se as permissões de rosto ou impressão digital estão ativadas~", + face: "Desbloqueio facial", + finger: "Desbloqueio por impressão digital", + gestureLock: "Desbloqueio por senha de gestos", + closed: "Desativado", + opened: "Aberto", + lanchProtect: "Proteção ao iniciar", + noProtect: "Sem proteção necessária", + lockSetting: "Configuração de desbloqueio", + drawLine: "Desenhar padrão de desbloqueio", + drawOldLine: "Desenhar seu padrão de desbloqueio antigo", + drawNewLine: "Desenhar seu novo padrão de desbloqueio", + setGesturePassword: "Definir senha de gestos", + checkGesturePassword: "Verificar senha de gestos", + needJoinPoint: "Pelo menos conecte 4 pontos, por favor, desenhe novamente", + noFriendAdd: "Não há amigos para adicionar", + noFriendDelete: "Não há amigos para remover", + noVerify: "É necessário verificação ao me adicionar", + drawAgain: "Por favor, desenhe novamente", + checkSuccessful: "Verificação bem-sucedida", + notInCommon: + "Incompatível com o último padrão desenhado, por favor, desenhe novamente", + changeGesturePassword: "Alterar senha de gestos", + toChange: "Ir para alteração", + change: "Alterar", + openSuccess: "Abertura bem-sucedida", + closeSuccess: "Fechamento bem-sucedido", + hasBeenMember: "Já é membro do grupo, não precisa se juntar novamente", + joinGroup: "entrar no grupo", + checking: "Verificação do administrador em andamento", + noUnlock: "Não é possível desbloquear?", + inputGesture: "Por favor, insira o desbloqueio de gestos", + faceDetect: "Por favor, faça o reconhecimento facial", + fingerDetect: "Por favor, faça o reconhecimento de impressão digital", + tryUnlocking: "Tente o seguinte: não é possível desbloquear?", + countRemain: "Padrão incorreto, você ainda pode tentar %s vezes", + reLoginUnlock: "Desbloqueio ao fazer login novamente", + selectVerityType: "Por favor, selecione o tipo de verificação ", + currentTypeNotSupport: "O tipo atual não suporta", + //------------- + updateNewVersion: "Nova versão encontrada", + updateOkUpdate: "Atualizar agora", + updateOpenDownloadFailed: "Falha ao abrir o download", + updateCurrentIsNew: "Atualização mais recente já instalada", + newVersionNotFound: + "Link para a versão mais recente não encontrado. Você pode visitar o site para baixar", + openUrlFailed: "Link inválido", + qmvMsgNotExit: "[Conteúdo citado não existe]", + qmvMsgNotSupport: "[Conteúdo citado não é suportado]", + dataError: "Erro na análise dos dados", + userNotBindEmail: "Usuário não vinculou o e-mail", + userNotRegister: "Usuário não registrado", + checkError: "Erro ao verificar", + csvTitle: "Configurações de bate-papo", + csvSetTopMsg: "Fixar conversa no topo", + csvSetOptMsg: "Silenciar notificações", + csvSetDisableMsg: "Bloquear mensagens", + csvClearMsg: "Limpar histórico de conversas", + csvClearLocal: "Limpar apenas o histórico de conversas local", + csvClearUser: "Limpar o histórico de conversas local e de %s", + csvSetErrorTip: "Falha ao configurar, por favor, verifique a conexão de rede", + gmiTitle: "Apelido do grupo:", + gmiSetTalk: "Conversa privada criptografada", + gmiSetMute: "Definir silêncio no grupo", + gmiDeleteUser: "Remover do grupo", + gmiMuteTo: "Silenciado até ", + gmiError1: + "Informações do membro não encontradas, possivelmente removido do grupo", + gmiError2: + "Informações do membro do grupo não encontradas, você pode ter sido removido do grupo", + gmiError3: "Tem certeza de que deseja remover este usuário?", + gmiError4: + "Remover este membro do grupo impedirá a troca de mensagens entre vocês", + gmiError5: "Você não tem permissão para remover este administrador", + gmiError6: "Você não tem permissão para remover este membro", + gmiError7: "Silenciamento removido", + gmiError8: + "Você não tem permissão para remover o silenciamento deste administrador", + gmiError9: "Você não tem permissão para remover o silenciamento deste membro", + gmiError10: "Você não tem permissão para silenciar este administrador", + gmiError11: "Você não tem permissão para silenciar este membro", + gmiError12: "Silenciamento removido", + gmiGroupOwnerNotFound: "Falha ao carregar informações do grupo", + smmvTitle: "Duração do silenciamento", + smmvMuteTime: "Selecione a duração do silenciamento", + smmvMuteTime_10m: "10 minutos", + smmvMuteTime_1hour: "1 hora", + smmvMuteTime_12hour: "12 horas", + smmvMuteTime_1day: "1 dia", + smmvMuteTime_30day: "30 dias", + smmvMuteTime_7day: "7 dias", + egnpTitle: "Editar nome do grupo", + egnpSetGroupName: "Por favor, defina o nome do grupo", + egnpSetTextDesc: "Por favor, defina entre 1 e 15 caracteres", + egnpSetTitleNoPermission: + "Você não tem permissão para modificar o nome do grupo", + egnpSetFailed: "Falha ao modificar, por favor, tente novamente mais tarde", + setGroupNameHint: "Por favor, defina o nome do grupo", + givTitle: "Editar nome do grupo", + givGroupName: "Nome do grupo", + givGroupFace: "Foto do grupo", + gmsvTitle: "Gerenciar grupo", + gmsvSetManager: "Definir administradores", + gmsvToSet: "Ir para configurações", + gmsvFindMsg: "Procurar histórico de mensagens", + gmsvFindMsgDesc: + "Quando ativado, os novos membros poderão ver as mensagens recentes", + gmsvMute: "Silenciar todos", + gmsvMuteDesc: + "Quando ativado, apenas o proprietário do grupo e os administradores podem enviar mensagens", + gmsvApply: "Confirmação de entrada no grupo", + gmsvApplyDesc: + "Quando ativado, a entrada no grupo requer confirmação do proprietário ou administradores", + gmsvSetManagerTip: + "Apenas o proprietário do grupo pode definir administradores", + gmsvSetMuteTipOn: "Silenciamento global ativado", + gmsvSetNuteTipOff: "Silenciamento global desativado", + gmsvApplyTipOn: "Confirmação de entrada no grupo ativada", + gmsvApplyTipOff: "Confirmação de entrada no grupo desativada", + gmsvFindGroupErr: "Falha ao obter informações do grupo", + gmsvFindMsgOn: "Pesquisa de histórico de mensagens ativada", + gmsvFindMsgOff: "Pesquisa de histórico de mensagens desativada", + gmsvSetFailed: "Falha ao definir", + gsvTitle: "Detalhes do grupo", + gsvGroupId: "ID do grupo", + gsvGroupMember: "Membros do grupo", + gsvGroupMemberValue: "Total de {num} pessoas", + gsvGroupNotification: "Aviso do grupo", + gsvMyName: "Meu apelido neste grupo", + gsvMyFace: "Minha foto neste grupo", + gsvGroupManager: "Gerenciamento do grupo", + gsvGroupApply: "Confirmação de entrada no grupo", + gsvFindMsg: "Procurar histórico de mensagens", + gsvSetTop: "Fixar", + gsvSetOpt: "Silenciar notificações", + gsvDeleteMsg: "Excluir histórico de mensagens", + gsvDeleteLocal: "Limpar apenas o histórico de mensagens local", + gsvDeleteAllMsg: + "Limpar o histórico de mensagens local e de todos os membros", + gsvDeleteOtherMsg: 'Limpar os registros de conversa locais e do outro usuário', + gsvGroupDelete: "Tem certeza de que deseja dissolver este grupo?", + gsvOutGroup: "Tem certeza de que deseja sair deste grupo?", + gsvNoNotification: "Nenhum aviso disponível", + gsvClearSuccess: "Limpeza concluída", + gsvClearFailed: "Falha ao limpar, por favor, tente novamente mais tarde", + cvAllPeople: "Todos", + cvVideo: "[Vídeo]", + cvPicture: "[Imagem]", + cvFile: "[Ficheiro]", + cvVoice: "[Voz]", + cvNotSupportMsg: "Mensagem não suportada", + cvSendMsgErrorNoPermission: + "Devido às configurações de permissão da outra parte, você não pode enviar mensagens para ela", + cvSendMsgPnMute: "Silenciado, incapaz de enviar mensagens", + cvCopyMsg: "Copiado para a área de transferência", + cvCopyMsgNotSupport: "Mensagem não suportada", + cvdeleteLocal: "Excluir localmente", + cvdeleteLocalAndServer: "Excluir localmente e no servidor", + cvdeleteLocalAndServerUser: "Excluir localmente e em {name}", + cvClearMsgTip: "Mensagens da conversa foram limpas", + cvGroupCreateSuccess: "Grupo criado com sucesso", + cvGroupCreateSuccessTip1: + "Parabéns pela criação do grupo. Clique no botão abaixo para convidar amigos para o grupo", + cvGroupCreateSuccessGo: "Convidar amigos para o grupo", + cvGroupNotification: "Aviso do grupo:", + sendMsg: "Enviar mensagem", + cvOnlineCount: "{count} pessoas online", + cvUserCount: "Total de {count} pessoas", + cvNotAtUser: + "Ainda não há membros do grupo disponíveis. @ Oh, vai adicionar membros agora", + all: "Todos", + clCloseConversation: "Ocultar conversa fixada", + clOpenConversation: "conversas fixadas", + clNoConversation: "Sem conversas recentes", + clTitle: "Conversas", + clDelete: "Esta conversa será excluída", + clDeleteDesc: + "Ao excluir esta conversa, o histórico de mensagens local será removido", + clDeleteSel: "Você não selecionou nenhuma conversa", + clDeleteSel1: "Esta conversa será excluída", + clDeleteSel2: "Estas conversas serão excluídas", + clDeleteDesc2: + "Ao excluir estas conversas, o histórico de mensagens local será removido", + homeItem0: "Conversas", + homeItem1: "Contatos", + homeItem2: "Meu perfil", + exitTip: "Pressione novamente para sair", + mineSigEmpty: "Este usuário não configurou uma assinatura ainda", + uaProtocol: "Termos de Serviço do Usuário", + savSearchTip: "Pesquisa rápida de conteúdo da conversa", + savMedia: "Imagens/Vídeos", + searchEmptyTip: "Nenhuma conversa encontrada", + savSearchMsg: "mensagens relacionadas", + savConversation: "Conversa", + savMsg: "Histórico de conversa", + savConversationNotFound: "Conversa não encontrada", + savMsgNotFound: "Mensagem não encontrada", + samTitle: "Mídia", + samTimeFormat: "Mês de yyyy", + notSupportType: "Tipo não suportado", + media: "Mídia", + openUrl: "Abrir link", + lkAddGroupUrl: "Link para entrar no grupo", + lkAddOtherUrl: "Link externo, pode ser aberto no navegador padrão", + lkUnknowUrl: "Link desconhecido", + openUrlFailed1: "Falha ao abrir o link", + yszcTitle: "Política de Privacidade", + slsmTitle: "Declaração jurídica", + + ctmRemove: "Remover", + ctmOkRemove: "Confirmar remoção", + ctmTopMsgCount: "{count} mensagens", + ctmEmptyText: "Nenhuma mensagem fixada", + recordTimeShort: "Tempo de gravação muito curto", + sendCodeExceErr: + "Falha ao enviar o código de verificação, tente novamente mais tarde", + bindEmailExecErr: "Falha ao vincular o e-mail, tente novamente mais tarde", + ctmSuccess: "Fixação bem-sucedida", + ctmRemoveSuccess: "Remoção da mensagem fixada com sucesso", + ctmMsgNotFound: "Esta mensagem não existe", + cvFirstTip: + "As mensagens e chamadas nesta conversa estão protegidas com criptografia de ponta a ponta", + clearGroupNotification: "A notificação do grupo foi limpa", + publicGroupNotification: " Publicar notificação", + dialog_tip1: " Dica", + deleteGroups: + " Após a exclusão do grupo, ele não poderá ser recuperado. Tem certeza de que deseja excluir este grupo?", + dialog_tip_token_invaild: " A sessão expirou, por favor faça login novamente", + notify_new_msg_title: "Você recebeu uma nova mensagem", + notify_new_msg_msg: "Conteúdo da mensagem: .....", + dataError1: "Erro de dados", + groupApply: " pedidos de entrada no grupo", + muteLate: "Desativar mudo após", + groupMuteing: "Mudo de grupo ativado", + modifyGroupName: "Modificar nome do grupo", + groupMemberNotFound: "Membro do grupo não encontrado", + deleteGround: "Excluir grupo", + bindEmailTipTitle: "Vincular E-mail", + bindEmailTipContent: + "Por motivos de segurança da sua conta, por favor vincule o seu e-mail", + bindEmailTipOk: "Vincular agora", + bindEmailTipCancle: "Não vincular agora", + alreadyBeOwner: + "Você selecionou o dono do grupo, que já possui os mais altos privilégios de gerenciamento", + managerApplying: "Aplicação de gerente em andamento", + quote: "Citar", + request_exception: "Exceção na solicitação", + clearMsg: "Limpar mensagens", + pressToSay: "Pressione para falar", + slideToCancelSend: "Deslize para cancelar o envio", + releaseToEnd: "Solte para finalizar", + releaseToCancelSend: "Solte o dedo para cancelar o envio", + invalidVoiceMessage: "Mensagem de voz inválida", + hit: "Atingir", + downloadingCache: "Baixando cache...", + unableToRecognize: "Incapaz de reconhecer", + scanResult: "Resultado da digitalização", + register_fail: + "Falha no registro. Verifique sua conexão de rede e tente novamente", + skip: "Pular", + toUse: "Usar agora", + sideToSide: "Fim a fim", + design: "Design", + guide1: + "Envie mensagens, transmita-as criptografadas e leia apenas para você", + guide2: "Comunique-se de forma mais livre, seguindo o seu próprio estilo", + guide3: "Construa uma plataforma de comunicação segura em torno de você", + inputGroupAc: + "Por favor, insira o anúncio do grupo (limite de caracteres 0-500)", + clickToEnjoy: + "Clique no link para salvar ou copie este trecho e abra o aplicativo para se juntar ao grupo.", + createOK: "Criado com sucesso", + noPravcy: "Você não tem permissão para criar grupos", + groupNumberToTop: "Limite máximo de grupos alcançado", + createFail: "Falha ao criar. Por favor, tente novamente mais tarde", + create: "Criar", + uploadGroupAvatar: "Enviar avatar do grupo", + useNewPassword: + "Senha alterada com sucesso, retornando para a página de login", + error_14: "Servidor com erro (14)", + error_500: "Servidor com erro (500)", + error_1000: "Token de assinatura do usuário inválido", + error_1001: "Erro nos parâmetros", + error_1002: "A versão atual já é a mais recente", + error_6001: "Este nome de usuário já está registrado", + error_6003: "A senha deve conter de 6 a 15 caracteres alfanuméricos", + error_6004: "Erro na verificação do nome de usuário", + error_6005: "Senha incorreta, por favor, tente novamente", + error_6006: "Limite de tentativas de senha excedido hoje", + error_6007: "Usuário não existe", + error_6008: "Senha antiga incorreta, por favor, insira novamente", + error_6009: + "A senha antiga é igual à nova senha, por favor, insira novamente", + error_6010: "Obtenção frequente de código de verificação", + error_6011: "Código de verificação incorreto, por favor, tente novamente", + error_6012: "Código de verificação expirado", + error_6013: "Muitas falhas na verificação de código", + error_6014: "Código de verificação já utilizado", + error_6015: "O email não existe", + error_6016: + "O email já está vinculado a outra pessoa, por favor, insira novamente", + error_6017: "Código de verificação não existe", + error_6018: "Esta conta foi banida", + error_6019: "Número de telefone já vinculado a outra pessoa", + error_6020: "Endereço de email já vinculado a outra pessoa", + error_6021: "Endereço de email incorreto, por favor, insira novamente", + error_6022: "Número de telefone incorreto, por favor, insira novamente", + error_6023: + "Esta conta foi banida e não suporta a recuperação de senha temporariamente", + error_6024: + "Esta conta não está vinculada a um endereço de email e não suporta a recuperação de senha temporariamente", + error_6025: + "Esta conta não está vinculada a um número de telefone e não suporta a recuperação de senha temporariamente", + error_6026: + "Número de telefone já vinculado a outra pessoa, por favor, insira novamente", + error_6030: "O servidor não está configurado para enviar emails", + error_6031: "Por favor, insira um número de telefone correto", + error_6032: "Por favor, insira um endereço de email correto", + error_100002: "Limite máximo de mensagens fixadas atingido", + error_100003: "Esta mensagem já está fixada", + error_api: "Erro na solicitação", + error_http: "Erro na solicitação de rede", + error_http_11: "Tempo limite de conexão de rede excedido", + error_http_12: "Tempo limite de conexão de rede excedido", + error_http_13: "Tempo limite de conexão de rede excedido", + error_http_14: "Erro no certificado de rede", + error_http_15: "Erro nos dados da solicitação", + error_http_16: "Solicitação cancelada", + error_http_17: "Tempo limite de conexão de rede excedido", + error_http_18: "Erro interno na solicitação de rede", + error_sdk_err_1002: "Operação sem permissão", + error_sdk_err: "Operação falhou", + search_hine_1: "Por favor, insira o conteúdo da pesquisa", + str_no_data: "Sem dados disponíveis", + str_no_net: "Sem conexão de rede", + download_not_support_type: "Formato não suportado", + download_save_at: "Arquivo salvo em: ", + download_not_url: "Link de download não reconhecido", + download_file_success: "Salvo com sucesso, você pode encontrar no álbum", + download_failed_file: "Falha ao salvar o arquivo", + download_failed: "Falha no download", + loading_failed_retry: "Falha ao carregar, clique para tentar novamente", + loading_ing: "Carregando...", + str_online_later_minute: "minutos atrás", + str_online_later_hour: "horas atrás", + str_online_later_llg: "Muito tempo sem conexão", + str_online_later_yesterday: "Online ontem", + str_yesterday: "ontem", + str_online_later_near: "Recentemente online", + str_minute: "minutos", + clean_notice: "Limpar avisos", + clean_notice_1: "Tem certeza de que deseja limpar os avisos do grupo?", + publish_notice: "Publicar aviso", + publish_notice_remind: + "Publicar este aviso irá notificar todos os membros do grupo", + quit_edit: "Sair da edição", + quit_the_edit: "Sair desta edição?", + continue_edit: "Continuar editando", + publish_success: "Publicado com sucesso", + face_success: "Desbloqueio facial ativado com sucesso", + finger_success: "Desbloqueio por impressão digital ativado com sucesso", + groupDismissed: "Este grupo foi dissolvido", + hasOut: "Expirado", + otherLoginStyle: "Outras formas de login", + otherRegisterStyle: "Outras formas de registro", + hasAccount: "Já tem uma conta? Faça login", + mobileCode: "Código de verificação do celular", + codeSendTo: "Código enviado para", + inputNumber: "Por favor, insira o número de telefone", + bindPhone: "Vincular número de telefone", + bindPhoneSuccess: "Número de telefone vinculado com sucesso", + bindPhoneTipChange: + "Se o seu número de telefone mudou, por favor, atualize-o imediatamente", + please_input_email: "Por favor, insira o endereço de email", + findPassByEmailTip: "Recuperar senha por email", + findPassByPhoneTip: "Recuperar senha por telefone", + resetPass: "Redefinir senha", + current_bind_phone: "Número de telefone vinculado atualmente:", + reget: "Reenviar", + getVerifyCode: "Obter código de verificação", + alreadyRegister: "Este número de telefone já está registrado", + alreadyRegister2: "Este endereço de email já está registrado", + netErrorRetry: "Houve um erro de rede, clique para recarregar a página~", + newPhoneNumber: "Novo número de telefone", + addFriendText1: "Adicionar como amigo", + qrSaveFail: "Falha ao salvar o código QR", + qrSavePhoto: "O código QR foi salvo na galeria", + userId: "ID do usuário:", + addMeToFriend: "Adicionar-me como amigo", + validTime: "Válido até", + clickRefresh: "Clique para atualizar", + qrUseCount: "Este código QR é válido por {count} usos", + share: "Compartilhar", + savePhoto: "Salvar imagem", + iAm: "Eu sou", + addFriendSuccessful: "Amigo adicionado com sucesso", + addFriendFaceToFace: "Adicionar amigo pessoalmente", + faceToFace: "Adicionar amigo pessoalmente", + inputSameKey: "Aqui estão os amigos que já digitaram o mesmo número", + sameKeyAddFriend: "Adicionar amigo que digitou o mesmo número", + self: "Próprio(a)", + age: "anos", + confirmAddFriend: "Confirmar adição como amigo", + inputUserId: "Por favor, insira o ID do usuário", + myQr: "Meu código QR", + back: "Voltar", + canNotAddSelf: "Você não pode adicionar a si mesmo aos contatos", + notExsit: "Este usuário não existe", + searchUserId: "Pesquisar ID do usuário:", + remarkInfo: "Por favor, forneça informações de verificação na observação", + applyRemark: "Observação", + last4Number: + "Por favor, insira os últimos 4 dígitos do número de telefone de login do outro usuário", + checkLast4Number: "Verificar os últimos 4 dígitos do número de telefone", + addFriendApply: "Pedido de adição como amigo", + friendNumberCatchTopLimit: "Você atingiu o limite máximo de amigos", + friendNumberCatchTopLimitCurrentDay: + "Você atingiu o limite máximo de adições de amigos hoje", + codeInvalid: "Chave inválida ou expirada, por favor, inicie novamente", + shareFail: "Falha ao compartilhar", + error_1003: "Por favor, tente novamente mais tarde", + error_6033: "Chave inválida ou expirada, por favor, inicie novamente", + error_6034: "Limite máximo de amigos atingido", + error_6035: "Limite máximo de adições de amigos hoje atingido", + error_6036: "O número de telefone deste usuário está vazio", + error_6037: "Falha na verificação dos últimos dígitos do número de telefone", + error_6040: "Grupo já existe", + error_6050: "O código QR expirou", + error_6051: "Limite máximo de uso do código QR atingido hoje", + error_6052: "Limite máximo de uso do código QR atingido", + error_6053: "Limite máximo de uso da função de scanner hoje atingido", + error_6054: "O usuário já adicionou você como amigo", + error_6055: "Chave inválida ou expirada, por favor, inicie novamente", + error_6066: "Saldo insuficiente na conta", + error_6067: "Todos os envelopes vermelhos foram distribuídos", + error_6068: "O envelope vermelho expirou", + error_6069: "Não é possível receber o envelope vermelho exclusivo", + error_6070: + "Você já recebeu o envelope vermelho, não é possível recebê-lo novamente", + deletedByFriendHint: "Você ainda não é amigo dele(a)", + deletedByFriendHint1: "Você ainda não é amigo dele(a),", + deletedByFriendHint2: "Adicionar como amigo", + deletedByFriendHint3: "Continuar conversando", + gotoAddFriend: "Adicionar como amigo", + addFriendGotoGroup: "Convidar amigo para grupo", + notContacts: "Sem contatos", + notContactsFound: "Não foi encontrado nenhum amigo relacionado com {name}", + remarkStr: "Observação", + remarkStrHint: "Por favor, insira uma observação, de 0 a 50 caracteres", + deleteConfirm: "Tem certeza de que deseja excluir este amigo?", + deleteFriend: "Excluir amigo", + searchContact: "Pesquisar contato", + notGroupTip: "Você ainda não participa de nenhum grupo", + notAddFriend: "Você ainda não adicionou novos amigos", + friendApplyAdd: "Pedido de amigo", + addSuccess: "Adicionado com sucesso", + hasSelectFriend: "Amigos selecionados", + myBalance: "Meu saldo", + revokeMsgTip: "Revogar mensagem", + error_10050: "Operação falhou (registro não encontrado)", + set_manager_tip_1: + "Você selecionou o dono do grupo, que já tem os mais altos privilégios de gerenciamento", + envelope: "Envelope vermelho", + uidStr: "ID do usuário", + showGetDetail: "Ver detalhes de recebimento", + waitOtherEnter: "Aguardando a entrada do outro usuário", + isFriend2SExit: "Tornou-se amigo, saindo automaticamente em 2 segundos", + sendRedPacket: "Enviar envelope vermelho", + redPacketNumber: "Número de envelopes vermelhos", + ge: "unidades", + total: "Total", + eachNumber: "Quantidade por envelope", + selectUser: "Selecionar usuário", + number: "Número", + redPacketType: "Tipo de envelope vermelho", + checkVerity: "Verificação de segurança", + balance: "Saldo da conta", + sendTo: "O código de verificação por SMS foi enviado para", + pin: "Envelope vermelho da sorte", + putong: "Envelope vermelho normal", + zhuanshu: "Envelope vermelho exclusivo", + dajidali: "Feliz Ano Novo! Boa sorte e sucesso!", + redPacketInfo: "Detalhes do envelope vermelho", + best: "Melhor sorte", + saveToAccount: "Salvo na conta", + minuteOut: "Esgotado em minutos", + hourOut: "Esgotado em horas", + showDetail: "Ver detalhes", + maxMemberNumber: + "O número de envelopes vermelhos não pode exceder o número atual de membros do grupo", + sended: "Enviado", + get: "Recebido", + waiting: "Aguardando o usuário receber", + gong: "envelopes vermelhos no total", + envelope_get_over: "dos envelopes vermelhos foram recebidos", + envelope_get: "recebeu", + de: "de", + envelope_get_message: "Mensagem de recebimento de envelope vermelho", + bind_phone_please: + "Por favor, vincule seu número de telefone para garantir a segurança da sua conta", + delay_bind: "Vincular mais tarde", + send_envelope_x: "Envelopes vermelhos enviados", + send_envelope_back_all: + "Este envelope vermelho não foi recebido após 24 horas\nFoi reembolsado", + envelope_x_can_get: "Apenas {name} pode ser recebido", + envelope_get_completed: "Você está atrasado, o envelope vermelho acabou", + send_envelope_back_some: + "Este envelope vermelho não foi completamente recebido após 24 horas\nParte foi reembolsada", + envelope_x_personal: "Envelope vermelho exclusivo para %s", + envelope_normal: "Envelope vermelho normal", + login_out_of_date: "A sessão expirou, faça login novamente", + get_envelope_completed: "Todos os envelopes vermelhos foram recebidos", + x_is_personal: "Exclusivo", + error_6072: "O valor máximo por envelope vermelho foi atingido", + error_6073: "O número de envelopes vermelhos não pode ser inferior a 0.01", + error_6074: "A função de envelope vermelho foi desativada", + distroyAccount: "Encerrar conta", + inComing: "Entrada", + outGoing: "Saída", + balance_order_detail: "Detalhes do saldo", + type: "Tipo", + payType: "Método de pagamento", + orderNo: "Número do pedido", + time: "Tempo", + itemDataNotSupport: "Esta versão atualmente não é suportada", + manager_change_account: "Ajuste de conta pelo administrador", + groupEnvelope: "Envelope vermelho de grupo", + accountPay: "Pagamento com saldo", + paySuccess: "Pagamento bem-sucedido", + groupEnvelopePersonal: "Envelope vermelho de grupo - Exclusivo", + groupEnvelopeNormal: "Envelope vermelho de grupo - Normal", + changeAccount: "Ajuste de conta", + groupEnvelopeRandom: "Envelope vermelho de grupo - Sorteio", + groupEnvelopeAllBack: "Envelope vermelho de grupo - Reembolso total", + groupEnvelopeSomeBack: "Envelope vermelho de grupo - Reembolso parcial", + distroyTips: + "Para garantir a segurança da sua conta,\nvamos realizar as seguintes verificações antes de encerrar sua conta", + safeStatus: "Conta em estado seguro", + safeTips: + "Certifique-se de que sua conta não está sujeita a atividades sensíveis, como mudança de senha, troca de número de telefone/email, ou risco de roubo de conta.", + strikeBalance: "Liquidação de ativos da conta", + strikeTips: + "Após encerrar sua conta, todos os dados do saldo de envelopes vermelhos serão excluídos e não podem ser recuperados.", + dataDelete: "Exclusão de dados da conta", + deleteTips: + "Após encerrar sua conta, todos os dados associados à sua conta serão excluídos permanentemente, incluindo histórico de conversas, participação em grupos e relacionamentos de amigos.", + applyDistroy: "Solicitar encerramento da conta", + distroyConform: "Tem certeza de que deseja encerrar sua conta?", + distroyConformTips: + "Você será desconectado após o encerramento. Por favor, proceda com cuidado!", + distroy: "Confirmar encerramento", + submitSuccess: "Enviado com sucesso", + submitted: "Sua solicitação de encerramento foi enviada!", + helpWord: + "A plataforma levará até {day} dias úteis para processar sua solicitação e excluir todos os seus dados. Se precisar de ajuda, entre em contato conosco através do nosso site oficial.", + know: "Entendi", + revoke: "Revogar solicitação", + revokeSuccess: "Revogação bem-sucedida", + recover: "Tem certeza de que deseja revogar a solicitação?", + toNormal: "Após a revogação, o status da conta voltará ao normal", + beRecover: "Confirmar recuperação", + recoverSuccess: "Recuperação bem-sucedida", + redPackageCountTip: + "O total de envelopes vermelhos não pode ser inferior a 0.01", + closeAccount: "Encerrar conta", + recoverAccount: "Recuperar conta", + recoverAccountTipTitle: "Tem certeza de que deseja recuperar sua conta?", + recoverAccountTipDesc: + "Após a recuperação, o status da conta voltará ao normal", + recoverAccountTipOk: "Confirmar recuperação", + closingAccount: "Sua conta está em processo de encerramento", + closingAccountEndTime: ", previsto para ser concluído em {num} dias úteis", + error_6079: "Você não está mais no grupo, não é possível receber", + error_6075: "Solicitação duplicada", + error_6076: "Nenhum registro de solicitação de encerramento encontrado", + error_6077: "Conta em processo de encerramento", + error_6078: "Usuário não existe ou já foi encerrado, por favor, verifique", + error_video_pre_err: "Erro ao gerar arquivo, falha ao carregar dados", + err_upload_file: "Falha ao carregar, por favor, tente novamente mais tarde", + err_msg_send_failed: + "Falha ao enviar mensagem, por favor, tente novamente mais tarde", + error_red_package_tip1: "Envelope vermelho expirado", + net_error_no_net: + "Sem conexão de rede, por favor, verifique suas configurações de rede", + error_6081: "Falha ao enviar, por favor, tente novamente mais tarde", + atTip: "Alguém mencionou você", + groupChatError: "Falha ao carregar o conteúdo, clique para tentar novamente", + legalNotice: "Aviso legal", + timeoutLock: "Bloqueio por tempo esgotado", + reTry: "Tentar novamente", + sysNotify: "Notificação do sistema", + exit_group_tip: "Saiu do grupo", + group_mute_ing: "Este grupo está silenciado", + w_edit_group_notification: "Editou o aviso do grupo", + removeGroupNotice: "Remover aviso do grupo", + messageNotify: "Notificação de mensagem", + groupEnterLimit: + "O número máximo de amigos convidados para o grupo foi atingido, por favor, verifique", + tipAllGroupMember: "Notificar todos os membros do grupo", + openPhotoPermission: "Ativar permissão de acesso ao Álbum", + gotoSettings: "Ir para configurações", + notSet: "Agora não", + openCameraPermission: "Ativar permissão de uso da Câmera", + openMicrophonePermission: "Ativar permissão de uso do Microfone", + selectMaxCountTip: "Você pode selecionar no máximo %s pessoas de cada vez", + getVideoImageFailed: "Falha ao obter a imagem de pré-visualização do vídeo", + error_6086: "O apelido de usuário já está em uso", + error_6085: "Formato de apelido incorreto", + error_6087: "Número de telefone não existe", + error_6088: "Endereço de e-mail não existe", + unRecard: "Não foi possível gravar:", + hasCreateGroup: "Criou uma conversa de grupo", + joinGroupForLink: "Junta-se a conversa em grupo através do link", + be: "cobertura", + moveoutGroup: "Eliminar da conversa de grupo", + groupOwnerChangeTo: "O líder do grupo foi mudado para", + changeGroupNameTo: "Mudar o nome do grupo para", + disbandedGroup: "Dissolved group chat", + bannedFromSpeaking: "Prohibição", + gagWasLifted: "A proibição foi levantada", + initiatedGroupGag: "A proibição de grupos foi ativada", + shutDownGroupGag: "Proíbição de grupos fechada", + target: "O outro partido", + groupSizeUpperLimit: "O número máximo de membros do grupo foi alcançado", + topUpperLimit: "O topo alcançou o limite superior", + topMessageRepeate: "A mensagem superior foi duplicada", + userHasReadMessage: "O usuário leu esta mensagem", + placeholder: { + allMuted: "O líder ou administrador do grupo ativou todas as proibições", + groupDisbanded: "O grupo foi disperso", + groupBanned: "Este grupo foi proibido", + singleBanned: "Você foi proibido de falar", + search: "pesquisa", + pleaseInput: "Por favor, entre", + sec: "segundo", + groupNoticePlaceholder: "Por favor, introduza o anúncio do grupo..", + inputPassword: "Por favor, introduz a senha", + confirmPassword: "Por favor, confirme a entrada da senha", + inputPhoneNumber: "Por favor, introduza seu número de telefone", + select: "Please select", + userOrGroupIDInput: "Por favor, entre{type}", + writeMessage: "Informação de entrada...", + inputGroupName: "Please enter the group name", + typingMessage: "Escreva uma mensagem aqui", + inputName: "Please enter your nickname" + }, + withoutQualification: + "Este usuário não atende aos requisitos para adicionar como amigo", + error_6084: "Falha na verificação do e-mail", + emailCheck: "Verificação por e-mail", + phoneCheck: "Verificação por telefone", + checkEmail: "Verificar endereço de e-mail", + inputOtherEmail: + "Por favor, insira o endereço de e-mail usado pelo outro usuário para fazer login", + groupMask: "Máscara de grupo", + groupMaskDescription: "Configure o apelido e a foto de perfil deste grupo", + + pleaseBind: + "Para garantir a segurança da sua conta e evitar restrições na função de adicionar amigos, por favor, faça a vinculação", + orStr: "ou", + andStr: "e", + confirmExit: "Confirmar saída", + pleaseBindTip1: + "Para garantir a segurança da sua conta, por favor, faça a vinculação", + skin_item_user_name: "张娜娜", + skin_item_msg1: "Olá, recebi sua resposta rápida", + skin_item_msg2: "Ok, entendi", + appearance: "Aparência", + bubbleBgColor: "Cor de fundo do balão de conversa", + chatBackground: "Fundo da conversa", + moreSettings: "Mais configurações", + highPerformanceModel: "Modo Limpo", + highPerformanceModelDesc: "Ative para não exibir efeitos de aparência", + skinPreview: "Pré-visualização da aparência", + defaultText: "Padrão", + fontSizePreview: "Pré-visualização do tamanho da fonte, arraste o controle deslizante abaixo para ajustar o tamanho da fonte. Após a configuração, todos os textos das páginas terão seu tamanho alterado.", + changedText: "Ok, já foi alterado", + imageAlpha: "Transparência da imagem", + min: "Mínimo", + max: "Máximo", + backgroundColor: "Cor de fundo", + changeSaveTip: "Você modificou as configurações, deseja salvar e voltar?", + saveAndBack: "Salvar e voltar", + changeAvatar: "Alterar avatar", + appFontSize: "Tamanho da fonte do aplicativo", + appFontSizeTip: + "Tamanho da fonte de pré-visualização, arraste o controle deslizante abaixo para definir o tamanho da fonte. Depois de definido, o tamanho da fonte de todos os textos dentro do aplicativo será alterado.", + maskDesc: + "Quando ativado, os membros do grupo poderão definir o apelido e a foto de perfil deste grupo", + groupMaskOpenTip: " %s abriu %s a máscara de grupo", + groupMaskOpenTipNew: " abriu a máscara de grupo", + open: "Abrir", + close: "Fechar", + groupMaskOpenMsgErrorTip: "Dica de alternância de função de máscara de grupo", + maskOpen: "Máscara de grupo aberta", + maskClose: "Máscara de grupo fechada", + + transmit: "Encaminhar", + multiSelect: "Seleção múltipla", + singleShareMsg: "Encaminhar individualmente", + groupShareMsg: "Encaminhar em grupo", + notGroupFound: "Nenhum grupo encontrado relacionado a {name}", + rememberPassword: "Lembrar senha", + mobileLogin: "Login pelo celular", + emailLogin: "Login por e-mail", + recentConversation: "Conversas recentes", + selectContact: "Selecionar contato", + selectGroup: "Selecionar grupo", + noGroup: "Nenhum grupo", + notSelectMessage: "Você ainda não selecionou mensagens", + hiddenMsg: "Ocultar mensagem", + hiddenMsgTip: "Deseja ocultar a fonte da mensagem?", + yes: "Sim", + no: "Não", + mergeMessageList: "Lista de mensagens para encaminhamento", + singleMlCount: "Encaminhamento individual de %s mensagens", + notFoundUser: "Usuário não encontrado", + notShouldFoundUser: "Tidak dapat menemukan pengguna tersebut", + notFoundGroup: "Grupo não encontrado", + selectAt: "Selecionar pessoa para {'@'}", + welcomeToQDDChat: "Bem-vindo ao QDDChat", + choiceYourAvatar: "Escolha seu avatar", + hasSelect: "Selecionado", + groupChat: "Chat em grupo", + groupMember1: "Membros do grupo", + groupSendMsg: "Enviar mensagem em grupo", + msgNotSupportTransmit: "Esta mensagem não pode ser encaminhada", + bindGoogle: "Vincular ao Google", + bindFacebook: "Vincular ao Facebook", + unbind: "Desvincular", + accountSettings: "Configurações da conta", + thirdPartyAccountBinding: "Vinculação de conta de terceiros", + otherSettings: "Outras configurações", + groupChatLoadFailed: "Falha ao carregar informações do chat em grupo", + addGroupSend: "Iniciar envio em grupo", + groupSendNoData: "Não há registro de envio em grupo", + removeBinding: "Remover vínculo", + unBindToastPart1: "Após remover o vínculo, você não poderá mais usar", + unBindToastPart2: "para fazer login. Deseja continuar?", + noOpPermission: "Não há permissão para operar", + bindingPhoneOrEmail: + "Por favor, vincule seu número de telefone ou e-mail antes de remover o vínculo para garantir a segurança dos seus dados da conta.", + unbindingSuccess: "Desvinculação bem-sucedida", + unbindingFair: "Falha na desvinculação", + showAll: "Mostrar tudo", + allCount: "Total de {num}", + bindingPhoneOrEmailSetPassword: + "Por favor, vincule seu número de telefone ou e-mail antes de definir a senha para garantir a segurança dos seus dados da conta.", + recoverSuccessLoginAgain: + "Recuperação bem-sucedida. Por favor, faça login novamente.", + createGroupSend: "Criar envio em grupo", + groupSend: "Envio em grupo", + notSelect: "Você ainda não selecionou", + confirmDeleteTitle: "Confirmar exclusão do envio em grupo selecionado?", + totalGe: "Total de %s", + sendTotalGe: "Enviar para ({num})", + uploadFailed: "Falha no upload", + selectMax: "Número máximo de seleção atingido", + newFriendApply: "Você tem novos amigos, clique para ver", + notSupportLoginStyle: + "Método de login não suportado no momento, por favor, use outro método para fazer login", + error_6091: "Número de seleção excede o limite", + error_6092: "ID da mensagem já existe", + pay_password: "Senha de pagamento", + pleaseEnter: "Por favor, insira", + loadingPage: "Carregando página", + payTip: + "Você ainda não definiu sua senha de pagamento, por favor, conclua a configuração antes de prosseguir.", + deleteRedPackageErrTip: "Mensagem de pacote vermelho não pode ser revogada", + applyIconTip: + "Solicitação enviada, será possível alterar o avatar após a aprovação", + applyIconProgress: "Ver progresso da solicitação", + applyRecord: "Registro de solicitação", + repealWithdrawApply: + "Tem certeza de que deseja cancelar a solicitação de retirada?", + withdrawNotes: "Histórico de retirada", + inReview: "Em revisão", + passTheAudit: "Aprovado", + rejectedPayment: "Rejeitado", + revoked: "Revogado", + undone: "Desfeito", + withdrawalQuantity: "Quantidade de retirada", + withdrawal: "Retirada", + withdrawalBack: "Retorno de retirada", + update_tip1: "Se não conseguir atualizar", + update_tip2: "pode baixar no site oficial", + update_ing: "Atualizando", + update_install: "Instalar agora", + installFailed: "Não instalado, por favor, tente novamente mais tarde", + installNotFoundFile: + "Arquivo de instalação não encontrado, por favor, tente novamente mais tarde", + error_6093: "Método de login não suportado", + error_6094: "Limite máximo de avatares alcançado", + deleteIcon: "Deseja excluir este avatar?", + balanceWithdraw: "Retirada de saldo", + canBalanceWithdraw: "Saldo disponível para retirada", + inputPayPassword: "Digite a senha de pagamento", + withdrawalSuccess: "Retirada bem-sucedida", + personIcon: "Avatar pessoal", + groupIcon: "Avatar de grupo", + error_6096: + "Por favor, não defina uma senha de pagamento com números repetidos ou consecutivos", + forgetPayPassword: "Esqueci a senha de pagamento", + downloadFailedTip: "Falha ao baixar, por favor, tente novamente mais tarde", + error_6097: "Saldo insuficiente", + passed: "Aprovado", + headRejected: "Rejeitado", + notSet2: "Não definido", + readDeleteTime: "Tempo de Expiração da Mensagem", + readDeleteTimeTitle: "Tempo de Expiração", + error_6090: "Conta Já Vinculada", + retracted_h5: "Retirou", + message_h5: "mensagem de", + inputRemark: "Por favor, insira sua observação", + authFailed: "Falha na autorização", + userNullError: " Esta conta de usuário é anormal", + community: "Comunidade", + join: + "Ingresso no grupo de bate-papo, explore mais atividades da comunidade~", + posteviews: "Ainda não há atividades publicadas~", + views: "vezes assistidas", + ended: "A atividade já terminou", + configError: "Erro de configuração, não é possível abrir temporariamente", + burnMsgOpen: "{name} definir mensagem lida {time} após queimar", + burnMsgClose: + "{name} desligou leitura e queima, mensagens não se queimam mais automaticamente", + burnMsgNotify: "Notificação de leitura e queima", + homeItem3: "Comunidade", + messageTimeOut: "Mensagem expirada", + burnContentTip: + "A mensagem está sendo queimada, deseja sair e queimar com um clique?", + cancelLater: "Vou verificar mais tarde", + burnConfirm: "Queimar imediatamente", + setNickNameTimes: + ", o apelido só pode ser modificado uma vez dentro de %s dias", + voiceSendErrTip1: "Suporte para enviar voz apenas de %s a %s segundos", + textSendErrTip1: "Máximo de entrada de %s caracteres", + pictureSendErrTip1: "Suporte para enviar imagens apenas de %s a %sM", + videoSendErrTip1: "Suporte para enviar vídeos apenas de %s a %sM", + your: "Seu", + joinCommunity: + "Ingresso no grupo de bate-papo, explore mais atividades da comunidade~", + noActivity: "Ainda não há atividades publicadas~", + views: "vezes assistidas", + ended: "A atividade já terminou", + errorLink: "Não encontrado link", + set_payment_password: "Por favor, defina a senha de pagamento de 6 dígitos", + unset_payment_password: + "Por favor, não defina uma senha de pagamento repetida ou sequencial", + Confirm_Payment_Password: "Confirmar Senha de Pagamento", + Please_confirm_payment_password: + "Por favor, digite novamente para confirmar a senha de pagamento", + error_6100: + "Desculpe, você só pode modificar o seu apelido pessoal uma vez a cada {day} dias", + viewDetailed: "Ver informações detalhadas dos membros do grupo", + inviter: "Convidador", + lastLoginTime: "Último horário de login", + DeleteThisUsersgroup: + "Excluir as mensagens do grupo enviadas por este usuário neste grupo", + AddGroupMembers: + "Adicionar membros ao grupo sem consentimento de verificação", + error_6103: "Este usuário é um membro regular e não tem permissões", + error_6104: "A pessoa adicionada não está no grupo", + error_6108: "Conta anormal", + group_remark: "Observação do Grupo", + input_group_remark: "Por favor, insira o conteúdo da observação", + await_accepting: "Nova solicitação de autorização de dispositivo enviada", + apply_tips: "Este dispositivo não é confiável ou não passou na análise. Deseja enviar informações do dispositivo para solicitar autorização?", + apply_send: "Enviar solicitação", + apply_ttl: "Autorização de Login do Dispositivo", + group_limit500: "Limite de 500 pessoas por grupo", + select_group: "Selecionar grupo de amigos", + cantfind: "Não foi possível encontrar contatos relacionados a {name}", + error_6109: "O número de amigos do usuário atingiu o limite", + error_6106: "Aguarde a aprovação do dispositivo antes de fazer login", + selectGroups: 'Selecionar Grupos', + group_member_info: "Informações do Membro do Grupo", + join_time: "Hora de Entrada", + inviter_user: "Convidado por", + last_login_time: "Último horário de login", + sync_group_avatar: 'Deseja modificar o avatar do grupo ao mesmo tempo?', + invite_fail: 'a conta de {usernames} está com problemas!', + enterAndCtrl:'Enter para enviar / Enter+Ctrl para nova linha', + enterAndCtrlIOS:'Enter para enviar / Enter+control para nova linha', + scanLogin: 'Login com código QR', + formLogin: 'Login com senha', + scanText: 'Login usando o aplicativo Qudi Dai com código QR', + scanSubText: 'Use o aplicativo Qudi Dai para fazer login com código QR', + qrcodeExpired: 'Código QR expirado', + expiredTime10: 'Este código QR é válido por 10 minutos', + reflesh: 'Clique para atualizar', + scanSuccess: 'Leitura do código QR bem-sucedida', + confromLogin: 'Por favor, confirme o login', + onlineStatus: "Status Online:", + networkDiagnostic: "Diagnóstico de Rede", + "setPaymentPassword": "Por favor, defina a senha de pagamento de 6 dígitos", + "unSetPaymentPassword": + "Por favor, não defina uma senha de pagamento repetida ou sequencial", + "verifyPassword": "Verificar senha de pagamento", + "confirmPaymentPassword": "Confirmar senha de pagamento", + "verifyCurrentPassword": "Verifique a senha de pagamento atual", + "reInsetPaymentPassword": + "Por favor, insira novamente a senha de pagamento para confirmar", + "paymentPasswordDifferent": "Senha diferente, por favor, insira novamente", + "getPaymentPasswordWay": + "Por favor, escolha o método de recuperação de senha", + "selectFriendGroup": "Selecionar Grupo de Amigos", + "selectGroups": "Selecionar Grupos", + "eachGroupLimitNumber": "O número máximo de pessoas em cada grupo é de {p}", + "selectChatLimitNumber": "Você só pode selecionar até {p} chats", + "deviceID": "ID do dispositivo:", + "groupChatRemark": "Observação do grupo de bate-papo", + "pleaseEnterRemarks": "Por favor, insira o conteúdo da observação", + "groupMembershipInfo": "Informações de adesão ao grupo", + "error_6102": "Limite máximo de 500 pessoas por grupo", + "distroyed": "Cancelado", + "forbiddened": "Banido", + "addGroupMemberWithoutApply": + "Adicionar membros ao grupo sem verificação de consentimento", + "error_6109": "O número de amigos deste usuário atingiu o limite máximo.", + "apply_ttl": "Autorização de login do dispositivo", + "apply_tips": + "Este dispositivo não é confiável ou não foi aprovado na auditoria. Você deseja enviar informações do dispositivo para solicitar autorização?", + "apply_send": "Enviar solicitação", + "device_auth_send": "Solicitação de autorização do dispositivo enviada", + "using_trusted_device": + "O login com este dispositivo apresenta riscos, por favor, faça login com um dispositivo confiável", + "account_closed": "Conta encerrada", + "google_code": "Código de Verificação do Google", + "input_google_code": "Por favor, insira o Código de Verificação do Google", + "inviterUser": "Convidado por", + "lastLoginTime": "Último horário de login", + "error_6105": "Dispositivo não confiável, por favor envie um pedido", + "error_6106": + "Por favor aguarde a aprovação do dispositivo antes de fazer login", + "error_6107": "Aprovação do dispositivo não foi concedida", + "hasDetailInfoPermission": "Ver informações detalhadas do membro do grupo", + "editUserHeaderWithGroupHeader": + "Deseja alterar o cabeçalho do bate-papo em grupo ao mesmo tempo?", + "darkModel": "Modo escuro", + "darkModelSetDesc": + "Ativar ou desativar o modo escuro seguindo as configurações do sistema", + "whiteModel": "Modo claro", + "fileNotFound": "Arquivo não encontrado", + "openFileInApp": "Visualizar arquivo", + "openFileOtherApp": "Abrir em outro aplicativo", + "openByBrowser": "Baixar usando o navegador", + "pauseDownload": "Pausar download", + "resumeDownload": "Continuar download", + "downloadFailed": "Download falhou", + "downloading": "Baixando", + "notdownloaded": "Não baixado", + "cancelDownloading": + "O arquivo está sendo baixado. Deseja cancelar o download?", + "failedToDownload": + "Falha ao baixar o arquivo. Deseja tentar baixar novamente?", + "cancelDownload": "Cancelar download", + "sending": "Enviando", + "fileTypeNotSupported": "Formato de arquivo não suportado para upload", + "fileSizeOut": "Tamanho do arquivo excedido", + "restrict": "Restringir", + "getWithPhone": "Recuperar com número de telefone", + "getWithEmail": "Recuperar com endereço de e-mail", + "fileSize": "Tamanho do arquivo", + "whetherDownloadFile": "Se quer fazer o download do arquivo", + "downloadFailedTip1": + "Falha ao baixar, por favor verifique a rede e tente novamente", + "downloadQueueTip": "Adicionado à fila de downloads, aguardando download", + "notSupportTransmitTip": "Esta mensagem não pode ser encaminhada no momento", + "voiceSize": "O tamanho do arquivo de áudio não pode exceder 100MB.", + "wordSize": "O tamanho do arquivo de documento não pode exceder 100MB.", + "apkSize": + "O tamanho do arquivo de pacote de instalação não pode exceder 300MB.", + "zipSize": "O tamanho do arquivo zip não pode exceder 300MB.", + "groupDeleteUserTip": + "Eliminar este usuário juntamente com as mensagens enviadas por este usuário no grupo", + "nPersonRead": "{p} pessoa(s) já leu/leram", + "darkModeEnabled": + "Modo escuro ativado, reinicie o aplicativo para que as alterações entrem em vigor", + "darkModeOff": + "Modo escuro desativado, reinicie o aplicativo para que as alterações entrem em vigor", + "otherUser": "Outro usuário", + "selectMsgDeleteTime": "Selecionar tempo de retenção", + "selectMsgDeleteTimeTip": + "O histórico de conversas será excluído automaticamente após o período de retenção. Deseja continuar?", + "msgSaveTime": "Tempo de retenção do histórico de conversas", + "msgSaveTimeDesc": + "Limpar automaticamente os registros de conversa que excederem o limite de tempo", + "hasTimeDeleteMsgPermission": + "Configuração de tempo de retenção de mensagens de chat em grupo", + "accountAbnormal": "Exceção de conta, convite falhou!", + "onlyBrowser": "Suporte apenas para visualização no navegador", + "cancelSendSuccess": "Envio cancelado com sucesso", + "allStr1": "Global", + "modelStr1": "Modo dia e noite", + "atMaxCountTip": "@ no máximo {p} pessoas", + "transmitTo": "Encaminhar para", + "moreActions": "Mais ações", + "Indonesian": "Aksi lainnya", + "refreshing": "Atualizar", + "openInBrowser": "Abrir no navegador", + "coverFlow": "Fluxo de capa", + "checkNet": "Verificação de rede", + "autoChangeServer": "Mudança automática para o melhor servidor", + "serverLine": "Linha do servidor", + "timeOut": "Tempo esgotado", + "serverLineChange": "Alterar linha do servidor", + "netCheck": "Diagnóstico de rede", + "netDelayCheck": "Verificação de atraso de rede", + "netDelay1Check": "Atraso na internet", + "msgDelay1Check": "Atraso de mensagem", + "imgDelay1Check": "Atraso de imagem", + "netLine": "Linha de rede", + "reCheck": "Verificar novamente", + "netChecking": "Verificando rede", + "locationAddr": "Endereço de localização", + "selectPerfectLineTip": "Selecionada a melhor linha para você", + "selectPerfectLineTip2": "Atualmente é a melhor linha, não precisa mudar", + "selectPerfectLineTipNoNet": + "Não foi possível identificar a melhor linha, verifique sua conexão e tente novamente", + "changing": "Mudando", + "changeSuccess": "Mudança bem-sucedida", + "autoChanging": "Mudança automática em andamento, por favor, aguarde", + "waitAnswer": "Aguardando resposta", + "callFinish": "Chamada encerrada", + "fileSizeConnotZero": "O tamanho do arquivo não pode ser zero", + "confirmLogin": "Confirmar login", + "toolboxAudioCall": "Chamada de áudio", + "requestTimeOut": "Tempo limite da solicitação, tente novamente mais tarde", + "error_6115": "Código QR expirado", + "groupNotFound": "Grupo não encontrado", + "userNotFound": "Usuário não encontrado", + "microphoneOpen": "Microfone aberto", + "speakerOpen": "Alto-falante aberto", + "voiceWithYou": "Convidando você para chamada de áudio", + "callInterceptor": "Chamada interceptada", + "startCallError": "Erro ao iniciar chamada", + "callHungUp": "Chamada encerrada", + "callRemoteHungUp": "Chamada encerrada pelo outro lado", + "callReceiverError": "Erro ao receber chamada", + "starting": "Iniciando", + "startTimeOut": "Tempo esgotado ao iniciar chamada de áudio", + "callWaitReceiver": "Aguardando o outro lado atender", + "callRemoteNotAccept": "O outro lado não atendeu", + "callStartSendError": "Erro ao iniciar chamada de áudio", + "notAccept": "Não aceito", + "callAccepting": "Aceitando chamada", + "call": "Chamada", + "callDoing": "Linha ocupada, cancelada", + "netBad": "Condição de rede atual ruim", + "netDown": "Rede desconectada", + "onCalling": "Em chamada", + "error_1303": "Você ainda não é amigo da outra pessoa", + "remoteAccountError": "Problema na conta da outra pessoa", + "userOnlineStatus": "Estado online", + "error_100008": + "Este usuário não atende aos requisitos para se tornar um administrador", + "callTimeLimit": "Limite de tempo de chamada de {p} minutos por vez", + "notLoginOnline": "Nunca fez login", + "callRemoteDoing": "Ocupado", + "exitLoginIng": "Saindo", + "usePhoneRecovery": "Use o número do telefone para recuperação", + "useEmailRecovery": "Use o e-mail para recuperação", + "otherRecoveryMethods": "Outros métodos de recuperação", + "emailAddress": "Endereço de e-mail", + "phoneErrorOrNotExist": "Número de telefone incorreto ou inexistente", + "phoneNotExist": "Número de telefone inexistente", + "verificationCodeFailure": "Falha ao obter o código de verificação, por favor, tente novamente", + verificationCodeSent: "Código de verificação enviado", + hiWelcomeLogin: "Oi~ Bem-vindo ao login", + otherLoginMethods: "Outros métodos de login", + usernamePhoneEmail: "Nome de Usuário/Número de Telefone/Email", + "error_1303": "Você ainda não é amigo da outra pessoa", + "remoteAccountError": "Problema na conta da outra pessoa", + "userOnlineStatus": "Estado online", + "error_100008": + "Este usuário não atende aos requisitos para se tornar um administrador", + "callTimeLimit": "Limite de tempo de chamada de %s minutos por vez", + "notLoginOnline": "Nunca fez login", + "callRemoteDoing": "Ocupado", + "exitLoginIng": "Saindo", + "searchAreaCode": "Buscar país/região", + "otherFindStyle": "Outros métodos de recuperação", + "welcome": "Bem-vindo ao login", + + "fileReadError": "Erro ao ler o arquivo", + "docPreviewError": "Não é possível visualizar o arquivo", + "docPreviewUseOther": "Se devido a problemas de rede ou incompatibilidade do dispositivo, não for possível visualizar o arquivo, você pode usar", + "netTimeOut": "Tempo de conexão com a rede expirado", + "docReadError": "Não é possível ler o arquivo, verifique se o formato do arquivo está correto", + "errorDetail": "Detalhes do erro", + ip: "IP", + imageLoadingRoute: "Rota de carregamento de imagem", + networkChecking: "Verificando a rede...", + noPermission: "Sem permissão, por favor, faça login após obter permissão", + pageNotFound: "Página não encontrada", + serverError: "Erro no servidor", + databaseError: "Erro de consulta ao banco de dados", + networkError: "Erro de conexão com a rede" +}; diff --git a/src/lang/th.js b/src/lang/th.js new file mode 100644 index 0000000..e262675 --- /dev/null +++ b/src/lang/th.js @@ -0,0 +1,1729 @@ +// 泰语 +export default { + checkTopMessage:'ตรวจสอบข้อความ', + unsupportedVideoFormat: "ไม่รองรับรูปแบบวิดีโอนี้", + welcome: "ยินดีต้อนรับสู่ OpenIM", + phoneNumber: "หมายเลขโทรศัพท์", + plsEnterPhoneNumber: "โปรดป้อนหมายเลขโทรศัพท์ของคุณ", + password: "รหัสผ่าน", + plsEnterPassword: "โปรดป้อนรหัสผ่านของคุณ", + plsEnterPassword6_15: "รหัสผ่าน (6-15 ตัวอักษรและตัวเลข)", + account: "บัญชี", + plsEnterAccount: "โปรดป้อนบัญชีของคุณ", + plsEnterAccount6_15: "ชื่อผู้ใช้ (6-15 ตัวอักษรและตัวเลข)", + forgetPassword: "ลืมรหัสผ่าน", + createAccount: "สร้างบัญชี", + verificationCodeLogin: "เข้าสู่ระบบด้วยรหัสยืนยัน", + login: "เข้าสู่ระบบ", + noAccountYet: "ยังไม่มีบัญชีใช่ไหม?", + loginNow: "เข้าสู่ระบบทันที", + registerNow: "ลงทะเบียนทันที", + lockPwdErrorHint: "รหัสผ่านผิด %s ครั้ง", + newUserRegister: "ลงทะเบียนผู้ใช้ใหม่", + verificationCode: "รหัสยืนยัน", + verificationSuccessful: "ยืนยันสำเร็จ", + sendVerificationCode: "ส่งรหัสยืนยัน", + verification_code1: "โปรดใส่รหัสยืนยัน", + correct_email_address: "โปรดใส่ที่อยู่อีเมลที่ถูกต้อง", + Resend: "ส่งใหม่", + bind_email: "ผูกอีเมล", + email_verification: "การยืนยันอีเมล", + login_details: "รายละเอียดการเข้าสู่ระบบ", + verification_code: "โปรดใส่รหัสยืนยันทางอีเมล", + email_verification_code: "รหัสยืนยันทางอีเมล", + currently_bound_email: "อีเมลที่ผูกอยู่ในปัจจุบัน:", + text_email: "หากอีเมลของคุณไม่ใช้งานอีกต่อไป โปรดเปลี่ยนทันที", + remove_this_device: "คุณแน่ใจหรือไม่ว่าต้องการลบอุปกรณ์นี้?", + remove_this_device1: + "คุณแน่ใจหรือไม่ว่าต้องการลบอุปกรณ์นี้? เมื่อลบแล้ว คุณจำเป็นต้องยืนยันการเข้าสู่ระบบใหม่เมื่อใช้อุปกรณ์นี้อีกครั้ง", + go_to_settings: "ไปที่การตั้งค่า", + go_to_binding: "ไปที่การผูก", + date_of_birth: "วันเกิด", + confidential: "ลับ", + year: "ปี", + not_perfection: "ยังไม่สมบูรณ์", + old_password: "รหัสผ่านเก่า", + myCreatedGroup: "กลุ่มที่ฉันสร้าง", + myManageGroup: "กลุ่มที่ฉันจัดการ", + myJoinGroup: "กลุ่มที่ฉันเข้าร่วม", + please_enter_old_password: "โปรดใส่รหัสผ่านเก่าของคุณ", + trusted_device: "อุปกรณ์ที่เชื่อถือได้", + change_email_address: "เปลี่ยนอีเมล", + current_device: "อุปกรณ์ปัจจุบัน", + remove_device: "ลบอุปกรณ์", + current_device_records: "นี่คือบันทึกการเข้าสู่ระบบของอุปกรณ์ทั้งหมดของคุณ", + text_device: "การจัดการอุปกรณ์เข้าสู่ระบบ", + verification_time: "เวลาการยืนยัน:", + text_device1: + "นี่คืออุปกรณ์เข้าสู่ระบบทั้งหมดของคุณ คลิกเพื่อดูรายละเอียดและบันทึกการเข้าสู่ระบบของอุปกรณ์นี้", + change_the_binding: "เปลี่ยนการผูกข้อมูล", + resendVerificationCode: "ส่งรหัสยืนยันอีกครั้ง", + verificationCodeTimingReminder: "รับรหัสยืนยันใหม่ใน %s วินาที", + defaultVerificationCode: "(รหัสยืนยันเริ่มต้น: %s)", + plsEnterVerificationCode: "โปรดป้อนรหัสยืนยันของคุณ", + invitationCode: "รหัสเชิญ", + device_list: "รายการอุปกรณ์", + plsEnterInvitationCode: "โปรดป้อนรหัสเชิญของคุณ %s", + optional: "ตัวเลือก", + nextStep: "ขั้นตอนถัดไป", + plsEnterRightPhone: "โปรดป้อนหมายเลขโทรศัพท์ที่ถูกต้อง", + enterVerificationCode: "ป้อนรหัสยืนยันโทรศัพท์", + setPassword: "ตั้งรหัสผ่าน", + plsConfirmPasswordAgain: "โปรดยืนยันรหัสผ่านของคุณอีกครั้ง", + text_password2: "โปรดยืนยันรหัสผ่านเข้าสู่ระบบอีกครั้ง", + please_enter_new_password_again: "โปรดป้อนรหัสผ่านใหม่ของคุณอีกครั้ง", + confirmPassword: "ยืนยันรหัสผ่าน", + wrongPasswordFormat: "รูปแบบรหัสผ่านไม่ถูกต้อง", + plsCompleteInfo: "โปรดกรอกข้อมูลส่วนตัวของคุณ", + plsEnterYourNickname: "โปรดป้อนชื่อเล่นของคุณ", + pleaseEnterYourUsername: "โปรดป้อนชื่อผู้ใช้ของคุณ", + enterYourUsernameToHelpUsIdentifyYourAccount: + "ป้อนชื่อผู้ใช้ของคุณเพื่อช่วยให้เราระบุบัญชีของคุณ", + text_1: "บัญชีนี้ยังไม่ผูกอีเมล ดังนั้นไม่สามารถกู้คืนรหัสผ่านได้ชั่วคราว", + setInfo: "ตั้งค่าข้อมูล", + loginPwdFormat: "6-20 ตัวเลขและตัวอักษร", + passwordLogin: "เข้าสู่ระบบด้วยรหัสผ่าน", + contacts: "รายชื่อผู้ติดต่อ", + workbench: "เวิร์กเบนช์", + mine: "ของฉัน", + me: "ฉัน", + draftText: "ดร๊าฟ", + everyone: "ทุกคน", + you: "คุณ", + groupAc: "ประกาศกลุ่ม", + createGroupNtf: "%s ได้สร้างการสนทนากลุ่ม", + editGroupInfoNtf: "%s แก้ไขข้อมูลกลุ่ม", + quitGroupNtf: "%s ออกจากการสนทนากลุ่ม", + invitedJoinGroupNtf: "%s เชิญ %s เข้าร่วมการสนทนากลุ่ม", + kickedGroupNtf: "%s ถูกไล่ออกจากการสนทนากลุ่มโดย %s", + joinGroupNtf: "%s เข้าร่วมการสนทนากลุ่ม", + dismissGroupNtf: "%s ทำการยุบกลุ่ม", + transferredGroupNtf: "%s โอนสิทธิการจัดการกลุ่มให้กับ %s", + muteMemberNtf: "%s ถูกปิดเสียงโดย %s เป็นเวลา %s", + muteCancelMemberNtf: "%s การปิดเสียงถูกยกเลิกโดย %s", + muteGroupNtf: "%s เปิดการปิดเสียงกลุ่ม", + muteCancelGroupNtf: "%s ปิดการปิดเสียงกลุ่ม", + friendAddedNtf: "คุณได้เพิ่มเพื่อนแล้ว คุณสามารถเริ่มสนทนาได้", + openPrivateChatNtf: "เปิดการสนทนาส่วนตัวแล้ว", + closePrivateChatNtf: "ปิดการสนทนาส่วนตัวแล้ว", + memberInfoChangedNtf: "%s แก้ไขข้อมูลสมาชิกกลุ่มของตัวเอง", + unsupportedMessage: "ประเภทข้อความที่ไม่รองรับชั่วคราว", + picture: "รูปภาพ", + video: "วิดีโอ", + voice: "เสียง", + safety: "ความปลอดภัย", + location: "ตำแหน่ง", + file: "ไฟล์", + carte: "นามบัตร", + emoji: "อีโมจิที่กำหนดเอง", + chatRecord: "บันทึกการสนทนา", + revokeMsg: "ถอดการยกเลิกข้อความ", + aRevokeBMsg: "%s ได้ถอดการยกเลิกข้อความของ %s", + blockedByFriendHint: "ข้อความถูกส่งไปแล้ว แต่ถูกปฏิเสธโดยอีกฝ่าย", + sendFriendVerification: "ส่งคำขอการตรวจสอบเพื่อเพิ่มเป็นเพื่อน", + removedFromGroupHint: "คุณถูกนำออกจากกลุ่มแล้ว", + groupDisbanded: "กลุ่มได้ถูกยุบแล้ว", + createdGroup: "คุณยังไม่ได้สร้างกลุ่ม", + search: "ค้นหา", + newGroups: "กลุ่มใหม่", + groupNames: "โปรดป้อนชื่อกลุ่ม", + synchronizing: "การซิงโครไนซ์", + syncFailed: "การซิงค์ล้มเหลว", + connecting: "การเชื่อมต่อ", + connectionFailed: "การเชื่อมต่อล้มเหลว", + top: "ด้านบน", + cancelTop: "ยกเลิกด้านบน", + markHasRead: "ทำเครื่องหมายว่าอ่านแล้ว", + delete: "ลบ", + nPieces: "%s ชิ้น", + online: "ออนไลน์", + offline: "ออฟไลน์", + phoneOnline: "โทรศัพท์มือถือ", + pcOnline: "คอมพิวเตอร์", + webOnline: "เว็บ", + webMiniOnline: "เว็บมินิ", + upgradeFind: "พบเวอร์ชันใหม่", + upgradeVersion: "ตรวจพบเวอร์ชันใหม่ที่ใช้ได้ %s ปัจจุบันเป็นเวอร์ชัน %s", + upgradeDescription: "คำอธิบายการอัปเดต:", + upgradeIgnore: "ละเว้น", + upgradeLater: "อัปเดตภายหลัง", + upgradeNow: "อัปเดตทันที", + upgradeNever: "ไม่อัปเดตในขณะนี้", + inviteYouCall: "%s เชิญคุณเข้าร่วม %s", + rejectCall: "ปฏิเสธ", + acceptCall: "ยอมรับ", + callVoice: "สายโทรเสียง", + callVideo: "สายโทรวิดีโอ", + sentSuccessfully: "ส่งสำเร็จ", + copySuccessfully: "คัดลอกสำเร็จ", + binding_successful: "ผูกพันสำเร็จ", + day: "วัน", + hour: "ชั่วโมง", + hours: "ชั่วโมง", + minute: "นาที", + seconds: "วินาที", + cancel: "ยกเลิก", + determine: "ตกลง", + failed_to_load: "โหลดล้มเหลว", + failed_to_load1: "โหลดล้มเหลว โปรดลองอีกครั้งในภายหลัง", + removal_failed: "การลบล้มเหลว", + toolboxAlbum: "อัลบั้ม", + toolboxCall: "สายวิดีโอ", + toolboxCamera: "ถ่ายภาพ", + toolboxCard: "นามบัตร", + toolboxFile: "ไฟล์", + toolboxLocation: "ตำแหน่ง", + send: "ส่ง", + holdTalk: "กดค้าง เพื่อพูด", + releaseToSend: "ปล่อย เพื่อส่ง", + releaseToSendSwipeUpToCancel: "ปล่อยเพื่อส่ง ลากขึ้นเพื่อยกเลิก", + liftFingerToCancelSend: "ยกนิ้วเพื่อยกเลิกการส่ง", + callDuration: "ระยะเวลาการโทร %s", + cancelled: "ยกเลิกแล้ว", + cancelledByCaller: "ถูกยกเลิกโดยผู้โทร", + rejectedByCaller: "ถูกปฏิเสธโดยผู้โทร", + callTimeout: "หมดเวลาโทร", + rejected: "ถูกปฏิเสธ", + forwardMaxCountHint: + "ขณะนี้รองรับการส่งต่อสูงสุดยอดของข้อความไม่เกินยี่สิบข้อความ~", + typing: "กำลังพิมพ์...", + addSuccessfully: "เพิ่มสำเร็จ", + addFailed: "เพิ่มล้มเหลว", + setSuccessfully: "ตั้งค่าสำเร็จ", + callingBusy: "คุณกำลังในสายโทรและไม่สามารถดำเนินการนี้ได้!", + groupCallHint: + "กลุ่มปัจจุบันกำลังในการสนทนา คุณแน่ใจหรือไม่ว่าต้องการเข้าร่วมสนทนาปัจจุบัน?", + joinIn: "เข้าร่วม", + menuCopy: "คัดลอก", + menuDel: "ลบ", + menuForward: "ส่งต่อ", + menuReply: "ตอบกลับ", + menuMulti: "เลือกหลายรายการ", + menuRevoke: "เรียกคืน", + menuAdd: "เพิ่ม", + nMessage: "%s ข้อความใหม่", + plsSelectLocation: "โปรดเลือกตำแหน่ง", + groupAudioCallHint: "%s คนกำลังพูดโทร", + groupVideoCallHint: "%s คนกำลังวิดีโอโทร", + reEdit: "แก้ไขอีกครั้ง", + playSpeed: "ความเร็วการเล่น", + download: "ดาวน์โหลด", + download1: "ดาวน์โหลดทันที", + nicknames: "ชื่อเล่นผู้ใช้", + downloadAPP: "ดาวน์โหลดแอพ", + private_chat: "แชทส่วนตัว", + googleMap: "แผนที่ Google", + check_for_updates: "ตรวจสอบการอัปเดต", + appleMap: "แผนที่ Apple", + baiduMap: "แผนที่ Baidu", + amapMap: "แผนที่ Amap", + signature: "ลายเซ็น", + please_enter_user_nickname: "โปรดป้อนชื่อเล่นผู้ใช้", + set_characters: "โปรดตั้งค่า 4-15 ตัวอักษร", + personalized_signature: "โปรดป้อนลายเซ็นส่วนตัว (จำกัดตัวอักษร 6-50)", + tencentMap: "แผนที่ Tencent", + offlineMeetingMessage: "คุณได้รับข้อความเชิญประชุมแบบออฟไลน์", + offlineMessage: "คุณมีข้อความใหม่", + offlineCallMessage: "คุณได้รับข้อความเชิญโทรออกแบบออฟไลน์", + logoutHint: "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?", + myInfo: "ข้อมูลของฉัน", + workingCircle: "วงการทำงาน", + accountSetup: "การตั้งค่าบัญชี", + aboutUs: "เกี่ยวกับเรา", + about: "เกี่ยวกับ", + bgColor: "สีพื้นหลัง", + + logout: "ออกจากระบบ", + qrcode: "รหัส QR", + qrcodeHint: "สแกนรหัส QR ด้านล่างเพื่อเพิ่มฉันเป็นเพื่อน", + favoriteFace: "รูปประกอบที่ชื่นชอบ", + favoriteManage: "การจัดการ", + favoriteCount: "รวม %s รูปประกอบ", + favoriteDel: "ลบ (%s)", + hasRead: "อ่านแล้ว", + unread: "ยังไม่ได้อ่าน", + nPersonUnRead: "%s คนยังไม่ได้อ่าน", + allRead: "อ่านทั้งหมด", + messageRecipientList: "รายการผู้รับข้อความ", + hasReadCount: "อ่านแล้ว (%s)", + unreadCount: "ยังไม่ได้อ่าน (%s)", + newFriend: "เพื่อนใหม่", + newFriendList: "รายการคำขอเพื่อน", + newGroup: "การสนทนาใหม่", + myFriend: "เพื่อนของฉัน", + myGroup: "กลุ่มของฉัน", + addManager: "เพิ่มผู้จัดการ", + privateSetting: "การตั้งค่าส่วนตัว", + add: "เพิ่ม", + scan: "สแกน", + scanHint: "สแกนบัตรนามรหัส QR", + addFriend: "เพิ่มเพื่อน", + addFriendHint: "ค้นหาและเพิ่มด้วยรหัส ID", + createGroup: "สร้างกลุ่มสนทนา", + createGroupHint: "สร้างกลุ่มสนทนา ใช้ OpenIM อย่างเต็มรูปแบบ", + addGroup: "เพิ่มกลุ่ม", + addGroupHint: "ถามผู้ดูแลระบบหรือสมาชิกกลุ่มเพื่อรหัส ID", + searchIDAddFriend: "ค้นหา ID เพื่อเพิ่มเพื่อน", + searchIDAddGroup: "ค้นหา ID เพื่อเพิ่มกลุ่ม", + searchIDIs: "ID: %s", + searchPhoneIs: "โทรศัพท์: %s", + searchEmailIs: "อีเมล: %s", + searchNicknameIs: "ชื่อเล่น: %s", + searchGroupNicknameIs: "ชื่อเล่นกลุ่ม: %s", + noFoundUser: "ไม่พบผู้ใช้นี้", + noFoundGroup: "ไม่สามารถพบกลุ่มสนทนานี้", + joinGroupDate: "วันที่เข้าร่วมกลุ่ม", + joinGroupMethod: "วิธีเข้าร่วมกลุ่ม", + byInviteJoinGroup: "%s เชิญเข้าร่วมกลุ่ม", + byIDJoinGroup: "ค้นหา ID กลุ่ม", + byQrcodeJoinGroup: "รหัส QR กลุ่ม", + groupID: "รหัส ID กลุ่ม", + setAsAdmin: "ตั้งเป็นผู้ดูแลระบบ", + setMute: "ตั้งค่าการปิดเสียง", + organizationInfo: "ข้อมูลองค์กร", + organization: "องค์กร", + department: "แผนก", + position: "ตำแหน่ง", + personalInfo: "ข้อมูลส่วนตัว", + viewDynamics: "ดูดีไนมิกส์", + audioAndVideoCall: "โทรออกเสียงและวิดีโอ", + sendMessage: "ส่งข้อความ", + avatar: "อวตาร", + name: "ชื่อ", + nickname: "ชื่อเล่น", + gender: "เพศ", + englishName: "ชื่ออังกฤษ", + birthDay: "วันเกิด", + tel: "โทรศัพท์", + mobile: "เบอร์โทรศัพท์มือถือ", + email: "อีเมล", + man: "ชาย", + woman: "หญิง", + friendSetup: "ตั้งค่าเพื่อน", + setupRemark: "ตั้งค่าหมายเหตุ", + recommendToFriend: "แนะนำให้เพื่อน", + addToBlacklist: "เพิ่มในรายการบัญชีดำ", + unfriend: "ยกเลิกความเป็นเพื่อน", + areYouSureDelFriend: "คุณแน่ใจหรือไม่ว่าต้องการลบเพื่อน?", + areYouSureAddBlacklist: "คุณแน่ใจหรือไม่ว่าต้องการเพิ่มเพื่อนเข้ารายชื่อดำ?", + remark: "หมายเหตุ", + save: "บันทึก", + saveSuccessfully: "บันทึกสำเร็จ", + saveFailed: "การบันทึกล้มเหลว", + groupVerification: "การตรวจสอบกลุ่ม", + friendVerification: "การตรวจสอบเพื่อน", + email_address: "โปรดป้อนที่อยู่อีเมล", + sendEnterGroupApplication: "ส่งคำขอเข้าร่วมกลุ่ม", + sendToBeFriendApplication: "ส่งคำขอเป็นเพื่อน", + sendSuccessfully: "ส่งสำเร็จ", + reset_successful: "รีเซ็ตสำเร็จ", + bind_new_email: "ผูกอีเมลใหม่", + Login_failed: "เข้าสู่ระบบล้มเหลว", + successful_home: "ลงทะเบียนสำเร็จ", + sendFailed: "ส่งล้มเหลว", + canNotAddFriends: "ผู้ใช้นี้ได้ตั้งค่าไม่อนุญาตให้เพิ่ม!", + mutedAll: "ปิดเสียงทั้งหมด", + tenMinutes: "10 นาที", + oneHour: "1 ชั่วโมง", + twelveHours: "12 ชั่วโมง", + oneDay: "1 วัน", + custom: "กำหนดเอง", + unmute: "ยกเลิกการปิดเสียง", + youMuted: "คุณถูกปิดเสียง", + groupMuted: "ปิดเสียงกลุ่มแล้ว", + notDisturbMode: "โหมดไม่รบกวน", + allowRing: "เสียงแจ้งเตือนข้อความใหม่", + allowVibrate: "การสั่นเมื่อมีข้อความใหม่", + forbidAddMeToFriend: "ห้ามให้เพิ่มเป็นเพื่อน", + blacklist: "รายชื่อดำในสมุดที่อยู่", + unlockSettings: "ปลดล็อกการตั้งค่า", + changePassword: "เปลี่ยนรหัสผ่าน", + clearChatHistory: "ล้างประวัติการสนทนา", + confirmClearChatHistory: "ยืนยันการล้างประวัติการสนทนาทั้งหมดหรือไม่?", + languageSetup: "การตั้งค่าภาษา", + language: "ภาษา", + english: "อังกฤษ", + setting_up: "กำลังตั้งค่า...", + chinese: "จีนตัวย่อ", + traditional_chinese: "จีนตัวเต็ม", + followSystem: "ตามระบบ", + blacklistEmpty: "ไม่มีรายชื่อดำ", + remove: "เอาออก", + removeFriend: "เอาเพื่อนออก", + fingerprint: "ลายนิ้วมือ", + gesture: "ท่าทาง", + biometrics: "การระบุตัวด้วยชีววิทยา", + plsEnterPwd: "โปรดใส่รหัสผ่าน", + plsEnterOldPwd: "โปรดใส่รหัสผ่านเดิม", + plsEnterNewPwd: "โปรดใส่รหัสผ่านใหม่", + plsConfirmNewPwd: "โปรดยืนยันรหัสผ่านใหม่", + reset: "รีเซ็ต", + oldPwd: "รหัสผ่านเดิม", + newPwd: "รหัสผ่านใหม่", + confirmNewPwd: "ยืนยันรหัสผ่านใหม่:", + plsEnterConfirmPwd: "โปรดใส่รหัสผ่านยืนยัน", + twicePwdNoSame: "รหัสผ่านที่ป้อนสองครั้งไม่ตรงกัน", + twicePwd: "รหัสผ่านใหม่ไม่ตรงกัน โปรดตั้งค่าใหม่", + changedSuccessfully: "เปลี่ยนแปลงสำเร็จ", + deleteSuccessfully: "ลบสำเร็จ", + checkNewVersion: "ตรวจสอบเวอร์ชันใหม่", + noAddGround: "ยังไม่มีการเพิ่มเพื่อนในกลุ่ม", + chatContent: "เนื้อหาการสนทนา", + topContacts: "ติดต่อยอดนิยม", + messageNotDisturb: "ไม่รบกวนข้อความ", + messageNotDisturbHint: "รับข้อความแต่ไม่แจ้งเตือน", + burnAfterReading: "เผาหลังอ่าน", + timeSet: "การตั้งเวลา", + setChatBackground: "ตั้งค่าพื้นหลังการสนทนา", + fontFace: "แบบอักษร", + fontSize: "ขนาดตัวอักษร", + little: "เล็ก", + standard: "มาตรฐาน", + big: "ใหญ่", + thirtySeconds: "30 วินาที", + fiveMinutes: "5 นาที", + clearAll: "ล้างทั้งหมด", + clearSuccessfully: "ล้างสำเร็จ", + groupChatSetup: "การตั้งค่าการสนทนาในกลุ่ม", + viewAllGroupMembers: "ดูสมาชิกทั้งหมดในกลุ่ม (%s)", + groupManage: "จัดการกลุ่ม", + myGroupMemberNickname: "ชื่อของฉันในกลุ่มนี้", + topChat: "สนทนาด้านบน", + muteAllMember: "ปิดเสียงสมาชิกทั้งหมด", + exitGroup: "ออกจากกลุ่มสนทนา", + dismissGroup: "สิ้นสุดกลุ่มสนทนา", + dismissGroupHint: + "สมาชิกทั้งหมดจะออกจากกลุ่มนี้และไม่สามารถส่งหรือรับข้อความได้", + quitGroupHint: "หลังจากออกจากกลุ่มสนทนาคุณจะไม่ได้รับข้อความจากกลุ่มนี้", + joinGroupSet: "การตั้งค่าเข้าร่วมกลุ่ม", + allowAnyoneJoinGroup: "อนุญาตให้ทุกคนเข้าร่วมกลุ่ม", + inviteNotVerification: "ไม่ต้องยืนยันคำเชิญของสมาชิกในกลุ่ม", + needVerification: "ต้องการส่งข้อความยืนยัน", + addMember: "เพิ่ม", + delMember: "ลบ", + groupOwner: "เจ้าของกลุ่ม", + groupAdmin: "ผู้ดูแลกลุ่ม", + notAllowSeeMemberProfile: "ไม่อนุญาตให้ดูข้อมูลสมาชิกกลุ่มอื่น", + notAllAddMemberToBeFriend: "ไม่อนุญาตให้เพิ่มสมาชิกในกลุ่มเป็นเพื่อน", + transferGroupOwnerRight: "โอนสิทธิ์เจ้าของกลุ่ม", + groupName: "ชื่อกลุ่ม", + groupAcPermissionTips: "เฉพาะเจ้าของกลุ่มและผู้ดูแลเท่านั้นที่สามารถแก้ไขได้", + plsEnterGroupAc: "โปรดป้อนข้อความประกาศกลุ่ม", + edit: "แก้ไข", + publish: "เผยแพร่", + groupMember: "สมาชิกกลุ่ม", + selectedPeopleCount: "เลือกแล้ว (%s)", + confirmSelectedPeople: "ยืนยัน (%s/%s)", + confirm: "ยืนยัน", + log_in_time: "เวลาเข้าสู่ระบบ:", + log_in_time1: "เวลาเข้าสู่ระบบ", + login_IP: "IP เข้าสู่ระบบ:", + confirmTransferGroupToUser: "ยืนยันการโอนสิทธิ์เจ้าของกลุ่มไปยัง: %s?", + removeGroupMember: "ลบสมาชิกกลุ่ม", + searchNotResult: "ไม่พบผลลัพธ์ในการค้นหา", + groupQrcode: "รหัส QR กลุ่ม", + groupQrcodeHint: "สแกนรหัส QR ด้านล่างเพื่อเข้าร่วมการสนทนากลุ่มทันที", + approved: "ได้รับการอนุมัติแล้ว", + accept: "ยอมรับ", + reject: "ปฏิเสธ", + friendGrouping: "การจัดกลุ่มเพื่อน", + waitingForVerification: "กำลังรอการยืนยัน", + rejectSuccessfully: "ปฏิเสธสำเร็จ", + rejectFailed: "การปฏิเสธล้มเหลว", + applyJoin: "ยื่นขอเข้าร่วม", + enterGroup: "เข้าสู่กลุ่ม", + applyReason: "เหตุผลการยื่นขอ: %s", + invite: "เชิญ", + sourceFrom: "มาจาก: %s", + byMemberInvite: "เชิญ", + bySearch: "โดยการค้นหา", + byScanQrcode: "สแกนรหัส QR", + iCreatedGroup: "กลุ่มที่สร้างขึ้นโดยฉัน", + iJoinedGroup: "กลุ่มที่เข้าร่วมโดยฉัน", + nPerson: "{count} คน", + searchNotFound: "ไม่พบผลลัพธ์ที่เกี่ยวข้อง", + searchNotFoundMember: "ไม่พบผู้ใช้ที่เกี่ยวข้อง", + organizationStructure: "โครงสร้างองค์กร", + recentConversations: "การสนทนาล่าสุด", + selectAll: "เลือกทั้งหมด", + plsEnterGroupNameHint: "ใส่ชื่อกลุ่มเพื่อความสะดวกในการค้นหาภายหลัง", + completeCreation: "สร้างเสร็จสมบูรณ์", + sendCarteConfirmHint: "ยืนยันการส่งข้อมูลติดต่อนี้ไปยังการสนทนานี้หรือไม่?", + sentSeparatelyTo: "ส่งแยกออกไปยัง:", + sentTo: "ส่งไปยัง:", + leaveMessage: "ข้อความที่เหลือ", + mergeForwardHint: "[รวมการส่งต่อ] รวมทั้งหมด %s ข้อความ", + mergeForward: "รวมการส่งต่อ", + quicklyFindChatHistory: "ค้นหาประวัติการแชทอย่างรวดเร็ว", + notFoundChatHistory: "ไม่พบเนื้อหาที่เกี่ยวข้องกับ %s", + globalSearchAll: "ค้นหาทั้งหมด", + globalSearchContacts: "รายชื่อผู้ติดต่อ", + globalSearchGroup: "กลุ่ม", + globalSearchChatHistory: "ประวัติการแชท", + globalSearchChatFile: "ไฟล์", + relatedChatHistory: "%s รายการประวัติการแชทที่เกี่ยวข้อง", + seeMoreRelatedContacts: "ดูรายชื่อผู้ติดต่อเพิ่มเติม", + seeMoreRelatedGroup: "ดูกลุ่มเพิ่มเติมที่เกี่ยวข้อง", + seeMoreRelatedChatHistory: "ดูประวัติการแชทเพิ่มเติมที่เกี่ยวข้อง", + seeMoreRelatedFile: "ดูไฟล์เพิ่มเติมที่เกี่ยวข้อง", + publishPicture: "เผยแพร่รูปภาพ", + publishVideo: "เผยแพร่วิดีโอ", + mentioned: "กล่าวถึง: %s", + comment: "ความคิดเห็น", + like: "ถูกใจ", + reply: "ตอบ", + rollUp: "ยุบ", + fullText: "ข้อความเต็ม", + selectAssetsFromCamera: "เลือกจากกล้อง", + selectAssetsFromAlbum: "เลือกจากอัลบั้ม", + whoCanWatch: "ใครสามารถดูได้", + remindWhoToWatch: "เตือนใครให้ดู", + public: "สาธารณะ", + everyoneCanSee: "ทุกคนสามารถเห็นได้", + partiallyVisible: "มองเห็นบางส่วน", + visibleToTheSelected: "มองเห็นได้โดยผู้ที่เลือก", + partiallyInvisible: "ไม่ให้คนดู", + invisibleToTheSelected: "ไม่สามารถมองเห็นได้โดยผู้ที่เลือก", + private: "ส่วนตัว", + onlyVisibleToMe: "เห็นได้เฉพาะฉันเท่านั้น", + selectVideoLimit: "ระยะเวลาไม่ควรเกิน 15 วินาที", + selectContactsLimit: "เลือกอย่างน้อยหนึ่งท่าน", + message: "ข้อความ", + commentedYou: "แสดงความคิดเห็นในคุณ: %s", + likedYou: "ถูกใจคุณ", + mentionedYou: "กล่าวถึงคุณ", + replied: "ตอบกลับ", + detail: "รายละเอียด", + totalNPicture: "รวมทั้งหมด %s รูป", + noDynamic: "ไม่มีแบบดินามิก", + callRecords: "บันทึกการโทร", + allCall: "การโทรทั้งหมด", + missedCall: "สายที่ไม่ได้รับ", + incomingCall: "สายเข้า", + outgoingCall: "สายออก", + microphone: "ไมโครโฟน", + speaker: "ลำโพง", + hangUp: "วางสาย", + pickUp: "รับสาย", + waitingCallHint: "กำลังโทรอยู่...", + waitingVoiceCallHint: "กำลังรอคู่ตอบรับการโทรเสียง...", + invitedVoiceCallHint: "เชิญคุณเข้าร่วมการโทรเสียง...", + waitingVideoCallHint: "กำลังรอคู่ตอบรับคำเชิญวิดีโอ", + invitedVideoCallHint: "เชิญคุณเข้าร่วมการโทรวิดีโอ...", + waitingToAnswer: "กำลังรอคำตอบ", + invitedYouToCall: "เชิญคุณเข้าร่วมการโทร", + calling: "กำลังโทร", + nPeopleCalling: "มีคน %s คนกำลังโทร", + whoInvitedVoiceCallHint: "%s เชิญคุณเข้าร่วมการโทรเสียง", + whoInvitedVideoCallHint: "%s เชิญคุณเข้าร่วมการโทรวิดีโอ", + plsInputMeetingSubject: "โปรดใส่หัวข้อการประชุม", + meetingStartTime: "เวลาเริ่มการประชุม", + meetingDuration: "ระยะเวลาการประชุม", + enterMeeting: "เข้าร่วมการประชุม", + meetingNo: "หมายเลขการประชุม", + yourMeetingName: "ชื่อของคุณ", + plsInputMeetingNo: "โปรดใส่หมายเลขการประชุม", + plsInputYouMeetingName: "โปรดใส่ชื่อของคุณ", + meetingSubjectIs: "หัวข้อการประชุม: %s", + meetingStartTimeIs: "เวลาเริ่ม: %s", + meetingDurationIs: "ระยะเวลาการประชุม: %s", + meetingHostIs: "เจ้าบ่าว: %s", + meetingNoIs: "หมายเลขการประชุม: %s", + meetingMessageClickHint: "คลิกที่ข้อความนี้เพื่อเข้าร่วมการประชุมโดยตรง", + meetingMessage: "ข้อความการประชุม", + openMeeting: "การประชุมที่ยังไม่สิ้นสุด", + didNotStart: "ยังไม่เริ่ม", + started: "เริ่มแล้ว", + meetingInitiatorIs: "การประชุมวิดีโอที่เริ่มโดย %s", + meetingDetail: "รายละเอียดการประชุม", + meetingOrganizerIs: "ผู้จัดการ: %s", + updateMeetingInfo: "อัปเดตข้อมูลการประชุม", + cancelMeeting: "ยกเลิกการประชุม", + videoMeeting: "การประชุมวิดีโอ", + joinMeeting: "เข้าร่วมการประชุม", + bookAMeeting: "จองการประชุม", + quickMeeting: "การประชุมด่วน", + confirmTheChanges: "ยืนยันการเปลี่ยนแปลง", + invitesYouToVideoConference: "%s เชิญคุณเข้าร่วมการประชุมวิดีโอ", + over: "สิ้นสุด", + meetingMute: "ปิดเสียง", + meetingUnmute: "เปิดเสียง", + meetingCloseVideo: "ปิดวิดีโอ", + meetingOpenVideo: "เปิดวิดีโอ", + meetingEndSharing: "สิ้นสุดการแชร์", + meetingShareScreen: "แชร์หน้าจอ", + meetingMembers: "สมาชิก(%s)", + settings: "การตั้งค่า", + leaveMeeting: "ออกจากการประชุม", + endMeeting: "สิ้นสุดการประชุม", + leaveMeetingConfirmHint: "คุณแน่ใจหรือไม่ที่จะออกจากการประชุม?", + endMeetingConfirmHit: "คุณแน่ใจหรือไม่ที่จะสิ้นสุดการประชุม?", + meetingSettings: "การตั้งค่าการประชุม", + allowMembersOpenMic: "อนุญาตสมาชิกปลดการปิดไมค์เอง", + allowMembersOpenVideo: "อนุญาตสมาชิกเปิดวิดีโอเอง", + onlyHostShareScreen: "เฉพาะเจ้าภาพเท่านั้นที่สามารถแชร์หน้าจอ", + onlyHostInviteMember: + "เฉพาะเจ้าภาพเท่านั้นที่สามารถเชิญสมาชิกเข้าร่วมการประชุมได้", + defaultMuteMembers: "ปิดเสียงสมาชิกที่เข้าร่วมประชุม", + pinThisMember: "ปักหมุดสมาชิกนี้", + unpinThisMember: "ยกเลิกการปักหมุด", + allSeeHim: "ทุกคนเห็นเขา", + cancelAllSeeHim: "ยกเลิกการให้ทุกคนเห็นเขา", + muteAll: "ปิดเสียงทุกคน", + unmuteAll: "ยกเลิกการปิดเสียงทุกคน", + members: "สมาชิก", + screenShare: "แชร์หน้าจอ", + screenShareHint: "กำลังแชร์หน้าจอ", + meetingClosedHint: + "การประชุมได้ถูกปิดหรือตัดการเชื่อมต่อแล้ว คุณแน่ใจหรือไม่ที่จะออก?", + meetingIsOver: "การประชุมจบลงแล้ว!", + networkError: "มีปัญหาในเครือข่าย โปรดลองอีกครั้งในภายหลัง!", + shareSuccessfully: "แชร์สำเร็จ!", + notFoundMinP: "ยังไม่มีการเผยแพร่แอปพลิเคชันเล็กน้อย", + notSendMessageNotInGroup: + "ไม่สามารถส่งข้อความในการแชทแบบกลุ่มที่คุณออกจากได้", + whoModifyGroupName: "{name} เปลี่ยนชื่อกลุ่มเป็น ", + accountWarn: "คำเตือน!", + accountException: + "บัญชีของคุณเข้าสู่ระบบในอุปกรณ์อื่นแล้ว กรุณาเปลี่ยนรหัสผ่านทันที", + tagGroup: "แท็ก", + issueNotice: "แจ้งเตือน", + createTagGroup: "สร้างแท็กกลุ่ม", + plsEnterTagGroupName: "โปรดป้อนชื่อแท็ก", + tagGroupMember: "สมาชิกแท็ก", + completeEdit: "แก้ไขเสร็จสิ้น", + finish: "เสร็จสิ้น", + emptyTagGroup: "ไม่มีกลุ่มแท็ก", + confirmDelTagGroupHint: "คุณแน่ใจหรือไม่ที่จะลบกลุ่มแท็กนี้?", + editTagGroup: " แก้ไขแท็ก", + newBuild: " สร้างใหม่", + receiveMember: " สมาชิกที่ได้รับ", + emptyNotification: "ไม่มีการแจ้งเตือน", + notificationReceiver: "ผู้รับ %s คน: %s", + sendAnother: "ส่งอีกครั้ง", + confirmDelTagNotificationHint: "คุณแน่ใจหรือไม่ที่จะลบบันทึกการแจ้งเตือนนี้?", + contentNotBlank: "เนื้อหาต้องไม่ว่างเปล่า", + plsEnterDescription: "กรุณาใส่คำอธิบาย", + gifNotSupported: "ไม่รองรับภาพ GIF", + register: "ลงทะเบียน", + hk: "ตัวอักษรฮ่องกง", + RegisterSuccess: "ลงทะเบียนสำเร็จ", + LoginSuccess: "เข้าสู่ระบบสำเร็จ", + init_validate_1: "ชื่อผู้ใช้ต้องประกอบด้วยตัวอักษรและตัวเลข 6-15 ตัว", + init_validate_10: + "หมายเลขโทรศัพท์มือถือหรือรหัสผ่านไม่ถูกต้อง โปรดลองใหม่อีกครั้ง", + init_validate_5: "รหัสผ่านต้องประกอบด้วยอักษรและตัวเลข 6-15 หลัก โปรดตั้งรหัสผ่านใหม่", + init_validate_2: "รหัสผ่านไม่ตรงกัน โปรดตั้งรหัสผ่านใหม่", + init_validate_3: + "รหัสผ่านต้องประกอบด้วยตัวเลขและตัวอักษร 6-15 ตัว โปรดตั้งรหัสผ่านใหม่", + init_validate_6: "รหัสผ่านต้องประกอบด้วยตัวเลขและตัวอักษร 6-15 ตัว", + init_validate_4: "ลงทะเบียนสำเร็จ กำลังกลับไปที่หน้าเข้าสู่ระบบ", + init_hint_1: "ตัวเลขหรือตัวอักษร 6-15 ตัว", + init_hint_2: "ยืนยันรหัสผ่าน", + init_hint_3: "รหัสผ่าน", + username: "ชื่อผู้ใช้", + to_modify: "ไปแก้ไข", + confirm_password: "ยืนยันรหัสผ่าน", + notification_settings: "การตั้งค่าการแจ้งเตือน", + init_text_1: "การลงทะเบียนและเข้าสู่ระบบถือเป็นการยินยอม", + init_text_2: "ข้อกำหนดการให้บริการของผู้ใช้", + init_text_3: " และ ", + init_text_4: "นโยบายความเป็นส่วนตัว", + init_text_5: "ข้อกำหนดการให้บริการและนโยบายความเป็นส่วนตัว", + init_text_6: + "เพื่อปกป้องสิทธิ์และผลประโยชน์ทางกฎหมายของคุณ โปรดอ่านและยินยอมตามข้อตกลงต่อไปนี้", + refuse: "ปฏิเสธ", + refuse_continue: "ยอมรับและดำเนินการต่อ", + forget_password: "ลืมรหัสผ่าน?", + register_user: "ลงทะเบียนบัญชี", + gotoLogin: "เข้าสู่ระบบ", + gotoRegister: "สร้างบัญชีใหม่", + enterGroupCheck: "เข้าสู่การตรวจสอบกลุ่ม", + openPhotoError_1: "ไม่ได้รับข้อมูลไฟล์ที่เลือก", + openPhotoError_2: "การอัปโหลดไฟล์ล้มเหลว", + openPhotoError_3: "ไม่ได้รับข้อมูลไฟล์", + agreeAll: "ยอมรับทั้งหมด", + whatCanManagerDo: "สิทธิ์การจัดการกลุ่ม", + changeGroupName: "เปลี่ยนชื่อกลุ่ม", + deleteMemberMessage: "ลบข้อความสมาชิก", + chatPrivate: "สนทนาส่วนตัวที่เข้ารหัสกับสมาชิก", + memberMute: "ปิดเสียงสมาชิก", + deleteMember: "ลบสมาชิก", + changeNotice: "เผยแพร่/แก้ไข/ลบประกาศกลุ่ม", + checkInGroup: "ตรวจสอบเข้ากลุ่ม", + setAdmin: "ตั้งเป็นผู้ดูแล", + deleteAdmin: "ลบผู้ดูแล", + deleteAdminTitle: "คุณแน่ใจหรือไม่ว่าต้องการลบผู้ดูแลระบบนี้?", + deleteAdminDetail: + "การลบสมาชิกนี้ออกจากตำแหน่งผู้ดูแลระบบจะไม่สามารถจัดการข้อความในกลุ่มได้", + deleteSuccess: "ลบสำเร็จ", + mobile_web_page: "เข้าถึงเว็บไซต์บนมือถือ", + deleteMemberTitle: "คุณแน่ใจหรือไม่ว่าต้องการลบสมาชิกนี้?", + deleteMemberDetail: + "การลบสมาชิกนี้ออกจากกลุ่มนี้จะไม่สามารถรับหรือส่งข้อความได้", + deleteSelf: "ไม่สามารถลบตัวเองได้", + handled: "ได้รับการดำเนินการ", + unhandle: "รอการดำเนินการ", + noCheck: "ยังไม่มีคำขอที่ต้องดำเนินการ", + success: "ดำเนินการสำเร็จ", + nameLenth: "โปรดตั้งชื่อที่มี 1-15 อักขระ", + editNickname: "แก้ไขชื่อเล่นกลุ่ม", + text_password: "โปรดตั้งรหัสผ่านเข้าสู่ระบบใหม่", + quit: "ออก", + waitAMoment: "ฉันยังกำลังดู", + editWarningTitle: "คุณแน่ใจหรือไม่ว่าต้องการออกจากการแก้ไขชื่อเล่นกลุ่ม?", + editWarningDetail: + "ชื่อเล่นกลุ่มที่คุณแก้ไขยังไม่ได้บันทึก คุณต้องการออกหรือไม่?", + editGroupAvatar: "แก้ไขภาพโปรไฟล์กลุ่มนี้", + myGroupAvatar: "ภาพโปรไฟล์กลุ่มของฉัน", + inviteLink: "ลิงก์เชิญ", + inviteTips: + "ผู้ใช้ที่ติดตั้งแอปนี้จะสามารถเข้าร่วมกลุ่มของคุณผ่านลิงก์นี้ได้", + inviteLinkCopy: "ลิงก์เชิญถูกคัดลอกแล้ว", + shareLink: "แชร์ลิงก์", + manageLink: "จัดการลิงก์เชิญ", + copyLink: "คัดลอกลิงก์", + deleteLink: "ลบลิงก์", + deleteLinkTipsTitle: "คุณแน่ใจหรือไม่ว่าต้องการยกเลิกลิงก์นี้?", + deleteLinkTipsDetail: + "คุณต้องการยกเลิกลิงก์เชิญนี้หรือไม่? เมื่อยกเลิกแล้ว ผู้อื่นจะไม่สามารถเข้าร่วมกลุ่มนี้ได้", + linkManage: "การจัดการลิงก์", + openManage: "เมื่อเปิดแล้ว ผู้ดูแลหรือผู้ดูแลระบบจะต้องอนุมัติก่อนเข้ากลุ่ม", + timeValid: "จำกัดเวลาที่ถูกต้อง", + linkInvalidTips: "ลิงก์เชิญจะหมดอายุหลังจากวันที่นี้ และไม่", + opened: "เปิดแล้ว", + lanchProtect: "ป้องกันเมื่อเริ่มต้น", + noProtect: "ไม่จำเป็นต้องป้องกัน", + lockSetting: "ตั้งค่าการปลดล็อค", + drawLine: "วาดรูปแบบการปลดล็อค", + drawOldLine: "วาดรูปแบบการปลดล็อคเก่าของคุณ", + drawNewLine: "วาดรูปแบบการปลดล็อคใหม่ของคุณ", + setGesturePassword: "ตั้งรหัสผ่านด้วยเครื่องหมาย", + checkGesturePassword: "ตรวจสอบรหัสผ่านด้วยเครื่องหมาย", + needJoinPoint: "ต้องเชื่อมต่อจุดอย่างน้อย 4 จุด โปรดวาดใหม่", + noFriendAdd: "ไม่มีเพื่อนที่สามารถเพิ่ม", + noFriendDelete: "ไม่มีเพื่อนที่สามารถลบได้", + noVerify: "ต้องการการยืนยันเมื่อเพิ่มฉันเป็นเพื่อน", + drawAgain: "โปรดวาดอีกครั้ง", + checkSuccessful: "ตรวจสอบสำเร็จ", + notInCommon: "ไม่ตรงกันกับการวาดครั้งก่อน โปรดวาดใหม่", + changeGesturePassword: "เปลี่ยนรหัสผ่านด้วยเครื่องหมาย", + toChange: "ไปเปลี่ยน", + change: "เปลี่ยน", + openSuccess: "เปิดใช้งานสำเร็จ", + closeSuccess: "ปิดใช้งานสำเร็จ", + hasBeenMember: "เป็นสมาชิกของกลุ่มแล้ว ไม่ต้องเพิ่มอีกครั้ง", + joinGroup: "เข้าร่วมกลุ่ม", + checking: "รอการตรวจสอบจากผู้ดูแล", + noUnlock: "ไม่สามารถปลดล็อค?", + inputGesture: "โปรดป้อนรหัสผ่านด้วยเครื่องหมาย", + faceDetect: "โปรดทำการตรวจจับใบหน้า", + fingerDetect: "โปรดทำการตรวจจับนิ้วมือ", + tryUnlocking: "โปรดลอง: ไม่สามารถปลดล็อค?", + countRemain: "รหัสผิด คุณยังสามารถป้อนได้อีก %s ครั้ง", + reLoginUnlock: "เข้าสู่ระบบใหม่เพื่อปลดล็อค", + selectVerityType: "โปรดเลือกวิธีการตรวจสอบ ", + currentTypeNotSupport: "ประเภทปัจจุบันไม่รองรับ", + //------------- + updateNewVersion: "พบเวอร์ชันใหม่", + updateOkUpdate: "อัปเดตทันที", + updateOpenDownloadFailed: "เปิดการดาวน์โหลดล้มเหลว", + updateCurrentIsNew: "เวอร์ชันปัจจุบันเป็นเวอร์ชันล่าสุดแล้ว", + newVersionNotFound: + "ไม่พบลิงก์เวอร์ชันล่าสุด คุณสามารถเข้าชมเว็บไซต์เพื่อดาวน์โหลดได้", + openUrlFailed: "ลิงก์ไม่ถูกต้อง", + qmvMsgNotExit: "[ไม่มีเนื้อหาที่อ้างอิง]", + qmvMsgNotSupport: "[ไม่รองรับการแสดงเนื้อหาที่อ้างอิง]", + dataError: "ข้อมูลผิดพลาด", + userNotBindEmail: "ผู้ใช้ยังไม่ได้ผูกอีเมล", + userNotRegister: "ผู้ใช้ยังไม่ได้ลงทะเบียน", + checkError: "การตรวจสอบล้มเหลว", + csvTitle: "การตั้งค่าการสนทนา", + csvSetTopMsg: "ตั้งค่าการสนทนาด้านบน", + csvSetOptMsg: "ไม่รบกวนข้อความ", + csvSetDisableMsg: "บล็อกข้อความ", + csvClearMsg: "ล้างประวัติสนทนา", + csvClearLocal: "ล้างประวัติสนทนาที่เก็บไว้ในอุปกรณ์นี้เท่านั้น", + csvClearUser: "ล้างประวัติสนทนาทั้งในอุปกรณ์นี้และกับ%s", + csvSetErrorTip: "การตั้งค่าล้มเหลว โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ต", + gmiTitle: "ชื่อกลุ่ม:", + gmiSetTalk: "เปิดการสนทนาเข้ารหัส", + gmiSetMute: "ตั้งค่าการปิดเสียงในกลุ่ม", + gmiDeleteUser: "ลบออกจากกลุ่มนี้", + gmiMuteTo: "ปิดเสียงถึง ", + gmiError1: "ไม่พบข้อมูลสมาชิกนี้ อาจถูกเอาออกจากกลุ่มแชท", + gmiError2: "ไม่พบข้อมูลสมาชิกของคุณในกลุ่ม อาจถูกเอาออกจากกลุ่มแชท", + gmiError3: "คุณแน่ใจว่าต้องการเอาผู้ใช้นี้ออก?", + gmiError4: "เมื่อเอาสมาชิกนี้ออกจากกลุ่ม จะไม่สามารถส่งหรือรับข้อความได้", + gmiError5: "คุณไม่มีสิทธิ์ลบผู้ดูแลระบบนี้", + gmiError6: "คุณไม่มีสิทธิ์ลบสมาชิกนี้", + gmiError7: "ยกเลิกการปิดเสียงแล้ว", + gmiError8: "คุณไม่มีสิทธิ์ยกเลิกการปิดเสียงสมาชิกดูแลระบบนี้", + gmiError9: "คุณไม่มีสิทธิ์ยกเลิกการปิดเสียงสมาชิกนี้", + gmiError10: "คุณไม่มีสิทธิ์ปิดเสียงสมาชิกดูแลระบบนี้", + gmiError11: "คุณไม่มีสิทธิ์ปิดเสียงสมาชิกนี้", + gmiError12: "ยกเลิกการปิดเสียงแล้ว", + gmiGroupOwnerNotFound: "โหลดข้อมูลกลุ่มล้มเหลว", + smmvTitle: "ระยะเวลาการปิดเสียง", + smmvMuteTime: "เลือกระยะเวลาการปิดเสียง", + smmvMuteTime_10m: "10 นาที", + smmvMuteTime_1hour: "1 ชั่วโมง", + smmvMuteTime_12hour: "12 ชั่วโมง", + smmvMuteTime_1day: "1 วัน", + smmvMuteTime_30day: "30 วัน", + smmvMuteTime_7day: "7 วัน", + egnpTitle: "แก้ไขชื่อกลุ่มแชท", + egnpSetGroupName: "โปรดตั้งชื่อกลุ่ม", + egnpSetTextDesc: "โปรดตั้ง 1-15 ตัวอักษร", + egnpSetTitleNoPermission: "คุณไม่มีสิทธิ์แก้ไขชื่อกลุ่มแชท", + egnpSetFailed: "การแก้ไขล้มเหลว โปรดลองอีกครั้งในภายหลัง", + setGroupNameHint: "โปรดตั้งชื่อกลุ่ม", + givTitle: "แก้ไขชื่อกลุ่มแชท", + givGroupName: "ชื่อกลุ่ม", + givGroupFace: "รูปโปรไฟล์กลุ่ม", + gmsvTitle: "จัดการกลุ่ม", + gmsvSetManager: "ตั้งเป็นผู้ดูแล", + gmsvToSet: "ไปที่การตั้งค่า", + gmsvFindMsg: "ค้นหาบันทึกแชท", + gmsvFindMsgDesc: + "เปิดใช้งานเพื่อให้สมาชิกเข้ากลุ่มเห็นข้อความย้อนหลังเมื่อเร็วๆ นี้", + gmsvMute: "ปิดเสียงทั้งหมด", + gmsvMuteDesc: + "เมื่อเปิดใช้งาน จะอนุญาตให้เฉพาะเจ้าของกลุ่มและผู้ดูแลเท่านั้นที่สามารถพูดได้", + gmsvApply: "ยืนยันการเข้ากลุ่ม", + gmsvApplyDesc: + "เมื่อเปิดใช้งาน จำเป็นต้องมีการยืนยันจากเจ้าของกลุ่มหรือผู้ดูแลเพื่อเข้ากลุ่ม", + gmsvSetManagerTip: "เฉพาะเจ้าของกลุ่มเท่านั้นที่สามารถตั้งเป็นผู้ดูแลได้", + gmsvSetMuteTipOn: "ปิดเสียงทั้งหมดแล้ว", + gmsvSetNuteTipOff: "ยกเลิกการปิดเสียงทั้งหมดแล้ว", + gmsvApplyTipOn: "การยืนยันการเข้ากลุ่มเปิดใช้งานแล้ว", + gmsvApplyTipOff: "การยืนยันการเข้ากลุ่มปิดใช้งานแล้ว", + gmsvFindGroupErr: "ล้มเหลวในการรับข้อมูลกลุ่ม", + gmsvFindMsgOn: "การค้นหาบันทึกแชทเปิดใช้งานแล้ว", + gmsvFindMsgOff: "การค้นหาบันทึกแชทปิดใช้งานแล้ว", + gmsvSetFailed: "การตั้งค่าล้มเหลว", + gsvTitle: "รายละเอียดกลุ่ม", + gsvGroupId: "รหัสกลุ่ม", + gsvGroupMember: "สมาชิกกลุ่ม", + gsvGroupMemberValue: "ทั้งหมด {num} คน", + gsvGroupNotification: "ประกาศกลุ่ม", + gsvMyName: "ชื่อเล่นของฉันในกลุ่มนี้", + gsvMyFace: "รูปโปรไฟล์ของฉันในกลุ่มนี้", + gsvGroupManager: "ผู้จัดการกลุ่ม", + gsvGroupApply: "การยืนยันการเข้ากลุ่ม", + gsvFindMsg: "ค้นหาบันทึกแชท", + gsvSetTop: "ตั้งเป็นด้านบน", + gsvSetOpt: "ปิดเสียงข้อความ", + gsvDeleteMsg: "ลบบันทึกแชท", + gsvDeleteLocal: "ลบบันทึกแชทที่เก็บไว้ในอุปกรณ์เท่านั้น", + gsvDeleteAllMsg: "ล้างบันทึกแชทของทุกคนทั้งในอุปกรณ์และที่เก็บไว้ในอุปกรณ์", + gsvDeleteOtherMsg: '“ลบบันทึกการแชททັງในเครื่องและของฝ่ายตรงข้าม', + gsvGroupDelete: "คุณแน่ใจหรือว่าต้องการยุติการแชทในกลุ่มนี้?", + gsvOutGroup: "คุณแน่ใจหรือว่าต้องการออกจากกลุ่มนี้?", + gsvNoNotification: "ไม่มีประกาศในขณะนี้", + gsvClearSuccess: "ล้างเรียบร้อย", + gsvClearFailed: "ล้างล้มเหลว โปรดลองอีกครั้งในภายหลัง", + cvAllPeople: "ทุกคน", + cvVideo: "[วิดีโอ]", + cvPicture: "[รูป]", + cvFile: "[ไฟล์]", + cvVoice: "[เสียง]", + cvNotSupportMsg: "ข้อความที่ไม่รองรับในขณะนี้", + cvSendMsgErrorNoPermission: + "เนื่องจากการตั้งค่าสิทธิ์ของอีกฝ่าย คุณไม่สามารถส่งข้อความถึงเขาได้", + cvSendMsgPnMute: "อยู่ในโหมดติดกุญแจ ไม่สามารถส่งข้อความได้", + cvCopyMsg: "คัดลอกลงคลิปบอร์ดแล้ว", + cvCopyMsgNotSupport: "ข้อความที่ไม่รองรับในขณะนี้", + cvdeleteLocal: "ลบจากอุปกรณ์", + cvdeleteLocalAndServer: "ลบจากอุปกรณ์และเซิร์ฟเวอร์", + cvdeleteLocalAndServerUser: "ลบจากอุปกรณ์และ {name}", + cvClearMsgTip: "ล้างข้อความแชทเรียบร้อยแล้ว", + cvGroupCreateSuccess: "สร้างกลุ่มแชทสำเร็จ", + cvGroupCreateSuccessTip1: + "ยินดีด้วยที่สร้างกลุ่มสำเร็จ คลิกที่ปุ่มด้านล่างเพื่อเชิญเพื่อนเข้าร่วมกลุ่ม", + cvGroupCreateSuccessGo: "เชิญเพื่อนเข้าร่วม", + cvGroupNotification: "ประกาศกลุ่ม:", + sendMsg: "ส่งข้อความ", + cvOnlineCount: "{count} คนออนไลน์", + cvUserCount: "ทั้งหมด {count} คน", + cvNotAtUser: "ยังไม่มีคนกลุ่มไหนได้@อ๋อ รีบไปเพิ่มสมาชิกเลย", + all: "ทั้งหมด", + clCloseConversation: "ยุบบทสนทนาที่ติดปัญหา", + clOpenConversation: "บทสนทนาที่ถูกติดด้านบน", + clNoConversation: "ไม่มีการสนทนาล่าสุด", + clTitle: "การสนทนา", + clDelete: "การสนทนานี้จะถูกลบ", + clDeleteDesc: "การลบการสนทนานี้จะลบบันทึกการสนทนาในอุปกรณ์", + clDeleteSel: "คุณยังไม่ได้เลือกการสนทนา", + clDeleteSel1: "การสนทนานี้จะถูกลบ", + clDeleteSel2: "การสนทนาเหล่านี้จะถูกลบ", + clDeleteDesc2: "การลบการสนทนาเหล่านี้จะลบบันทึกการสนทนาในอุปกรณ์", + homeItem0: "การสนทนา", + homeItem1: "รายชื่อผู้ติดต่อ", + homeItem2: "ของฉัน", + exitTip: "คลิกปุ่มย้อนกลับอีกครั้งเพื่อออก", + mineSigEmpty: "ผู้ใช้นี้เป็นคนขี้เกียจมาก ยังไม่มีลายเซ็นต์~", + uaProtocol: "ข้อตกลงการให้บริการของผู้ใช้", + savSearchTip: "ค้นหาเร็วสำหรับเนื้อหาการสนทนา", + savMedia: "รูปภาพ/วิดีโอ", + searchEmptyTip: "ไม่พบบันทึกการสนทนาที่เกี่ยวข้อง", + savSearchMsg: "รายการบันทึกการสนทนาที่เกี่ยวข้อง", + savConversation: "การสนทนา", + savMsg: "บันทึกการสนทนา", + savConversationNotFound: "ไม่พบการสนทนา", + savMsgNotFound: "ไม่พบข้อมูลนี้", + samTitle: "สื่อ", + samTimeFormat: "ปี %s เดือน %s", + notSupportType: "ไม่รองรับประเภทนี้", + media: "สื่อ", + openUrl: "เปิดลิงก์", + lkAddGroupUrl: "ลิงก์เข้าร่วมกลุ่ม", + lkAddOtherUrl: "ลิงก์ภายนอก สามารถเปิดด้วยเบราว์เซอร์เริ่มต้น", + lkUnknowUrl: "ลิงก์ที่ไม่รู้จัก", + openUrlFailed1: "เปิดลิงก์ล้มเหลว", + yszcTitle: "นโยบายความเป็นส่วนตัว", + slsmTitle: "ประกาศทางกฎหมาย", + + ctmRemove: "เอาออก", + ctmOkRemove: "ยืนยันการเอาออก", + ctmTopMsgCount: "ทั้งหมด {count} ข้อความ", + ctmEmptyText: "ไม่มีข้อความด้านบน", + recordTimeShort: "เวลาบันทึกสั้นเกินไป", + sendCodeExceErr: "ส่งรหัสยืนยันล้มเหลว โปรดลองอีกครั้งในภายหลัง", + bindEmailExecErr: "ผูกอีเมลล์ล้มเหลว โปรดลองอีกครั้งในภายหลัง", + ctmSuccess: "การตั้งเป็นด้านบนสำเร็จ", + ctmRemoveSuccess: "การลบข้อความด้านบนสำเร็จ", + ctmMsgNotFound: "ข้อความนี้ไม่มีอยู่", + cvFirstTip: + "ข้อมูลและการสนทนาในการสนทนานี้ได้รับการเข้ารหัสปลายทางจนถึงปลายทาง", + clearGroupNotification: "ล้างประกาศกลุ่มแล้ว", + publicGroupNotification: " ประกาศ", + dialog_tip1: " คำแนะนำ", + deleteGroups: + " หลังจากลบกลุ่มแล้วจะไม่สามารถกู้คืนได้ คุณแน่ใจหรือไม่ว่าต้องการลบกลุ่มนี้?", + dialog_tip_token_invaild: " เข้าสู่ระบบหมดอายุ โปรดเข้าสู่ระบบอีกครั้ง", + notify_new_msg_title: "คุณได้รับข้อความใหม่", + notify_new_msg_msg: "เนื้อหาข้อความ: .....", + dataError1: "ข้อมูลผิดพลาด", + groupApply: " การสมัครเข้ากลุ่ม", + muteLate: "ปิดไมค์หลังจาก", + groupMuteing: "กลุ่มอยู่ในโหมดปิดไมค์", + modifyGroupName: "แก้ไขชื่อกลุ่ม", + groupMemberNotFound: "ไม่พบข้อมูลสมาชิกกลุ่ม", + deleteGround: "ลบกลุ่ม", + bindEmailTipTitle: "ผูกอีเมล", + bindEmailTipContent: "เพื่อความปลอดภัยของบัญชีของคุณ โปรดผูกอีเมลของคุณ", + bindEmailTipOk: "ไปที่การผูก", + bindEmailTipCancle: "ไม่ผูกตอนนี้", + alreadyBeOwner: "คุณเลือกเป็นเจ้าของกลุ่มแล้ว มีสิทธิ์การจัดการสูงสุดแล้ว", + managerApplying: "รอการตรวจสอบเป็นผู้ดูแล", + quote: "อ้างถึง", + request_exception: "ข้อขัดข้องในการร้องขอ", + clearMsg: "ล้างข้อความ", + pressToSay: "กดค้างเพื่อพูด", + slideToCancelSend: "ลื่นไปด้านบนเพื่อยกเลิกการส่ง", + releaseToEnd: "ปล่อยเพื่อจบ", + releaseToCancelSend: "ปล่อยนิ้วเพื่อยกเลิกการส่ง", + invalidVoiceMessage: "ข้อความเสียงไม่ถูกต้อง", + hit: "ตี", + downloadingCache: "กำลังดาวน์โหลดแคช...", + unableToRecognize: "ไม่สามารถรับรู้ได้", + scanResult: "ผลการสแกน", + register_fail: + "การลงทะเบียนล้มเหลว โปรดตรวจสอบการเชื่อมต่ออินเทอร์เน็ตแล้วลองอีกครั้ง", + skip: "ข้าม", + toUse: "ใช้เดี๋ยวนี้", + sideToSide: "การเข้ารหัสปลายทางถึงปลายทาง", + design: "การออกแบบ", + guide1: + "ส่งข้อความและถ่ายทอดข้อมูลโดยเข้ารหัส ใช้เฉพาะการอ่านของตนเองเท่านั้น", + guide2: "การสื่อสารอย่างอิสระตามวิธีของคุณเอง", + guide3: "แพลตฟอร์มการสื่อสารที่ปลอดภัยที่สร้างขึ้นให้เพื่อคุณ", + inputGroupAc: "โปรดใส่ประกาศกลุ่ม (จำกัดตัวอักษร 0-500)", + clickToEnjoy: + "คลิกลิงก์เพื่อบันทึกหรือคัดลอกเนื้อหานี้ และเปิดแอป [球帝帶] เพื่อเข้าร่วมกลุ่ม", + createOK: "สร้างสำเร็จ", + noPravcy: "คุณไม่มีสิทธิ์ในการสร้างกลุ่ม", + groupNumberToTop: "จำนวนกลุ่มถึงขีดจำกัด", + createFail: "การสร้างล้มเหลว โปรดลองอีกครั้งในภายหลัง", + create: "สร้าง", + uploadGroupAvatar: "อัปโหลดรูปโปรไฟล์กลุ่ม", + useNewPassword: "เปลี่ยนรหัสผ่านสำเร็จ กำลังกลับไปที่หน้าเข้าสู่ระบบ", + error_14: "ข้อผิดพลาดของเซิร์ฟเวอร์ (14)", + error_500: "ข้อผิดพลาดของเซิร์ฟเวอร์ (500)", + error_1000: "โทเค็นลายเซ็นผู้ใช้ไม่ถูกต้อง", + error_1001: "พารามิเตอร์ไม่ถูกต้อง", + error_1002: "เวอร์ชันปัจจุบันเป็นเวอร์ชันล่าสุดแล้ว", + error_6001: "ชื่อผู้ใช้นี้ถูกจดทะเบียนแล้ว", + error_6003: "รหัสผ่านต้องประกอบด้วยตัวอักษรและตัวเลขระหว่าง 6-15 ตัว", + error_6004: "การตรวจสอบชื่อผู้ใช้ผิดพลาด", + error_6005: "รหัสผ่านไม่ถูกต้อง กรุณากรอกใหม่อีกครั้ง", + error_6006: "จำนวนครั้งที่รหัสผ่านผิดวันนี้เกินขีดจำกัด", + error_6007: "ผู้ใช้ไม่มีอยู่", + error_6008: "รหัสผ่านเก่าผิด โปรดป้อนใหม่", + error_6009: "รหัสผ่านเก่าเหมือนกับรหัสผ่านใหม่ โปรดป้อนใหม่", + error_6010: "การรับรหัสยืนยันบ่อยเกินไป", + error_6011: "รหัสยืนยันไม่ถูกต้อง กรุณาลองอีกครั้ง", + error_6012: "รหัสยืนยันหมดอายุ", + error_6013: "ครั้งล้มเหลวมากเกินไปในการยืนยันรหัสยืนยัน", + error_6014: "รหัสยืนยันถูกใช้แล้ว", + error_6015: "ไม่พบอีเมล", + error_6016: "อีเมลถูกผูกไว้กับผู้ใช้อื่นแล้ว โปรดใส่ใหม่", + error_6017: "รหัสยืนยันไม่มีอยู่", + error_6018: "บัญชีผู้ใช้นี้ถูกระงับแล้ว", + error_6019: "หมายเลขโทรศัพท์มีการผูกกับผู้อื่นแล้ว", + error_6020: "ที่อยู่อีเมลถูกผูกกับผู้อื่นแล้ว", + error_6021: "ที่อยู่อีเมลไม่ถูกต้อง โปรดป้อนใหม่", + error_6022: "หมายเลขโทรศัพท์ไม่ถูกต้อง โปรดป้อนใหม่", + error_6023: "บัญชีนี้ถูกระงับ ไม่สามารถกู้คืนรหัสผ่านได้ชั่วคราว", + error_6024: "บัญชีนี้ยังไม่ผูกอีเมล ชั่วคราวไม่สามารถกู้คืนรหัสผ่านได้", + error_6025: + "บัญชีนี้ยังไม่ผูกหมายเลขโทรศัพท์ ชั่วคราวไม่สามารถกู้คืนรหัสผ่านได้", + error_6026: "หมายเลขโทรศัพท์ถูกผูกกับผู้อื่นแล้ว โปรดป้อนใหม่", + error_6030: "ไม่มีการกำหนดค่าอีเมลเซิร์ฟเวอร์", + error_6031: "โปรดป้อนหมายเลขโทรศัพท์ที่ถูกต้อง", + error_6032: "โปรดป้อนที่อยู่อีเมลที่ถูกต้อง", + error_100002: "จำนวนข้อความด้านบนถึงขีดจำกัดแล้ว", + error_100003: "ข้อความนี้ได้ถูกตั้งเป็นด้านบนแล้ว", + error_api: "ข้อขัดข้องในการร้องขอ", + error_http: "ข้อผิดพลาดในการร้องขอเครือข่าย", + error_http_11: "การเชื่อมต่อเครือข่ายหมดเวลา", + error_http_12: "การเชื่อมต่อเครือข่ายหมดเวลา", + error_http_13: "การเชื่อมต่อเครือข่ายหมดเวลา", + error_http_14: "ข้อผิดพลาดใบรับรองเครือข่าย", + error_http_15: "ข้อมูลการร้องขอผิดพลาด", + error_http_16: "การร้องขอถูกยกเลิกแล้ว", + error_http_17: "การเชื่อมต่อเครือข่ายหมดเวลา", + error_http_18: "ข้อผิดพลาดภายในการร้องขอเครือข่าย", + error_sdk_err_1002: "ไม่มีสิทธิ์ดำเนินการ", + error_sdk_err: "การดำเนินการล้มเหลว", + search_hine_1: "โปรดใส่เนื้อหาที่ต้องการค้นหา", + str_no_data: "ไม่มีข้อมูลในขณะนี้", + str_no_net: "ไม่มีการเชื่อมต่ออินเทอร์เน็ต", + download_not_support_type: "รูปแบบที่ไม่รองรับ", + download_save_at: "ไฟล์ถูกบันทึกที่: ", + download_not_url: "ไม่พบลิงก์ดาวน์โหลด", + download_file_success: "บันทึกสำเร็จ คุณสามารถดูในอัลบั้ม", + download_failed_file: "การบันทึกไฟล์ล้มเหลว", + download_failed: "การดาวน์โหลดล้มเหลว", + loading_failed_retry: "โหลดล้มเหลว คลิกลองอีกครั้ง", + loading_ing: "กำลังโหลด...", + str_online_later_minute: "ออนไลน์เมื่อ %s นาทีที่แล้ว", + str_online_later_hour: "ออนไลน์เมื่อ %s ชั่วโมงที่แล้ว", + str_online_later_llg: "ไม่ออนไลน์มานานแล้ว", + str_online_later_yesterday: "เมื่อวานนี้ออนไลน์", + str_yesterday: "เมื่อวานนี้", + str_online_later_near: "ออนไลน์เมื่อไม่นานมานี้", + str_minute: "นาที", + clean_notice: "ล้างประกาศ", + clean_notice_1: "คุณแน่ใจหรือว่าต้องการล้างประกาศของกลุ่ม?", + publish_notice: "เผยแพร่ประกาศ", + publish_notice_remind: "การเผยแพร่ประกาศนี้จะแจ้งเตือนสมาชิกกลุ่มทุกคน", + quit_edit: "ออกจากการแก้ไข", + quit_the_edit: "ออกจากการแก้ไขนี้?", + continue_edit: "ดำเนินการต่อ", + publish_success: "เผยแพร่สำเร็จ", + face_success: "การปลดล็อกด้วยใบหน้าเปิดใช้งานสำเร็จ", + finger_success: "การปลดล็อกด้วยลายนิ้วมือเปิดใช้งานสำเร็จ", + groupDismissed: "กลุ่มนี้ได้ถูกยุบ", + hasOut: "หมดอายุแล้ว", + otherLoginStyle: "วิธีการเข้าสู่ระบบอื่น", + otherRegisterStyle: "วิธีการลงทะเบียนอื่น", + hasAccount: "มีบัญชีแล้ว ไปที่หน้าเข้าสู่ระบบ", + mobileCode: "รหัสยืนยันทางโทรศัพท์มือถือ", + codeSendTo: "รหัสยืนยันถูกส่งไปที่", + inputNumber: "โปรดใส่หมายเลขโทรศัพท์", + bindPhone: "ผูกหมายเลขโทรศัพท์", + bindPhoneSuccess: "ผูกหมายเลขโทรศัพท์สำเร็จ", + bindPhoneTipChange: + "หากหมายเลขโทรศัพท์ของคุณไม่ใช้งานอีกต่อไป โปรดเปลี่ยนทันที", + please_input_email: "โปรดใส่ที่อยู่อีเมล", + findPassByEmailTip: "ค้นหารหัสผ่านโดยใช้อีเมล", + findPassByPhoneTip: "ค้นหารหัสผ่านโดยใช้หมายเลขโทรศัพท์", + resetPass: "รีเซ็ตรหัสผ่าน", + current_bind_phone: "หมายเลขโทรศัพท์ที่ผูกอยู่ขณะนี้:", + reget: "ขอรหัสใหม่", + getVerifyCode: "รับรหัสยืนยัน", + alreadyRegister: "หมายเลขโทรศัพท์นี้ได้ลงทะเบียนแล้ว", + alreadyRegister2: "ที่อยู่อีเมลนี้ได้ลงทะเบียนแล้ว", + netErrorRetry: "มีปัญหาเครือข่าย กรุณาคลิกเพื่อโหลดหน้าใหม่~", + newPhoneNumber: "หมายเลขโทรศัพท์ใหม่", + addFriendText1: "เพิ่มเป็นเพื่อน", + qrSaveFail: "การบันทึก QR ล้มเหลว", + qrSavePhoto: "QR ได้ถูกบันทึกไว้ในอัลบั้ม", + userId: "รหัสผู้ใช้:", + addMeToFriend: "เพิ่มฉันเป็นเพื่อน", + validTime: "หมดอายุเมื่อ", + clickRefresh: "คลิกเพื่อรีเฟรช", + qrUseCount: "QR นี้มีอายุ {count} ครั้ง", + share: "แชร์", + savePhoto: "บันทึกรูปภาพ", + iAm: "ฉันคือ", + addFriendSuccessful: "เพิ่มเพื่อนสำเร็จ", + addFriendFaceToFace: "เพิ่มเพื่อนโดยการพบหน้า", + faceToFace: "เพิ่มเพื่อนโดยการพบหน้า", + inputSameKey: "ต่อไปนี้คือเพื่อนที่ป้อนตัวเลขเดียวกัน", + sameKeyAddFriend: "เพิ่มเพื่อนโดยป้อนตัวเลขเดียวกันกับเพื่อน", + self: "ตัวเอง", + age: "ปี", + confirmAddFriend: "ยืนยันการเพิ่มเพื่อน", + inputUserId: "โปรดใส่รหัสผู้ใช้", + myQr: "QR ของฉัน", + back: "ย้อนกลับ", + canNotAddSelf: "คุณไม่สามารถเพิ่มตนเองเป็นผู้ติดต่อได้", + notExsit: "ผู้ใช้นี้ไม่มีอยู่", + searchUserId: "ค้นหารหัสผู้ใช้:", + remarkInfo: "โปรดระบุข้อมูลยืนยัน", + applyRemark: "หมายเหตุ", + last4Number: "โปรดป้อนหลักสี่หลักของหมายเลขโทรศัพท์ที่เข้าสู่ระบบของคู่สนทนา", + checkLast4Number: "ตรวจสอบหลักสี่หลักสุดท้ายของหมายเลขโทรศัพท์", + addFriendApply: "สมัครเพื่อเพิ่มเพื่อน", + friendNumberCatchTopLimit: "จำนวนเพื่อนของคุณมีจำกัดแล้ว", + friendNumberCatchTopLimitCurrentDay: "เพิ่มเพื่อนในวันนี้เต็มแล้ว", + codeInvalid: "รหัสผิดพลาดหรือหมดอายุ โปรดลองอีกครั้ง", + shareFail: "การแชร์ล้มเหลว", + error_1003: "โปรดลองอีกครั้งในภายหลัง", + error_6033: "รหัสไม่ถูกต้องหรือหมดอายุ โปรดลองใหม่", + error_6034: "จำนวนเพื่อนทั้งหมดมีขีดจำกัดแล้ว", + error_6035: "การเพิ่มเพื่อนในวันนี้เต็มแล้ว", + error_6036: "หมายเลขโทรศัพท์มือถือของผู้ใช้นี้ว่างเปล่า", + error_6037: "การตรวจสอบหลักสุดท้ายของโทรศัพท์มือถือล้มเหลว", + error_6040: "กลุ่มนี้มีอยู่แล้ว", + error_6050: "รหัส QR หมดอายุแล้ว", + error_6051: "จำนวนการใช้งานรหัส QR ในวันนี้เต็มแล้ว", + error_6052: "จำนวนการใช้งานรหัส QR มีขีดจำกัดแล้ว", + error_6053: "จำนวนการใช้งานสแกน QR ในวันนี้เต็มแล้ว", + error_6054: "ผู้อื่นได้เพิ่มคุณเป็นเพื่อนแล้ว", + error_6055: "รหัสไม่ถูกต้องหรือหมดอายุ โปรดลองใหม่", + error_6066: "ยอดเงินในบัญชีไม่เพียงพอ", + error_6067: "แพ็คเกจสีแดงหมดแล้ว", + error_6068: "แพ็คเกจสีแดงหมดอายุแล้ว", + error_6069: "ไม่สามารถรับแพ็คเกจสีแดงพิเศษได้", + error_6070: "คุณได้รับแพ็คเกจสีแดงแล้ว ไม่สามารถรับเพิ่มได้อีก", + deletedByFriendHint: "คุณยังไม่ได้เป็นเพื่อนกับเขา", + deletedByFriendHint1: "คุณยังไม่ได้เป็นเพื่อนกับเขา", + deletedByFriendHint2: "เพิ่มเพื่อน", + deletedByFriendHint3: "ดำเนินการต่อการสนทนา", + gotoAddFriend: "ไปเพิ่มเพื่อน", + addFriendGotoGroup: "เชิญเพื่อนเข้ากลุ่ม", + notContacts: "ไม่มีผู้ติดต่อ", + notContactsFound: "ไม่พบเพื่อนที่เกี่ยวข้องกับ {name}", + remarkStr: "หมายเหตุ", + remarkStrHint: "โปรดใส่หมายเหตุ, 0-50 อักขระ", + deleteConfirm: "คุณแน่ใจหรือไม่ว่าต้องการลบเพื่อน", + deleteFriend: "ลบเพื่อน", + searchContact: "ค้นหาผู้ติดต่อ", + notGroupTip: "ยังไม่ได้เข้าร่วมการแชทกลุ่ม", + notAddFriend: "ยังไม่ได้เพิ่มเพื่อนใหม่", + friendApplyAdd: "คำขอเพื่อน", + addSuccess: "เพิ่มเพื่อนสำเร็จ", + hasSelectFriend: "เลือกเพื่อนแล้ว", + myBalance: "ยอดเงินคงเหลือของฉัน", + revokeMsgTip: "ยกเลิกข้อความ", + error_10050: "การดำเนินการล้มเหลว (ไม่พบบันทึก)", + set_manager_tip_1: + "คุณเลือกเป็นเจ้าของกลุ่มแล้ว มีสิทธิ์ในการจัดการสูงสุดแล้ว", + envelope: "ซองจดหมายแดง", + uidStr: "รหัสผู้ใช้", + showGetDetail: "ดูรายละเอียดการรับ", + waitOtherEnter: "รอให้คู่สนิทเข้าร่วม", + isFriend2SExit: "เป็นเพื่อนกันแล้ว ออกโดยอัตโนมัติใน 2 วินาที", + sendRedPacket: "ส่งอั่งเปา", + redPacketNumber: "จำนวนอั่งเปา", + ge: "ชิ้น", + total: "ทั้งหมด", + eachNumber: "จำนวนแต่ละ", + selectUser: "เลือกผู้ใช้", + number: "จำนวน", + redPacketType: "ประเภทอั่งเปา", + checkVerity: "การตรวจสอบความปลอดภัย", + balance: "ยอดคงเหลือในบัญชี", + sendTo: "รหัสยืนยันผ่านทาง SMS ถูกส่งไปที่", + pin: "สุ่มสลับอั่งเปา", + putong: "อั่งเปาทั่วไป", + zhuanshu: "อั่งเปาพิเศษ", + dajidali: "ขอให้โชคดีและร่ำรวย! ", + redPacketInfo: "รายละเอียดอั่งเปา", + best: "โชคดีที่สุด", + saveToAccount: "บันทึกลงบัญชีแล้ว", + minuteOut: "หมดไปในไม่กี่นาที", + hourOut: "หมดไปในไม่กี่ชั่วโมง", + showDetail: "ดูรายละเอียด", + maxMemberNumber: "จำนวนอั่งเปาต้องไม่เกินจำนวนสมาชิกในกลุ่มปัจจุบัน", + sended: "ที่ส่ง", + get: "ได้รับแล้ว", + waiting: "รอการรับ", + gong: "อั่งเปาทั้งหมด", + envelope_get_over: "อั่งเปาถูกรับครบแล้ว", + envelope_get: "ได้รับแล้ว", + de: "ของ", + envelope_get_message: "ข้อความการรับอั่งเปา", + bind_phone_please: "เพื่อความปลอดภัยของบัญชีของคุณ โปรดผูกหมายเลขโทรศัพท์", + delay_bind: "ยังไม่ผูก", + send_envelope_x: "อั่งเปาที่ส่ง", + send_envelope_back_all: + "อั่งเปานี้ไม่ได้รับการรับเป็นเวลากว่า 24 ชั่วโมง\nถูกส่งคืนแล้ว", + envelope_x_can_get: "สามารถรับได้เฉพาะ {name} เท่านั้น", + envelope_get_completed: "ช้าไปแล้ว อั่งเปาหมดแล้ว", + send_envelope_back_some: + "อั่งเปานี้ไม่ได้รับการรับเป็นเวลากว่า 24 ชั่วโมง\nส่วนหนึ่งถูกส่งคืนแล้ว", + envelope_x_personal: "อั่งเปาพิเศษสำหรับ %s", + envelope_normal: "อั่งเปาทั่วไป", + login_out_of_date: "การเข้าสู่ระบบหมดอายุ โปรดเข้าสู่ระบบใหม่", + get_envelope_completed: "ได้รับครบแล้ว", + x_is_personal: "พิเศษ", + error_6072: "มียอดเงินสูงสุดสำหรับอั่งเปาแต่ละรายแล้ว", + error_6073: "จำนวนของแต่ละอั่งเปาต้องไม่น้อยกว่า 0.01", + error_6074: "ฟังก์ชั่นอั่งเปาถูกปิดแล้ว", + distroyAccount: "ปิดบัญชี", + inComing: "รายได้", + outGoing: "รายจ่าย", + balance_order_detail: "รายละเอียดยอดคงเหลือ", + type: "ประเภท", + payType: "วิธีการชำระเงิน", + orderNo: "หมายเลขคำสั่งซื้อ", + time: "เวลา", + itemDataNotSupport: "เวอร์ชันปัจจุบันไม่รองรับ", + manager_change_account: "ผู้จัดการปรับบัญชี", + groupEnvelope: "อั่งเปากลุ่ม", + accountPay: "ชำระด้วยยอดคงเหลือ", + paySuccess: "ชำระเงินสำเร็จ", + groupEnvelopePersonal: "อั่งเปากลุ่ม - อั่งเปาพิเศษ", + groupEnvelopeNormal: "อั่งเปากลุ่ม - อั่งเปาปกติ", + changeAccount: "ปรับบัญชี", + groupEnvelopeRandom: "อั่งเปากลุ่ม - อั่งเปาสุ่ม", + groupEnvelopeAllBack: "อั่งเปากลุ่ม - คืนทั้งหมด", + groupEnvelopeSomeBack: "อั่งเปากลุ่ม - คืนบางส่วน", + distroyTips: + "เพื่อยืนยันความปลอดภัยของบัญชีของคุณ\nเราจะดำเนินการตรวจสอบต่อไปนี้ก่อนที่คุณจะทำการปิดบัญชี", + safeStatus: "บัญชีอยู่ในสถานะที่ปลอดภัย", + safeTips: + "โปรดตรวจสอบให้แน่ใจว่าบัญชีของคุณไม่มีการเปลี่ยนรหัสผ่าน การเปลี่ยนแปลงหมายเลขโทรศัพท์มือถือ / อีเมล หรือการดำเนินการที่เกี่ยวข้องกับความปลอดภัยอื่น ๆ และไม่มีความเสี่ยงที่บัญชีจะถูกขโมย", + strikeBalance: "การชำระยอดบัญชี", + strikeTips: + "หลังจากปิดบัญชีแล้ว ข้อมูลยอดคงเหลือของคุณจะถูกลบและไม่สามารถกู้คืนได้", + dataDelete: "ลบข้อมูลบัญชี", + deleteTips: + "หลังจากปิดบัญชีแล้ว ข้อมูลทั้งหมดในบัญชีนั้นจะถูกลบและไม่สามารถกู้คืนได้ ข้อมูลรวมถึงประวัติการสนทนาของคุณ การเข้าร่วมกลุ่มแชท และความสัมพันธ์กับเพื่อน", + applyDistroy: "ยื่นคำขอปิดบัญชี", + distroyConform: "แน่ใจหรือว่าต้องการที่จะปิดบัญชี?", + distroyConformTips: + "หลังจากปิดบัญชี คุณจะถูกออกจากระบบการเข้าสู่ระบบ กรุณาใช้ความระมัดระวัง!", + distroy: "ยืนยันการปิดบัญชี", + submitSuccess: "ส่งคำขอสำเร็จ", + submitted: "คำขอปิดบัญชีของคุณถูกส่งแล้ว!", + helpWord: + "แพลตฟอร์มจะดำเนินการตรวจสอบคำขอของคุณและลบข้อมูลทั้งหมดของคุณภายใน {day} วันทำการ หากต้องการความช่วยเหลือ โปรดติดต่อเราทางเว็บไซต์", + know: "ฉันเข้าใจแล้ว", + revoke: "ยกเลิกคำขอ", + revokeSuccess: "ยกเลิกคำขอสำเร็จ", + recover: "แน่ใจหรือว่าต้องการยกเลิกคำขอ?", + toNormal: "หลังจากยกเลิก สถานะบัญชีจะกลับมาเป็นปกติ", + beRecover: "ยืนยันการกู้คืน", + recoverSuccess: "กู้คืนสำเร็จ", + redPackageCountTip: "ยอดสุดท้ายของบัตรเงินสดต่ำสุดต้องไม่น้อยกว่า 0.01", + closeAccount: "ปิดบัญชี", + recoverAccount: "กู้คืนบัญชี", + recoverAccountTipTitle: "แน่ใจหรือว่าต้องการที่จะกู้คืนบัญชี?", + recoverAccountTipDesc: "หลังจากกู้คืน สถานะบัญชีจะกลับมาเป็นปกติ", + recoverAccountTipOk: "ยืนยันการกู้คืน", + closingAccount: "บัญชีอยู่ในขั้นตอนการตรวจสอบการปิดบัญชี", + closingAccountEndTime: " โดยคาดว่าจะเสร็จสิ้นการลบภายใน {num} วันทำการ", + error_6079: "คุณไม่ได้อยู่ในการสนทนากลุ่มและไม่สามารถรับได้", + error_6075: "คำขอซ้ำ", + error_6076: "ไม่พบบันทึกคำขอปิดบัญชี", + error_6077: "อยู่ในขั้นตอนการตรวจสอบการปิดบัญชี", + error_6078: "ไม่พบผู้ใช้หรือได้ทำการปิดบัญชีแล้ว โปรดตรวจสอบและยืนยัน", + error_video_pre_err: "เกิดข้อผิดพลาดในการสร้างไฟล์ การโหลดข้อมูลล้มเหลว", + err_upload_file: "การอัปโหลดล้มเหลว โปรดลองอีกครั้งในภายหลัง", + err_msg_send_failed: "การส่งล้มเหลว โปรดลองอีกครั้งในภายหลัง", + error_red_package_tip1: "หมดอายุแล้ว", + net_error_no_net: + "เครือข่ายไม่สามารถใช้งานได้ โปรดตรวจสอบการตั้งค่าเครือข่ายของคุณ", + error_6081: "การส่งล้มเหลว โปรดลองใหม่ในภายหลัง", + atTip: "มีคน{'@'}คุณ", + groupChatError: "โหลดเนื้อหาล้มเหลว โปรดลองอีกครั้ง", + legalNotice: "คำชี้แจงทางกฎหมาย", + timeoutLock: "ล็อกเกินเวลา", + reTry: "ลองอีกครั้ง", + sysNotify: "แจ้งเตือนระบบ", + exit_group_tip: "ออกจากการสนทนากลุ่มแล้ว", + group_mute_ing: "กลุ่มนี้ถูกปิดเสียง", + w_edit_group_notification: "แก้ไขประกาศกลุ่ม", + removeGroupNotice: "การลบประกาศกลุ่มแจ้งเตือน", + messageNotify: "การแจ้งเตือนข้อความ", + groupEnterLimit: + "จำนวนเพื่อนที่ได้รับเชิญให้เข้าร่วมกลุ่มได้มีจำนวนเต็ม โปรดตรวจสอบ", + tipAllGroupMember: "แจ้งเตือนสมาชิกกลุ่มทั้งหมด", + openPhotoPermission: "เปิดการเข้าถึงใน 'อัลบั้มภาพ'", + gotoSettings: "ไปที่การตั้งค่า", + notSet: "ไม่ตั้งค่า", + openCameraPermission: "เปิดการใช้ 'กล้อง'", + openMicrophonePermission: "เปิดการใช้ 'ไมโครโฟน'", + selectMaxCountTip: "เลือกได้สูงสุด %s คนต่อครั้ง", + getVideoImageFailed: "การดึงภาพตัวอย่างวิดีโอล้มเหลว", + error_6086: "ชื่อเล่นของผู้ใช้ถูกใช้ไปแล้ว", + error_6085: "รูปแบบชื่อเล่นไม่ถูกต้อง", + error_6087: "หมายเลขโทรศัพท์ไม่มีอยู่", + error_6088: "อีเมลไม่มีอยู่", + unRecard: "ไม่สามารถบันทึกเสียงได้:", + hasCreateGroup: "สร้างกลุ่มแชท", + joinGroupForLink: "เข้าร่วมกลุ่มแชทผ่านลิงค์", + be: "โดน", + moveoutGroup: "ย้ายออกจากกลุ่มแชท", + groupOwnerChangeTo: "หัวหน้ากลุ่มได้เปลี่ยนเป็น", + changeGroupNameTo: "เปลี่ยนชื่อกลุ่มเป็น", + disbandedGroup: "เลิกแชทกลุ่ม", + bannedFromSpeaking: "คำต้องห้าม", + gagWasLifted: "ยกเลิกการห้ามพูด", + initiatedGroupGag: "เปิดกลุ่มห้ามพูด", + shutDownGroupGag: "ปิดการห้ามพูดของกลุ่ม", + target: "อีกฝ่าย", + groupSizeUpperLimit: "จำนวนกลุ่มถึงเพดาน", + topUpperLimit: "วางด้านบนถึงขีด จำกัด บน", + topMessageRepeate: "ข้อความด้านบนซ้ำแล้วซ้ำอีก", + userHasReadMessage: "ผู้ใช้ได้อ่านข้อความนี้แล้ว", + placeholder: { + allMuted: "เจ้าของกลุ่มหรือผู้ดูแลระบบได้เปิดข้อห้ามทั้งหมด", + groupDisbanded: "กลุ่มถูกยุบ", + groupBanned: "กลุ่มถูกแบน", + singleBanned: "คุณถูกแบน", + search: "ค้นหา", + pleaseInput: "กรุณากรอก", + sec: "วินาที", + groupNoticePlaceholder: "โปรดป้อนประกาศกลุ่ม..", + inputPassword: "กรุณากรอกรหัสผ่าน", + confirmPassword: "กรุณายืนยันรหัสผ่าน", + inputPhoneNumber: "กรุณากรอกหมายเลขโทรศัพท์มือถือ", + select: "กรุณาเลือก", + userOrGroupIDInput: "กรุณากรอก{type}", + writeMessage: "ป้อนข้อมูล...", + inputGroupName: "กรุณากรอกชื่อกลุ่ม", + typingMessage: "ป้อนข้อความที่นี่", + inputName: "กรุณากรอกชื่อเล่น" + }, + withoutQualification: "ผู้ใช้นี้ไม่มีคุณสมบัติในการเพิ่มเพื่อน", + error_6084: "การยืนยันอีเมลล้มเหลว", + emailCheck: "ยืนยันผ่านทางอีเมล", + phoneCheck: "ยืนยันผ่านทางหมายเลขโทรศัพท์", + checkEmail: "ตรวจสอบที่อยู่อีเมล", + inputOtherEmail: "โปรดป้อนที่อยู่อีเมลของผู้ใช้ที่ต้องการเข้าสู่ระบบ", + groupMask: "หน้ากากแชทกลุ่ม", + groupMaskDescription: "ตั้งชื่อเล่นและรูปโปรไฟล์ของกลุ่มนี้", + + pleaseBind: + "เพื่อความปลอดภัยของบัญชีของคุณและหลีกเลี่ยงการจำกัดฟังก์ชันการเพิ่มเพื่อน โปรดผูก", + orStr: "หรือ", + andStr: "และ", + confirmExit: "ยืนยันการออก", + pleaseBindTip1: "เพื่อความปลอดภัยของบัญชีของคุณ โปรดผูก", + skin_item_user_name: "张娜娜", + skin_item_msg1: "สวัสดี ได้รับการตอบกลับทันเวลา", + skin_item_msg2: "ดีค่ะ ได้รับแล้ว", + appearance: "ลักษณะภายนอก", + bubbleBgColor: "สีพื้นหลังของบอกแชท", + chatBackground: "พื้นหลังแชท", + moreSettings: "การตั้งค่าเพิ่มเติม", + highPerformanceModel: "โหมดบริสุทธิ์", + highPerformanceModelDesc: "เมื่อเปิดใช้งานจะไม่แสดงเอฟเฟกต์ลักษณะภายนอก", + skinPreview: "ตัวอย่างผลลัพธ์", + defaultText: "ค่าเริ่มต้น", + fontSizePreview: "ดูตัวอย่างขนาดฟอนต์ ลากสไลเดอร์ด้านล่างนี้เพื่อปรับขนาดฟอนต์ หลังจากตั้งค่าแล้ว ข้อความทั้งหมดในหน้าจะเปลี่ยนขนาด", + changedText: "โอเค ได้ทำการเปลี่ยนแล้ว", + imageAlpha: "ความโปร่งใสของรูปภาพ", + min: "น้อยที่สุด", + max: "สูงสุด", + backgroundColor: "สีพื้นหลัง", + changeSaveTip: "คุณได้ทำการเปลี่ยนแปลงการตั้งค่า ต้องการบันทึกและกลับหรือไม่", + saveAndBack: "บันทึกและกลับ", + changeAvatar: "เปลี่ยนรูปโปรไฟล์", + appFontSize: "ขนาดตัวอักษรทั่วไปในแอป", + appFontSizeTip: + "ขนาดตัวอักษรที่ได้รับการตั้งค่าล่วงหน้า ลากแถบเลื่อนด้านล่างเพื่อตั้งค่าขนาดตัวอักษร การตั้งค่านี้จะเปลี่ยนแปลงขนาดตัวอักษรของข้อความทั้งหมดในแอป", + maskDesc: + "เมื่อเปิดการใช้งาน สมาชิกในกลุ่มสามารถตั้งค่าชื่อเล่นและรูปโปรไฟล์ของกลุ่มนี้ได้", + groupMaskOpenTip: " %s เปิด%s หน้ากากแชทกลุ่ม", + groupMaskOpenTipNew: " เปิด หน้ากากแชทกลุ่ม", + open: "เปิด", + close: "ปิด", + groupMaskOpenMsgErrorTip: "การแจ้งเตือนเปิด/ปิดฟังก์ชันหน้ากากแชทกลุ่ม", + maskOpen: "หน้ากากแชทกลุ่มเปิดแล้ว", + maskClose: "หน้ากากแชทกลุ่มถูกปิดแล้ว", + + transmit: "ส่งต่อ", + multiSelect: "เลือกหลายรายการ", + singleShareMsg: "ส่งต่อทีละข้อความ", + groupShareMsg: "ส่งต่อรวม", + notGroupFound: "ไม่พบการสนทนาในกลุ่มที่เกี่ยวข้องกับ{name}", + rememberPassword: "จดจำรหัสผ่าน", + mobileLogin: "เข้าสู่ระบบผ่านโทรศัพท์มือถือ", + emailLogin: "เข้าสู่ระบบผ่านอีเมล", + recentConversation: "การสนทนาล่าสุด", + selectContact: "เลือกติดต่อ", + selectGroup: "เลือกกลุ่ม", + noGroup: "ไม่มีกลุ่ม", + notSelectMessage: "คุณยังไม่ได้เลือกข้อความ", + hiddenMsg: "ซ่อนข้อความ", + hiddenMsgTip: "คุณต้องการซ่อนที่มาของข้อความหรือไม่?", + yes: "ใช่", + no: "ไม่", + mergeMessageList: "【ส่งต่อรวม】บันทึกการสนทนา", + singleMlCount: "【ส่งต่อทีละข้อความ】ทั้งหมด%sข้อความ", + notFoundUser: "ไม่พบผู้ใช้นี้", + notShouldFoundUser: "User not found", + notFoundGroup: "ไม่พบกลุ่มนี้", + selectAt: "เลือกคนที่ต้องการ {'@'}", + welcomeToQDDChat: "ยินดีต้อนรับสู่ QDDChat", + choiceYourAvatar: "โปรดเลือกอวาตาร์ของคุณ", + hasSelect: "เลือกแล้ว", + groupChat: "แชทกลุ่ม", + groupMember1: "สมาชิกในกลุ่ม", + groupSendMsg: "ส่งข้อความในกลุ่ม", + msgNotSupportTransmit: "ข้อความไม่รองรับการส่งต่อ", + bindGoogle: "ผูกบัญชีกูเกิล", + bindFacebook: "ผูกบัญชี Facebook", + unbind: "ยกเลิกผูก", + accountSettings: "การตั้งค่าบัญชี", + thirdPartyAccountBinding: "ผูกบัญชีบุคคลที่สาม", + otherSettings: "การตั้งค่าอื่นๆ", + groupChatLoadFailed: "ข้อมูลแชทกลุ่มโหลดไม่สำเร็จ", + addGroupSend: "ส่งในกลุ่ม", + groupSendNoData: "ยังไม่มีบันทึกการส่งข้อความในกลุ่ม", + removeBinding: "ยกเลิกการผูก", + unBindToastPart1: "หลังยกเลิกการผูก คุณจะไม่สามารถใช้", + unBindToastPart2: "เข้าสู่ระบบอีกครั้ง คุณต้องการที่จะยกเลิกไหม?", + noOpPermission: "ยังไม่มีสิทธิ์ในการดำเนินการ", + bindingPhoneOrEmail: + "เพื่อความปลอดภัยของข้อมูลบัญชีของคุณ โปรดผูกหมายเลขโทรศัพท์มือถือหรืออีเมลก่อนที่จะยกเลิกการผูก", + unbindingSuccess: "ยกเลิกการผูกสำเร็จ", + unbindingFair: "การยกเลิกการผูกล้มเหลว", + showAll: "ดูทั้งหมด", + allCount: "ทั้งหมดรวม{num}รายการ", + bindingPhoneOrEmailSetPassword: + "เพื่อความปลอดภัยของข้อมูลบัญชีของคุณ โปรดผูกหมายเลขโทรศัพท์มือถือหรืออีเมลก่อนที่จะตั้งรหัสผ่าน", + recoverSuccessLoginAgain: "กู้คืนสำเร็จ กรุณาเข้าสู่ระบบอีกครั้ง", + createGroupSend: "สร้างการส่งในกลุ่ม", + groupSend: "ส่งในกลุ่ม", + notSelect: "คุณยังไม่ได้เลือก", + confirmDeleteTitle: "ยืนยันการลบการส่งในกลุ่มที่เลือก?", + totalGe: "รวม%sรายการ", + sendTotalGe: "ส่งให้ ({num}รายการ)", + uploadFailed: "การอัปโหลดล้มเหลว", + selectMax: "เลือกจำนวนมากสุด", + newFriendApply: "คุณมีเพื่อนใหม่คลิกเพื่อดู", + notSupportLoginStyle: + "ไม่รองรับช่องทางการเข้าสู่ระบบนี้ชั่วคราว โปรดใช้วิธีอื่น", + error_6091: "เลือกจำนวนคนเกินขีดจำกัด", + error_6092: "รหัสข้อความซ้ำกันอยู่แล้ว", + pay_password: "รหัสผ่านการชำระเงิน", + pleaseEnter: "โปรดป้อน", + loadingPage: "กำลังโหลดหน้า", + payTip: "คุณยังไม่ได้ตั้งค่ารหัสผ่านการชำระเงิน โปรดตั้งค่าก่อนดำเนินการต่อ", + deleteRedPackageErrTip: "ไม่สามารถยกเลิกข้อความแพ็คเกจแดง", + applyIconTip: "คำขอได้ถูกส่งไปแล้ว จะได้รับการอนุมัติและเปลี่ยนรูปโปรไฟล์", + applyIconProgress: "ตรวจสอบความคืบหน้าของคำขอ", + applyRecord: "บันทึกคำขอ", + repealWithdrawApply: "ยืนยันการเพิกถอนคำขอถอนเงิน?", + withdrawNotes: "บันทึกการถอนเงิน", + inReview: "อยู่ในระหว่างตรวจสอบ", + passTheAudit: "อนุมัติแล้ว", + rejectedPayment: "การชำระเงินถูกปฏิเสธ", + revoked: "ถูกเพิกถอน", + undone: "ยังไม่เสร็จ", + withdrawalQuantity: "จำนวนการถอนเงิน", + withdrawal: "ถอนเงิน", + withdrawalBack: "การคืนเงินถอน", + update_tip1: "หากไม่สามารถอัปเดต", + update_tip2: "คุณสามารถดาวน์โหลดได้ที่เว็บไซต์อย่างเป็นทางการ", + update_ing: "กำลังอัปเดต", + update_install: "ติดตั้งทันที", + installFailed: "ไม่ได้ติดตั้งโปรดลองอีกครั้งในภายหลัง", + installNotFoundFile: "ไม่พบไฟล์การติดตั้งโปรดลองอีกครั้งในภายหลัง", + error_6093: "ไม่รองรับวิธีการเข้าสู่ระบบ", + error_6094: "จำนวนรูปโปรไฟล์เต็ม", + deleteIcon: "คุณต้องการลบรูปโปรไฟล์นี้หรือไม่?", + balanceWithdraw: "ถอนเงินคงเหลือ", + canBalanceWithdraw: "ยอดเงินคงเหลือที่สามารถถอนได้", + inputPayPassword: "โปรดป้อนรหัสผ่านการชำระเงิน", + withdrawalSuccess: "ถอนเงินสำเร็จ", + personIcon: "รูปโปรไฟล์ส่วนบุคคล", + groupIcon: "รูปโปรไฟล์กลุ่ม", + error_6096: "โปรดอย่าตั้งรหัสผ่านการชำระเงินเป็นตัวเลขซ้ำๆ", + forgetPayPassword: "ลืมรหัสผ่านการชำระเงิน", + downloadFailedTip: "การดาวน์โหลดล้มเหลว โปรดลองอีกครั้งในภายหลัง", + error_6097: "ยอดเงินไม่เพียงพอ", + passed: "ผ่าน", + headRejected: "ถูกปฏิเสธ", + notSet2: "ไม่มีการตั้งค่า", + readDeleteTime: "ระยะเวลาการลบข้อความที่อ่านแล้ว", + readDeleteTimeTitle: "ระยะเวลาการลบข้อความ", + error_6090: "บัญชีถูกผูก", + retracted_h5: "ถูกยกเลิก", + message_h5: "ข้อความของ", + inputRemark: "โปรดใส่หมายเหตุของคุณ", + authFailed: "การให้สิทธิล้มเหลว", + userNullError: "บัญชีผู้ใช้ผิดปกติ", + community: "ชุมชน", + join: "เข้าร่วมกลุ่มแชทและสำรวจกิจกรรมชุมชนเพิ่มเติม~", + posteviews: "ยังไม่ออกแคมเปญ ~", + views: "ดูเวลา", + ended: "กิจกรรมสิ้นสุดลงแล้ว", + configError: "ข้อผิดพลาดในการกำหนดค่า ไม่สามารถเปิดใช้งานชั่วคราวได้", + burnMsgOpen: "{name}ตั้งค่าข้อความเป็นอ่านแล้ว{time}แล้วทำลาย", + burnMsgClose: "{name} ปิดการอ่านและเผา ข้อความไม่ไหล่อัตโนมัติอีกต่อไป", + burnMsgNotify: "การแจ้งเตือนการอ่านและเผา", + homeItem3: "ชุมชน", + messageTimeOut: "ข้อความหมดอายุแล้ว", + burnContentTip: "ข้อความกำลังเผา คุณต้องการออกและเผาด้วยคลิกเดียวหรือไม่?", + cancelLater: "ฉันจะตรวจสอบในภายหลัง", + burnConfirm: "เผาทันที", + setNickNameTimes: "ชื่อเล่นสามารถแก้ไขได้เพียงครั้งเดียวภายใน %s วันเท่านั้น", + voiceSendErrTip1: "รองรับการส่งเสียงเฉพาะระหว่าง %s-%s วินาทีเท่านั้น", + textSendErrTip1: "การป้อนข้อมูลสูงสุด %s ตัวอักษร", + pictureSendErrTip1: "รองรับการส่งรูปภาพเฉพาะระหว่าง %s-%sM เท่านั้น", + videoSendErrTip1: "รองรับการส่งวิดีโอเฉพาะระหว่าง %s-%sM เท่านั้น", + your: "ของคุณ", + joinCommunity: "เข้าร่วมกลุ่มแชทและสำรวจกิจกรรมชุมชนเพิ่มเติม~", + noActivity: "ยังไม่ออกแคมเปญ ~", + views: "ดูเวลา", + ended: "กิจกรรมสิ้นสุดลงแล้ว", + errorLink: "ไม่พบการเชื่อมโยง", + set_payment_password: "ตั้งรหัสผ่านการชำระเงิน 6 หลัก", + unset_payment_password: + "กรุณาอย่าตั้งรหัสผ่านการชำระเงินที่ซ้ำกันหรือต่อเนื่อง", + Confirm_Payment_Password: "ยืนยันรหัสผ่านการชำระเงิน", + Please_confirm_payment_password: + "โปรดใส่รหัสผ่านการชำระเงินอีกครั้งเพื่อยืนยัน", + error_6100: + "ขอโทษครับ/ค่ะ คุณสามารถเปลี่ยนชื่อเรียกส่วนตัวได้เพียงครั้งเดียวต่อวัน{day} ครั้ง", + viewDetailed: "ดูข้อมูลสมาชิกในกลุ่ม", + inviter: "ผู้เชิญ", + lastLoginTime: "เวลาเข้าสู่ระบบล่าสุด", + DeleteThisUsersgroup: "ลบข้อความแชทกลุ่มที่ผู้ใช้นี้ส่งในกลุ่มนี้", + AddGroupMembers: "เพิ่มสมาชิกกลุ่มโดยไม่ต้องยืนยันการเข้าร่วม", + error_6103: "ผู้ใช้นี้เป็นสมาชิกทั่วไป ไม่มีสิทธิ์", + error_6104: "ผู้ถูกเพิ่มไม่ได้อยู่ในกลุ่ม", + error_6108: "บัญชีผิดปกติ", + group_remark: "หมายเหตุกลุ่ม", + input_group_remark: "โปรดใส่เนื้อหาหมายเหตุ", + await_accepting: "การขออนุญาตอุปกรณ์ใหม่กำลังรอการตรวจสอบ", + apply_tips: "อุปกรณ์ใหม่ที่เข้าสู่ระบบต้องผ่านการตรวจสอบระบบ คุณต้องการส่งข้อมูลอุปกรณ์เพื่อสมัครอนุญาตหรือไม่?", + apply_send: "ส่งคำขอ", + apply_ttl: "การอนุญาตเข้าสู่ระบบอุปกรณ์", + group_limit500: "จำนวนคนสูงสุดในแต่ละกลุ่มคือ 500 คน", + select_group: "เลือกกลุ่มเพื่อน", + cantfind: "ไม่พบผู้ติดต่อที่เกี่ยวข้องกับ {name}", + error_6109: "จำนวนเพื่อนของผู้ใช้ถึงขีดจำกัด", + error_6106: "โปรดรอการตรวจสอบอุปกรณ์ก่อนเข้าสู่ระบบ", + selectGroups: 'เลือกกลุ่ม', + group_member_info: "ข้อมูลสมาชิกกลุ่ม", + join_time: "เวลาเข้าร่วม", + inviter_user: "ผู้เชิญ", + last_login_time: "เวลาเข้าสู่ระบบครั้งล่าสุด", + sync_group_avatar: 'ต้องการเปลี่ยนรูปโปรไฟล์กลุ่มพร้อมกันไหม?', + invite_fail: '{usernames}ไม่สำเร็จเนื่องจากบัญชีผิดปกติ!', + enterAndCtrl:'Enter เพื่อส่ง / Enter+Ctrl เพื่อขึ้นบรรทัดใหม่', + enterAndCtrlIOS:'Enter เพื่อส่ง / Enter+control เพื่อขึ้นบรรทัดใหม่', + scanLogin: 'เข้าสู่ระบบด้วยการสแกน', + formLogin: 'เข้าสู่ระบบด้วยรหัสผ่าน', + scanText: 'เข้าสู่ระบบด้วยแอพ บอลดี้ด้วยการสแกน', + scanSubText: 'ใช้แอพ บอลดี้ด้วยการสแกนเพื่อเข้าสู่ระบบ', + qrcodeExpired: 'รหัส QR หมดอายุแล้ว', + expiredTime10: 'รหัส QR ใช้ได้ภายใน 10 นาที', + reflesh: 'คลิกเพื่อรีเฟรช', + scanSuccess: 'สแกนสำเร็จ', + confromLogin: 'กรุณายืนยันการเข้าสู่ระบบ', + onlineStatus: "สถานะออนไลน์:", + networkDiagnostic: "การวินิจฉัยเครือข่าย", + "setPaymentPassword": "ตั้งรหัสผ่านการชำระเงิน 6 หลัก", + "unSetPaymentPassword": + "กรุณาอย่าตั้งรหัสผ่านการชำระเงินที่ซ้ำกันหรือต่อเนื่อง", + "verifyPassword": "ยืนยันรหัสผ่านการชำระเงิน", + "confirmPaymentPassword": "ยืนยันรหัสผ่านการชำระเงิน", + "verifyCurrentPassword": "กรุณายืนยันรหัสผ่านการชำระเงินปัจจุบัน", + "reInsetPaymentPassword": "กรุณาป้อนรหัสผ่านการชำระเงินอีกครั้งเพื่อยืนยัน", + "paymentPasswordDifferent": "รหัสผ่านไม่ตรงกัน โปรดป้อนใหม่", + "getPaymentPasswordWay": "โปรดเลือกวิธีการกู้รหัสผ่าน", + "selectFriendGroup": "เลือกกลุ่มเพื่อน", + "selectGroups": "เลือกกลุ่ม", + "eachGroupLimitNumber": "จำนวนสูงสุดของผู้ในแต่ละกลุ่มคือ {p} คน", + "selectChatLimitNumber": "คุณสามารถเลือกสูงสุด {p} การสนทนา", + "deviceID": "รหัสอุปกรณ์:", + "groupChatRemark": "หมายเหตุกลุ่มสนทนา", + "pleaseEnterRemarks": "โปรดป้อนเนื้อหาหมายเหตุ", + "groupMembershipInfo": "ข้อมูลสมาชิกในกลุ่ม", + "error_6102": "จำนวนคนสูงสุดในกลุ่มละ 500 คน", + "distroyed": "ถูกยกเลิก", + "forbiddened": "ถูกระงับ", + "addGroupMemberWithoutApply": "เพิ่มสมาชิกกลุ่มโดยไม่ต้องตรวจสอบความยินยอม", + "error_6109": "จำนวนเพื่อนของผู้ใช้รายนี้ได้ถึงขีดจำกัดสูงสุดแล้ว", + "apply_ttl": "การอนุญาตเข้าสู่ระบบของอุปกรณ์", + "apply_tips": + "อุปกรณ์นี้ไม่น่าเชื่อถือหรือไม่ผ่านการตรวจสอบ คุณต้องการส่งข้อมูลอุปกรณ์เพื่อขออนุญาตหรือไม่?", + "apply_send": "ส่งคำขอ", + "device_auth_send": "ส่งคำขออนุญาตอุปกรณ์เรียบร้อยแล้ว", + "using_trusted_device": + "การเข้าสู่ระบบด้วยอุปกรณ์นี้เสี่ยงต่อความเสี่ยง โปรดเข้าสู่ระบบด้วยอุปกรณ์ที่น่าเชื่อถือ", + "account_closed": "บัญชีถูกปิดแล้ว", + "google_code": "รหัสยืนยันของ Google", + "input_google_code": "โปรดป้อนรหัสยืนยันของ Google", + "inviterUser": "ผู้เชิญ", + "lastLoginTime": "เวลาเข้าสู่ระบบครั้งล่าสุด", + "error_6105": "อุปกรณ์ที่ไม่น่าเชื่อถือ โปรดยื่นใบสมัคร", + "error_6106": "โปรดรอการตรวจสอบของอุปกรณ์ก่อนเข้าสู่ระบบ", + "error_6107": "การตรวจสอบอุปกรณ์ไม่ผ่าน", + "hasDetailInfoPermission": "ดูข้อมูลสมาชิกกลุ่มอย่างละเอียด", + "editUserHeaderWithGroupHeader": + "ต้องการแก้ไขรูปภาพโปรไฟล์ของกลุ่มพร้อมกันหรือไม่?", + "darkModel": "โหมดสีเข้ม", + "darkModelSetDesc": "เปิดหรือปิดโหมดสีเข้มตามระบบ", + "whiteModel": "โหมดสีอ่อน", + "fileNotFound": "ไม่พบไฟล์", + "openFileInApp": "ดูตัวอย่างไฟล์", + "openFileOtherApp": "เปิดด้วยแอปอื่น", + "openByBrowser": "ดาวน์โหลดด้วยเบราว์เซอร์", + "pauseDownload": "หยุดการดาวน์โหลดชั่วคราว", + "resumeDownload": "ดำเนินการดาวน์โหลดต่อ", + "downloadFailed": "ดาวน์โหลดล้มเหลว", + "downloading": "กำลังดาวน์โหลด", + "notdownloaded": "ยังไม่ได้ดาวน์โหลด", + "cancelDownloading": + "ไฟล์กำลังดาวน์โหลด คุณต้องการยกเลิกการดาวน์โหลดหรือไม่?", + "failedToDownload": + "การดาวน์โหลดไฟล์ล้มเหลว คุณต้องการดาวน์โหลดอีกครั้งหรือไม่?", + "cancelDownload": "ยกเลิกการดาวน์โหลด", + "sending": "กำลังส่ง", + "fileTypeNotSupported": "ไม่รองรับการอัปโหลดไฟล์ประเภทนี้", + "fileSizeOut": "ขนาดไฟล์เกินขนาดสูงสุด", + "restrict": "จำกัด", + "getWithPhone": "ค้นหาผ่านหมายเลขโทรศัพท์", + "getWithEmail": "ค้นหาผ่านที่อยู่อีเมล", + "fileSize": "ขนาดของไฟล์", + "whetherDownloadFile": "การดาวน์โหลดไฟล์หรือไม่", + "downloadFailedTip1": "ดาวน์โหลดล้มเหลว โปรดตรวจสอบเครือข่ายและลองอีกครั้ง", + "downloadQueueTip": "เพิ่มลงในคิวการดาวน์โหลด รอการดาวน์โหลด", + "notSupportTransmitTip": "ข้อความนี้ไม่สามารถส่งต่อได้ในขณะนี้", + "voiceSize": "ขนาดไฟล์เสียงต้องไม่เกิน 100 เมกะไบต์", + "wordSize": "ขนาดไฟล์เอกสารต้องไม่เกิน 100 เมกะไบต์", + "apkSize": "ขนาดไฟล์แพ็คเกจติดตั้งต้องไม่เกิน 300 เมกะไบต์", + "zipSize": "ขนาดไฟล์ซิปต้องไม่เกิน 300 เมกะไบต์", + "groupDeleteUserTip": + "ลบผู้ใช้รายนี้พร้อมกับข้อความที่ผู้ใช้รายนี้ส่งในกลุ่ม", + "nPersonRead": "{p} คนอ่านแล้ว", + "darkModeEnabled": "เปิดโหมดดำแล้ว รีสตาร์ทแอปเพื่อให้เกิดผล", + "darkModeOff": "ปิดโหมดดำแล้ว รีสตาร์ทแอปเพื่อให้เกิดผล", + "otherUser": "อีกฝ่าย", + "selectMsgDeleteTime": "เลือกเวลาการเก็บ", + "selectMsgDeleteTimeTip": + "บันทึกการสนทนาจะถูกลบโดยอัตโนมัติหลังจากเกินเวลาที่กำหนด คุณต้องการดำเนินการต่อหรือไม่?", + "msgSaveTime": "เวลาการเก็บบันทึกการสนทนา", + "msgSaveTimeDesc": "ล้างบันทึกสนทนาที่เกินกำหนดอัตโนมัติ", + "hasTimeDeleteMsgPermission": "การกำหนดเวลาเก็บข้อความในกลุ่ม", + "accountAbnormal": "ข้อยกเว้นของบัญชี, คำเชิญล้มเหลว!", + "onlyBrowser": "รองรับเฉพาะในมุมมองบนเบราว์เซอร์เท่านั้น", + "cancelSendSuccess": "ยกเลิกการส่งสำเร็จ", + "allStr1": "ทั่วโลก", + "modelStr1": "โหมดกลางคืน", + "atMaxCountTip": "เพียง @ สูงสุด {p} คน", + "transmitTo": "ส่งถึง", + "moreActions": "การดำเนินการเพิ่มเติม", + "refreshing": "รีเฟรช", + "openInBrowser": "เปิดในเบราว์เซอร์", + "coverFlow": "การฟลอว์ปก", + "checkNet": "การตรวจสอบเครือข่าย", + "autoChangeServer": "เปลี่ยนเซิร์ฟเวอร์ด้วยคลิกเดียว", + "serverLine": "เส้นทางเซิร์ฟเวอร์", + "timeOut": "หมดเวลา", + "serverLineChange": "เปลี่ยนเส้นทางเซิร์ฟเวอร์", + "netCheck": "การวินิจฉัยเครือข่าย", + "netDelayCheck": "การตรวจสอบความล่าช้าของเครือข่าย", + "netDelay1Check": "ความล่าช้าของอินเทอร์เน็ต", + "msgDelay1Check": "ความล่าช้าของข้อความ", + "imgDelay1Check": "ความล่าช้าของรูปภาพ", + "netLine": "เส้นทางเครือข่าย", + "reCheck": "ทดสอบอีกครั้ง", + "netChecking": "กำลังตรวจสอบเครือข่าย", + "locationAddr": "สถานที่ตั้ง", + "selectPerfectLineTip": "เลือกเส้นทางที่ดีที่สุดสำหรับคุณแล้ว", + "selectPerfectLineTip2": + "เป็นเส้นทางที่ดีที่สุดอยู่แล้ว ไม่จำเป็นต้องเปลี่ยน", + "selectPerfectLineTipNoNet": + "ไม่พบเส้นทางที่ดีที่สุด กรุณาตรวจสอบเครือข่ายแล้วลองอีกครั้ง", + "changing": "กำลังเปลี่ยน", + "changeSuccess": "เปลี่ยนสำเร็จ", + "autoChanging": "กำลังเปลี่ยนอัตโนมัติ โปรดรอสักครู่", + "waitAnswer": "รอการตอบรับ", + "callFinish": "การโทรสิ้นสุด", + "fileSizeConnotZero": "ขนาดไฟล์ต้องไม่เป็นศูนย์", + "confirmLogin": "ยืนยันการเข้าสู่ระบบ", + "toolboxAudioCall": "โทรออกด้วยเสียง", + "requestTimeOut": "คำขอหมดเวลา โปรดลองใหม่ภายหลัง", + "error_6115": "รหัส QR หมดอายุแล้ว", + "groupNotFound": "ไม่พบกลุ่มนี้", + "userNotFound": "ไม่พบผู้ใช้งานนี้", + "microphoneOpen": "เปิดไมค์แล้ว", + "speakerOpen": "เปิดลำโพงแล้ว", + "voiceWithYou": "เชิญคุณเข้าร่วมสนทนาเสียง", + "callInterceptor": "การโทรถูกตัด", + "startCallError": "เริ่มโทรมีข้อผิดพลาด", + "callHungUp": "สายโทรถูกตัดแล้ว", + "callRemoteHungUp": "อีกฝ่ายตัดสาย", + "callReceiverError": "ข้อผิดพลาดในการรับสาย", + "starting": "กำลังเริ่มต้น", + "startTimeOut": "เรียกใช้การโทรออกด้วยเสียงหมดเวลา", + "callWaitReceiver": "รอการรับสายจากอีกฝ่าย", + "callRemoteNotAccept": "อีกฝ่ายไม่รับสาย", + "callStartSendError": "ไม่สามารถเริ่มสนทนาเสียงได้", + "notAccept": "ไม่รับสาย", + "callAccepting": "กำลังรับสาย", + "call": "สายโทร", + "callDoing": "เส้นทางไม่ว่าง ยกเลิกแล้ว", + "netBad": "สภาพเครือข่ายในปัจจุบันไม่ดี", + "netDown": "เครือข่ายหยุดทำงาน", + "onCalling": "กำลังโทร", + "error_1303": "คุณยังไม่ได้เป็นเพื่อนกับอีกฝ่าย", + "remoteAccountError": "บัญชีของอีกฝ่ายผิดปกติ", + "userOnlineStatus": "สถานะออนไลน์", + "error_100008": "ผู้ใช้งานนี้ไม่มีเงื่อนไขที่จะเป็นผู้ดูแลระบบ", + "callTimeLimit": "จำกัดเวลาการโทรสายทุกครั้งสูงสุด {p} นาที", + "notLoginOnline": "ไม่เคยล็อกอิน", + "callRemoteDoing": "ยุ่งอยู่", + "exitLoginIng": "กำลังออกจากระบบ", + "usePhoneRecovery": "ใช้หมายเลขโทรศัพท์ในการกู้คืน", + "useEmailRecovery": "ใช้อีเมลในการกู้คืน", + "otherRecoveryMethods": "วิธีการกู้คืนอื่นๆ", + "emailAddress": "ที่อยู่อีเมล", + "phoneErrorOrNotExist": "หมายเลขโทรศัพท์ผิดพลาดหรือไม่มีอยู่", + "phoneNotExist": "หมายเลขโทรศัพท์ไม่มีอยู่", + "verificationCodeFailure": "การรับรหัสยืนยันล้มเหลว กรุณาลองอีกครั้ง", + verificationCodeSent: "รหัสยืนยันได้ถูกส่งแล้ว", + hiWelcomeLogin: "สวัสดี~ ยินดีต้อนรับเข้าสู่การเข้าสู่ระบบ", + otherLoginMethods: "วิธีการเข้าสู่ระบบอื่น ๆ", + usernamePhoneEmail: "ชื่อผู้ใช้/หมายเลขโทรศัพท์/อีเมล", + "error_1303": "คุณยังไม่ได้เป็นเพื่อนกับอีกฝ่าย", + "remoteAccountError": "บัญชีของอีกฝ่ายผิดปกติ", + "userOnlineStatus": "สถานะออนไลน์", + "error_100008": "ผู้ใช้งานนี้ไม่มีเงื่อนไขที่จะเป็นผู้ดูแลระบบ", + "callTimeLimit": "จำกัดเวลาการโทรสายทุกครั้งสูงสุด %s นาที", + "notLoginOnline": "ไม่เคยล็อกอิน", + "callRemoteDoing": "ยุ่งอยู่", + "exitLoginIng": "กำลังออกจากระบบ", + "searchAreaCode": "ค้นหาประเทศ/เขต", + "otherFindStyle": "วิธีการกู้คืนอื่น ๆ", + "welcome": "ยินดีต้อนรับเข้าสู่ระบบ", + "fileReadError": "ข้อผิดพลาดในการอ่านไฟล์", + "docPreviewError": "ไม่สามารถดูตัวอย่างเอกสารได้", + "docPreviewUseOther": "หากเนื่องจากเครือข่ายหรือความไม่เข้ากันของอุปกรณ์ทำให้ไม่สามารถดูตัวอย่างเอกสารได้ คุณสามารถใช้", + "netTimeOut": "หมดเวลาการเชื่อมต่อเครือข่าย", + "docReadError": "ไม่สามารถอ่านไฟล์ได้ โปรดตรวจสอบรูปแบบไฟล์ว่าถูกต้องหรือไม่", + "errorDetail": "รายละเอียดข้อผิดพลาด", + ip: "IP", + imageLoadingRoute: "เส้นทางการโหลดภาพ", + networkChecking: "กำลังตรวจสอบเครือข่าย...", + noPermission: "ไม่มีสิทธิ์, กรุณาเข้าสู่ระบบหลังจากได้รับสิทธิ์", + pageNotFound: "ไม่พบหน้า", + serverError: "ข้อผิดพลาดของเซิร์ฟเวอร์", + databaseError: "ข้อผิดพลาดในการค้นหาฐานข้อมูล", + networkError: "ข้อผิดพลาดในการเชื่อมต่อเครือข่าย" + +}; diff --git a/src/lang/us.js b/src/lang/us.js new file mode 100644 index 0000000..703289f --- /dev/null +++ b/src/lang/us.js @@ -0,0 +1,1769 @@ +export default { + checkTopMessage:'Check messages', + unsupportedVideoFormat: "Unsupported video format", + welcome: "Welcome to OpenIM", + phoneNumber: "Phone number", + plsEnterPhoneNumber: "Please enter your phone number", + password: "Password", + plsEnterPassword: "Please enter your password", + account: "Account", + plsEnterAccount: "Please enter your account", + forgetPassword: "Forgot Password", + verificationCodeLogin: "Verification Code Login", + login: "Login", + noAccountYet: "Don't have an account yet?", + loginNow: "Login now", + registerNow: "Register now", + lockPwdErrorHint: "Error %s times", + newUserRegister: "New User Registration", + verificationCode: "Verification Code", + sendVerificationCode: "Send Verification Code", + resendVerificationCode: "Resend Verification Code", + verificationCodeTimingReminder: "Get Verification Code Again in %s seconds", + defaultVerificationCode: "(Default Verification Code: %s)", + plsEnterVerificationCode: "Please enter your verification code", + invitationCode: "Invitation Code", + plsEnterInvitationCode: "Please enter your invitation code %s", + optional: "Optional", + nextStep: "Next Step", + plsEnterRightPhone: "Please enter a valid phone number", + enterVerificationCode: "Enter phone verification code", + setPassword: "Set Password", + plsConfirmPasswordAgain: "Please confirm your password again", + confirmPassword: "Confirm Password", + wrongPasswordFormat: "Invalid password format", + plsCompleteInfo: "Please complete your personal information", + plsEnterYourNickname: "Please enter your nickname", + pleaseEnterYourUsername: "Please enter your username", + enterYourUsernameToHelpUsIdentifyYourAccount: + "Enter your username to help us identify your account", + setInfo: "Set Information", + loginPwdFormat: "6-20 digits + letters", + passwordLogin: "Password Login", + verificationSuccessful: "Verification successful", + text_1: + "This account is not bound to an email address and password retrieval is not currently supported.", + contacts: "Contacts", + workbench: "Workbench", + mine: "Mine", + me: "Me", + draftText: "Draft", + everyone: "Everyone", + you: "You", + groupAc: "Group Notification", + createGroupNtf: "%s created a group", + editGroupInfoNtf: "%s modified group information", + quitGroupNtf: "%s quit the group", + invitedJoinGroupNtf: "%s invited %s to join the group", + kickedGroupNtf: "%s was removed from the group by %s", + joinGroupNtf: "%s joined the group", + dismissGroupNtf: "%s dismissed the group", + transferredGroupNtf: "%s transferred group management authority to %s", + muteMemberNtf: "%s was muted by %s for %s", + muteCancelMemberNtf: "%s had their mute canceled by %s", + muteGroupNtf: "%s enabled group mute", + muteCancelGroupNtf: "%s disabled group mute", + friendAddedNtf: "You are now friends and can start chatting", + openPrivateChatNtf: "Enable self-destructing messages", + closePrivateChatNtf: "Disable self-destructing messages", + memberInfoChangedNtf: "%s edited their group member profile", + unsupportedMessage: "Unsupported message type", + picture: "Picture", + video: "Video", + voice: "Voice", + location: "Location", + file: "File", + carte: "Card", + emoji: "Custom Emoji", + chatRecord: "Chat Records", + revokeMsg: "withdrew a message", + aRevokeBMsg: "%s withdrew %s's message", + blockedByFriendHint: + "The message has been sent but rejected by the recipient", + deletedByFriendHint: " You are not their friend yet.", + sendFriendVerification: "Send Friend Verification", + removedFromGroupHint: "You have been removed from the group", + groupDisbanded: "The group has been disbanded", + search: "Search", + synchronizing: "Synchronizing", + syncFailed: "Sync Failed", + connecting: "Connecting", + connectionFailed: "Connection Failed", + top: "Top", + cancelTop: "Cancel Top", + markHasRead: "Mark as Read", + delete: "Delete", + nPieces: "%%s pieces", + online: "Online", + offline: "Offline", + phoneOnline: "Phone", + pcOnline: "Pc", + webOnline: "Web", + webMiniOnline: "Mini Program", + upgradeFind: "Discover New Version", + upgradeVersion: "A new version %s is available. Your current version is %s.", + upgradeDescription: "Update Description:", + upgradeIgnore: "Ignore", + upgradeLater: "Later", + upgradeNow: "Update Now", + upgradeNever: "Not update Now", + inviteYouCall: "%s invited you to %s", + rejectCall: "Reject", + acceptCall: "Accept", + callVoice: "Voice Call", + callVideo: "Video Call", + sentSuccessfully: "Sent Successfully", + copySuccessfully: "Copied Successfully", + day: "day", + hour: "hour", + hours: "hours", + minute: "minute", + seconds: "seconds", + cancel: "Cancel", + determine: "OK", + toolboxAlbum: "Album", + toolboxCall: "Video Call", + toolboxCamera: "Camera", + toolboxCard: "Card", + toolboxFile: "File", + toolboxLocation: "Location", + send: "Send", + holdTalk: "Hold to Talk", + releaseToSend: "Release to Send", + releaseToSendSwipeUpToCancel: "Release to Send, Swipe Up to Cancel", + liftFingerToCancelSend: "Lift Finger to Cancel Send", + callDuration: "Call Duration: %s", + cancelled: "Cancelled", + cancelledByCaller: "Cancelled by Caller", + rejectedByCaller: "Rejected by Caller", + callTimeout: "Call Timeout", + rejected: "Rejected", + forwardMaxCountHint: "you can only forward up to 20 messages.", + typing: "Typing...", + addSuccessfully: "Added Successfully", + addFailed: "Add Failed", + setSuccessfully: "Set Successfully", + callingBusy: "You are already in a call and cannot perform this operation!", + groupCallHint: + "The group is currently in a call. Are you sure you want to join the ongoing call?", + joinIn: "Join", + menuCopy: "Copy", + menuDel: "Delete", + menuForward: "Forward", + menuReply: "Reply", + menuMulti: "Select Multiple", + menuRevoke: "Revoke", + menuAdd: "Add", + nMessage: "%s New Messages", + plsSelectLocation: "Please select a location", + groupAudioCallHint: "%s people in a voice call", + groupVideoCallHint: "%s people in a video call", + reEdit: "Re-edit", + playSpeed: "Playback Speed", + download: "Download", + googleMap: "Google Maps", + appleMap: "Apple Maps", + baiduMap: "Baidu Maps", + amapMap: "AMap", + tencentMap: "Tencent Maps", + offlineMeetingMessage: "You received a meeting invitation message", + offlineMessage: "You received a new message", + offlineCallMessage: "You received a call invitation message", + logoutHint: "Are you sure you want to log out?", + myInfo: "My Info", + workingCircle: "Work Circle", + accountSetup: "Account Setup", + aboutUs: "About Us", + logout: "Log Out", + qrcode: "QR Code", + qrcodeHint: "Scan the QR code below to add me as a friend", + favoriteFace: "Favorite Emojis", + favoriteManage: "Manage", + favoriteCount: "%s Favorites", + favoriteDel: "Delete (%s)", + hasRead: "Read", + unread: "Unread", + nPersonUnRead: "%s unread", + allRead: "All Read", + messageRecipientList: "Message Recipient List", + hasReadCount: "Read (%s)", + unreadCount: "Unread (%s)", + newFriend: "New Friend", + newFriendList: "Friend Application List", + newGroup: "New Group", + myFriend: "My Friends", + myGroup: "My Groups", + addManager: "Add Admin", + add: "Add", + scan: "Scan", + scanHint: "Scan business card QR code", + addFriend: "Add Friend", + addFriendHint: "Add by ID", + friendGrouping: "Friend Grouping", + createGroup: "Create Group", + createGroupHint: "Create a group, fully utilize OpenIM", + addGroup: "Add Group", + addGroupHint: "Ask the admin or members for the ID", + searchIDAddFriend: "Search ID to Add Friend", + searchIDAddGroup: "Search ID to Add Group", + searchIDIs: "ID: %s", + searchPhoneIs: "Phone: %s", + searchEmailIs: "Email: %s", + searchNicknameIs: "Nickname: %s", + searchGroupNicknameIs: "Group Nickname: {name}", + noFoundUser: "User not found", + noFoundGroup: "Group not found", + joinGroupDate: "Join Group Date", + joinGroupMethod: "Join Group Method", + byInviteJoinGroup: "Joined by invitation from %s", + byIDJoinGroup: "Joined by searching group ID", + byQrcodeJoinGroup: "Joined via group QR code", + groupID: "Group ID", + setAsAdmin: "Set as Admin", + setMute: "Set Mute", + organizationInfo: "Organization Info", + organization: "Organization", + department: "Department", + position: "Position", + personalInfo: "Personal Info", + viewDynamics: "View Dynamics", + audioAndVideoCall: "Call", + sendMessage: "Message", + avatar: "Avatar", + name: "Name", + nickname: "Nickname", + gender: "Gender", + englishName: "English Name", + birthDay: "Birthday", + tel: "Tel", + mobile: "Mobile", + email: "Email", + man: "Male", + woman: "Female", + friendSetup: "Friend Setup", + setupRemark: "Set Remark", + recommendToFriend: "Recommend to Friend", + addToBlacklist: "Add to Blacklist", + unfriend: "Unfriend", + areYouSureDelFriend: "Are you sure you want to delete this friend?", + areYouSureAddBlacklist: + "Are you sure you want to add this friend to the blacklist?", + remark: "Remark", + save: "Save", + saveSuccessfully: "Saved Successfully", + saveFailed: "Save Failed", + groupVerification: "Group Verification", + friendVerification: "Friend Verification", + sendEnterGroupApplication: "Send Enter Group Application", + sendToBeFriendApplication: "Send Friend Request", + sendSuccessfully: "Sent Successfully", + sendFailed: "Send Failed", + canNotAddFriends: "This user has set not to be added!", + mutedAll: "Mute All", + tenMinutes: "10 Minutes", + oneHour: "1 Hour", + twelveHours: "12 Hours", + oneDay: "1 Day", + custom: "Custom", + unmute: "Unmute", + youMuted: "You have been muted", + groupMuted: "Group Muted", + notDisturbMode: "Do Not Disturb Mode", + allowRing: "New Message Sound", + allowVibrate: "New Message Vibration", + forbidAddMeToFriend: "Disallow Add Me as a Friend", + blacklist: "Contacts Blacklist", + unlockSettings: "Unlock Settings", + changePassword: "Change Password", + change: "Change", + clearChatHistory: "Clear Chat History", + confirmClearChatHistory: "Confirm Clearing All Chat History?", + languageSetup: "Language Setup", + language: "Language", + english: "English", + chinese: "Chinese", + newGroups: "New Group", + createdGroup: "You Haven't Greated A Group Yet", + followSystem: "Follow System", + blacklistEmpty: "No Blacklisted Contacts", + remove: "Remove", + fingerprint: "Fingerprint", + modifyGroupName: "Modify Group Name", + gesture: "Gesture", + biometrics: "Biometrics", + deleteGroups: + "After Deleting The Group, It Will Not Be Recoverable. Are You Sure You Want To Delete The Group?", + groupNames: "Please Enter The Group Name", + current_device: "Current device", + verification_time: "Verification time:", + login_details: "Login details", + plsEnterPwd: "Please Enter Password", + plsEnterOldPwd: "Please Enter Old Password", + plsEnterNewPwd: "Please Enter New Password", + plsConfirmNewPwd: "Please Confirm New Password", + verification_code1: "Please enter verification code", + binding_successful: "Binding successful", + correct_email_address: "Please input the correct email address", + email_address: "Please enter your email", + log_in_time: "Log in time", + log_in_time1: "Log in time", + bind_new_email: "Bind new email", + successful_home: "Registration successful", + Login_failed: "Login failed", + please_enter_user_nickname: "Please enter user nickname", + set_characters: "Please set 4-15 characters", + reset_successful: "Reset successful", + personalized_signature: + "Please enter your personalized signature (character limit 6-50)", + login_IP: "Login IP:", + failed_to_load: "Failed to load", + failed_to_load1: "Loading failed, please try again later", + removal_failed: "Removal failed", + reset: "Reset", + oldPwd: "Old Password", + newPwd: "New Password", + remove_this_device1: + "Are you sure you want to remove this device? If you reuse the device after removing it, re-verification is required to log in", + remove_device: "Remove device", + remove_this_device: "Are you sure you want to remove this device?", + confirmNewPwd: "Confirm Password:", + please_enter_new_password_again: "Please enter new password again", + init_validate_6: "Password must consist of 6-15 letters and numbers", + twicePwd: "The new password is inconsistent, please reset it", + currently_bound_email: "Currently bound email:", + plsEnterConfirmPwd: "Please Enter Confirmation Password", + change_the_binding: "Change the binding", + text_device: "Log in to device management", + current_device_records: + "The following are all your login records for this device", + text_device1: + "The following are all your login devices. Click to enter details to view the login record of this device", + twicePwdNoSame: "The entered passwords do not match", + text_email: + "If your email address is no longer in use, please change it promptly", + changedSuccessfully: "Changed Successfully", + noAddGround: "I Haven't Added Friends To The Group Yet", + deleteGround: "Delete Ground", + removeFriend: "Remove Friend", + noFriendAdd: "No Friends To Add", + noFriendDelete: "No Friends To Remove", + noVerify: "Verification Required When Adding Me As A Friend", + deleteSuccessfully: "Delete Successfully", + email_verification: "E-mail verification", + email_verification_code: "E-mail verification code", + verification_code: "Please enter verification code", + checkNewVersion: "Check for New Version", + year: "year", + myCreatedGroup: "The Group Chat I Created", + myJoinGroup: "The Group Chat I Joined", + myManageGroup: "The Group Chat I Manage", + device_list: "Device List", + chatContent: "Chat Content", + topContacts: "Top Contacts", + messageNotDisturb: "Message Do Not Disturb", + messageNotDisturbHint: "Receive messages but no notification", + burnAfterReading: "Burn After Reading", + timeSet: "Time Settings", + setChatBackground: "Set Chat Background", + fontFace: "Typeface", + fontSize: "Font Size", + little: "Small", + standard: "Standard", + big: "Large", + thirtySeconds: "30 Seconds", + fiveMinutes: "5 Minutes", + clearAll: "Clear All", + clearSuccessfully: "Cleared Successfully", + groupChatSetup: "Group Settings", + viewAllGroupMembers: "View All Group Members (%s)", + groupManage: "Management", + myGroupMemberNickname: "My Nickname", + topChat: "Pin Chat", + muteAllMember: "Mute All", + exitGroup: "Exit Group", + dismissGroup: "Dismiss Group", + dismissGroupHint: + "Once the group is dismissed, you will lose contact with group members", + quitGroupHint: + "After quitting the group, you will no longer receive messages from this group.", + joinGroupSet: "Group Verification", + allowAnyoneJoinGroup: "Allow Anyone", + inviteNotVerification: "No Verification for Member Invitation", + needVerification: "Verification Required", + addMember: "Add", + delMember: "Remove", + groupOwner: "Group Owner", + groupAdmin: "Group Admin", + notAllowSeeMemberProfile: "Disallow View Members' Profiles", + notAllAddMemberToBeFriend: "Disallow Add Members as Friends", + transferGroupOwnerRight: "Transfer Group Owner", + groupName: "Group Name", + groupAcPermissionTips: "Only owner and admin can edit", + plsEnterGroupAc: "Please enter group announcement", + edit: "Edit", + publish: "Publish", + groupMember: "Group Members", + selectedPeopleCount: "Selected (%s)", + confirmSelectedPeople: "Confirm (%s/%s)", + confirm: "Confirm", + alreadyBeOwner: + "You have selected the group owner and have the highest administrative rights ", + confirmTransferGroupToUser: + "Are you sure you want to transfer group ownership to: %s?", + removeGroupMember: "Remove Group Member", + searchNotResult: "No results found", + groupQrcode: "Group QR Code", + groupQrcodeHint: "Scan the QR code below to join the group", + approved: "Approved", + accept: "Accept", + reject: "Reject", + waitingForVerification: "Waiting", + rejectSuccessfully: "Rejected successfully", + rejectFailed: "Rejection failed", + applyJoin: "Apply to join", + enterGroup: "Enter Group", + applyReason: "Reason: %s", + invite: "Invite", + sourceFrom: "Source: %s", + byMemberInvite: "Invitation", + bySearch: "By Search", + byScanQrcode: "By QR Code", + iCreatedGroup: "I Created", + iJoinedGroup: "I Joined", + nPerson: "{count} people", + searchNotFound: "No relevant results found", + searchNotFoundMember: "No relevant member found", + organizationStructure: "Organization Structure", + recentConversations: "Recent Conversations", + selectAll: "Select All", + plsEnterGroupNameHint: "Choose a group name for easy search", + completeCreation: "Complete Creation", + sendCarteConfirmHint: + "Are you sure you want to send this contact to the chat?", + sentSeparatelyTo: "Sent separately to:", + sentTo: "Sent to:", + leaveMessage: "Leave a message", + mergeForwardHint: "[Merge Forward] %s messages", + mergeForward: "Merge Forward", + quicklyFindChatHistory: "Quickly find chat history", + notFoundChatHistory: 'No "%s" related content found', + globalSearchAll: "All", + globalSearchContacts: "Contacts", + globalSearchGroup: "Groups", + globalSearchChatHistory: "Chat History", + globalSearchChatFile: "Files", + relatedChatHistory: "%s related chat history", + seeMoreRelatedContacts: "See more related contacts", + seeMoreRelatedGroup: "See more related groups", + seeMoreRelatedChatHistory: "See more related chat history", + seeMoreRelatedFile: "See more related documents", + publishPicture: "Publish Picture", + publishVideo: "Publish Video", + mentioned: "Mentioned: %s", + comment: "Comment", + like: "Like", + reply: "Reply", + rollUp: "Roll Up", + fullText: "Full Text", + selectAssetsFromCamera: "Take Photo", + selectAssetsFromAlbum: "Choose from Album", + whoCanWatch: "Who can watch", + remindWhoToWatch: "Remind who to watch", + public: "Public", + everyoneCanSee: "Everyone can see", + partiallyVisible: "Partially visible", + visibleToTheSelected: "Visible to the selected", + partiallyInvisible: "Not visible to some", + invisibleToTheSelected: "Invisible to the selected", + private: "Private", + onlyVisibleToMe: "Only visible to me", + selectVideoLimit: "Duration cannot exceed 15 seconds", + selectContactsLimit: "Please select at least one contact", + message: "Message", + commentedYou: "Commented on your post: %s", + likedYou: "Liked your post", + mentionedYou: "Mentioned you", + replied: "Replied", + detail: "Detail", + totalNPicture: "Total %s pictures", + noDynamic: "No posts yet", + callRecords: "Call Records", + allCall: "All Calls", + missedCall: "Missed Calls", + incomingCall: "Incoming", + outgoingCall: "Outgoing", + microphone: "Microphone", + speaker: "Speaker", + hangUp: "Hang Up", + pickUp: "Pick Up", + waitingCallHint: "Calling...", + waitingVoiceCallHint: "Waiting for the other party to pick up...", + invitedVoiceCallHint: "Inviting you for a voice call...", + waitingVideoCallHint: "Waiting for the other party to accept the invitation", + invitedVideoCallHint: "Inviting you for a video call...", + waitingToAnswer: "Waiting to answer", + invitedYouToCall: "Invited you to a call", + calling: "Calling...", + nPeopleCalling: "%s people on the call", + whoInvitedVoiceCallHint: "%s invited you for a voice call", + whoInvitedVideoCallHint: "%s invited you for a video call", + plsInputMeetingSubject: "Please enter meeting subject", + meetingStartTime: "Start Time", + meetingDuration: "Meeting Duration", + enterMeeting: "Enter Meeting", + meetingNo: "Meeting Number", + yourMeetingName: "Your Name", + plsInputMeetingNo: "Please enter meeting number", + plsInputYouMeetingName: "Please enter your name", + meetingSubjectIs: "Subject: %s", + meetingStartTimeIs: "Start: %s", + meetingDurationIs: "Duration: %s", + meetingHostIs: "Host: %s", + meetingNoIs: "Number: %s", + meetingMessageClickHint: "Click this message to join the meeting directly", + meetingMessage: "Meeting Message", + openMeeting: "Ongoing Meeting", + didNotStart: "Did not start", + started: "Started", + meetingInitiatorIs: "Video meeting initiated by %s", + meetingDetail: "Meeting Details", + meetingOrganizerIs: "Organizer: %s", + updateMeetingInfo: "Update Meeting Information", + cancelMeeting: "Cancel Meeting", + videoMeeting: "Video Meeting", + joinMeeting: "Join Meeting", + bookAMeeting: "Book a Meeting", + quickMeeting: "Quick Meeting", + confirmTheChanges: "Confirm Changes", + invitesYouToVideoConference: "%s invites you to join a video conference", + over: "End", + meetingMute: "Mute", + meetingUnmute: "Unmute", + meetingCloseVideo: "Off", + meetingOpenVideo: "On", + meetingEndSharing: "End Sharing", + meetingShareScreen: "Share Screen", + meetingMembers: "Members (%s)", + settings: "Settings", + leaveMeeting: "Leave Meeting", + endMeeting: "End Meeting", + leaveMeetingConfirmHint: "Are you sure you want to leave the meeting?", + endMeetingConfirmHit: "Are you sure you want to end the meeting?", + meetingSettings: "Meeting Settings", + allowMembersOpenMic: "Allow members to unmute themselves", + allowMembersOpenVideo: "Allow members to turn on video", + onlyHostShareScreen: "Only the host can share screen", + onlyHostInviteMember: "Only the host can invite meeting members", + defaultMuteMembers: "Mute members upon entry", + pinThisMember: "Pin", + unpinThisMember: "Unpin", + allSeeHim: "See him", + cancelAllSeeHim: "no see", + muteAll: "Mute all", + unmuteAll: "Unmute all", + members: "Members", + screenShare: "Screen Share", + screenShareHint: "Sharing the screen.", + meetingClosedHint: + "The meeting has been closed or the connection has been lost. Are you sure you want to leave?", + meetingIsOver: "The meeting has ended!", + networkError: "Network error. Please try again later!", + shareSuccessfully: "Shared successfully!", + notFoundMinP: "No mini-programs published yet", + notSendMessageNotInGroup: + "Can't send messages in a group chat that's signed out", + whoModifyGroupName: "{name} modified the group name", + accountWarn: "Warn!", + accountException: + "Your account has been logged in to another device, please change your password in time.", + tagGroup: "TAG", + issueNotice: "Notification Issued", + createTagGroup: "Create tags", + plsEnterTagGroupName: "Please enter a tag name", + tagGroupMember: "Tag member", + completeEdit: "Complete Edit", + finish: "Finish", + emptyTagGroup: "No tag group yet", + confirmDelTagGroupHint: "Are you sure to remove this tag group?", + editTagGroup: " Edit Tag", + newBuild: " New", + receiveMember: " Receive member", + emptyNotification: "No notice yet", + notificationReceiver: "%s recipients: %s", + sendAnother: "Send another", + confirmDelTagNotificationHint: + "Are you sure to remove this notification record?", + contentNotBlank: "The content can not be blank", + plsEnterDescription: "Please enter a description", + gifNotSupported: "Gif images are not supported", + register: "register", + hk: "Fan Ti", + RegisterSuccess: "Register Success", + LoginSuccess: "Login Success", + init_validate_1: "Username consists of 6-15 letters and numbers", + init_validate_10: + "Mobile phone number or password is incorrect, please re-enter ", + init_validate_5: "Password must be composed of 6-15 letters and numbers, please reset your password", + init_validate_2: "Two passwords do not match, please reset", + init_validate_3: + "Password must consist of letters and numbers from 6 to 15. Please reset your password.", + init_validate_4: "Successful registration, about to return to the login page", + init_hint_1: "6-15 digits or English ", + init_hint_2: " Confirm login password ", + init_hint_3: " Login password ", + text_password2: " Please confirm your login password again ", + username: "Username", + Resend: "Resend", + traditional_chinese: "Traditional Chinese", + notification_settings: "Notification settings", + downloadAPP: "DownloadAPP", + safety: "Safety", + about: "About", + bind_email: "Bind email", + trusted_device: "Trusted device", + old_password: "Old Password", + please_enter_old_password: "Please enter old password", + go_to_binding: "Go to binding", + go_to_settings: "Go to settings", + to_modify: "To modify", + change_email_address: "Change email address", + confirm_password: " Confirm password", + init_text_1: "Sign in and agree ", + createAccount: " Create Account ", + init_text_2: " User Service Agreement ", + setting_up: " Setting up...", + init_text_3: " and ", + init_text_4: " Privacy Policy", + download1: "Download", + confidential: "Confidential", + date_of_birth: "Date of birth", + not_perfection: "Not perfection", + init_text_5: "Service Agreement and Privacy Policy ", + init_text_6: + "In order to protect your legal rights, please read and agree to the following agreement ", + refuse: "refuse", + refuse_continue: "Agree and continue", + forget_password: "Forgotten password", + register_user: "Register an account", + check_for_updates: "Check for updates", + nicknames: "User's Nickname", + signature: "Signature", + gotoLogin: "Login", + gotoRegister: "go to register", + enterGroupCheck: "Group Check", + openPhotoError_1: "The selected file was not read ", + openPhotoError_2: "File upload failed ", + openPhotoError_3: "File information not read ", + agreeAll: "All agree ", + whatCanManagerDo: "What can the group manager do?", + changeGroupName: "Change the group name ", + deleteMemberMessage: "Delete group member message ", + chatPrivate: "Encrypted private chat with group members ", + memberMute: "group mute ", + deleteMember: "Remove member ", + changeNotice: "Publish/modify/delete Group Announcements ", + checkInGroup: "Check in group ", + setAdmin: "Set administrator ", + deleteAdmin: "Remove administrator ", + deleteAdminTitle: "Sure you want to remove this administrator?", + deleteAdminDetail: + "Remove this member from the administrator, unable to manage group messages ", + deleteSuccess: "Removed successfully ", + mobile_web_page: "Access mobile web pages ", + deleteMemberTitle: "Sure you want to remove this member?", + deleteMemberDetail: + "This member is removed from the group, unable to send or receive messages ", + deleteSelf: "Can't remove yourself ", + handled: "handled", + unhandle: "unhandle", + noCheck: "No application is being processed at this time ~ ", + success: "Operation success", + nameLenth: "Please set 1-15 characters ", + editNickname: "Edit group nickname ", + text_password: "Please set a new login password", + quit: "quit", + waitAMoment: "I'm looking ", + editWarningTitle: "Are you sure to exit the edit group nickname?", + editWarningDetail: + "The group nickname you modified has not been saved, do you want to continue to exit?", + editGroupAvatar: "Edit the group avatar ", + myGroupAvatar: "My group avatar ", + inviteLink: "Invite link ", + inviteTips: + "Any user who has installed this APP will be able to join your group chat via this link ", + inviteLinkCopy: "Invite link copied ", + shareLink: "Share link ", + manageLink: "Manage invitation links ", + copyLink: "Copy link ", + deleteLink: "Delete link ", + deleteLinkTipsTitle: "Are you sure you want to undo this link?", + deleteLinkTipsDetail: + "Are you sure you want to revoke this invite link? Once withdrawn, others will not be able to join this group through this link.", + linkManage: "Link Management ", + openManage: + "After enabled, the group owner or administrator needs to confirm before joining the group ", + timeValid: "Valid time limit ", + linkInvalidTips: + "Invitation link will no longer be available after this date ", + valid: "valid inside ", + dayValid: "valid within a day ", + forever: "forever", + foreverValid: "Permanently valid ", + attendTips: + "The join request has been sent, after the administrator reviews your request, you can join the group ", + memberCountLimit: + "The number of people in this group chat has reached online ", + linkIsOut: "Invitation link expired ", + requestAttend: "Request to join a group chat ", + needCheck: "This group chat can only be joined after administrator review ", + countLimit: "The number of administrators has reached the upper limit ", + openLock: "Timeout needs to be unlocking again", + closeLock: "No validation required when starting the app ", + checkTips: + "Please check if your face or fingerprint permissions are enabled ~", + face: "Face unlock ", + finger: "fingerprint unlock ", + gestureLock: "Gesture password unlock ", + noUnlock: "No unlock?", + inputGesture: "Please input gesture", + faceDetect: "Please face detect", + fingerDetect: "Please finger detect", + tryUnlocking: "Try can't unlock", + countRemain: "Gesture error,you can input %s count", + reLoginUnlock: "Relogin to unlock", + selectVerityType: "Please select verity type ", + currentTypeNotSupport: "The current type does not support", + closed: "Not opened ", + opened: "opened", + lanchProtect: "Protect at startup ", + noProtect: "No need to protect ", + lockSetting: "Unlock setting ", + drawLine: "Draw the unlock pattern ", + drawOldLine: "Draw your old gesture password ", + drawNewLine: "Draw your new gesture password ", + setGesturePassword: "Set a gesture password ", + checkGesturePassword: "Verify gesture password ", + needJoinPoint: "Connect at least 4 points, please redraw ", + drawAgain: "Draw again ", + checkSuccessful: "Verified ", + notInCommon: "Not consistent with the last drawing, please redraw", + changeGesturePassword: "Change gesture password ", + toChange: "to change ", + openSuccess: "Enabled successfully ", + closeSuccess: "Closed successfully ", + hasBeenMember: "Already a group member, do not join again ", + joinGroup: "Join a group chat ", + checking: "Checking by the administrator", + updateNewVersion: "Discover new version ", + updateOkUpdate: "Update now ", + updateOpenDownloadFailed: "Open the download failed", + updateCurrentIsNew: "The current version is the latest ", + newVersionNotFound: + "No link to the latest version was found, you can visit the page to download it ", + openUrlFailed: "Invalid link", + qmvMsgNotExit: "[Reference does not exist]", + qmvMsgNotSupport: "[Referenced content is not supported for display]", + dataError: "Data parsing error ", + userNotBindEmail: "User is not bound to mailbox ", + userNotRegister: "User is not registered ", + checkError: "Check failed ", + csvTitle: "Chat Settings", + csvSetTopMsg: "Top chat ", + csvSetOptMsg: "Messages do not disturb ", + csvSetDisableMsg: "Mask messages ", + csvClearMsg: "Clear chat history ", + csvClearLocal: "Clear local chat history only ", + csvClearUser: "Clear local and %s chat history ", + csvSetErrorTip: "Setup failed, please check the network ", + gmiTitle: "Group nickname:", + gmiSetTalk: "Encrypted private chat ", + gmiSetMute: "Set group mute ", + gmiDeleteUser: "Move out of this group ", + gmiMuteTo: "gag until ", + gmiError1: + "This member information was not found, may have been moved out of the group chat ", + gmiError2: + "Your group membership information was not found, you may have been removed from the group chat ", + gmiError3: "Sure you want to remove this user?", + gmiError4: + "This member is removed from this group, cannot send or receive messages ", + gmiError5: "You do not have permission to delete this administrator ", + gmiError6: "You do not have permission to delete this member ", + gmiError7: "Gag has been lifted ", + gmiError8: "You do not have permission to unblock this administrator ", + gmiError9: "You do not have permission to unblock this member ", + gmiError10: "You do not have permission to ban this administrator ", + gmiError11: "You do not have permission to ban this member ", + gmiError12: "Lift the gag ", + gmiGroupOwnerNotFound: "群信息加载失败", + smmvTitle: "Gag duration ", + smmvMuteTime: "Select Gag duration ", + smmvMuteTime_10m: "10 minutes ", + smmvMuteTime_1hour: "1 hour", + smmvMuteTime_12hour: "12 hours ", + smmvMuteTime_1day: "1 day", + smmvMuteTime_30day: "30 days ", + smmvMuteTime_7day: "7 days ", + egnpTitle: "Edit group chat name ", + egnpSetGroupName: "Please set the group name ", + egnpSetTextDesc: "Please set 1-15 characters ", + egnpSetTitleNoPermission: + "You do not have permission to change the group chat name ", + egnpSetFailed: "Modification failed, please try again later ", + setGroupNameHint: "Please set the group name ", + givTitle: "Edit group chat name ", + givGroupName: "Group name ", + givGroupFace: "Group avatar", + gmsvTitle: "Management Group ", + gmsvSetManager: "Settings Manager", + gmsvToSet: "Go to set ", + gmsvFindMsg: "Find chat history ", + gmsvFindMsgDesc: + "When enabled, incoming group members will see recent history messages ", + gmsvMute: "All personnel cannot speak ", + gmsvMuteDesc: + "Only allow group owners and administrators to speak after this function is enabled ", + gmsvApply: "Group confirmation ", + gmsvApplyDesc: + "After this function is enabled, the group owner or administrator needs to confirm before entering the group ", + gmsvSetManagerTip: "Only the group master can set the administrator ", + gmsvSetMuteTipOn: "All personnel have been banned ", + gmsvSetNuteTipOff: "All gag has been lifted ", + gmsvApplyTipOn: "Group confirmation is enabled ", + gmsvApplyTipOff: "Group confirmation closed ", + gmsvFindGroupErr: "Failed to get group information ", + gmsvFindMsgOn: "Find chat history turned on ", + gmsvFindMsgOff: "Find chat history turned off ", + gmsvSetFailed: "Setting failed ", + gsvTitle: "Group Details ", + gsvGroupId: "Group ID", + gsvGroupMember: "Group chat member ", + gsvGroupMemberValue: "Total {num} people ", + gsvGroupNotification: "GroupNotification", + gsvMyName: "My group nickname ", + gsvMyFace: "My group avatar ", + gsvGroupManager: "Group Management ", + gsvGroupApply: "Group confirmation ", + gsvFindMsg: "Find chat history ", + gsvSetTop: "Top", + gsvSetOpt: "Messages do not disturb ", + gsvDeleteMsg: "Delete chat history ", + gsvDeleteLocal: "Clear local chat history only ", + gsvDeleteAllMsg: "Clear local and all member chat history ", + gsvDeleteOtherMsg: 'Clear the chat history of local and all members', + gsvGroupDelete: "Sure you want to disband the group chat?", + gsvOutGroup: "Are you sure you want to quit this group chat?", + gsvNoNotification: "No announcement yet ", + gsvClearSuccess: "Clear successfully ", + gsvClearFailed: "Clearing failed, please try again later ", + cvAllPeople: "All people ", + cvVideo: "[video]", + cvPicture: "[picture]", + cvFile: "[Dataja]", + cvVoice: "[voice]", + cvNotSupportMsg: "Unsupported messages ", + cvSendMsgErrorNoPermission: + "because the other side of the permissions, you can't send message to the TA", + cvSendMsgPnMute: "In silence, cannot send message ", + cvCopyMsg: "Copied to clipboard ", + cvCopyMsgNotSupport: "Messages not supported yet ", + cvdeleteLocal: "Delete from local ", + cvdeleteLocalAndServer: "Removed from local and server ", + cvdeleteLocalAndServerUser: "removed from local and {name}", + cvClearMsgTip: "Cleared session messages ", + cvGroupCreateSuccess: "Group chat created successfully ", + cvGroupCreateSuccessTip1: + "Congratulations on your successful group creation, click the button below to invite friends to join the group ", + cvGroupCreateSuccessGo: "Invite friends", + cvGroupNotification: "GroupNotification: ", + sendMsg: "Send message ", + cvOnlineCount: "{count} online ", + cvUserCount: "Total {count} people ", + cvNotAtUser: "There is no group member for @oh, go ahead and add members ", + all: "all", + clCloseConversation: "Fold top chat ", + clOpenConversation: "A top conversation ", + clNoConversation: "No recent conversation ~ ", + clTitle: "Chat ", + clDelete: "This session will be deleted ", + clDeleteDesc: + "Deleting this conversation will delete the local chat history ", + clDeleteSel: "You did not select a session ", + clDeleteSel1: "This session will be deleted ", + clDeleteSel2: "These conversations will be deleted ", + clDeleteDesc2: + "Deleting these conversations will delete the local chat history ", + homeItem0: "Dialogue ", + homeItem1: "Contacts ", + homeItem2: "My ", + exitTip: "Click back again to exit ", + mineSigEmpty: "This user is lazy and has no signature ~ ", + uaProtocol: "User Service Protocol ", + savSearchTip: "Quick search for chat content ", + savMedia: "image/video", + searchEmptyTip: "No relevant chat history found ", + savSearchMsg: "related chat history ", + savConversation: "conversation ", + savMsg: "Chat history ", + savConversationNotFound: "Conversation not found ", + savMsgNotFound: "This information was not found ", + samTitle: "Media ", + samTimeFormat: "yyyy/M", + notSupportType: "Unsupported type", + media: "Media ", + openUrl: "Open link ", + lkAddGroupUrl: "Join group chat link ", + lkAddOtherUrl: "External link, open with default browser ", + lkUnknowUrl: "unrecognized link ", + openUrlFailed1: "Failed to open the link ", + yszcTitle: "Privacy Policy ", + slsmTitle: "Legal Statement", + ctmRemove: "Remove", + ctmOkRemove: "Confirm removal ", + ctmTopMsgCount: "Total {count} bars ", + ctmEmptyText: "No top message ", + recordTimeShort: "The recording time is too short ", + ctmSuccess: "Top placement success ", + ctmRemoveSuccess: "Remove the top message successfully ", + ctmMsgNotFound: "This message does not exist ", + clearGroupNotification: "Group notification cleared ", + publicGroupNotification: "Release notification ", + dialog_tip1: "Warm reminder ", + dialog_tip_token_invaild: "Login has expired, please log in again ", + cvFirstTip: + "Information and calls in this conversation have been encrypted end-to-end ", + notify_new_msg_title: "You have received a new message ", + notify_new_msg_msg: "Message content:.....", + dataError1: "Data error ", + groupApply: "Groupapply ", + muteLate: "after lifting the gag ", + groupMuteing: "in group silence ", + groupMemberNotFound: "Group member information not found ", + bindEmailTipTitle: "Bind mailbox ", + bindEmailTipContent: + "For the security of your account, please bind your email ", + bindEmailTipOk: "unbind ", + bindEmailTipCancle: "Do not bind yet ", + managerApplying: "Administrator applying ", + quote: "Quote ", + request_exception: "Request exception", + clearMsg: "Clear message ", + pressToSay: "Press and speak ", + slideToCancelSend: "SlidetoCancelsend ", + releaseToEnd: "Release to end ", + releaseToCancelSend: "Release your finger to cancel the send ", + invalidVoiceMessage: "Invalid voice message ", + hit: "hit", + downloadingCache: "Download cache...", + unableToRecognize: "Not recognized!", + scanResult: "scan result ", + register_fail: "Registration failed, please check the network and try again ", + skip: "skip", + toUse: "Experience it now ", + sideToSide: "end-to-end encryption ", + design: "Design ", + guide1: "Send message, encrypted transmission, for your own reading only ", + guide2: "More freedom to communicate on your own terms ", + guide3: "A secure communication platform built around you ", + inputGroupAc: "Please enter group Bulletin (character limit 0-500) ", + clickToEnjoy: + "Click on the link to save, or copy this paragraph, open the QDDchat APP, and join the group chat.", + createOK: "Created successfully ", + noPravcy: "You do not have permission to create group chats ", + groupNumberToTop: "The number of groups reaches the upper limit.", + createFail: "Create failed, please try again later ", + create: "Create ", + uploadGroupAvatar: "Upload group avatar ", + useNewPassword: + "Password changed successfully, redirecting to login page", + error_14: "server error (14)", + error_500: "server error (500)", + error_1000: "The user signature token is invalid ", + error_1001: "Parameter error", + error_1002: "The current version is the latest version ", + error_6001: "This username is already registered", + error_6003: "The password must consist of 6-15 letters and numbers ", + error_6004: "User name verification error", + error_6005: "Password is incorrect, please try again", + error_6006: + "The number of incorrect passwords today exceeds the upper limit ", + error_6007: "The user does not exist ", + error_6008: "The old password is incorrect, please re-enter ", + error_6009: + "The old password is the same as the new password, please re-enter ", + error_6010: "Frequently obtain verification codes ", + error_6011: "Verification code is incorrect, please try again", + error_6012: "The verification code has expired ", + error_6013: "Too many verification code failures ", + error_6014: "The verification code has been used ", + error_6015: "The mailbox does not exist ", + error_6016: "The mailbox has been bound by another person, please re-enter ", + error_6017: "Verification code does not exist ", + error_6018: "The account has since been suspended ", + error_6030: "The server mailbox is not configured", + error_100002: "The top message reached the upper limit ", + error_100003: "This message is at the top ", + error_api: "Request error ", + error_http: "Network request error ", + error_http_11: " Network connection timeout ", + error_http_12: " Network connection timeout ", + error_http_13: " Network connection timeout ", + error_http_14: " Network certificate error ", + error_http_15: " Request data error ", + error_http_16: " Request cancelled ", + error_http_17: " Network connection timeout ", + error_http_18: " There was an error inside the network request ", + error_sdk_err_1002: " Unauthorized operation ", + error_sdk_err: " Operation failed ", + search_hine_1: "search", + str_no_data: "no data", + str_no_net: "no net", + download_not_support_type: "Unsupported format", + download_save_at: "files stored in: ", + download_not_url: "Download link not recognized ", + download_file_success: "Saved successfully, you can view it in the album ", + download_failed_file: " Failed to save the file.", + download_failed: "Download failed.", + loading_failed_retry: " Loading failed, click retry", + loading_ing: " loading...", + str_online_later_minute: " Online a minute ago ", + str_online_later_hour: " online hours ago ", + str_online_later_llg: " Not online for a long time ", + str_online_later_yesterday: " online yesterday", + str_yesterday: "Yesterday", + str_online_later_near: " Online recently ", + str_minute: " minute", + clean_notice: "Clean the notice", + clean_notice_1: "Are you sure you want to empty group notices?", + publish_notice: "Publish an announcement ", + publish_notice_remind: "Publish this notice to alert all group members ", + quit_edit: "Exit edit", + quit_the_edit: "Exit this edit?", + continue_edit: "Continue to edit", + publish_success: "Publishing success", + face_success: "Face unlock successfully opened ", + finger_success: "Fingerprint unlocking successfully enabled ", + groupDismissed: "The group is dismissed", + hasOut: "Expired ", + otherLoginStyle: "Other login methods ", + otherRegisterStyle: "Other registration methods ", + hasAccount: "Already have an account, go to login ", + mobileCode: "Mobile verification code ", + codeSendTo: "The verification code was sent to ", + inputNumber: "Please enter the phone number ", + error_6019: "The phone number has been bound by another person", + error_6020: "The email address has been bound to another person ", + error_6021: "The email address is wrong, please re-enter ", + error_6022: "The phone number is incorrect, please re-enter ", + error_6023: + "This account has been blocked, the password cannot be recovered ", + error_6024: + "This account is not bound to the mailbox, the password cannot be recovered ", + error_6025: + "The account is not bound to the mobile phone number, the password recovery is not supported", + error_6026: + "The phone number has been bound by someone else, please re-enter ", + error_6031: "Please enter the correct mobile number ", + error_6032: "Please enter the correct email address ", + bindPhone: " Bind phone number ", + bindPhoneSuccess: " Phone number binding successful ", + bindPhoneTipChange: + " If your mobile phone number is no longer used, please change it in time ", + please_input_email: " Please enter your email address ", + findPassByEmailTip: " Retrieve password by email ", + findPassByPhoneTip: " Find your password with your phone number ", + resetPass: " Reset password", + current_bind_phone: " Current mobile phone number: ", + reget: " regain", + getVerifyCode: " Get the verification code ", + alreadyRegister: " This mobile number has been registered", + alreadyRegister2: " This email address has been registered", + netErrorRetry: "Network error, click page reload ~", + newPhoneNumber: "New phone number ", + qrSaveFail: "QR code saving failed ", + qrSavePhoto: "QR code saved to album ", + userId: "User ID: ", + addMeToFriend: "Add me as a friend ", + validTime: "Valid to ", + clickRefresh: "Click Refresh", + qrUseCount: "This QR code is valid for {count} times ", + share: "share", + savePhoto: "Save the picture ", + private_chat: "QDDchat", + iAm: "I am ", + addFriendSuccessful: "Add friends successfully ", + addFriendFaceToFace: "Add friends face to face ", + faceToFace: "Face To Face", + inputSameKey: "Here are the friends who have entered the same number ", + sameKeyAddFriend: "Add friend by entering the same number as your friend ", + self: "self", + age: "age", + confirmAddFriend: "Confirm to be added as a friend ", + inputUserId: "Please enter a user ID", + myQr: "My QR code ", + back: "return ", + canNotAddSelf: "You can not add yourself to a contact ", + notExsit: "This user does not exist ", + searchUserId: "Search user ID: ", + remarkInfo: "Please remark the verification information ", + applyRemark: "Application remarks ", + last4Number: + "Please enter the last4 digits of the other party login phone number ", + checkLast4Number: "Verify the last 4 digits of the phone number ", + addFriendApply: "Add friend request ", + codeInvalid: "Key invalid, please re-initiate ", + shareFail: "Share failure ", + addFriendText1: "Add as friend ", + friendNumberCatchTopLimit: "your friends number has reached upper limit", + friendNumberCatchTopLimitCurrentDay: + "add buddy has reached its upper limit on the same day", + error_1003: "Please try again later", + error_6033: "Invalid key or invalid, please try again ", + error_6034: "Maximum number of friends ", + error_6035: "Maximum number of friends for the day ", + error_6036: "The user's mobile number is empty ", + error_6037: "Failed to verify the phone end number ", + error_6040: "Group already exists ", + error_6050: "QR code has expired ", + error_6051: + "The number of QR codes used that day has reached the upper limit ", + error_6052: "The number of QR code usage has reached the upper limit ", + error_6053: "The number of scan functions today has reached the upper limit ", + error_6054: "The other party has added you as a friend", + error_6055: "Invalid key or invalid, please try again ", + error_6066: "Account balance is insufficient ", + error_6067: "The red packet has been received ", + error_6068: "The red packet has expired ", + error_6069: "Unable to receive exclusive red packet ", + error_6070: "The red packet has been received, can not receive any more ", + deletedByFriendHint1: "You're not their friend yet, ", + deletedByFriendHint2: "Add friends", + deletedByFriendHint3: "Keep chatting", + gotoAddFriend: " Go to add friend ", + addFriendGotoGroup: " Invite friends to join a group ", + notContacts: " No contacts yet ", + notContactsFound: " No friends associated with %s ", + remarkStr: "Remark", + remarkStrHint: " Please enter remarks ", + deleteConfirm: " Do you confirm to delete friends ", + deleteFriend: " Delete friend ", + searchContact: "Search for contacts", + notGroupTip: " Haven't joined the group chat yet ", + notAddFriend: " No new friends have been added yet ", + friendApplyAdd: "Friend Request", + addSuccess: " Add success ", + hasSelectFriend: "Selected friend", + myBalance: "My Balance", + revokeMsgTip: "withdraw the message ", + error_10050: "Operation failed (no record found)", + set_manager_tip_1: + "You have selected the group master and have the highest administrative rights ", + envelope: "red envelope ", + uidStr: "User ID", + showGetDetail: "View claim details ", + waitOtherEnter: "Wait for the other party to join ", + isFriend2SExit: "Become a friend, automatically quit after 2 seconds ", + sendRedPacket: "Send red packets ", + redPacketNumber: "Red packet number ", + ge: "a ", + total: "Total ", + eachNumber: "single quantity ", + selectUser: "Select user ", + number: "quantity ", + redPacketType: "red packet type", + checkVerity: "Security verification ", + balance: "Account balance", + sendTo: "SMS verification code sent to ", + pin: "Lucky luck red envelope ", + putong: "ordinary red envelope ", + zhuanshu: "Exclusive red envelope ", + dajidali: "May you come into a good fortune!", + redPacketInfo: "Red packet details ", + best: "best luck ", + saveToAccount: "deposited into account ", + minuteOut: "minute out ", + hourOut: "Hourout ", + showDetail: "View details ", + maxMemberNumber: "Number is less than the members number", + sended: " sent ", + get: "Received ", + waiting: "Waiting for the user to collect ", + gong: " red envelope ", + envelope_get_over: " The red envelope has been collected ", + envelope_get: " get ", + de: " of ", + envelope_get_message: "Receive red envelope message", + bind_phone_please: + "For the security of your account, please bind the phone number ", + delay_bind: "Do not bind yet ", + send_envelope_x: "sent red envelope ", + send_envelope_back_all: + "The red envelope has been returned after more than 24 hours ", + envelope_x_can_get: "Only {name} retrievable ", + envelope_get_completed: "Slow hand, red envelope snatched ", + send_envelope_back_some: + "The red envelope has been returned for more than 24 hours ", + envelope_x_personal: "Exclusive red envelope for %s ", + envelope_normal: "ordinary red envelope ", + login_out_of_date: "Login has expired, login again ", + get_envelope_completed: "has been taken ", + x_is_personal: "Exclusive ", + error_6072: "The maximum amount of a single red packet has been reached ", + error_6073: "The number of a single red packet cannot be less than 0.01", + error_6074: "Red packet function is off ", + distroyAccount: "Log off account ", + inComing: "income ", + outGoing: "amount paid", + balance_order_detail: "Balance details ", + type: "Type", + payType: "payment method ", + orderNo: "Order number ", + time: "Time ", + itemDataNotSupport: "Currently not supported ", + manager_change_account: "Administrator adjustment ", + groupEnvelope: "Group red envelopes ", + accountPay: "Balance payment ", + paySuccess: "Payment success ", + groupEnvelopePersonal: "Group Red packet - Exclusive Red packet ", + groupEnvelopeNormal: "Group red packets - normal red packets ", + changeAccount: "transfer account ", + groupEnvelopeRandom: " Group red packets - Luck red packets ", + groupEnvelopeAllBack: "Group red packets - all returned ", + groupEnvelopeSomeBack: "Group red packet - partial refund ", + distroyTips: + "In order to confirm the security of your account
we will perform the following verification before you close your account ", + safeStatus: "Account is safe ", + safeTips: + "Please ensure that your account does not change the password, change the mobile phone/email and other sensitive operations, the account is not at risk of theft.", + strikeBalance: "Account property settlement ", + strikeTips: + "After you close your account, the red envelope balance data of your account will be deleted and cannot be recovered.", + dataDelete: "Account data deletion ", + deleteTips: + "After the account is deleted, all data under the account will be deleted and cannot be recovered. Data includes your chat history, group chats you have joined, friend relationships, and more. ", + applyDistroy: "Apply for account cancellation ", + distroyConform: "Are you sure you want to log out?", + distroyConformTips: + "After logging out, you will exit the login. Perform this operation with caution!", + distroy: "Confirm logout ", + submitSuccess: "Submission successful ", + submitted: "Your deregistration application has been submitted!", + helpWord: + "The platform will process your application and delete all your data within {day} working days, if you need help, please contact us through the official website.", + know: "I know", + revoke: "Revoke", + revokeSuccess: "Revoke success", + recover: "Are you sure you want to revoke the account?", + toNormal: "Account status becomes normal after revoke ", + beRecover: "Confirm recovery ", + recoverSuccess: "Recovery success ", + redPackageCountTip: "Single red packet cannot be less than 0.01", + closeAccount: " Close Account", + recoverAccount: " Recoveraccount ", + recoverAccountTipTitle: " Are you sure you want to restore your account?", + recoverAccountTipDesc: " Account status becomes normal after recovery ", + recoverAccountTipOk: " Recovery confirmed ", + closingAccount: " The account is under cancellation review ", + closingAccountEndTime: + ", the deletion is expected to be completed in {num} working days ", + error_6075: "Repeat application ", + error_6076: "No record of cancellation request found ", + error_6077: "Logout review ", + error_6078: + "The user does not exist or has been logged out, please check and confirm ", + error_6079: " You are no longer in the group chat, can not collect ", + error_video_pre_err: "File generation error, data loading failure.", + err_upload_file: "Upload failed, please try again later ", + sendCodeExceErr: + "Failed to send the verification code, please try again later ", + bindEmailExecErr: "Failed to bind the mailbox, please try again later ", + err_msg_send_failed: "Sending failed, please try again later ", + error_red_package_tip1: " Red packet has expired", + upload: "upload", + error_6081: " Failed to send, please try again later", + privateSetting: "Privacy setting", + atTip: "{'@'}you", + timeoutLock: "Timeout Lock", + reTry: "Try again", + plsEnterPassword6_15: "Password (6-15 alphanumeric)", + plsEnterAccount6_15: "User name (6-15 alphanumeric)", + removeGroupNotice: "Remove group notice", + net_error_no_net: "Network unavailable, please check your network Settings ", + groupChatError: "Content failed to load, click retry ", + legalNotice: "Legal notice ", + sysNotify: "System notification ", + exit_group_tip: "Exit_group_TIP ", + group_mute_ing: "This group has been banned ", + w_edit_group_notification: "The group bulletin has been edited ", + messageNotify: "Message notification ", + groupEnterLimit: + "The number of invited friends in the group has reached the upper limit, please check", + unRecard: "Unable to record:", + notSet: "not yet ", + hasCreateGroup: "A group chat has been created", + joinGroupForLink: "Join the group chat via the link", + be: "be", + moveoutGroup: "Move out of the group chat", + groupOwnerChangeTo: "The group owner has changed to", + changeGroupNameTo: "Change the group name to", + disbandedGroup: "Disbanded the group chat", + bannedFromSpeaking: "Banned from speaking", + gagWasLifted: "The gag was lifted", + initiatedGroupGag: "Initiated a group gag", + shutDownGroupGag: "Shut down the group gag", + target: "target", + groupSizeUpperLimit: "The group size reaches the upper limit", + topUpperLimit: "The top reaches the upper limit", + topMessageRepeate: "The top message was repeated", + userHasReadMessage: "The user has read the message", + placeholder: { + allMuted: "Group owner or admin has muted everyone", + groupDisbanded: "The group has been disbanded", + groupBanned: "The group has been banned", + singleBanned: "You have been muted", + search: "Search", + pleaseInput: "Please input", + sec: "sec", + groupNoticePlaceholder: "Input group notice..", + inputPassword: "Input password", + confirmPassword: "Confirm input password", + inputPhoneNumber: "Input phone number", + select: "Select", + userOrGroupIDInput: "Input {type}", + writeMessage: "Write message...", + inputGroupName: "Input group name", + typingMessage: "Type message here", + inputName: "Input your nickname" + }, + withoutQualification: + "The user does not meet the qualification for adding friends", + groupMask: "Group chat mask", + groupMaskDescription: "Set the group nickname and avatar", + pleaseBind: + "For the security of your account and to avoid restricted friend adding function, please bind", + orStr: " or ", + andStr: " and ", + confirmExit: "Confirm Exit", + pleaseBindTip1: "For the security of your account, please bind it", + skin_item_user_name: "Zhang Nana", + skin_item_msg1: "Hello, received a timely reply", + skin_item_msg2: "Okay, received", + appearance: "Appearance", + bubbleBgColor: "Dialogue bubble color", + chatBackground: "Chat background", + moreSettings: "More Settings", + highPerformanceModel: "Clean Mode", + highPerformanceModelDesc: "If enabled, do not display appearance effects", + skinPreview: "Effect Preview", + defaultText: "Default", + fontSizePreview: "Preview font size, drag the slider below to adjust the font size. After setting, all page text will change the font size.", + changedText: "Alright, changed", + imageAlpha: "Pattern Transparency", + min: "minimum", + max: "maximum", + backgroundColor: "Background Color", + changeSaveTip: + "You have modified the settings. Do you want to save and return?", + saveAndBack: "Save and Return", + changeAvatar: "Change avatar", + appFontSize: "Global font size", + appFontSizeTip: + "Preview font size, drag the slider below to set font size. After setting, it will change the font size of all text within the application.", + maskDesc: + "Once enabled, group members can set their own group nickname and avatar.", + groupMaskOpenTip: " %s group chat mask %s", + groupMaskOpenTipNew: " group chat mask", + open: "Open", + close: "Close", + groupMaskOpenMsgErrorTip: "Group chat mask function switch prompt", + maskOpen: "The group chat mask has been turned on", + maskClose: "The group chat mask has been closed", + transmit: " transmit", + multiSelect: " multiselect ", + singleShareMsg: " Forward article by article ", + groupShareMsg: " Merge forward ", + notGroupFound: "No group chat related to {name} found ", + rememberPassword: "Remember passwords ", + mobileLogin: "Mobile login ", + emailLogin: "Email login ", + recentConversation: " Recent conversation ", + selectContact: " Select a contact ", + selectGroup: " Select group chat ", + noGroup: " No group chat ", + notSelectMessage: " You have not selected the message ", + hiddenMsg: " Hide message ", + hiddenMsgTip: " Do you hide the source?", + yes: " Yes ", + no: " No ", + mergeMessageList: " [merge forward] chat history ", + singleMlCount: " [Forward item by item] total {count} messages ", + notFoundUser: " User not found ", + notShouldFoundUser: "User not found", + notFoundGroup: "The group chat cannot be found", + selectAt: " Select the person who wants {'@'} ", + welcomeToQDDChat: "Welcome to the Ball Belt ", + choiceYourAvatar: "Please select your avatar ", + enterPawAgain: "Please enter your password again", + Someone: "Someone {'@'}me", + hasSelect: "Selected ", + groupChat: "Groupchat ", + groupMember1: "Group member ", + groupSendMsg: "Group message ", + msgNotSupportTransmit: "indicates that the message cannot be forwarded.", + bindGoogle: "Bind to Google", + bindFacebook: "Bind to Facebook", + unbind: "Unbind ", + accountSettings: "Account Settings", + thirdPartyAccountBinding: "Third-party account binding ", + otherSettings: "Other Settings", + groupChatLoadFailed: "Group chat information failed to be loaded.", + addGroupSend: "Send a group message ", + groupSendNoData: "No group messages have been sent yet ", + removeBinding: "Unbind ", + unBindToastPart1: "After unbinding, will no longer be used ", + unBindToastPart2: "Account login, do you want to continue to cancel?", + noOpPermission: "There is no operable permission yet ", + bindingPhoneOrEmail: + "For the security of your account data, please bind the phone number or email address first and then unbind it.", + unbindingSuccess: "Unbind success ", + unbindingFair: "Unbind failed ", + bindingPhoneOrEmailSetPassword: + "For the security of your account data, please bind your mobile phone number or email address before setting a password.", + showAll: "View all ", + allCount: "All total {num} ", + BindingPhoneOrEmailSetPassword: + "in order to data security of your account, please first binding mobile phone number or email to set the password again.", + recoverSuccessLoginAgain: "Recovery successful, please log in again ", + createGroupSend: "Send a group message ", + groupSend: "to send a group message.", + notSelect: "You have not selected yet ", + confirmDeleteTitle: "Are you sure to delete the selected group?", + totalGe: "Total %s ", + sendTotalGe: "Send to ({num}) ", + uploadFailed: "Upload failed.", + selectMax: "The number of selections reached the upper limit ", + newFriendApply: "You have a new friend, click to view ", + error_6091: "The number of selections exceeds the upper limit ", + error_6092: "Message id already exists ", + notSupportLoginStyle: "Not supported yet, please use another login method ", + pay_password: "Payment password", + pleaseEnter: "Please enter ", + loadingPage: " Page loading ", + payTip: + " You have not set the payment password, please complete the setting before proceeding.", + deleteRedPackageErrTip: " The red packet message is irrevocable ", + applyIconTip: + "The application has been submitted, the avatar can be replaced after approval ", + applyIconProgress: "View application progress ", + applyRecord: "Request record ", + repealWithdrawApply: "Are you sure to withdraw the withdrawal application?", + withdrawNotes: "Withdrawal record ", + inReview: "under review ", + passTheAudit: "Audit passed ", + rejectedPayment: "Rejection ", + revoked: "revoked", + undone: "Undone", + withdrawalQuantity: "Withdrawal quantity ", + withdrawalBack: "withdrawal returned ", + update_tip1: " If not updated ", + update_tip2: " Go to the official website to download ", + update_ing: " updating ", + update_install: " Install now ", + installFailed: " Not installed, please try again later ", + installNotFoundFile: " Installation file not found, please try again later ", + error_6093: "Login mode is not supported ", + error_6094: "The number of avatars has reached the upper limit", + deleteIcon: "Do you want to delete this profile picture?", + balanceWithdraw: "Balance withdraw", + inputPayPassword: "Please enter the payment password ", + withdrawalSuccess: "Withdrawal success ", + personIcon: "Personal avatar ", + groupIcon: "Group chat avatar ", + error_6096: + "Do not set a payment password with repeated, consecutive digits ", + forgetPayPassword: "Forgot your payment password ", + downloadFailedTip: "Download failed, please try again later.", + withdrawal: "withdraw", + canBalanceWithdraw: "Withdrawable balance", + error_6097: "not sufficient funds", + headRejected: "Rejected", + retracted_h5: "Retracted", + message_h5: "message from", + notSet2: "Is not set", + passed: "Passed", + readDeleteTime: "Read Message Burn Time", + readDeleteTimeTitle: "Burn Time", + error_6090: "Account Bound", + inputRemark: "Please enter your remark", + authFailed: "Authorization failed", + userNullError: "The user account is abnormal", + community: "Community", + join: "Join the group chat to explore more community activities~", + posteviews: "No activities have been posted yet~", + views: "views", + ended: "Event has ended", + configError: "Configuration error, unable to open temporarily", + burnMsgOpen: "{name} set message read {time} after burning", + burnMsgClose: "{name} turned off read-and-burn, messages no longer auto-burn", + burnMsgNotify: "Read and burn notification", + homeItem3: "Community", + messageTimeOut: "Message has expired", + burnContentTip: + "Message is burning, do you want to exit and burn with one click?", + cancelLater: "I'll check later", + burnConfirm: "Burn immediately", + setNickNameTimes: ", nickname can only be modified once within %s days", + voiceSendErrTip1: "Support sending voice from %s-%s seconds only", + textSendErrTip1: "Maximum input %s characters", + pictureSendErrTip1: "Support sending pictures from %s-%sM only", + videoSendErrTip1: "Support sending videos from %s-%sM only", + your: "Your", + joinCommunity: "Join the group chat to explore more community activities~", + noActivity: "No activities have been posted yet~", + views: "views", + ended: "Event has ended", + errorLink: "Cannot find the link", + set_payment_password: "Please set a 6-digit payment password", + unset_payment_password: + "Please do not set a payment password with repeated or consecutive digits", + Confirm_Payment_Password: "Confirm Payment Password", + Please_confirm_payment_password: + "Please re-enter to confirm payment password", + error_6100: + "Sorry, you can only modify your personal nickname once every {day} days", + viewDetailed: "View detailed information of group members", + inviter: "Inviter", + lastLoginTime: "Last login time", + DeleteThisUsersgroup: "Delete this user's group chat messages in this group", + AddGroupMembers: "Add group members without verification consent", + error_6103: "This user is a regular member and does not have permissions", + error_6104: "The person added is not in the group", + error_6108: "Account anomaly", + group_remark: "Group Remark", + input_group_remark: "Please enter the remark content", + await_accepting: "Device authorization request has been sent", + apply_tips: "This device is not trusted or did not pass the review. Do you want to send device information for authorization application?", + apply_send: "Send application", + apply_ttl: "Device Login Authorization", + group_limit500: "Maximum of 500 people per group", + select_group: "Select friend group", + cantfind: "No contacts related to {name} found", + error_6109: "The user's number of friends has reached the limit", + error_6106: "Please wait for device approval before logging in", + selectGroups: 'Select Groups', + group_member_info: "Group Member Info", + join_time: "Join Time", + inviter_user: "Inviter", + last_login_time: "Last Login Time", + sync_group_avatar: 'Do you want to change the group avatar simultaneously?', + invite_fail: "{usernames}'s account is abnormal!", + enterAndCtrl:'Enter to send / Enter+Ctrl for new line', + enterAndCtrlIOS:'Enter to send / Enter+control for new line', + scanLogin: 'Scan to Login', + formLogin: 'Login with Password', + scanText: 'Login using Qudi Dai app with QR Code scan', + scanSubText: 'Use Qudi Dai app to scan QR Code for login', + qrcodeExpired: 'QR Code expired', + expiredTime10: 'This QR Code is valid for 10 minutes', + reflesh: 'Click to refresh', + scanSuccess: 'Scan successful', + confromLogin: 'Please confirm login', + onlineStatus: "Online Status:", + networkDiagnostic: "Network Diagnostic", + "setPaymentPassword": "Please set a 6-digit payment password", + "unSetPaymentPassword": + "Please do not set a payment password with repeated or consecutive digits", + "verifyPassword": "Verify payment password", + "confirmPaymentPassword": "Confirm payment password", + "verifyCurrentPassword": "Please verify your current payment password", + "reInsetPaymentPassword": + "Please re-enter your confirmation payment password", + "paymentPasswordDifferent": "Password is different, please re-enter", + "getPaymentPasswordWay": "Please select a way to retrieve your password", + "selectFriendGroup": "Select Friend Group", + "selectGroups": "Select Groups", + "eachGroupLimitNumber": "The maximum number for each group is {p}", + "selectChatLimitNumber": "You can select up to {p} chats", + "deviceID": "Device ID:", + "groupChatRemark": "Group chat remark", + "pleaseEnterRemarks": "Please enter remarks content", + "groupMembershipInfo": "Group Membership Information", + "error_6102": "Maximum of 500 people per group", + "distroyed": "Deactivated", + "forbiddened": "Banned", + "addGroupMemberWithoutApply": "Add members without verification consent", + "error_6109": "This user's friend list has reached the maximum limit.", + "apply_ttl": "Device Login Authorization", + "apply_tips": + "This device is not trusted or has not been approved, do you want to send device information for authorization request?", + "apply_send": "Send Request", + "device_auth_send": "Device authorization request has been sent", + "using_trusted_device": + "Logging in with this device is risky, please login with a trusted device", + "account_closed": "Account closed", + "google_code": "Google Code", + "input_google_code": "Please enter the Google Code", + "inviterUser": "Inviter", + "lastLoginTime": "Last Login Time", + "error_6105": "Untrusted device, please submit an application", + "error_6106": "Please wait for device approval before logging in", + "error_6107": "Device approval not passed", + "hasDetailInfoPermission": "View detailed group member information", + "editUserHeaderWithGroupHeader": + "Do you want to change the group chat header at the same time?", + "darkModel": "Dark mode", + "darkModelSetDesc": "Turn on or off dark mode following the system settings", + "whiteModel": "Light mode", + "fileNotFound": "File not found", + "openFileInApp": "Preview file", + "openFileOtherApp": "Open in other app", + "openByBrowser": "Download using browser", + "pauseDownload": "Pause download", + "resumeDownload": "Resume download", + "downloadFailed": "Download failed", + "downloading": "Downloading", + "notdownloaded": "Not downloaded", + "cancelDownloading": + "File is currently being downloaded. Do you want to cancel the download?", + "failedToDownload": "File download failed. Do you want to retry downloading?", + "cancelDownload": "Cancel download", + "sending": "Sending", + "fileTypeNotSupported": "Unsupported file format for upload", + "fileSizeOut": "File size exceeded", + "restrict": "Restrict", + "getWithPhone": "Recover with phone number", + "getWithEmail": "Recover with email address", + "fileSize": "File size", + "whetherDownloadFile": "Whether to download the file", + "downloadFailedTip1": + "Download failed, Please check your network and try again", + "downloadQueueTip": "Added to the download queue, Waiting for download", + "notSupportTransmitTip": "This message cannot be forwarded at the moment", + "voiceSize": "Audio file size must not exceed 100MB.", + "wordSize": "Document file size must not exceed 100MB.", + "apkSize": "Installation package file size must not exceed 300MB.", + "zipSize": "Zip file size must not exceed 300MB.", + "groupDeleteUserTip": + "Simultaneously delete the user's messages sent in this group", + "nPersonRead": "{p} person(s) have read", + "darkModeEnabled": "Dark mode enabled, restart the app to take effect", + "darkModeOff": "Dark mode disabled, restart the app to take effect", + "otherUser": "Other user", + "selectMsgDeleteTime": "Select retention time", + "selectMsgDeleteTimeTip": + "Chat history will be automatically deleted after the retention period, continue?", + "msgSaveTime": "Chat history retention time", + "msgSaveTimeDesc": + "Automatically clears chat records that exceed the time limit", + "hasTimeDeleteMsgPermission": + "Group chat message retention time configuration", + "accountAbnormal": "Account abnormal, invitation failed!", + "onlyBrowser": "Only supported in browser view", + "cancelSendSuccess": "Send canceled", + "allStr1": "Global", + "modelStr1": "Day and night mode", + "atMaxCountTip": "At most @ {p} people", + "transmitTo": "Forward to", + "moreActions": "More", + "refreshing": "Refresh", + "openInBrowser": "Open in browser", + "coverFlow": "Cover flow", + "checkNet": "Network Check", + "autoChangeServer": "Auto Change to Optimal Server", + "serverLine": "Server Line", + "timeOut": "Timeout", + "serverLineChange": "Change Server Line", + "netCheck": "Network Diagnosis", + "netDelayCheck": "Network Delay Check", + "netDelay1Check": "Internet Delay", + "msgDelay1Check": "Message Delay", + "imgDelay1Check": "Image Delay", + "netLine": "Network Line", + "reCheck": "Check Again", + "netChecking": "Checking Network", + "locationAddr": "Location Address", + "selectPerfectLineTip": "Selected the Best Line for You", + "selectPerfectLineTip2": "Already on the Best Line, No Need to Switch", + "selectPerfectLineTipNoNet": + "Unable to Identify the Best Line, Please Check Network and Retry", + "changing": "Changing", + "changeSuccess": "Change Successful", + "autoChanging": "Auto Changing, Please Wait", + "waitAnswer": " Waiting for Answer", + "callFinish": " Call Ended", + "fileSizeConnotZero": "File size cannot be zero", + "confirmLogin": "Confirm login", + "toolboxAudioCall": "Audio call", + "requestTimeOut": "Request timeout, please try again later", + "error_6115": "QR code expired", + "groupNotFound": "Group not found", + "userNotFound": "User not found", + "microphoneOpen": "Microphone opened", + "speakerOpen": "Speaker opened", + "voiceWithYou": "Inviting you to voice call", + "callInterceptor": "Call interrupted", + "startCallError": "Error starting call", + "callHungUp": "Call hung up", + "callRemoteHungUp": "Remote party hung up", + "callReceiverError": "Error receiving call", + "starting": "Starting", + "startTimeOut": "Voice call initiation timeout", + "callWaitReceiver": "Waiting for other party to answer", + "callRemoteNotAccept": "Other party did not accept", + "callStartSendError": "Failed to initiate voice call", + "notAccept": "Not accepted", + "callAccepting": "Accepting call", + "call": "Call", + "callDoing": "Line busy, canceled", + "netBad": "Current network environment is poor", + "netDown": "Network disconnected", + "onCalling": "On call", + "error_1303": "You are not yet friends with the other party", + "remoteAccountError": "Issue with other party's account", + "userOnlineStatus": "Online status", + "error_100008": + "This user does not meet the requirements to become an administrator", + "callTimeLimit": "Maximum {p} minutes per call", + "notLoginOnline": "Never logged in", + "callRemoteDoing": "Busy", + "exitLoginIng": "Logging out", + "usePhoneRecovery": "Use phone number for recovery", + "useEmailRecovery": "Use email for recovery", + "otherRecoveryMethods": "Other recovery methods", + "emailAddress": "Email address", + "phoneErrorOrNotExist": "Phone number error or does not exist", + "phoneNotExist": "Phone number does not exist", + "verificationCodeFailure": "Failed to get verification code, please try again", + verificationCodeSent: "Verification code sent", + hiWelcomeLogin: "Hi~ Welcome to login", + otherLoginMethods: "Other login methods", + usernamePhoneEmail: "Username/Phone Number/Email", + "error_1303": "You are not yet friends with the other party", + "remoteAccountError": "Issue with other party's account", + "userOnlineStatus": "Online status", + "error_100008": + "This user does not meet the requirements to become an administrator", + "callTimeLimit": "Maximum %s minutes per call", + "notLoginOnline": "Never logged in", + "callRemoteDoing": "Busy", + "exitLoginIng": "Logging out", + "searchAreaCode": "Search country/region", + "otherFindStyle": "Other recovery methods", + "welcome": "Welcome to login", + "fileReadError": "File read error", + "docPreviewError": "Unable to preview file", + "docPreviewUseOther": + "If the file cannot be previewed due to network issues or device incompatibility, you can use", + "netTimeOut": "Network timeout", + "docReadError": + "File cannot be read, please check if the file format is correct", + "errorDetail": "Error details", + ip: "IP", + imageLoadingRoute: "Image loading route", + networkChecking: "Checking network...", + noPermission: "No permission, please log in after obtaining permission", + pageNotFound: "Page not found", + serverError: "Server error", + databaseError: "Database query error", + networkError: "Network connection error" +}; diff --git a/src/lang/vi.js b/src/lang/vi.js new file mode 100644 index 0000000..d31f03b --- /dev/null +++ b/src/lang/vi.js @@ -0,0 +1,1877 @@ +/*越南语*/ +export default { + checkTopMessage:'Xem tin nhắn', + unsupportedVideoFormat: "Không hỗ trợ định dạng video này", + phoneNumber: "Số điện thoại di động", + plsEnterPhoneNumber: "Vui lòng nhập số điện thoại di động của bạn", + password: "mật khẩu", + plsEnterPassword: "Vui lòng nhập mật khẩu của bạn", + plsEnterPassword6_15: "Mật khẩu (6-15 là chữ cái và số)", + account: "Số tài khoản", + plsEnterAccount: "Vui lòng nhập số tài khoản của bạn", + plsEnterAccount6_15: "Tên người dùng (6-15 là chữ cái và số)", + forgetPassword: "Quên mật khẩu", + createAccount: "Tạo tài khoản", + verificationCodeLogin: "Đăng nhập Captcha", + login: "Đăng nhập", + noAccountYet: "Chưa có tài khoản?", + + loginNow: "Đăng nhập ngay bây giờ", + + registerNow: "Đăng ký ngay bây giờ", + + lockPwdErrorHint: "Lỗi%s lần", + + newUserRegister: "Đăng ký người dùng mới", + + verificationCode: "Mã xác nhận", + + verificationSuccessful: "Xác minh thành công", + + sendVerificationCode: "Gửi mã xác minh", + + verification_code1: "Vui lòng nhập CAPTCHA", + + correct_email_address: "Vui lòng nhập địa chỉ email chính xác", + + Resend: "Gửi lại", + + bind_email: "Hộp thư bị ràng buộc", + email_verification: "Xác thực hộp thư", + login_details: "Chi tiết đăng nhập", + verification_code: "Vui lòng nhập mã xác minh hộp thư", + email_verification_code: "Mã xác minh hộp thư", + currently_bound_email: "Hộp thư hiện đang bị ràng buộc:", + text_email: + "Nếu hộp thư của bạn không còn sử dụng, vui lòng thay thế kịp thời", + remove_this_device: "Bạn chắc chắn muốn xóa thiết bị?", + remove_this_device1: + "Bạn chắc chắn muốn xóa thiết bị? Sử dụng lại thiết bị sau khi xóa, đăng nhập cần được xác minh lại", + go_to_settings: "bỏ cài đặt", + go_to_binding: "không ràng buộc", + date_of_birth: "Ngày sinh", + confidential: "bảo mật", + year: "Tuổi", + not_perfection: "chưa hoàn hảo", + old_password: "Mật khẩu cũ", + + myCreatedGroup: "Trò chuyện nhóm mà tôi tạo ra", + + myManageGroup: "Nhóm chat mà tôi quản lý", + + myJoinGroup: "Nhóm chat mà tôi tham gia", + + please_enter_old_password: "Vui lòng nhập mật khẩu cũ", + + trusted_device: "Thiết bị đáng tin cậy", + + change_email_address: "Đổi hộp thư", + + current_device: "Thiết bị hiện tại", + + remove_device: "Xóa thiết bị", + + current_device_records: + "Đây là toàn bộ hồ sơ đăng nhập của bạn cho thiết bị đó", + + text_device: "Quản lý thiết bị đăng nhập", + + verification_time: "Thời gian xác minh:", + + text_device1: + "Đây là tất cả các thiết bị đã đăng nhập của bạn, hãy nhấp vào Enter Details để xem lịch sử đăng nhập của thiết bị đó", + + change_the_binding: "Thay dây buộc", + + resendVerificationCode: " Gửi lại mã xác minh ", + + verificationCodeTimingReminder: "%sS lấy lại mã xác minh", + + defaultVerificationCode: "(mã xác minh mặc định:%s)", + + plsEnterVerificationCode: "Vui lòng nhập mã xác minh của bạn", + + invitationCode: "Mã mời", + device_list: "Danh sách các thiết bị", + plsEnterInvitationCode: "Vui lòng nhập mã mời%s của bạn", + optional: "Tùy chọn", + nextStep: "Tiếp theo", + + plsEnterRightPhone: " vui lòng nhập đúng số điện thoại ", + enterVerificationCode: " nhập mã xác minh điện thoại di động ", + setPassword: " thiết lập mật khẩu ", + plsConfirmPasswordAgain: " hãy xác nhận lại mật khẩu của bạn", + text_password2: " hãy xác nhận lại mật khẩu đăng nhập ", + please_enter_new_password_again: " vui lòng nhập mật khẩu mới một lần nữa", + confirmPassword: " xác nhận mật khẩu ", + wrongPasswordFormat: "Wrong định dạng mật khẩu", + plsCompleteInfo: " xin hoàn thiện thông tin cá nhân ", + plsEnterYourNickname: " hãy nhập tên của bạn ", + pleaseEnterYourUsername: " hãy nhập tên người dùng của bạn ", + enterYourUsernameToHelpUsIdentifyYourAccount: + "ở đây hãy nhập vào người dùng danh của ngài giúp chúng ta nhận diện tài khoản của bạn", + text_1: + " tài khoản không bị ràng buộc hộp thư, không hỗ trợ việc lấy lại mật khẩu ", + setInfo: " thiết lập thông tin ", + + loginPwdFormat: "6-20 chữ số+chữ cái", + passwordLogin: "Đăng nhập bằng mật khẩu", + contacts: "Sổ địa chỉ", + workbench: "bàn làm việc", + mine: "của tôi", + me: "tôi", + draftText: "bản nháp", + everyone: "Tất cả mọi người.", + you: "Anh", + groupAc: "Thông báo nhóm", + createGroupNtf: "%s đã tạo trò chuyện nhóm", + editGroupInfoNtf: "%s thay đổi dữ liệu nhóm", + + quitGroupNtf: "%s thoát khỏi cuộc trò chuyện nhóm", + invitedJoinGroupNtf: "%s mời%s tham gia trò chuyện nhóm", + kickedGroupNtf: "%s được chuyển ra khỏi cuộc trò chuyện nhóm bởi%s", + joinGroupNtf: "%s tham gia trò chuyện nhóm", + dismissGroupNtf: "%s giải tán cuộc trò chuyện nhóm", + transferredGroupNtf: "%s đã chuyển quyền quản trị nhóm cho%s", + muteMemberNtf: "%s bị cấm bởi%s%s", + muteCancelMemberNtf: "%s đã bị cấm bởi%s", + muteGroupNtf: "%s mở lệnh cấm nhóm", + muteCancelGroupNtf: "%s đóng lệnh cấm nhóm", + friendAddedNtf: "Hai người đã trở thành bạn tốt, có thể bắt đầu trò chuyện.", + openPrivateChatNtf: "Sau khi mở ra sẽ bị đốt cháy", + closePrivateChatNtf: "Đã đóng cửa duyệt tức là đốt cháy", + memberInfoChangedNtf: + "%s đã chỉnh sửa dữ liệu thành viên nhóm của riêng mình", + unsupportedMessage: "Kiểu tin nhắn không được hỗ trợ tạm thời", + + picture: "hình ảnh", + video: "video", + voice: "giọng nói", + safety: "an toàn", + location: "địa điểm", + file: "tập tin", + carte: "Danh thiếp", + emoji: "biểu cảm tùy chỉnh", + chatRecord: "Lịch sử trò chuyện", + revokeMsg: "Đã rút một tin nhắn", + aRevokeBMsg: "%s đã thu hồi thư của%s", + + blockedByFriendHint: "Tin nhắn đã được gửi đi nhưng bị từ chối bởi bên kia", + sendFriendVerification: "Xác minh thêm", + removedFromGroupHint: "Bạn đã được chuyển ra khỏi cuộc trò chuyện nhóm", + groupDisbanded: "Nhóm đã giải tán", + createdGroup: "Bạn chưa tạo nhóm", + search: "tìm kiếm", + newGroups: "Nhóm mới", + groupNames: "Vui lòng nhập tên nhóm", + synchronizing: "đồng bộ hóa", + syncFailed: "Đồng bộ hóa thất bại", + connecting: "Đang kết nối", + connectionFailed: "Kết nối không thành công", + top: "Lên đỉnh", + + cancelTop: "Hủy bỏ vị trí hàng đầu", + markHasRead: "Đánh dấu đã đọc", + delete: "xóa", + nPieces: "%s thanh", + online: "trực tuyến", + offline: "ngoại tuyến", + phoneOnline: "Điện thoại di động", + pcOnline: "máy tính", + webOnline: "Web", + webMiniOnline: "Ứng dụng nhỏ", + upgradeFind: "Khám phá phiên bản mới", + upgradeVersion: + "Một phiên bản mới có sẵn%s đã được phát hiện và phiên bản hiện tại của bạn là%s", + upgradeDescription: "Hướng dẫn cập nhật:", + upgradeIgnore: "Bỏ qua", + upgradeLater: "Cập nhật sau", + upgradeNow: "Cập nhật ngay bây giờ", + upgradeNever: "Chưa cập nhật", + inviteYouCall: "%s mời bạn thực hiện %s", + rejectCall: "Từ chối", + acceptCall: "Đồng ý", + callVoice: "cuộc gọi thoại", + callVideo: "cuộc gọi video", + sentSuccessfully: "Gửi thành công", + copySuccessfully: "Sao chép thành công", + binding_successful: "Binding thành công", + + day: "ngày", + hour: "thời gian", + hours: "giờ", + minute: "phút", + seconds: "giây", + cancel: "hủy bỏ", + determine: "chắc chắn", + failed_to_load: "Tải không thành công", + failed_to_load1: "Tải không thành công, vui lòng thử lại sau", + removal_failed: "Xóa không thành công", + toolboxAlbum: "Album ảnh", + toolboxCall: "Cuộc gọi video", + toolboxCamera: "Bắn súng", + toolboxCard: "Danh thiếp", + toolboxFile: "Tập tin", + toolboxLocation: "Vị trí", + send: "gửi", + holdTalk: "Giữ và nói chuyện", + releaseToSend: "Thả gửi", + releaseToSendSwipeUpToCancel: "Thả gửi, trượt lên hủy", + liftFingerToCancelSend: "Thả ngón tay, hủy gửi", + callDuration: "thời gian gọi %s", + cancelled: "bị hủy bỏ", + + cancelledByCaller: "Bên kia đã bị hủy bỏ", + rejectedByCaller: "Đối phương đã từ chối", + callTimeout: "Thời gian chờ không phản hồi", + rejected: "bị từ chối", + forwardMaxCountHint: "Hiện tại chỉ hỗ trợ chuyển tiếp tối đa 20 tin nhắn~", + typing: "Nhập...", + addSuccessfully: "Thêm thành công", + addFailed: "Thêm thất bại", + setSuccessfully: "Thiết lập thành công", + callingBusy: "Bạn đang gọi và không thể thực hiện việc này!", + groupCallHint: + " Nhóm hiện tại đang gọi, bạn có chắc chắn tham gia cuộc gọi hiện tại không?", + joinIn: "tham gia", + menuCopy: "Sao chép", + menuDel: "Đã xóa", + menuForward: "Chuyển tiếp", + menuReply: "Trả lời", + menuMulti: "Nhiều lựa chọn", + menuRevoke: "Rút lui", + menuAdd: "Thêm vào", + nMessage: "%s thư mới", + plsSelectLocation: "Vui lòng chọn một địa điểm", + groupAudioCallHint: "Người %s đang gọi thoại", + groupVideoCallHint: "%s mọi người đang gọi video", + + reEdit: "Chỉnh sửa lại", + playSpeed: "Tốc độ chơi", + download: "Tải xuống", + download1: "Tải xuống ngay bây giờ", + nicknames: "Biệt danh người dùng", + downloadAPP: "Tải xuống APP", + private_chat: "vành đai của các vị vua bóng đá", + googleMap: "Bản đồ Google", + check_for_updates: "Kiểm tra cập nhật", + appleMap: "Bản đồ Apple", + baiduMap: "Bản đồ Baidu", + amapMap: "Bản đồ Gaude", + signature: "Chữ ký cá nhân", + please_enter_user_nickname: "Vui lòng nhập tên người dùng", + set_characters: "Vui lòng đặt 4-15 ký tự", + personalized_signature: "Vui lòng nhập chữ ký cá nhân (giới hạn ký tự 6-50)", + tencentMap: "Bản đồ Tencent", + offlineMeetingMessage: "Bạn nhận được một thông báo mời họp", + offlineMessage: "Bạn có một tin nhắn mới", + offlineCallMessage: "Bạn nhận được tin nhắn mời gọi điện thoại", + logoutHint: "Bạn có chắc muốn đăng xuất không?", + myInfo: "Thông tin của tôi", + workingCircle: "Vòng tròn công việc", + accountSetup: "Cài đặt tài khoản", + + aboutUs: "Về chúng tôi", + about: "Về", + logout: "Thoát đăng nhập", + qrcode: "Mã QR", + qrcodeHint: "Quét mã QR bên dưới và thêm tôi làm bạn tốt", + favoriteFace: "bộ sưu tập emoji", + favoriteManage: "Quản lý", + favoriteCount: "của%s biểu hiện", + favoriteDel: " Xóa (%s)", + hasRead: "Đã đọc", + unread: "chưa đọc", + nPersonUnRead: "%s người chưa đọc", + allRead: "Tất cả đã đọc", + messageRecipientList: "Danh sách người nhận tin nhắn", + hasReadCount: "Đã đọc (%s)", + unreadCount: "Chưa đọc (%s)", + newFriend: "Những người bạn mới", + newFriendList: "Danh sách đăng ký kết bạn", + newGroup: "Trò chuyện nhóm mới", + myFriend: "Bạn thân của tôi", + myGroup: "Trò chuyện nhóm của tôi", + addManager: "Thêm quản trị viên", + privateSetting: "thiết lập quyền", + add: "Thêm vào", + scan: "Quét một vòng", + scanHint: "Quét danh thiếp mã QR", + addFriend: "Thêm bạn bè", + addFriendHint: "Thêm bằng cách tìm kiếm số ID", + createGroup: "Tạo nhóm chat", + createGroupHint: "Tạo một cuộc trò chuyện nhóm, sử dụng OpenIM đầy đủ", + addGroup: "Thêm trò chuyện nhóm", + addGroupHint: "Hỏi quản trị viên hoặc thành viên trò chuyện nhóm về ID", + searchIDAddFriend: "Tìm kiếm ID Thêm bạn bè", + searchIDAddGroup: "ID tìm kiếm thêm trò chuyện nhóm", + + searchIDIs: "ID:%s", + searchPhoneIs: "Số điện thoại di động:%s", + searchEmailIs: "hộp thư:%s", + searchNicknameIs: "Biệt danh:%s", + searchGroupNicknameIs: "Biệt danh nhóm:{name}", + noFoundUser: "Không thể tìm thấy người dùng", + noFoundGroup: "Không thể tìm thấy trò chuyện nhóm", + joinGroupDate: "Nhập nhóm thời gian", + joinGroupMethod: "Phương thức gia nhập nhóm", + byInviteJoinGroup: "%s mời vào nhóm", + byIDJoinGroup: "ID nhóm tìm kiếm", + byQrcodeJoinGroup: "Mã QR nhóm", + groupID: "Số ID nhóm", + setAsAdmin: "Đặt làm quản trị viên", + setMute: "Thiết lập cấm ngôn", + organizationInfo: "Thông tin tổ chức", + organization: "tổ chức", + department: "Bộ phận", + position: "vị trí", + personalInfo: "Hồ sơ cá nhân", + viewDynamics: "Xem động lực học", + audioAndVideoCall: "Cuộc gọi video âm thanh", + sendMessage: "Gửi tin nhắn", + avatar: "Avatar", + + name: "tên", + nickname: "Biệt danh", + gender: "giới tính", + englishName: "Tên tiếng Anh", + birthDay: "sinh nhật", + tel: "Điện thoại cố định.", + mobile: "Số điện thoại di động", + email: "hộp thư", + man: "đàn ông", + woman: "phụ nữ", + friendSetup: "Thiết lập bạn bè", + setupRemark: "Ghi chú thiết lập", + recommendToFriend: "Giới thiệu anh ấy với bạn bè", + addToBlacklist: "Thêm vào danh sách đen", + unfriend: "unfriend nghĩa là gì?", + areYouSureDelFriend: "Bạn có chắc chắn xóa bạn bè không?", + areYouSureAddBlacklist: "Bạn chắc chắn thêm bạn bè vào danh sách đen?", + remark: "Ghi chú", + save: "tiết kiệm", + + saveSuccessfully: "Lưu thành công", + saveFailed: "Lưu thất bại", + groupVerification: "Xác minh trò chuyện nhóm", + friendVerification: "Xác minh bạn bè", + email_address: "Vui lòng nhập địa chỉ email", + sendEnterGroupApplication: "Gửi ứng dụng vào nhóm", + sendToBeFriendApplication: "Gửi yêu cầu kết bạn", + sendSuccessfully: "Gửi thành công", + reset_successful: "reset thành công", + bind_new_email: "Liên kết hộp thư mới", + Login_failed: "Đăng nhập không thành công", + successful_home: "Đăng ký thành công", + sendFailed: "Gửi không thành công", + canNotAddFriends: "Người dùng này đã được thiết lập để không thể thêm vào!", + mutedAll: "Toàn bộ cấm ngôn", + tenMinutes: "10 phút", + oneHour: "1 giờ", + twelveHours: "12 giờ", + + oneDay: "một ngày", + custom: "tùy chỉnh", + unmute: "Hủy bỏ cấm ngôn", + youMuted: "Bạn đã bị cấm", + groupMuted: "Đã mở nhóm cấm ngôn", + notDisturbMode: "Chế độ không làm phiền", + allowRing: "Âm báo tin tức mới", + allowVibrate: "Tin tức mới rung chuyển", + forbidAddMeToFriend: "Cấm thêm tôi làm bạn tốt", + blacklist: "Danh sách đen sổ địa chỉ", + unlockSettings: "Cài đặt mở khóa", + changePassword: "Thay đổi mật khẩu", + clearChatHistory: "Xóa lịch sử trò chuyện", + confirmClearChatHistory: "Xác nhận xóa tất cả các cuộc trò chuyện của bạn?", + languageSetup: "Cài đặt ngôn ngữ", + + language: "ngôn ngữ", + english: "Tiếng Anh", + setting_up: "trong cài đặt...", + chinese: "Tiếng Trung giản thể", + traditional_chinese: "Truyền thống Trung Quốc", + followSystem: "hệ thống theo dõi", + blacklistEmpty: "Không có danh sách đen", + remove: "xóa bỏ", + removeFriend: "Xóa bạn bè", + fingerprint: "vân tay", + gesture: "cử chỉ", + biometrics: "sinh trắc học", + plsEnterPwd: "Vui lòng nhập mật khẩu", + plsEnterOldPwd: "Vui lòng nhập mật khẩu gốc", + plsEnterNewPwd: "Vui lòng nhập mật khẩu mới", + plsConfirmNewPwd: "Vui lòng xác nhận mật khẩu mới", + + reset: "thiết lập lại", + oldPwd: "Mật khẩu gốc", + newPwd: "Mật khẩu mới", + confirmNewPwd: "Xác nhận mật khẩu mới:", + plsEnterConfirmPwd: "Vui lòng nhập mật khẩu xác nhận", + twicePwdNoSame: "Mật khẩu nhập hai lần không phù hợp", + twicePwd: "Mật khẩu mới không phù hợp, vui lòng đặt lại", + changedSuccessfully: "Sửa đổi thành công", + deleteSuccessfully: "Xóa thành công", + checkNewVersion: "Kiểm tra phiên bản mới", + noAddGround: "Chưa thêm bạn bè vào nhóm", + chatContent: "Nội dung trò chuyện", + topContacts: "Đặt địa chỉ liên lạc hàng đầu", + messageNotDisturb: "Tin nhắn không làm phiền", + messageNotDisturbHint: "Nhận tin nhắn nhưng không nhắc nhở", + burnAfterReading: "Sau khi đọc xong sẽ bị đốt cháy", + timeSet: "Cài đặt thời gian", + setChatBackground: "Thiết lập nền trò chuyện", + fontFace: "Kiểu chữ", + fontSize: "Kích thước phông chữ", + little: "nhỏ", + standard: "tiêu chuẩn", + big: "lớn", + + thirtySeconds: "30 giây", + fiveMinutes: "5 phút", + clearAll: "trống rỗng", + clearSuccessfully: "Xóa thành công", + groupChatSetup: " Cài đặt trò chuyện nhóm", + viewAllGroupMembers: "Xem tất cả thành viên nhóm (%s)", + groupManage: "Quản lý nhóm", + myGroupMemberNickname: "Tên của tôi trong nhóm", + topChat: "Trò chuyện trên đỉnh", + muteAllMember: "Toàn bộ cấm ngôn", + exitGroup: "Thoát khỏi cuộc trò chuyện nhóm", + dismissGroup: "Giải tán nhóm chat", + dismissGroupHint: + "Tất cả các thành viên của nhóm sẽ thoát khỏi nhóm này và không thể gửi tin nhắn", + quitGroupHint: + "sẽ không còn nhận được tin nhắn trò chuyện nhóm này sau khi thoát khỏi cuộc trò chuyện nhóm", + joinGroupSet: "Xác thực nhóm", + allowAnyoneJoinGroup: "Cho phép mọi người thêm nhóm", + inviteNotVerification: "Lời mời thành viên nhóm không cần xác minh", + needVerification: "Yêu cầu gửi thông tin xác minh", + addMember: "Thêm vào", + delMember: "Xóa", + groupOwner: "Chủ nhóm", + + groupAdmin: "Quản trị viên", + notAllowSeeMemberProfile: "Không cho phép xem dữ liệu thành viên nhóm khác", + notAllAddMemberToBeFriend: "Không cho phép thêm thành viên nhóm làm bạn bè", + transferGroupOwnerRight: "Chuyển giao quyền quản lý nhóm", + groupName: "Tên nhóm", + groupAcPermissionTips: "Chỉ có chủ nhóm và quản trị viên có thể chỉnh sửa", + plsEnterGroupAc: "Vui lòng nhập thông báo nhóm", + edit: "biên tập viên", + publish: "Xuất bản", + groupMember: "Thành viên nhóm chat", + selectedPeopleCount: "Đã chọn (%s)", + confirmSelectedPeople: "OK (%s/%s)", + confirm: "xác nhận", + log_in_time: "Thời gian đăng nhập:", + log_in_time1: "Thời gian đăng nhập", + login_IP: "Đăng nhập vào IP:", + confirmTransferGroupToUser: "Xác định chuyển giao nhóm chủ cho:%s?", + removeGroupMember: "Xóa thành viên nhóm", + searchNotResult: "Không có kết quả tìm kiếm", + groupQrcode: "Mã QR nhóm", + + groupQrcodeHint: + "Lướt qua mã QR bên dưới để tham gia trò chuyện nhóm ngay lập tức", + approved: "Đã đồng ý", + accept: "Chấp nhận", + reject: "từ chối", + friendGrouping: "nhóm bạn bè", + waitingForVerification: "Chờ người khác xác minh", + rejectSuccessfully: "từ chối thành công", + rejectFailed: "từ chối thất bại", + applyJoin: "Yêu cầu gia nhập", + enterGroup: "vào group chat", + applyReason: "Lý do ứng dụng:%s", + invite: "lời mời", + sourceFrom: " nguồn:%s", + byMemberInvite: "lời mời", + bySearch: "Theo tìm kiếm", + byScanQrcode: "Quét mã QR", + iCreatedGroup: "Tôi tạo ra nó", + iJoinedGroup: "Tôi tham gia", + nPerson: "{count} người", + searchNotFound: "Không tìm thấy kết quả liên quan", + searchNotFoundMember: "Không tìm thấy người dùng có liên quan", + organizationStructure: "Kiến trúc tổ chức", + recentConversations: "Phiên làm việc gần đây", + + selectAll: "Chọn tất cả", + plsEnterGroupNameHint: "Lấy tên nhóm để tìm kiếm tiếp theo", + completeCreation: "Hoàn thành việc tạo ra", + sendCarteConfirmHint: + "Bạn có chắc chắn gửi liên hệ này đến cuộc trò chuyện này không?", + sentSeparatelyTo: "Gửi riêng cho:", + sentTo: "Gửi đến:", + leaveMessage: "Để lại lời nhắn", + mergeForwardHint: "[MergeForwardHint] tổng số%s tin nhắn", + mergeForward: "Hợp nhất và chuyển tiếp", + quicklyFindChatHistory: "Tìm nhanh lịch sử trò chuyện", + notFoundChatHistory: 'Không tìm thấy "%s" Nội dung liên quan', + + globalSearchAll: "Tổng hợp", + globalSearchContacts: "Liên hệ", + globalSearchGroup: "Nhóm", + globalSearchChatHistory: "Lịch sử trò chuyện", + globalSearchChatFile: "Tập tin", + relatedChatHistory: "%s lịch sử trò chuyện liên quan", + seeMoreRelatedContacts: "Xem thêm các liên hệ có liên quan", + seeMoreRelatedGroup: "Xem thêm các nhóm có liên quan", + seeMoreRelatedChatHistory: "Xem thêm lịch sử trò chuyện có liên quan", + seeMoreRelatedFile: "Xem thêm các tài liệu liên quan", + publishPicture: "Đăng ảnh", + publishVideo: "Xuất bản video", + mentioned: "được đề cập đến:%s", + comment: "Bình luận", + + like: "Thích", + reply: "Trả lời", + rollUp: "thu gọn", + fullText: "Toàn văn bản", + selectAssetsFromCamera: "Bắn súng", + selectAssetsFromAlbum: "Chọn từ album", + whoCanWatch: "Ai có thể xem nó", + remindWhoToWatch: "Nhắc nhở ai xem", + public: "công khai", + everyoneCanSee: "Hiển thị cho tất cả mọi người", + partiallyVisible: "Hiển thị một phần", + visibleToTheSelected: "Người được chọn có thể nhìn thấy", + partiallyInvisible: "Không cho ai xem", + invisibleToTheSelected: "Người được chọn vô hình", + + private: "riêng tư", + onlyVisibleToMe: "Chỉ nhìn thấy chính mình", + selectVideoLimit: "Không quá 15 giây", + selectContactsLimit: "Chọn ít nhất một liên hệ", + message: "Thông điệp", + commentedYou: "Bình luận về bạn:%s", + likedYou: "Đã like cho bạn", + mentionedYou: "Đã đề cập đến bạn", + replied: "Đã trả lời", + detail: "Chi tiết", + totalNPicture: "Tổng số%s", + noDynamic: "Tạm thời không có động lực", + callRecords: "Lịch sử cuộc gọi", + allCall: "Tất cả các cuộc gọi", + missedCall: "Cuộc gọi nhỡ", + incomingCall: "gọi vào", + outgoingCall: "thở ra", + microphone: "micro", + speaker: "Diễn giả", + hangUp: "treo lên", + pickUp: "Trả lời", + + waitingCallHint: "Đang gọi...", + waitingVoiceCallHint: " Đang chờ người kia trả lời...", + invitedVoiceCallHint: "Mời bạn thực hiện cuộc gọi thoại...", + waitingVideoCallHint: "Đang chờ người kia chấp nhận lời mời", + invitedVideoCallHint: "Mời bạn thực hiện cuộc gọi video...", + waitingToAnswer: "Chờ trả lời", + invitedYouToCall: "Mời bạn gọi điện thoại", + calling: "Đang gọi", + nPeopleCalling: "%s người đang gọi", + whoInvitedVoiceCallHint: "%s mời bạn thực hiện cuộc gọi thoại", + whoInvitedVideoCallHint: "%s mời bạn thực hiện cuộc gọi video", + plsInputMeetingSubject: "Vui lòng nhập chủ đề cuộc họp", + meetingStartTime: "Thời gian bắt đầu", + meetingDuration: "Thời gian họp", + enterMeeting: "Nhập cuộc họp", + meetingNo: "Số cuộc họp", + yourMeetingName: "Tên của bạn", + + plsInputMeetingNo: "Vui lòng nhập số cuộc họp", + plsInputYouMeetingName: "Vui lòng nhập tên của bạn", + meetingSubjectIs: "Chủ đề cuộc họp:%s", + meetingStartTimeIs: "thời gian bắt đầu:%s", + meetingDurationIs: "Thời lượng cuộc họp:%s", + meetingHostIs: "Máy chủ:%s", + meetingNoIs: "Số cuộc họp:%s", + meetingMessageClickHint: + "Nhấp vào thông báo này để tham gia cuộc họp trực tiếp", + meetingMessage: "Thông điệp hội nghị", + openMeeting: "Cuộc họp chưa kết thúc", + didNotStart: "Chưa bắt đầu", + started: "bắt đầu", + meetingInitiatorIs: "%s bắt đầu hội nghị truyền hình", + meetingDetail: "Chi tiết cuộc họp", + meetingOrganizerIs: "Người khởi xướng:%s", + updateMeetingInfo: "Sửa đổi thông tin cuộc họp", + + cancelMeeting: "Hủy cuộc họp", + videoMeeting: "Hội nghị truyền hình", + joinMeeting: "Tham gia cuộc họp", + bookAMeeting: "Đặt lịch họp", + quickMeeting: "họp nhanh", + confirmTheChanges: "Xác nhận sửa đổi", + invitesYouToVideoConference: "%s mời bạn tham gia hội nghị truyền hình", + over: "Kết thúc", + meetingMute: "Im lặng", + meetingUnmute: "Bỏ im lặng", + meetingCloseVideo: "Đóng video", + meetingOpenVideo: "Mở video", + meetingEndSharing: "Kết thúc chia sẻ", + meetingShareScreen: "Chia sẻ màn hình", + meetingMembers: "Thành viên (%s)", + settings: "Cài đặt", + leaveMeeting: "Rời khỏi cuộc họp", + endMeeting: "Kết thúc cuộc họp", + leaveMeetingConfirmHint: "Bạn chắc chắn rời khỏi cuộc họp?", + endMeetingConfirmHit: "Bạn chắc chắn kết thúc cuộc họp?", + meetingSettings: "Thiết lập cuộc họp", + + allowMembersOpenMic: "Cho phép các thành viên tự tắt tiếng", + allowMembersOpenVideo: "Cho phép thành viên mở video", + onlyHostShareScreen: "Chỉ máy chủ mới có thể chia sẻ màn hình", + onlyHostInviteMember: + "Chỉ người điều hành mới có thể mời thành viên cuộc họp", + defaultMuteMembers: "Thành viên gia nhập cấm âm", + pinThisMember: "Đặt thành viên này lên hàng đầu", + unpinThisMember: "Hủy bỏ vị trí hàng đầu", + allSeeHim: "tất cả nhìn anh ấy", + cancelAllSeeHim: "Hủy xem anh ta", + muteAll: "Tất cả im lặng", + unmuteAll: "tắt tiếng mọi người", + members: "thành viên", + screenShare: "Chia sẻ màn hình", + screenShareHint: "Chia sẻ màn hình.", + meetingClosedHint: + "Cuộc họp đã đóng hoặc liên kết bị hỏng, bạn có chắc chắn để lại không?", + meetingIsOver: "Cuộc họp đã kết thúc!", + networkError: "ngoại lệ mạng vui lòng thử lại sau!", + shareSuccessfully: "Chia sẻ thành công!", + notFoundMinP: "Chương trình nhỏ chưa được phát hành tại thời điểm này", + notSendMessageNotInGroup: + "Không thể gửi tin nhắn trong cuộc trò chuyện nhóm đã thoát", + whoModifyGroupName: "{name} đã thay đổi tên trò chuyện nhóm thành", + accountWarn: "Cảnh báo!", + accountException: + "Tài khoản của bạn đã được đăng nhập trên thiết bị khác, vui lòng thay đổi mật khẩu kịp thời.", + + tagGroup: "thẻ", + issueNotice: "Thông báo gửi đi", + createTagGroup: "Tạo nhãn mác", + plsEnterTagGroupName: "Vui lòng nhập tên thẻ", + tagGroupMember: "Thành viên của Tag", + completeEdit: "Hoàn thành chỉnh sửa", + finish: "hoàn thành", + emptyTagGroup: "Nhóm không có nhãn tạm thời", + confirmDelTagGroupHint: "Xác nhận xóa nhóm nhãn này?", + editTagGroup: "Chỉnh sửa Tag", + newBuild: "mới xây dựng", + receiveMember: "Thành viên tiếp nhận", + emptyNotification: "Không báo trước", + notificationReceiver: "%s người nhận:%s", + sendAnother: "Gửi thêm một tin nhắn nữa", + confirmDelTagNotificationHint: "Xác nhận xóa bản ghi thông báo này?", + contentNotBlank: "Nội dung không thể rỗng", + plsEnterDescription: "Vui lòng nhập văn bản mô tả", + gifNotSupported: "Không hỗ trợ ảnh gif", + register: "Đăng ký", + hk: "truyền thống", + RegisterSuccess: "Đăng ký thành công", + LoginSuccess: "Đăng nhập thành công", + init_validate_1: "Tên người dùng bao gồm 6-15 chữ cái và số", + init_validate_10: + "Số điện thoại hoặc mật khẩu không chính xác, vui lòng nhập lại", + + init_validate_5: "Mật khẩu phải bao gồm 6-15 ký tự chữ và số, vui lòng đặt lại mật khẩu", + init_validate_2: "Hai lần mật khẩu không phù hợp, vui lòng đặt lại", + init_validate_3: + "Mật khẩu phải bao gồm 6-15 chữ cái và số, vui lòng đặt lại mật khẩu của bạn", + init_validate_6: "Mật khẩu phải bao gồm 6-15 chữ cái và số", + init_validate_4: "Đăng ký thành công, sắp trở lại trang đích", + init_hint_1: "6-15 chữ số hoặc tiếng Anh", + init_hint_2: "Xác nhận mật khẩu đăng nhập", + init_hint_3: "Mật khẩu đăng nhập", + username: "Tên người dùng", + to_modify: "đi sửa đổi", + confirm_password: "Xác nhận mật khẩu", + notification_settings: "Cài đặt thông báo", + init_text_1: "Đồng ý đăng ký để đăng nhập", + init_text_2: "Thỏa thuận dịch vụ người dùng", + init_text_3: "và", + init_text_4: "Chính sách bảo mật", + init_text_5: "Thỏa thuận dịch vụ và Chính sách bảo mật", + init_text_6: + "Để bảo vệ quyền và lợi ích hợp pháp của bạn, vui lòng đọc và đồng ý với thỏa thuận sau", + refuse: "từ chối", + + refuse_continue: "Đồng ý và tiếp tục", + forget_password: "Quên mật khẩu", + register_user: "Đăng ký tài khoản", + gotoLogin: "Đăng nhập", + gotoRegister: "Tạo tài khoản mới", + enterGroupCheck: "Ứng dụng nhóm", + openPhotoError_1: "Không đọc tập tin đã chọn", + openPhotoError_2: "Tải lên tập tin không thành công", + openPhotoError_3: "Không đọc thông tin tập tin", + agreeAll: "đồng ý tất cả", + whatCanManagerDo: "Quyền quản trị nhóm", + changeGroupName: "Thay đổi tên nhóm", + deleteMemberMessage: "Xóa tin nhắn nhóm", + chatPrivate: "Trò chuyện riêng tư được mã hóa với nhóm", + memberMute: "Nhóm viên cấm ngôn", + deleteMember: "Xóa thành viên", + + changeNotice: "Xuất bản/sửa đổi/xóa thông báo nhóm", + checkInGroup: "Kiểm toán nhóm", + setAdmin: "Quản trị viên thiết lập", + deleteAdmin: "Xóa quản trị viên", + deleteAdminTitle: "Bạn chắc chắn muốn loại bỏ người quản trị?", + deleteAdminDetail: + "Loại bỏ thành viên này khỏi quản trị viên và không thể quản lý thông điệp nhóm", + deleteSuccess: "Xóa thành công", + mobile_web_page: "Truy cập trang web di động", + deleteMemberTitle: "Bạn chắc chắn muốn loại bỏ thành viên?", + deleteMemberDetail: + "Di chuyển thành viên này ra khỏi nhóm và không thể gửi tin nhắn", + deleteSelf: "Không thể xóa chính mình", + handled: "Đã xử lý", + unhandle: "Đang chờ xử lý", + noCheck: "Chưa có yêu cầu xử lý", + success: "Hoạt động thành công", + nameLenth: "Vui lòng đặt 1-15 ký tự", + editNickname: "Biệt danh nhóm biên tập", + text_password: "Vui lòng đặt mật khẩu đăng nhập mới", + + quit: "rút lui", + waitAMoment: "Tôi đang xem đây", + editWarningTitle: + "Bạn có chắc chắn thoát khỏi biệt danh nhóm biên tập không?", + editWarningDetail: + "Biệt danh nhóm bạn đã sửa đổi vẫn chưa được lưu, bạn có tiếp tục thoát không?", + editGroupAvatar: "Chỉnh sửa avatar nhóm", + myGroupAvatar: "Đầu nhóm của tôi", + inviteLink: "Liên kết mời", + inviteTips: + " Bất kỳ người dùng nào đã cài đặt ứng dụng này sẽ có thể tham gia trò chuyện nhóm của bạn thông qua liên kết này", + inviteLinkCopy: "Liên kết mời đã được sao chép", + shareLink: "Chia sẻ liên kết", + manageLink: "Quản lý liên kết mời", + copyLink: "Sao chép liên kết", + deleteLink: "Xóa liên kết", + + deleteLinkTipsTitle: "Bạn có chắc chắn muốn hoàn tác liên kết không?", + deleteLinkTipsDetail: + "Bạn có chắc chắn muốn hủy bỏ liên kết mời này không? Sau khi hủy bỏ, người khác sẽ không thể tham gia nhóm thông qua liên kết này.", + linkManage: "Quản lý liên kết", + openManage: + "Sau khi mở cần có sự xác nhận của chủ nhóm hoặc quản trị viên để vào nhóm", + timeValid: "Giới hạn thời gian hợp lệ", + linkInvalidTips: + "Liên kết mời sẽ hết hiệu lực sau ngày này và không thể tiếp tục truy cập", + valid: "hợp lệ bên trong", + dayValid: "Hợp lệ trong ngày", + forever: "vĩnh viễn", + foreverValid: "Hợp lệ vĩnh viễn", + attendTips: + "Yêu cầu gia nhập đã được gửi và bạn có thể tham gia nhóm sau khi quản trị viên xem xét yêu cầu của bạn", + memberCountLimit: "Số lượng trò chuyện nhóm này đã lên mạng", + linkIsOut: "Liên kết mời đã hết hạn", + requestAttend: "Yêu cầu tham gia cuộc trò chuyện nhóm", + needCheck: + "Trò chuyện nhóm này chỉ có thể tham gia sau khi được quản trị viên xem xét", + countLimit: "Số lượng quản trị viên đã đạt đến giới hạn", + openLock: + "Sau khi rời khỏi ứng dụng, thời gian chờ cần sử dụng sơ đồ mở khóa", + closeLock: "Không cần xác minh khi khởi chạy ứng dụng", + checkTips: + "Vui lòng kiểm tra xem quyền khuôn mặt hoặc vân tay của bạn có bật không~", + face: "Mở khóa khuôn mặt", + + finger: "Mở khóa bằng vân tay", + gestureLock: "Cử chỉ mở khóa bằng mật khẩu", + closed: "Chưa mở", + opened: "Đã mở", + lanchProtect: "Bảo vệ khi khởi động", + noProtect: "Không cần bảo vệ", + lockSetting: "Mở khóa cài đặt", + drawLine: "Vẽ mô hình mở khóa", + drawOldLine: "Vẽ mật khẩu cử chỉ cũ của bạn", + drawNewLine: "Vẽ mật khẩu cử chỉ mới của bạn", + setGesturePassword: "Đặt mật khẩu cử chỉ", + checkGesturePassword: "Xác minh mật khẩu cử chỉ", + needJoinPoint: "Kết nối ít nhất 4 điểm, vui lòng vẽ lại", + noFriendAdd: "Không thêm bạn bè", + noFriendDelete: "Không có bạn bè có thể xóa", + noVerify: "Cần xác minh khi thêm tôi làm bạn bè", + drawAgain: "Vui lòng vẽ lại", + checkSuccessful: "Xác minh thông qua", + notInCommon: "Không phù hợp với bản vẽ cuối cùng, vui lòng vẽ lại", + changeGesturePassword: "Thay đổi mật khẩu cử chỉ", + toChange: "Thay đổi", + change: "Sửa đổi", + openSuccess: "Kích hoạt thành công", + closeSuccess: "Đóng cửa thành công", + hasBeenMember: "Đã là thành viên của nhóm, không cần tham gia lại", + joinGroup: "Tham gia trò chuyện nhóm", + checking: "Đang được quản trị viên xem xét", + noUnlock: "Không thể mở khóa?", + inputGesture: "Vui lòng nhập cử chỉ để mở khóa", + faceDetect: "Vui lòng nhận dạng khuôn mặt", + fingerDetect: "Vui lòng lấy dấu vân tay", + tryUnlocking: "Vui lòng thử: Không thể mở khóa?", + countRemain: "Cử chỉ sai, bạn cũng có thể nhập%s lần", + reLoginUnlock: "Đăng nhập lại để mở khóa", + selectVerityType: "Vui lòng chọn phương thức xác minh ", + currentTypeNotSupport: "Loại hiện tại không được hỗ trợ", + updateNewVersion: "Khám phá phiên bản mới", + updateOkUpdate: "Nâng cấp ngay bây giờ", + updateOpenDownloadFailed: "Mở tải xuống không thành công", + updateCurrentIsNew: "Hiện tại là phiên bản mới nhất", + newVersionNotFound: + "Không tìm thấy liên kết mới nhất, bạn có thể truy cập trang web để tải xuống", + openUrlFailed: "Liên kết không hợp lệ", + qmvMsgNotExit: "[Nội dung tham chiếu không tồn tại]", + qmvMsgNotSupport: "[Nội dung trích dẫn không hỗ trợ hiển thị]", + dataError: "Lỗi phân tích dữ liệu", + userNotBindEmail: "Hộp thư không bị ràng buộc bởi người dùng", + userNotRegister: "Người dùng chưa đăng ký", + checkError: "Kiểm tra thất bại", + csvTitle: "Cài đặt trò chuyện", + csvSetTopMsg: "Đặt trò chuyện hàng đầu", + csvSetOptMsg: "Tin nhắn không làm phiền", + csvSetDisableMsg: "Che chắn thông điệp", + csvClearMsg: "Xóa lịch sử trò chuyện", + csvClearLocal: "Chỉ trống lịch sử trò chuyện địa phương", + csvClearUser: "Xóa lịch sử trò chuyện cục bộ và%s", + csvSetErrorTip: "Thiết lập không thành công, vui lòng kiểm tra mạng", + gmiTitle: "Biệt danh nhóm:", + gmiSetTalk: "Trò chuyện riêng tư về mã hóa", + gmiSetMute: "Đặt lệnh cấm trong nhóm", + gmiDeleteUser: "Di chuyển ra khỏi nhóm", + gmiMuteTo: "Cấm ngôn chí", + gmiError1: + "Không tìm thấy thông tin thành viên này, có thể đã được chuyển ra khỏi cuộc trò chuyện nhóm", + gmiError2: + "Thông tin thành viên nhóm của bạn không được tìm thấy và bạn có thể đã bị chuyển ra khỏi cuộc trò chuyện nhóm", + gmiError3: "Bạn chắc chắn muốn xóa người dùng này?", + gmiError4: "Di chuyển thành viên này ra khỏi nhóm và không thể gửi tin nhắn", + gmiError5: "Bạn không có quyền xóa quản trị viên này", + gmiError6: "Bạn không có quyền xóa thành viên này", + gmiError7: "Đã dỡ bỏ cấm ngôn", + gmiError8: "Bạn không có quyền hủy bỏ lệnh cấm quản trị viên này", + + gmiError9: "Bạn không có quyền hủy bỏ lệnh cấm thành viên này", + gmiError10: "Bạn không có quyền cấm quản trị viên này", + gmiError11: "Bạn không có quyền cấm thành viên này", + gmiError12: "Bỏ cấm ngôn", + gmiGroupOwnerNotFound: "Tải thông tin nhóm không thành công", + smmvTitle: "Thời gian cấm nói", + smmvMuteTime: "Chọn thời gian cấm ngôn", + smmvMuteTime_10m: "10 phút", + smmvMuteTime_1hour: "1 giờ", + smmvMuteTime_12hour: "12 giờ", + smmvMuteTime_1day: "1 ngày", + smmvMuteTime_30day: "30 ngày", + smmvMuteTime_7day: "7 ngày", + egnpTitle: "Chỉnh sửa tên trò chuyện nhóm", + egnpSetGroupName: "Vui lòng đặt tên nhóm", + egnpSetTextDesc: "Vui lòng đặt 1-15 ký tự", + egnpSetTitleNoPermission: "Bạn không có quyền thay đổi tên trò chuyện nhóm", + egnpSetFailed: "Sửa đổi không thành công, vui lòng thử lại sau", + setGroupNameHint: "Vui lòng đặt tên nhóm", + givTitle: "Chỉnh sửa tên trò chuyện nhóm", + givGroupName: "Tên nhóm", + givGroupFace: "Avatar nhóm", + + gmsvTitle: "Quản lý nhóm", + gmsvSetManager: "Quản trị viên thiết lập", + gmsvToSet: "Bỏ cài đặt", + gmsvFindMsg: "Tìm lịch sử trò chuyện", + gmsvFindMsgDesc: + "Các thành viên trong nhóm sẽ thấy thông báo lịch sử gần đây sau khi mở", + gmsvMute: "Cấm ngôn toàn bộ", + gmsvMuteDesc: " Chỉ cho phép chủ nhóm và quản trị viên nói chuyện sau khi mở", + gmsvApply: "Xác nhận nhóm", + gmsvApplyDesc: + "Sau khi mở cần có sự xác nhận của chủ nhóm hoặc quản trị viên để vào nhóm", + gmsvSetManagerTip: "Chỉ chủ nhóm mới có thể thiết lập quản trị viên", + gmsvSetMuteTipOn: "Đã toàn bộ cấm ngôn", + gmsvSetNuteTipOff: "Toàn bộ cấm ngôn đã được dỡ bỏ", + gmsvApplyTipOn: "Xác nhận nhóm đã được bật", + gmsvApplyTipOff: "Xác nhận nhóm đã đóng", + gmsvFindGroupErr: "Không thể lấy được thông tin nhóm", + gmsvFindMsgOn: "Tìm lịch sử trò chuyện đang mở", + gmsvFindMsgOff: "Tìm lịch sử trò chuyện đã đóng", + gmsvSetFailed: "Thiết lập không thành công", + gsvTitle: "Chi tiết nhóm", + gsvGroupId: "ID nhóm", + gsvGroupMember: "Trò chuyện nhóm", + gsvGroupMemberValue: "của{num} người", + gsvGroupNotification: "Thông báo nhóm", + gsvMyName: "Biệt danh nhóm của tôi", + gsvMyFace: "Đầu nhóm của tôi", + gsvGroupManager: "Quản lý nhóm", + gsvGroupApply: "Xác nhận nhóm", + gsvFindMsg: "Tìm lịch sử trò chuyện", + gsvSetTop: "Đặt hàng đầu", + gsvSetOpt: "Tin nhắn không làm phiền", + gsvDeleteMsg: "Xóa lịch sử trò chuyện", + gsvDeleteLocal: "Chỉ trống lịch sử trò chuyện địa phương", + gsvDeleteAllMsg: "Xóa lịch sử trò chuyện địa phương và tất cả các thành viên", + gsvDeleteOtherMsg: "Tống không lịch sử trò chuyện cục bộ và của đối phương", + gsvGroupDelete: "Bạn chắc chắn muốn giải tán cuộc trò chuyện nhóm?", + + gsvOutGroup: "Bạn chắc chắn muốn thoát khỏi cuộc trò chuyện nhóm?", + gsvNoNotification: "Tạm thời không có thông báo", + gsvClearSuccess: "Xóa thành công", + gsvClearFailed: "Xóa không thành công, vui lòng thử lại sau", + cvAllPeople: "Tất cả mọi người", + cvVideo: "[Video]", + cvPicture: "[hình ảnh]", + cvFile: "[Tài liệu]", + cvVoice: "[Tiếng nói]", + cvNotSupportMsg: "Tin nhắn không được hỗ trợ tạm thời", + cvSendMsgErrorNoPermission: + "Bạn không thể gửi tin nhắn đến TA do cài đặt quyền của người khác", + cvSendMsgPnMute: "Không thể gửi tin nhắn", + cvCopyMsg: "Đã sao chép vào bảng cắt", + cvCopyMsgNotSupport: "Tin nhắn không được hỗ trợ tạm thời", + cvdeleteLocal: "Xóa khỏi cục bộ", + cvdeleteLocalAndServer: "Xóa khỏi cục bộ và máy chủ", + cvdeleteLocalAndServerUser: "Xóa khỏi cục bộ và{name}", + cvClearMsgTip: "Xóa tin nhắn phiên chạy", + cvGroupCreateSuccess: "Tạo nhóm chat thành công", + cvGroupCreateSuccessTip1: + "Chúc mừng bạn đã xây dựng thành công nhóm, nhấp vào nút bên dưới để mời bạn bè vào nhóm", + cvGroupCreateSuccessGo: "Mời bạn bè vào nhóm", + cvGroupNotification: "Thông báo nhóm:", + sendMsg: "Gửi tin nhắn", + cvOnlineCount: "{count} mọi người đang online", + cvUserCount: "của{count} người", + cvNotAtUser: + "Chưa có thành viên nào trong nhóm có thể @ ồ, mau đi thêm thành viên đi.", + all: "tất cả", + + clCloseConversation: "Trò chuyện xếp hàng đầu", + clOpenConversation: "Trò chuyện hàng đầu", + clNoConversation: "Không có phiên họp gần đây~", + clTitle: "Đối thoại", + clDelete: "Cuộc hội thoại này sẽ bị xóa", + clDeleteDesc: "Xóa cuộc hội thoại này, lịch sử trò chuyện cục bộ sẽ bị xóa", + clDeleteSel: "Bạn không chọn phiên chạy", + clDeleteSel1: "Cuộc hội thoại này sẽ bị xóa", + clDeleteSel2: "Những cuộc hội thoại này sẽ bị xóa", + clDeleteDesc2: + "Xóa các cuộc hội thoại này, lịch sử trò chuyện cục bộ sẽ bị xóa", + homeItem0: "đối thoại", + homeItem1: "Liên hệ", + homeItem2: "của tôi", + exitTip: "Click back một lần nữa để thoát ra", + mineSigEmpty: "Người dùng này rất lười, tạm thời không có chữ ký~", + uaProtocol: "Thỏa thuận dịch vụ người dùng", + savSearchTip: "Tìm kiếm nhanh nội dung trò chuyện", + savMedia: "Hình ảnh/Video", + searchEmptyTip: "Không tìm thấy lịch sử trò chuyện liên quan", + savSearchMsg: "Lịch sử trò chuyện liên quan đến thanh", + savConversation: "hội thoại", + savMsg: "Lịch sử trò chuyện", + savConversationNotFound: "Không tìm thấy hội thoại", + savMsgNotFound: "Không tìm thấy thông tin này", + + samTitle: "Truyền thông", + samTimeFormat: "Tháng M năm yyyy", + notSupportType: "Loại không được hỗ trợ", + media: "Truyền thông", + openUrl: "Mở liên kết", + lkAddGroupUrl: "Tham gia liên kết trò chuyện nhóm", + lkAddOtherUrl: "Liên kết ngoài, có thể mở bằng trình duyệt mặc định", + lkUnknowUrl: "Liên kết không xác định", + openUrlFailed1: "Mở liên kết không thành công", + yszcTitle: "Chính sách bảo mật", + ctmRemove: "Loại bỏ", + ctmOkRemove: "OK để xóa", + ctmTopMsgCount: "Tổng thanh{count}", + ctmEmptyText: "Không có tin nhắn hàng đầu", + recordTimeShort: "Thời gian ghi âm quá ngắn", + sendCodeExceErr: "Gửi CAPTCHA không thành công, vui lòng thử lại sau", + bindEmailExecErr: + "Hộp thư bị ràng buộc không thành công, vui lòng thử lại sau", + ctmSuccess: "Lên đỉnh thành công", + ctmRemoveSuccess: "Xóa tin nhắn hàng đầu thành công", + ctmMsgNotFound: "Thông điệp này không tồn tại", + cvFirstTip: + "Thông tin và cuộc gọi trong cuộc hội thoại này đã được mã hóa đầu cuối", + + clearGroupNotification: "Xóa thông báo nhóm", + publicGroupNotification: "Gửi thông báo", + dialog_tip1: "Gợi ý ấm áp", + deleteGroups: + "gói sẽ không thể phục hồi sau khi xóa, bạn có xác nhận xóa gói không?", + dialog_tip_token_invaild: "Đăng nhập đã hết hạn, vui lòng đăng nhập lại", + notify_new_msg_title: "Bạn nhận được tin nhắn mới", + notify_new_msg_msg: "Nội dung tin nhắn:...", + dataError1: "Lỗi dữ liệu", + groupApply: "Ứng dụng nhóm điều chỉnh", + muteLate: "Sau khi dỡ bỏ cấm ngôn", + groupMuteing: "Trong nhóm cấm ngôn", + modifyGroupName: "Sửa đổi tên nhóm", + groupMemberNotFound: "Không tìm thấy thông tin thành viên nhóm", + deleteGround: "Xóa nhóm", + bindEmailTipTitle: "Hộp thư bị ràng buộc", + bindEmailTipContent: + "Vì sự an toàn của tài khoản của bạn, vui lòng liên kết hộp thư của bạn", + bindEmailTipOk: "Không ràng buộc", + bindEmailTipCancle: "Tạm thời không bị ràng buộc", + alreadyBeOwner: "Bạn đã chọn chủ nhóm và đã có quyền quản trị cao nhất", + managerApplying: "Đang được quản trị viên xem xét", + quote: "trích dẫn", + + request_exception: "ngoại lệ yêu cầu", + clearMsg: "Xóa tin nhắn", + pressToSay: "Giữ và nói chuyện", + slideToCancelSend: "Trượt ngón tay, hủy gửi", + releaseToEnd: "Kết thúc việc nới lỏng", + releaseToCancelSend: "Thả ngón tay, hủy gửi", + invalidVoiceMessage: "tin nhắn thoại không hợp lệ", + hit: "đánh", + downloadingCache: "Đang tải xuống bộ nhớ cache...", + unableToRecognize: "Mã QR không hợp lệ", + scanResult: "Quét kết quả", + + register_fail: + "Đăng ký không thành công, vui lòng thử lại sau khi kiểm tra mạng", + skip: "nhảy qua", + toUse: "Trải nghiệm ngay lập tức", + sideToSide: "Mã hóa đầu cuối", + design: "Thiết kế", + guide1: "Gửi tin nhắn, mã hóa truyền, chỉ đọc cho chính mình", + guide2: "Giao tiếp tự do hơn theo cách của bạn", + guide3: "Nền tảng truyền thông an toàn được xây dựng xung quanh bạn", + inputGroupAc: "Vui lòng nhập thông báo nhóm (giới hạn ký tự 0-500)", + clickToEnjoy: + "Nhấn vào liên kết để lưu, hoặc sao chép nội dung của đoạn này, mở ứng dụng [đai bóng đá] và tham gia trò chuyện nhóm.", + createOK: "Tạo thành công", + noPravcy: "Bạn không có quyền tạo trò chuyện nhóm", + groupNumberToTop: "Số lượng nhóm đạt đến giới hạn trên", + createFail: "Tạo thất bại, hãy thử lại sau.", + create: "tạo ra", + uploadGroupAvatar: "Tải lên hình đại diện nhóm", + useNewPassword: + "Đổi mật khẩu thành công, đang quay lại trang đăng nhập", + + error_14: "Ngoại lệ máy chủ (14)", + error_500: "Ngoại lệ máy chủ (500)", + error_1000: "Token chữ ký của người dùng là không hợp pháp", + error_1001: "Lỗi tham số", + error_1002: "Phiên bản hiện tại là phiên bản mới nhất", + error_6001: "Tên người dùng này đã được đăng ký", + error_6003: "Mật khẩu phải bao gồm 6-15 chữ cái và số", + error_6004: "Lỗi kiểm tra tên người dùng", + error_6005: "Mật khẩu không đúng, vui lòng nhập lại", + error_6006: "Số lỗi mật khẩu vượt quá giới hạn ngày hôm nay", + error_6007: "Người dùng không tồn tại", + error_6008: "Mật khẩu cũ bị lỗi, vui lòng nhập lại", + error_6009: "Mật khẩu cũ phù hợp với mật khẩu mới, vui lòng nhập lại", + error_6010: "Nhận CAPTCHA thường xuyên", + error_6011: "Mã xác nhận không chính xác, vui lòng thử lại", + error_6012: "Mã xác nhận đã hết hạn", + error_6013: "Quá nhiều lần CAPTCHA thất bại", + error_6014: "CAPTCHA đã được sử dụng", + error_6015: "Hộp thư không tồn tại", + error_6016: "Hộp thư đã bị người khác ràng buộc, vui lòng nhập lại", + error_6017: "Captcha không tồn tại", + error_6018: "Tài khoản này đã bị chặn", + error_6019: "Số điện thoại di động đã bị người khác ràng buộc", + error_6020: "Địa chỉ email đã bị người khác ràng buộc", + error_6021: "Địa chỉ email bị lỗi, vui lòng nhập lại", + error_6022: "Số điện thoại bị lỗi, vui lòng nhập lại", + error_6023: + "Tài khoản này đã bị chặn, tạm thời không hỗ trợ lấy lại mật khẩu", + error_6024: + "Tài khoản này chưa liên kết với hòm thư, tạm thời không hỗ trợ tìm lại mật khẩu", + error_6025: + "Tài khoản này không liên kết số điện thoại, tạm thời không hỗ trợ tìm lại mật khẩu", + error_6026: "Số điện thoại đã bị người khác buộc, vui lòng nhập lại", + error_6030: "Hộp thư máy chủ không được cấu hình", + error_6031: "Vui lòng nhập đúng số điện thoại di động", + error_6032: "Vui lòng nhập địa chỉ email chính xác", + + error_100002: "Đặt tin nhắn hàng đầu để đạt giới hạn", + error_100003: "Thông điệp này đã được đưa lên hàng đầu", + error_api: "Lỗi yêu cầu", + error_http: "Lỗi yêu cầu mạng", + error_http_11: "Thời gian chờ kết nối mạng", + error_http_12: "Thời gian chờ kết nối mạng", + error_http_13: "Thời gian chờ kết nối mạng", + error_http_14: "Lỗi chứng chỉ mạng", + error_http_15: "Lỗi dữ liệu yêu cầu", + error_http_16: "Yêu cầu bị hủy bỏ", + error_http_17: "Thời gian chờ kết nối mạng", + error_http_18: "Lỗi nội bộ yêu cầu mạng", + error_sdk_err_1002: "Hoạt động không cho phép", + error_sdk_err: "Thao tác thất bại", + search_hine_1: "Vui lòng nhập nội dung tìm kiếm của bạn", + str_no_data: "Không có dữ liệu", + str_no_net: "Không có mạng tại thời điểm này", + download_not_support_type: "Định dạng không được hỗ trợ", + download_save_at: "tập tin được lưu trữ trong:", + download_not_url: "Không nhận ra liên kết tải xuống", + download_file_success: "Lưu thành công, bạn có thể vào album để xem", + download_failed_file: "Lưu tập tin không thành công", + download_failed: "Tải xuống không thành công", + loading_failed_retry: "Tải không thành công, nhấp để thử lại", + loading_ing: "Đang tải", + + str_online_later_minute: "Trực tuyến cách đây vài phút", + str_online_later_hour: "Trực tuyến trước giờ", + str_online_later_llg: "Lâu rồi không online", + str_online_later_yesterday: "Trực tuyến ngày hôm qua", + str_yesterday: "Hôm qua", + str_online_later_near: "Trực tuyến gần đây", + str_minute: "số phút", + clean_notice: "Thông báo trống", + clean_notice_1: "Bạn chắc chắn muốn xóa thông báo nhóm?", + publish_notice: "Gửi thông báo", + publish_notice_remind: + "Gửi thông báo này sẽ cảnh báo tất cả các thành viên của nhóm", + quit_edit: "Thoát khỏi biên tập viên", + quit_the_edit: "Thoát khỏi phiên bản này?", + continue_edit: "Tiếp tục chỉnh sửa", + publish_success: "Xuất bản thành công", + face_success: "Mở khóa khuôn mặt thành công", + finger_success: "Mở khóa vân tay đã thành công", + groupDismissed: "Nhóm đã giải tán", + hasOut: "Đã hết hạn", + + otherLoginStyle: "Các phương thức đăng nhập khác", + otherRegisterStyle: "Các phương thức đăng ký khác", + hasAccount: "Đã có tài khoản rồi, đăng nhập đi", + mobileCode: "Điện thoại di động Captcha", + codeSendTo: "Mã xác nhận đã được gửi đến", + inputNumber: "Vui lòng nhập số điện thoại di động", + bindPhone: "Số điện thoại bị ràng buộc", + bindPhoneSuccess: "Số điện thoại di động bị ràng buộc thành công", + bindPhoneTipChange: + "Nếu số điện thoại của bạn không còn sử dụng, vui lòng thay thế kịp thời", + please_input_email: "Vui lòng nhập địa chỉ email", + findPassByEmailTip: "Lấy lại mật khẩu bằng hộp thư của bạn", + findPassByPhoneTip: "Lấy lại mật khẩu bằng số điện thoại di động", + resetPass: "Đặt lại mật khẩu", + current_bind_phone: "Số điện thoại liên kết hiện tại:", + reget: "lấy lại", + getVerifyCode: "Lấy CAPTCHA", + alreadyRegister: "Số điện thoại này đã được đăng ký", + alreadyRegister2: "Địa chỉ email này đã được đăng ký", + netErrorRetry: "Mạng bị lỗi nhỏ, nhấp vào trang để tải lại~", + newPhoneNumber: "Số điện thoại mới", + addFriendText1: "Thêm bạn bè", + + qrSaveFail: "Lưu mã QR không thành công", + qrSavePhoto: "Mã QR đã được lưu vào album", + userId: "ID người dùng:", + addMeToFriend: "Thêm tôi làm bạn", + validTime: "Hết hạn sử dụng", + clickRefresh: "Nhấn để làm mới", + qrUseCount: "Mã QR này{count} hợp lệ lần", + share: "chia sẻ", + savePhoto: "Lưu ảnh", + iAm: "Tôi là", + addFriendSuccessful: "Thêm bạn bè để thành công", + addFriendFaceToFace: "mặt đối mặt kết bạn", + faceToFace: "mặt đối mặt thêm bạn bè", + inputSameKey: "Đây là những người bạn đã nhập cùng một số", + sameKeyAddFriend: "Nhập cùng số bạn bè để thêm bạn bè", + self: "bản thân", + age: "tuổi", + confirmAddFriend: "Xác nhận thêm bạn bè", + inputUserId: "Vui lòng nhập ID người dùng", + myQr: "Mã QR của tôi", + back: "trở lại", + canNotAddSelf: "Bạn không thể thêm mình vào danh bạ", + notExsit: "Người dùng này không tồn tại", + searchUserId: "Tìm kiếm ID người dùng:", + + remarkInfo: "Vui lòng ghi chú để xác minh thông tin", + applyRemark: "Ghi chú", + last4Number: + "Vui lòng nhập 4 chữ số sau khi đối phương đăng nhập vào số điện thoại di động", + checkLast4Number: "4 chữ số sau khi xác minh số điện thoại di động", + addFriendApply: "Thêm ứng dụng kết bạn", + friendNumberCatchTopLimit: "Số lượng bạn bè của bạn đã lên đến giới hạn", + friendNumberCatchTopLimitCurrentDay: + "Thêm bạn bè trong ngày đã đạt giới hạn tối đa", + codeInvalid: "Khóa không hợp lệ hoặc không hợp lệ, vui lòng khởi động lại", + shareFail: "Chia sẻ thất bại", + error_1003: "Vui lòng thử lại sau", + error_6033: "Khóa không hợp lệ hoặc không hợp lệ, vui lòng khởi động lại", + error_6034: "Tổng số bạn bè đã đạt đến giới hạn", + error_6035: "Thêm bạn bè trong ngày đã đạt giới hạn", + error_6036: "Số điện thoại của người dùng này trống rỗng", + error_6037: "Xác minh số đuôi điện thoại không thành công", + error_6040: "Gói đã tồn tại", + error_6050: "Mã QR đã hết hạn", + error_6051: "Số lần sử dụng mã QR đã đạt giới hạn tối đa trong ngày", + error_6052: "Số lần sử dụng mã QR đã đạt đến giới hạn trên", + error_6053: "Số lần quét hôm nay đã đạt giới hạn tối đa", + error_6054: "Đối phương đã thêm bạn làm bạn tốt", + error_6055: "Khóa không hợp lệ hoặc không hợp lệ, vui lòng khởi động lại", + error_6066: "Không đủ số dư tài khoản", + error_6067: "Tiền lì xì đã lãnh xong", + error_6068: "Phong bì đỏ đã hết hạn", + error_6069: "Không thể nhận phong bì màu đỏ đặc biệt", + error_6070: "Đã nhận được phong bì màu đỏ, không thể nhận được nữa", + + deletedByFriendHint: "Bạn chưa phải là bạn bè của tôi", + deletedByFriendHint1: "Bạn chưa phải là người bạn của tôi", + deletedByFriendHint2: "Thêm bạn bè", + deletedByFriendHint3: "Tiếp tục trò chuyện", + gotoAddFriend: "Đi và thêm bạn bè", + addFriendGotoGroup: "Mời bạn bè vào nhóm", + notContacts: "Không có liên hệ", + notContactsFound: "Không tìm thấy người bạn nào liên quan đến%s", + remarkStr: "Ghi chú", + remarkStrHint: "Vui lòng nhập ghi chú", + deleteConfirm: "Xác nhận xóa bạn bè", + deleteFriend: "Xóa bạn bè", + searchContact: "Tìm kiếm liên hệ", + notGroupTip: "Chưa tham gia trò chuyện nhóm", + notAddFriend: "Chưa thêm bạn mới", + friendApplyAdd: "Ứng dụng kết bạn", + addSuccess: "Thêm thành công", + hasSelectFriend: "Bạn bè đã chọn", + myBalance: "Số dư của tôi", + + //------------------ + + revokeMsgTip: "Rút tin nhắn", + error_10050: "Thao tác không thành công (không tìm thấy bản ghi)", + set_manager_tip_1: "Bạn đã chọn chủ nhóm và đã có quyền quản trị cao nhất.", + envelope: "phong bì màu đỏ", + uidStr: "ID người dùng", + showGetDetail: "Xem chi tiết yêu cầu bồi thường", + waitOtherEnter: "Chờ người khác tham gia", + isFriend2SExit: "Đã kết bạn, tự động thoát sau 2 giây", + sendRedPacket: "phát bao lì xì", + redPacketNumber: "Số lượng bao lì xì", + ge: "một", + total: "Tổng số", + eachNumber: "Số lượng đơn lẻ", + selectUser: "Chọn người dùng", + number: "số lượng", + redPacketType: "Loại phong bì màu đỏ", + checkVerity: "Xác minh bảo mật", + balance: "Số dư tài khoản", + sendTo: "Mã xác minh SMS đã được gửi đến", + pin: "Đánh vần vận may màu đỏ", + putong: "phong bì màu đỏ bình thường", + zhuanshu: "Phong bì màu đỏ đặc biệt", + dajidali: "Chúc mừng sự giàu có, Darjeelia!", + + redPacketInfo: "Chi tiết phong bì màu đỏ", + best: "Vận may tốt nhất", + saveToAccount: "Đã nạp tiền vào tài khoản", + minuteOut: "phút bị đánh cắp", + hourOut: "giờ cướp hết", + showDetail: "Xem chi tiết", + maxMemberNumber: + "Số lượng bao lì xì không được vượt quá số lượng chat nhóm hiện tại", + sended: "Đã gửi", + get: "Đã nhận", + waiting: "Chờ người dùng nhận nó", + gong: "một bao lì xì", + envelope_get_over: "Tiền lì xì đã được nhận hết", + envelope_get: "Đã nhận", + de: "của", + envelope_get_message: "Nhận tin nhắn phong bì đỏ", + bind_phone_please: + "Vì sự an toàn của tài khoản, vui lòng liên kết số điện thoại di động của bạn", + delay_bind: "Tạm thời không ràng buộc", + send_envelope_x: "phong bì màu đỏ phát ra", + send_envelope_back_all: "Phong bì đỏ này đã không được nhận trong hơn 24 giờ", + envelope_x_can_get: "{name} chỉ có thể yêu cầu", + envelope_get_completed: "Tay chậm, tiền lì xì đã bị cướp sạch", + send_envelope_back_some: + "Phong bì màu đỏ này đã không được nhận trong hơn 24 giờ.", + envelope_x_personal: "Phong bì màu đỏ dành riêng cho%s", + envelope_normal: "phong bì màu đỏ bình thường", + login_out_of_date: "Đăng nhập đã hết hạn, đăng nhập lại", + get_envelope_completed: "Đã hoàn thành", + x_is_personal: "Độc quyền", + error_6072: "Số tiền tối đa của một phong bì đỏ đã đạt được", + error_6073: "Số lượng bao lì xì riêng lẻ không thể nhỏ hơn 0.01", + error_6074: "Chức năng phong bì màu đỏ đã tắt", + distroyAccount: "Đăng xuất khỏi tài khoản", + + inComing: "thu nhập", + outGoing: "Chi phí", + balance_order_detail: "Chi tiết số dư", + type: "Loại", + payType: "Phương thức thanh toán", + orderNo: "Số thứ tự", + time: "thời gian", + itemDataNotSupport: "Phiên bản hiện tại không được hỗ trợ", + manager_change_account: "Điều chỉnh tài khoản quản trị viên", + groupEnvelope: "Phong bì đỏ nhóm", + accountPay: "thanh toán số dư", + paySuccess: "Thanh toán thành công", + groupEnvelopePersonal: "Phong bì màu đỏ nhóm - phong bì màu đỏ đặc biệt", + groupEnvelopeNormal: "Phong bì màu đỏ nhóm - phong bì màu đỏ thông thường", + changeAccount: "Chuyển khoản", + groupEnvelopeRandom: "Nhóm phong bì màu đỏ - đánh vần phong bì màu đỏ", + groupEnvelopeAllBack: "Nhóm phong bì màu đỏ - trả lại tất cả", + groupEnvelopeSomeBack: "Phong bì màu đỏ nhóm - trả lại một phần", + + distroyTips: + "Để xác nhận rằng tài khoản của bạn được bảo mật
chúng tôi sẽ thực hiện xác minh sau đây trước khi bạn đăng xuất khỏi tài khoản của mình", + safeStatus: "Tài khoản đang trong tình trạng an toàn", + safeTips: + "Vui lòng đảm bảo rằng tài khoản của bạn không có hành động nhạy cảm như thay đổi mật khẩu, thay đổi điện thoại/hộp thư và không có nguy cơ bị đánh cắp tài khoản của bạn", + strikeBalance: "Giải quyết tài sản tài khoản", + strikeTips: + "Sau khi đăng xuất khỏi tài khoản của bạn, tất cả dữ liệu số dư phong bì màu đỏ trong tài khoản của bạn sẽ bị xóa và không thể phục hồi được.", + dataDelete: "Xóa dữ liệu tài khoản", + deleteTips: + "Sau khi đăng xuất khỏi tài khoản, tất cả dữ liệu trong tài khoản đó sẽ bị xóa và không thể phục hồi. Dữ liệu bao gồm dữ liệu nội dung như lịch sử trò chuyện của bạn, các cuộc trò chuyện nhóm bạn đã tham gia, quan hệ bạn bè, v.v.", + applyDistroy: "Yêu cầu hủy tài khoản", + distroyConform: "Bạn có chắc muốn hủy tài khoản không?", + distroyConformTips: + "sẽ thoát khỏi đăng nhập sau khi đăng xuất, hãy cẩn thận!", + distroy: "Xác nhận đăng xuất", + submitSuccess: "Gửi thành công", + submitted: "Đơn xin hủy đăng ký của bạn đã được nộp!", + helpWord: + "Nền tảng sẽ xử lý đơn đăng ký của bạn và xóa tất cả dữ liệu của bạn trong vòng{day} ngày làm việc, để được trợ giúp, vui lòng liên hệ với chúng tôi qua trang web chính thức.", + know: "Tôi biết rồi", + revoke: "Đơn xin hủy bỏ", + revokeSuccess: "Hoàn tác thành công", + recover: "Bạn có chắc chắn muốn hủy bỏ đơn đăng ký không?", + toNormal: "Trạng thái tài khoản trở lại bình thường sau khi hủy bỏ", + beRecover: "Xác nhận khôi phục", + recoverSuccess: "Khôi phục thành công", + redPackageCountTip: + "Tổng số phong bì màu đỏ duy nhất không được nhỏ hơn 0,01", + + closeAccount: "Đăng xuất tài khoản", + recoverAccount: "Khôi phục tài khoản", + recoverAccountTipTitle: "Bạn chắc chắn muốn khôi phục tài khoản của mình?", + recoverAccountTipDesc: + "Trạng thái tài khoản trở lại bình thường sau khi khôi phục", + recoverAccountTipOk: "Xác nhận khôi phục", + closingAccount: "Tài khoản đang trong quá trình kiểm tra đăng xuất", + closingAccountEndTime: ", dự kiến sẽ xóa hoàn tất trong{num} ngày làm việc", + error_6079: "Bạn không còn trong nhóm trò chuyện, không thể nhận được nó", + error_6075: "Ứng dụng lặp lại", + error_6076: "Không tìm thấy hồ sơ yêu cầu hủy đăng ký", + error_6077: "Đang xem xét đăng xuất", + error_6078: + "Người dùng không tồn tại hoặc đã đăng xuất, vui lòng kiểm tra xác nhận", + error_video_pre_err: "Lỗi tạo tệp và tải dữ liệu không thành công.", + err_upload_file: "Tải lên không thành công, vui lòng thử lại sau", + err_msg_send_failed: "Gửi không thành công, vui lòng thử lại sau", + error_red_package_tip1: "Phong bì đỏ đã hết hạn", + net_error_no_net: "Mạng không khả dụng, hãy kiểm tra cài đặt mạng của bạn", + upload: "Tải lên", + error_6081: "Gửi không thành công, vui lòng thử lại sau", + atTip: "Ai đó {'@'} bạn", + legalNotice: "Tuyên bố pháp lý", + timeoutLock: "Khóa thời gian chờ", + reTry: "Thử lại", + sysNotify: "Thông báo hệ thống", + exit_group_tip: "Đã thoát khỏi cuộc trò chuyện nhóm", + group_mute_ing: "Nhóm này đã bị cấm", + removeGroupNotice: "Xóa thông báo nhóm", + groupChatError: "Tải nội dung thất bại, nhấn vào để thử lại", + w_edit_group_notification: "Biên tập thông báo nhóm", + messageNotify: "thông báo", + groupEnterLimit: + "Số bạn được mời vào nhóm đã lên đến giới hạn, xin vui lòng kiểm tra", + unRecard: "Không ghi âm được:", + notSet: "Không dùng", + hasCreateGroup: "Nhóm chat đã được tạo ra", + joinGroupForLink: "Kết nối để tham gia nhóm chat", + be: "bị", + moveoutGroup: "Di chuyển ra khỏi nhóm trò chuyện", + groupOwnerChangeTo: "Nhóm lãnh đạo đã thay đổi", + changeGroupNameTo: "Thay đổi tên nhóm là", + disbandedGroup: "Giải tán nhóm chat", + bannedFromSpeaking: "Cấm nói", + gagWasLifted: "Bỏ lệnh cấm", + initiatedGroupGag: "Mở các nhóm cấm ngôn ngữ", + shutDownGroupGag: "Đóng cửa nhóm cấm ngôn ngữ", + target: "Người kia", + groupSizeUpperLimit: "Số lượng nhóm lên đến mức tối đa", + topUpperLimit: "Nâng lên đến mức giới hạn", + topMessageRepeate: "Tin nhắn đặt hàng đã được lặp lại", + userHasReadMessage: "Người dùng đã đọc tin nhắn này", + placeholder: { + allMuted: "Chủ nhóm hoặc quản trị viên đã tắt chế độ câm toàn bộ", + groupDisbanded: "Nhóm đã bị giải tán", + groupBanned: "Nhóm đã bị cấm", + singleBanned: "Bạn đã bị câm", + search: "Tìm kiếm", + pleaseInput: "Vui lòng nhập", + sec: "giây", + groupNoticePlaceholder: "Nhập thông báo nhóm..", + inputPassword: "Nhập mật khẩu", + confirmPassword: "Xác nhận mật khẩu nhập", + inputPhoneNumber: "Nhập số điện thoại", + select: "Chọn", + userOrGroupIDInput: "Nhập {type}", + writeMessage: "Nhập tin nhắn...", + inputGroupName: "Nhập tên nhóm", + typingMessage: "Nhập tin nhắn ở đây", + inputName: "Nhập biệt danh của bạn" + }, + withoutQualification: "Người dùng này không đạt điều kiện để thêm bạn bè", + groupMask: "Mặt nạ trò chuyện nhóm", + groupMaskDescription: "Đặt biệt danh và hình đại diện của nhóm này", + pleaseBind: + "Vì an ninh tài khoản của bạn, tránh thêm bạn bè hạn chế chức năng, hãy ràng buộc", + orStr: " hoặc ", + andStr: " và ", + confirmExit: "Xác nhận lối ra", + pleaseBindTip1: "Vì an ninh tài khoản của bạn, hãy ràng buộc", + + skin_item_user_name: "Trương nana", + skin_item_msg1: "Xin chào, nhận được một trả lời kịp thời", + skin_item_msg2: "Được rồi, nghe rõ", + + appearance: "Hình ảnh", + bubbleBgColor: "Đối thoại màu bong bóng", + chatBackground: "Trò chuyện nền tảng", + + moreSettings: " cài đặt thêm", + + highPerformanceModel: "Chế Độ Sạch", + highPerformanceModelDesc: " mở không hiển thị hiệu ứng ", + skinPreview: " preview hiệu ứng ", + defaultText: "Mặc định", + fontSizePreview: "Xem trước kích thước phông chữ, kéo thanh trượt bên dưới để điều chỉnh kích thước phông chữ. Sau khi thiết lập, tất cả văn bản trên trang sẽ thay đổi kích thước phông chữ.", + changedText: "Được rồi, đã thay đổi", + imageAlpha: " hình ảnh minh bạch ", + min: "tối thiểu", + max: "lớn nhất", + backgroundColor: "màu nền", + changeSaveTip: "Bạn đã thay đổi cài đặt của mình, có nên lưu lại hay không", + saveAndBack: "Lưu và quay lại", + changeAvatar: "Thay thế avatar", + appFontSize: "Kích cỡ phông chữ toàn cục", + + appFontSizeTip: + "Xem trước kích thước phông chữ, kéo thanh trượt bên dưới để đặt kích thước phông chữ. Sau khi thiết lập, kích thước phông chữ sẽ thay đổi cho tất cả văn bản trong ứng dụng. ", + maskDesc: + "Sau khi mở nhóm thành viên sẽ có thể thiết lập nick name và avatar của nhóm.", + groupMaskOpenTip: " %sMặt nạ chat %s", + groupMaskOpenTipNew: " Mặt nạ chat", + open: "Mở ", + close: "Đóng cửa ", + groupMaskOpenMsgErrorTip: "Nhóm chat mặt nạ chức năng chuyển đổi Mẹo ", + maskOpen: "Mặt nạ trò chuyện được mở ", + maskClose: "Mặt nạ chat đóng cửa ", + + transmit: "chuyển tiếp", + + multiSelect: "đa lựa chọn", + + singleShareMsg: "Chuyển tiếp từng đoạn", + + groupShareMsg: "Hợp nhất và chuyển tiếp", + + notGroupFound: "Không tìm thấy {name} Trò chuyện nhóm liên quan", + + rememberPassword: "Nhớ mật khẩu", + + mobileLogin: "Đăng nhập điện thoại di động", + + emailLogin: "Đăng nhập hộp thư", + + recentConversation: "Trò chuyện gần đây", + + selectContact: "Chọn một liên hệ", + + selectGroup: "Chọn nhóm chat", + + noGroup: "không có nhóm chat", + + notSelectMessage: "Bạn chưa chọn tin nhắn", + + hiddenMsg: "Ẩn thông điệp", + + hiddenMsgTip: "Bạn có giấu nguồn không?", + + yes: "Vâng", + + no: "Không", + + mergeMessageList: "[Hợp nhất và Chuyển tiếp] Lịch sử trò chuyện", + + singleMlCount: "Tổng số{count} tin nhắn", + notFoundUser: "Không tìm thấy người dùng", + notFoundGroup: "Không tìm thấy nhóm", + notShouldFoundUser: "Không tìm thấy người dùng này", + selectAt: "Chọn người mà bạn muốn {'@'}", + welcomeToQDDChat: "Chào mừng đến với vành đai của các vị vua", + choiceYourAvatar: "Vui lòng chọn avatar của bạn", + enterPawAgain: "Vui lòng nhập lại mật khẩu", + Someone: "Ai đó {'@'} tôi", + + hasSelect: " đã chọn ", + groupChat: " nhóm chat ", + groupMember1: " thành viên nhóm ", + groupSendMsg: "Phát điện không hỗ trợ các dòng điện", + bindGoogle: "bindGoogle", + bindFacebook: "bindFacebook", + unbind: "unbind", + accountSettings: " thiết lập tài khoản ", + thirdPartyAccountBinding: " tài khoản thứ ba ràng buộc ", + otherSettings: " cài đặt khác ", + groupChatLoadFailed: " tải thông tin trò chuyện nhóm thất bại ", + msgNotSupportTransmit: "Tin nhắn không hỗ trợ chuyển tiếp", + + addGroupSend: "Bắt đầu nhóm", + groupSendNoData: "Không có ghi chép tin nhắn nhóm", + removeBinding: "Vô hiệu hóa", + unBindToastPart1: "Sau khi vô hiệu hóa nó, nó sẽ không còn được sử dụng nữa", + + unBindToastPart2: "Tài khoản đăng nhập, bạn có muốn tiếp tục gỡ bỏ?", + noOpPermission: "Vẫn chưa có quyền hành", + bindingPhoneOrEmail: + "Để bảo mật dữ liệu tài khoản của bạn, xin vui lòng ràng buộc số điện thoại hoặc hộp thư của bạn trước khi gỡ bỏ.", + unbindingSuccess: "Cởi trói thành công", + unbindingFair: "Giải thoát thất bại", + bindingPhoneOrEmailSetPassword: + "Để bảo mật dữ liệu tài khoản của bạn, vui lòng liên kết số điện thoại di động hoặc địa chỉ email của bạn trước khi đặt mật khẩu.", + + showAll: "Xem tất cả", + allCount: "Tất cả {num}", + recoverSuccessLoginAgain: "Phục hồi thành công, xin đăng nhập lại", + createGroupSend: "Bắt đầu nhóm", + groupSend: "Loan báo", + notSelect: "Bạn vẫn chưa chọn", + confirmDeleteTitle: "Hãy xác nhận việc xoá các nhóm đã chọn ?", + totalGe: "Tổng số %s", + sendTotalGe: "Gửi đến ({num})", + uploadFailed: "Tải lên thất bại", + selectMax: "Số lượng lựa chọn đạt đến giới hạn", + newFriendApply: "Bạn có một người bạn mới, nhấp vào xem", + error_6091: " số người được chọn vượt quá giới hạn ", + error_6092: " id thông báo đã tồn tại ", + newFriendApply: "Bạn có một người bạn mới, nhấp vào xem", + pay_password: "Mật khẩu thanh toán", + pleaseEnter: "Hãy nhập vào", + loadingPage: "Trang tải", + payTip: + "Bạn đã không thiết lập một mật khẩu thanh toán, xin vui lòng hoàn thành thiết lập trước khi hành động.", + deleteRedPackageErrTip: "Phong bì màu đỏ tin tức không thể rút lui", + applyIconTip: "Ứng dụng đã được gửi, phê duyệt có thể thay thế avatar", + applyIconProgress: "Xem tiến độ ứng dụng", + applyRecord: "Hồ sơ ứng dụng", + repealWithdrawApply: "Xác định hủy bỏ các ứng dụng rút tiền?", + withdrawNotes: "Hồ sơ rút tiền", + inReview: "Kiểm toán", + passTheAudit: "Phê duyệt", + rejectedPayment: "Phê duyệt bị bác bỏ", + revoked: "Đã bị hủy bỏ", + undone: "Hủy bỏ", + withdrawalQuantity: "Số tiền rút tiền mặt", + withdrawal: "DiXian", + withdrawalBack: "Rút lui", + update_tip1: "Nếu không thể cập nhật", + update_tip2: "Đến trang web chính thức để tải về", + update_ing: "Cập nhật", + update_install: "Lập tức cài đặt", + installFailed: "Không cài đặt, xin vui lòng thử lại sau", + installNotFoundFile: + "Không tìm thấy tập tin cài đặt, xin vui lòng thử lại sau", + error_6093: "Đăng nhập không được hỗ trợ", + error_6094: "Số avatar đã lên đến giới hạn", + deleteIcon: "Xóa avatar?", + balanceWithdraw: "Số dư rút tiền", + canBalanceWithdraw: "Số dư rút được", + inputPayPassword: "Vui lòng nhập mật khẩu thanh toán", + withdrawalSuccess: "Rút tiền thành công", + personIcon: "Hình ảnh cá nhân", + groupIcon: "Trò chuyện nhóm avatar", + error_6096: "Không đặt mật khẩu thanh toán bằng số lặp đi lặp lại", + forgetPayPassword: "Quên mật khẩu thanh toán", + downloadFailedTip: "Tải về thất bại, xin vui lòng thử lại sau", + + error_6097: "Số dư là không đủ", + notSet2: "Không được thiết lập", + passed: "Đã thông qua", + headRejected: "Đã từ chối", + readDeleteTime: "Thời gian đốt tin nhắn", + readDeleteTimeTitle: "Thời gian đốt", + error_6090: "Tài khoản đã bị ràng buộc", + retracted_h5: "Đã rút lại", + message_h5: "tin nhắn từ", + inputRemark: "Vui lòng nhập ghi chú của bạn", + authFailed: "Xác thực thất bại", + userNullError: "Ngoại lệ tài khoản người dùng", + community: "Cộng đồng", + join: "Tham gia trò chuyện nhóm và khám phá nhiều hoạt động cộng đồng hơn", + posteviews: "Chưa có hoạt động nào được công bố", + views: "Lượt xem", + ended: "Sự kiện đã kết thúc", + configError: "Lỗi cấu hình, tạm thời không thể mở", + burnMsgOpen: "{name} đã đặt tin nhắn đã đọc {time} sau khi đốt cháy", + burnMsgClose: + "{name} đã tắt chế độ đọc và đốt, tin nhắn không còn tự động đốt nữa", + burnMsgNotify: "Thông báo đọc và đốt", + homeItem3: "Cộng đồng", + messageTimeOut: "Tin nhắn đã hết hạn", + burnContentTip: + "Tin nhắn đang cháy, bạn có muốn thoát và đốt cháy chỉ với một cú nhấp chuột không?", + cancelLater: "Tôi sẽ kiểm tra sau", + burnConfirm: "Đốt ngay", + setNickNameTimes: ", biệt danh chỉ có thể sửa đổi một lần trong vòng %s ngày", + voiceSendErrTip1: "Chỉ hỗ trợ gửi giọng nói từ %s-%s giây", + textSendErrTip1: "Tối đa nhập %s ký tự", + pictureSendErrTip1: "Chỉ hỗ trợ gửi hình ảnh từ %s-%sM", + videoSendErrTip1: "Chỉ hỗ trợ gửi video từ %s-%sM", + your: "Của bạn", + joinCommunity: + "Tham gia trò chuyện nhóm và khám phá nhiều hoạt động cộng đồng hơn~", + noActivity: "Chưa có hoạt động nào được công bố~", + views: "Lượt xem", + ended: "Sự kiện đã kết thúc", + errorLink: "không tìm thấy link", + set_payment_password: "Vui lòng thiết lập mật khẩu thanh toán 6 chữ số", + unset_payment_password: + "Vui lòng không đặt mật khẩu thanh toán là các số lặp lại hoặc liên tục", + Confirm_Payment_Password: "Xác nhận mật khẩu thanh toán", + Please_confirm_payment_password: + "Vui lòng nhập lại mật khẩu thanh toán để xác nhận", + error_6100: + "Xin lỗi, bạn chỉ có thể thay đổi biệt danh cá nhân mỗi {day} ngày một lần", + viewDetailed: "Xem thông tin chi tiết của thành viên nhóm", + inviter: "Người mời", + lastLoginTime: "Thời gian đăng nhập cuối cùng", + DeleteThisUsersgroup: "Xóa tin nhắn nhóm do người dùng này gửi trong nhóm", + AddGroupMembers: "Thêm thành viên nhóm không cần xác nhậnnhận đồng ý", + error_6103: "Người dùng này là thành viên thông thường, không có quyền hạn", + error_6104: "Orang yang ditambahkan tidak ada dalam grup", + error_6108: "Tài khoản bất thường", + group_remark: "Ghi chú nhóm", + input_group_remark: "Vui lòng nhập nội dung ghi chú", + await_accepting: "Yêu cầu ủy quyền thiết bị mới đã được gửi", + apply_tips: "Thiết bị này không đáng tin cậy hoặc không được phê duyệt. Bạn có muốn gửi thông tin thiết bị để yêu cầu ủy quyền không?", + apply_send: "Gửi yêu cầu", + apply_ttl: "Ủy quyền Đăng nhập Thiết bị", + group_limit500: "Tối đa 500 người mỗi nhóm", + select_group: "Chọn nhóm bạn bè", + cantfind: "Không tìm thấy liên lạc liên quan đến {name}", + error_6109: "Số lượng bạn của người dùng đã đạt đến giới hạn", + error_6106: "Vui lòng đợi cho đến khi thiết bị được phê duyệt trước khi đăng nhập", + selectGroups: 'Chọn nhóm', + group_member_info: "Thông tin thành viên nhóm", + join_time: "Thời gian tham gia", + inviter_user: "Người mời", + last_login_time: "Thời gian đăng nhập lần cuối", + sync_group_avatar: 'Bạn có muốn đổi ảnh đại diện nhóm cùng lúc không?', + invite_fail: '{usernames}thất bại do tài khoản bất thường!', + enterAndCtrl:'Enter để gửi / Enter+Ctrl để xuống dòng', + enterAndCtrlIOS:'Enter để gửi / Enter+control để xuống dòng', + scanLogin: 'Đăng nhập bằng quét mã', + formLogin: 'Đăng nhập bằng mật khẩu', + scanText: 'Sử dụng ứng dụng Qủy Đỉ để quét mã đăng nhập', + scanSubText: 'Sử dụng ứng dụng Qủy Đỉ để quét mã đăng nhập', + qrcodeExpired: 'Mã QR đã hết hạn', + expiredTime10: 'Mã QR có hiệu lực trong vòng 10 phút', + reflesh: 'Nhấp để làm mới', + scanSuccess: 'Quét thành công', + confromLogin: 'Vui lòng xác nhận đăng nhập', + onlineStatus: "Trạng thái trực tuyến:", + networkDiagnostic: "Chẩn đoán mạng", + "setPaymentPassword": "Vui lòng thiết lập mật khẩu thanh toán 6 chữ số", + "unSetPaymentPassword": + "Vui lòng không đặt mật khẩu thanh toán là các số lặp lại hoặc liên tục", + "verifyPassword": "Xác nhận mật khẩu thanh toán", + "confirmPaymentPassword": "Xác nhận mật khẩu thanh toán", + "verifyCurrentPassword": "Vui lòng xác nhận mật khẩu thanh toán hiện tại", + "reInsetPaymentPassword": "Vui lòng nhập lại mật khẩu thanh toán để xác nhận", + "paymentPasswordDifferent": "Mật khẩu không khớp, vui lòng nhập lại", + "getPaymentPasswordWay": "Vui lòng chọn cách khôi phục mật khẩu", + "selectFriendGroup": "Chọn nhóm bạn bè", + "selectGroups": "Chọn nhóm", + "eachGroupLimitNumber": "Số lượng tối đa cho mỗi nhóm là {p} người", + "selectChatLimitNumber": "Bạn chỉ có thể chọn tối đa {p} cuộc trò chuyện", + "deviceID": "Mã thiết bị:", + "groupChatRemark": "Ghi chú nhóm chat", + "pleaseEnterRemarks": "Vui lòng nhập nội dung ghi chú", + "groupMembershipInfo": "Thông tin thành viên nhóm", + "error_6102": "Mỗi nhóm có tối đa 500 người", + "distroyed": "Đã hủy bỏ", + "forbiddened": "Đã bị cấm", + "addGroupMemberWithoutApply": "Thêm thành viên nhóm mà không cần xác nhận", + "error_6109": "Số lượng bạn bè của người dùng này đã đạt đến giới hạn.", + "apply_ttl": "Ủy quyền đăng nhập thiết bị", + "apply_tips": + "Thiết bị này không đáng tin cậy hoặc chưa được phê duyệt, bạn có muốn gửi thông tin thiết bị để yêu cầu ủy quyền không?", + "apply_send": "Gửi yêu cầu", + "device_auth_send": "Yêu cầu ủy quyền thiết bị đã được gửi", + "using_trusted_device": + "Việc đăng nhập bằng thiết bị này có nguy cơ, vui lòng đăng nhập bằng thiết bị đáng tin cậy", + "account_closed": "Tài khoản đã bị đóng", + "google_code": "Mã xác minh Google", + "input_google_code": "Vui lòng nhập mã xác minh Google", + "inviterUser": "Người mời", + "lastLoginTime": "Thời gian đăng nhập lần cuối", + "error_6105": "Thiết bị không đáng tin cậy, vui lòng nộp đơn", + "error_6106": "Vui lòng đợi cho thiết bị được duyệt trước khi đăng nhập", + "error_6107": "Thiết bị chưa được duyệt trong quá trình kiểm tra", + "hasDetailInfoPermission": "Xem thông tin chi tiết thành viên nhóm", + "editUserHeaderWithGroupHeader": + "Bạn có muốn chỉnh sửa ảnh đại diện nhóm cùng lúc không?", + "darkModel": "Chế độ tối", + "darkModelSetDesc": "Mở hoặc đóng chế độ tối theo hệ thống", + "whiteModel": "Chế độ sáng", + "fileNotFound": "Không tìm thấy tệp", + "openFileInApp": "Xem trước tệp", + "openFileOtherApp": "Mở bằng ứng dụng khác", + "openByBrowser": "Tải xuống bằng trình duyệt", + "pauseDownload": "Tạm dừng tải xuống", + "resumeDownload": "Tiếp tục tải xuống", + "downloadFailed": "Tải xuống thất bại", + "downloading": "Đang tải xuống", + "notdownloaded": "Chưa tải xuống", + "cancelDownloading": "Tệp đang tải xuống, bạn có muốn hủy tải xuống không?", + "failedToDownload": "Tải xuống tệp thất bại, bạn có muốn tải lại không?", + "cancelDownload": "Hủy tải xuống", + "sending": "Đang gửi", + "fileTypeNotSupported": "Không hỗ trợ tải lên định dạng tệp này", + "fileSizeOut": "Kích thước tệp vượt quá", + "restrict": "Hạn chế", + "getWithPhone": "Tìm lại bằng số điện thoại", + "getWithEmail": "Tìm lại bằng địa chỉ email", + "fileSize": "Kích thước tệp", + "whetherDownloadFile": "Có muốn tải tệp không", + "downloadFailedTip1": "Tải xuống thất bại, vui lòng kiểm tra mạng và thử lại", + "downloadQueueTip": "Đã thêm vào hàng đợi tải xuống, đang chờ tải", + "notSupportTransmitTip": "Tin nhắn này hiện không thể chuyển tiếp", + "voiceSize": "Kích thước tệp âm thanh không được vượt quá 100MB.", + "wordSize": "Kích thước tệp tài liệu không được vượt quá 100MB.", + "apkSize": "Kích thước tệp gói cài đặt không được vượt quá 300MB.", + "zipSize": "Kích thước tệp nén không được vượt quá 300MB.", + "groupDeleteUserTip": + "Xóa người dùng này cũng như tin nhắn mà người dùng này đã gửi trong nhóm", + "nPersonRead": "{p} người đã đọc", + "darkModeEnabled": + "Chế độ tối đã được bật, khởi động lại ứng dụng để có hiệu lực", + "darkModeOff": + "Chế độ tối đã được tắt, khởi động lại ứng dụng để có hiệu lực", + "otherUser": "bên kia", + "selectMsgDeleteTime": "Chọn thời gian lưu trữ", + "selectMsgDeleteTimeTip": + "Lịch sử trò chuyện sẽ tự động xóa sau khi vượt quá thời hạn lưu trữ. Tiếp tục?", + "msgSaveTime": "Thời gian lưu trữ lịch sử trò chuyện", + "msgSaveTimeDesc": + "Tự động xóa các bản ghi trò chuyện vượt quá thời gian giới hạn", + "hasTimeDeleteMsgPermission": "Cấu hình thời gian lưu trữ tin nhắn nhóm", + "accountAbnormal": "Tài khoản bất thường, mời thất bại!", + "onlyBrowser": "Chỉ hỗ trợ xem trên trình duyệt", + "cancelSendSuccess": "Đã hủy gửi thành công", + "allStr1": "Toàn cầu", + "modelStr1": "Chế độ ngày đêm", + "atMaxCountTip": "@ tối đa {p} người", + + "transmitTo": "Chuyển tới", + "moreActions": "Thêm hành động", + "refreshing": "Làm mới", + "openInBrowser": "Mở trong trình duyệt", + "coverFlow": "Dòng bìa", + + "checkNet": "Kiểm tra mạng", + "autoChangeServer": "Chuyển đổi máy chủ tự động", + "serverLine": "Dòng máy chủ", + "timeOut": "Hết thời gian", + "serverLineChange": "Thay đổi dòng máy chủ", + "netCheck": "Chẩn đoán mạng", + "netDelayCheck": "Đo độ trễ mạng", + "netDelay1Check": "Độ trễ Internet", + "msgDelay1Check": "Độ trễ tin nhắn", + "imgDelay1Check": "Độ trễ hình ảnh", + "netLine": "Dòng mạng", + "reCheck": "Kiểm tra lại", + "netChecking": "Đang kiểm tra mạng", + "locationAddr": "Địa chỉ", + "selectPerfectLineTip": "Đã chọn dòng mạng tốt nhất cho bạn", + "selectPerfectLineTip2": "Đang ở dòng mạng tốt nhất, không cần chuyển đổi", + "selectPerfectLineTipNoNet": + "Không nhận diện được dòng mạng tốt nhất, vui lòng kiểm tra lại mạng và thử lại", + "changing": "Đang thay đổi", + "changeSuccess": "Thay đổi thành công", + "autoChanging": "Đang chuyển đổi tự động, vui lòng thử lại sau", + "waitAnswer": "Đang chờ trả lời", + "callFinish": "Cuộc gọi kết thúc", + "fileSizeConnotZero": "Kích thước tệp không thể là 0", + "confirmLogin": "Xác nhận đăng nhập", + "toolboxAudioCall": "Cuộc gọi âm thanh", + "requestTimeOut": "Yêu cầu đã hết thời gian, vui lòng thử lại sau", + "error_6115": "Mã QR đã hết hạn", + "groupNotFound": "Không tìm thấy nhóm chat này", + "userNotFound": "Không tìm thấy người dùng này", + "microphoneOpen": "Đã mở microphone", + "speakerOpen": "Đã mở loa", + "voiceWithYou": "Mời bạn tham gia cuộc gọi âm thanh", + "callInterceptor": "Cuộc gọi bị gián đoạn", + "startCallError": "Lỗi khi bắt đầu cuộc gọi", + "callHungUp": "Cuộc gọi đã bị cắt", + "callRemoteHungUp": "Đối phương đã cắt máy", + "callReceiverError": "Lỗi khi nhận cuộc gọi", + "starting": "Đang khởi động", + "startTimeOut": "Khởi động cuộc gọi âm thanh đã hết thời gian", + "callWaitReceiver": "Đang chờ đối phương nhận máy", + "callRemoteNotAccept": "Đối phương không nhận máy", + "callStartSendError": "Không thể bắt đầu cuộc gọi âm thanh", + "notAccept": "Không nhận máy", + "callAccepting": "Đang nhận máy", + "call": "Cuộc gọi", + "callDoing": "Đường truyền bận, đã hủy", + "netBad": "Môi trường mạng hiện tại không tốt", + "netDown": "Mạng đã ngừng hoạt động", + "onCalling": "Đang gọi điện thoại", + "error_1303": "Bạn chưa là bạn của người kia", + "remoteAccountError": "Tài khoản của đối phương có vấn đề", + "userOnlineStatus": "Trạng thái trực tuyến", + "error_100008": "Người dùng này không đáp ứng được điều kiện để trở thành quản trị viên", + "callTimeLimit": "Mỗi lần gọi có thời gian tối đa {p} phút", + "notLoginOnline": "Chưa đăng nhập bao giờ", + "callRemoteDoing": "Đang bận", + "exitLoginIng": "Đang đăng xuất", + "usePhoneRecovery": "Sử dụng số điện thoại để khôi phục", + "useEmailRecovery": "Sử dụng email để khôi phục", + "otherRecoveryMethods": "Các phương pháp khôi phục khác", + "emailAddress": "Địa chỉ email", + "phoneErrorOrNotExist": "Số điện thoại không đúng hoặc không tồn tại", + "phoneNotExist": "Số điện thoại không tồn tại", + "verificationCodeFailure": "Lấy mã xác minh thất bại, vui lòng thử lại", + verificationCodeSent: "Mã xác nhận đã được gửi", + hiWelcomeLogin: "Chào~ Chào mừng bạn đến với đăng nhập", + otherLoginMethods: "Các phương thức đăng nhập khác", + usernamePhoneEmail: "Tên người dùng/Số điện thoại/Email", + "error_1303": "Bạn chưa là bạn của người kia", + "remoteAccountError": "Tài khoản của đối phương có vấn đề", + "userOnlineStatus": "Trạng thái trực tuyến", + "error_100008": + "Người dùng này không đáp ứng được điều kiện để trở thành quản trị viên", + "callTimeLimit": "Mỗi lần gọi có thời gian tối đa %s phút", + "notLoginOnline": "Chưa đăng nhập bao giờ", + "callRemoteDoing": "Đang bận", + "exitLoginIng": "Đang đăng xuất", + + "searchAreaCode": "Tìm kiếm quốc gia/khu vực", + "otherFindStyle": "Các phương thức khôi phục khác", + "welcome": "Chào mừng đăng nhập", + + "fileReadError": "Lỗi đọc tệp", + "docPreviewError": "Không thể xem trước tài liệu", + "docPreviewUseOther": + "Nếu vì lý do mạng hoặc thiết bị không tương thích mà tài liệu không thể xem trước, bạn có thể sử dụng", + "netTimeOut": "Hết thời gian kết nối mạng", + + + "docReadError": "Không thể đọc tệp, vui lòng kiểm tra xem định dạng tệp có đúng không", + "errorDetail": "Chi tiết lỗi", + ip: "IP", + imageLoadingRoute: "Tuyến tải ảnh", + networkChecking: "Đang kiểm tra mạng...", + noPermission: "Không có quyền, vui lòng đăng nhập sau khi có quyền", + pageNotFound: "Không tìm thấy trang", + serverError: "Lỗi máy chủ", + databaseError: "Lỗi truy vấn cơ sở dữ liệu", + networkError: "Lỗi kết nối mạng" +}; diff --git a/src/lang/zh-cn.js b/src/lang/zh-cn.js new file mode 100644 index 0000000..6dbf318 --- /dev/null +++ b/src/lang/zh-cn.js @@ -0,0 +1,1766 @@ +export default { + checkTopMessage:'查看置顶消息', + welcome: "欢迎使用OpenIM", + phoneNumber: "手机号", + plsEnterPhoneNumber: "请输入您的手机号", + password: "密码", + plsEnterPassword: "请输入您的密码", + account: "账号", + plsEnterAccount: "请输入您的账号", + forgetPassword: "忘记密码", + createAccount: "创建账号", + verificationCodeLogin: "验证码登录", + login: "登录", + noAccountYet: "没有账号?", + loginNow: "立即登录", + registerNow: "立即注册", + lockPwdErrorHint: "错误%s次", + newUserRegister: "新用户注册", + verificationCode: "验证码", + verificationSuccessful: "验证成功", + sendVerificationCode: "发送验证码", + verification_code1: "请输入验证码", + correct_email_address: "请输入正确的邮箱地址", + Resend: "重新发送", + bind_email: "绑定邮箱", + email_verification: "邮箱验证", + login_details: "登录详情", + verification_code: "请输入邮箱验证码", + email_verification_code: "邮箱验证码", + currently_bound_email: "当前绑定邮箱:", + text_email: "如您的邮箱已不用,请及时更换", + remove_this_device: "确定要移除该设备吗?", + remove_this_device1: + "确定要移除该设备吗?移除后重新使用该设备,登录需重新验证", + go_to_settings: "去设置", + go_to_binding: "去绑定", + date_of_birth: "出生日期", + confidential: "保密", + year: "岁", + not_perfection: "未完善", + old_password: "旧密码", + myCreatedGroup: "我创建的群聊", + myManageGroup: "我管理的群聊", + myJoinGroup: "我加入的群聊", + please_enter_old_password: "请输入旧密码", + trusted_device: "可信设备", + change_email_address: "换绑邮箱", + current_device: "当前设备", + remove_device: "移除设备", + current_device_records: "以下是您该设备的全部登录记录", + text_device: "登录设备管理", + verification_time: "验证时间:", + text_device1: "以下是您的全部登录设备,点击进入详情,查看该设备的登录记录", + change_the_binding: "换绑", + resendVerificationCode: "重发验证码", + verificationCodeTimingReminder: "%sS后重新获取验证码", + defaultVerificationCode: "(默认验证码:%s)", + plsEnterVerificationCode: "请输入您的验证码", + invitationCode: "邀请码", + device_list: "设备列表", + plsEnterInvitationCode: "请输入您的邀请码%s", + optional: "选填", + nextStep: "下一步", + plsEnterRightPhone: "请输入正确的手机号", + enterVerificationCode: "输入手机验证码", + setPassword: "设置密码", + plsConfirmPasswordAgain: "请再次确认您的密码", + text_password2: "请再次确认登录密码", + please_enter_new_password_again: "请再次输入新密码", + confirmPassword: "确认密码", + wrongPasswordFormat: "密码格式错误", + plsCompleteInfo: "请完善个人信息", + plsEnterYourNickname: "请输入您的昵称", + pleaseEnterYourUsername: "请输入您的用户名", + enterYourUsernameToHelpUsIdentifyYourAccount: + "输入您的用户名以帮助我们识别您的账号", + text_1: "该账户未绑定邮箱,暂不支持找回密码", + setInfo: "设置信息", + loginPwdFormat: "6-20位数字+字母", + passwordLogin: "密码登录", + contacts: "通讯录", + workbench: "工作台", + mine: "我的", + me: "我", + draftText: "草稿", + everyone: "所有人", + you: "你", + groupAc: "群公告", + createGroupNtf: "%s 已创建群聊 ", + editGroupInfoNtf: "%s 修改了群资料", + quitGroupNtf: "%s 退出了群聊", + invitedJoinGroupNtf: "%s 邀请 %s 加入群聊", + kickedGroupNtf: "%s 被 %s 移出群聊", + joinGroupNtf: "%s 加入群聊", + dismissGroupNtf: "%s 解散了群聊", + transferredGroupNtf: "%s 将群管理权限转让给了 %s", + muteMemberNtf: "%s 被 %s 禁言%s", + muteCancelMemberNtf: "%s 被 %s 取消了禁言", + muteGroupNtf: "%s 开起了群禁言", + muteCancelGroupNtf: "%s 关闭了群禁言", + friendAddedNtf: "你们已成为好友,可以开始聊天了", + openPrivateChatNtf: "已开启阅后即焚", + closePrivateChatNtf: "已关闭阅后即焚", + memberInfoChangedNtf: "%s 编辑了自己的群成员资料", + unsupportedMessage: "暂不支持的消息类型", + picture: "图片", + video: "视频", + voice: "语音", + safety: "安全", + location: "位置", + file: "文件", + carte: "名片", + emoji: "自定义表情", + chatRecord: "聊天记录", + revokeMsg: "撤回了一条消息", + aRevokeBMsg: "%s 撤回了 %s 的消息", + blockedByFriendHint: "消息已发出,但被对方拒收了", + sendFriendVerification: "验证添加", + removedFromGroupHint: "你已被移出群聊", + groupDisbanded: "群已经解散", + createdGroup: "您还没有创建分组", + search: "搜索", + newGroups: "新建分组", + groupNames: "请输入分组名称", + synchronizing: "同步中", + syncFailed: "同步失败", + connecting: "连接中", + connectionFailed: "连接失败", + top: "置顶", + cancelTop: "取消置顶", + markHasRead: "标记已读", + delete: "删除", + nPieces: "%s条", + online: "在线", + offline: "离线", + phoneOnline: "手机", + pcOnline: "电脑", + webOnline: "Web", + webMiniOnline: "小程序", + upgradeFind: "发现新版本", + upgradeVersion: "检测到一个新的可用版本%s,你当前版本是%s", + upgradeDescription: "更新说明:", + upgradeIgnore: "忽略", + upgradeLater: "稍后更新", + upgradeNow: "立即更新", + upgradeNever: "暂不更新", + inviteYouCall: "%s邀请你进行%s", + rejectCall: "拒绝", + acceptCall: "同意", + callVoice: "语音通话", + callVideo: "视频通话", + sentSuccessfully: "发送成功", + copySuccessfully: "复制成功", + binding_successful: "绑定成功", + day: "天", + hour: "时", + hours: "小时", + minute: "分钟", + seconds: "秒", + cancel: "取消", + determine: "确定", + failed_to_load: "加载失败", + failed_to_load1: "加载失败,请稍后再试", + removal_failed: "移除失败", + toolboxAlbum: "相册", + toolboxCall: "视频通话", + toolboxCamera: "拍摄", + toolboxCard: "名片", + toolboxFile: "文件", + toolboxLocation: "位置", + send: "发送", + holdTalk: "按住 说话", + releaseToSend: "松开 发送", + releaseToSendSwipeUpToCancel: "松开发送,上滑取消", + liftFingerToCancelSend: "松开手指,取消发送", + callDuration: "通话时长 %s", + cancelled: "已取消", + cancelledByCaller: "对方已取消", + rejectedByCaller: "对方已拒绝", + callTimeout: "超时无响应", + rejected: "已拒绝", + forwardMaxCountHint: "当前仅支持转发最多二十条消息~", + typing: "正在输入...", + addSuccessfully: "添加成功", + addFailed: "添加失败", + setSuccessfully: "设置成功", + callingBusy: "你已在通话中,不能进行此操作!", + groupCallHint: "当前群正在通话中,你确定加入当前通话吗?", + joinIn: "加入", + menuCopy: "复制", + menuDel: "删除", + menuForward: "转发", + menuReply: "回复", + menuMulti: "多选", + menuRevoke: "撤回", + menuAdd: "添加", + nMessage: "%s条新消息", + plsSelectLocation: "请选择一个位置", + groupAudioCallHint: "%s人正在语音通话", + groupVideoCallHint: "%s人正在视频通话", + reEdit: "重新编辑", + playSpeed: "播放速度", + download: "下载", + download1: "立即下载", + nicknames: "用户昵称", + downloadAPP: "下载APP", + private_chat: "球帝带", + googleMap: "谷歌地图", + check_for_updates: "检查更新", + appleMap: "Apple地图", + baiduMap: "百度地图", + amapMap: "高德地图", + signature: "个性签名", + please_enter_user_nickname: "请输入用户昵称", + set_characters: "请设置4-15个字符", + personalized_signature: "请输入个性签名 (字符限制6-50)", + tencentMap: "腾讯地图", + offlineMeetingMessage: "你收到了一条会议邀请消息", + offlineMessage: "你收到了一条新消息", + offlineCallMessage: "你收到了一条通话邀请消息", + logoutHint: "您确定要退出登录吗?", + myInfo: "我的信息", + workingCircle: "工作圈", + accountSetup: "账号设置", + aboutUs: "关于我们", + about: "关于", + + appearance: "外观", + bubbleBgColor: "对话气泡颜色", + chatBackground: "聊天背景", + moreSettings: "更多设置", + + highPerformanceModel: "高性能模式", + highPerformanceModelDesc: "开启则不显示外观效果", + skinPreview: "效果预览", + defaultText: "默认", + fontSizePreview: "预览字体大小,拖动下面的滑块,可调整字体大小。设置后,会改变所有页面文本的字体大小。", + changedText: "好的,已经更改", + imageAlpha: "图案透明度", + bgColor: "背景色", + + logout: "退出登录", + qrcode: "二维码", + qrcodeHint: "扫一扫下面的二维码,添加我为好友", + favoriteFace: "收藏表情", + favoriteManage: "管理", + favoriteCount: "共%s个表情", + favoriteDel: "删除(%s)", + hasRead: "已读", + unread: "未读", + nPersonUnRead: "%s人未读", + allRead: "全部已读", + messageRecipientList: "消息接收人列表", + hasReadCount: "已读(%s)", + unreadCount: "未读(%s)", + newFriend: "新的朋友", + newFriendList: "好友申请列表", + newGroup: "新的群聊", + myFriend: "我的好友", + myGroup: "我的群聊", + addManager: "添加管理员", + add: "添加", + scan: "扫一扫", + scanHint: "扫描二维码名片", + addFriend: "添加好友", + addFriendHint: "通过ID号搜索添加", + createGroup: "创建群聊", + createGroupHint: "创建群聊,全面使用OpenIM", + addGroup: "添加群聊", + addGroupHint: "向管理员或群聊成员询问ID", + searchIDAddFriend: "搜索ID添加好友", + searchIDAddGroup: "搜索ID添加群聊", + searchIDIs: "ID:%s", + searchPhoneIs: "手机号:%s", + searchEmailIs: "邮箱:%s", + searchNicknameIs: "昵称:%s", + searchGroupNicknameIs: "群昵称:{name}", + noFoundUser: "无法找到该用户", + noFoundGroup: "无法找到该群聊", + joinGroupDate: "入群时间", + joinGroupMethod: "入群方式", + byInviteJoinGroup: "%s邀请进群", + byIDJoinGroup: "搜索群ID", + byQrcodeJoinGroup: "群二维码", + groupID: "群ID号", + setAsAdmin: "设为管理员", + setMute: "设置禁言", + organizationInfo: "组织信息", + organization: "组织", + department: "部门", + position: "职位", + personalInfo: "个人资料", + viewDynamics: "查看动态", + audioAndVideoCall: "音视频通话", + sendMessage: "发送消息", + avatar: "头像", + name: "姓名", + nickname: "昵称", + gender: "性别", + englishName: "英文名", + birthDay: "生日", + tel: "座机", + mobile: "手机号码", + email: "邮箱", + man: "男", + woman: "女", + friendSetup: "好友设置", + setupRemark: "设置备注", + recommendToFriend: "把他推荐给朋友", + addToBlacklist: "加入黑名单", + unfriend: "解除好友关系", + areYouSureDelFriend: "确定删除好友吗?", + areYouSureAddBlacklist: "确定将好友加入黑名单吗?", + remark: "备注", + save: "保存", + saveSuccessfully: "保存成功", + saveFailed: "保存失败", + groupVerification: "群聊验证", + friendVerification: "好友验证", + email_address: "请输入您的邮箱", + sendEnterGroupApplication: "发送入群申请", + sendToBeFriendApplication: "发送好友申请", + sendSuccessfully: "发送成功", + reset_successful: "重置成功", + bind_new_email: "绑定新邮箱", + Login_failed: "登录失败", + successful_home: "注册成功", + sendFailed: "发送失败", + canNotAddFriends: "该用户已设置不可添加!", + mutedAll: "全体禁言", + tenMinutes: "10分钟", + oneHour: "1小时", + twelveHours: "12小时", + oneDay: "1天", + custom: "自定义", + unmute: "取消禁言", + youMuted: "你已被禁言", + groupMuted: "已开启群禁言", + notDisturbMode: "勿扰模式", + allowRing: "新消息提示音", + allowVibrate: "新消息震动", + forbidAddMeToFriend: "禁止加我为好友", + blacklist: "通讯录黑名单", + unlockSettings: "解锁设置", + changePassword: "修改密码", + clearChatHistory: "清空聊天记录", + confirmClearChatHistory: "确认清空所有聊天记录吗?", + languageSetup: "语言设置", + language: "语言", + english: "英文", + setting_up: "设置中...", + chinese: "简体中文", + traditional_chinese: "繁体中文", + followSystem: "跟随系统", + blacklistEmpty: "暂无黑名单", + remove: "移除", + removeFriend: "移除好友", + fingerprint: "指纹", + gesture: "手势", + biometrics: "生物识别", + plsEnterPwd: "请输入密码", + plsEnterOldPwd: "请输入原始密码", + plsEnterNewPwd: "请输入新密码", + plsConfirmNewPwd: "请确认新密码", + reset: "重置", + oldPwd: "原密码", + newPwd: "新密码", + confirmNewPwd: "确认新密码:", + plsEnterConfirmPwd: "请输入确认密码", + twicePwdNoSame: "两次输入的密码不一致", + twicePwd: "新密码不一致,请重新设置", + changedSuccessfully: "修改成功", + deleteSuccessfully: "删除成功", + checkNewVersion: "检查新版本", + noAddGround: "还没有添加好友到分组", + chatContent: "聊天内容", + topContacts: "置顶联系人", + messageNotDisturb: "消息免打扰", + messageNotDisturbHint: "接收消息但不提醒", + burnAfterReading: "阅后即焚", + timeSet: "时间设置", + setChatBackground: "设置聊天背景", + fontFace: "字体", + fontSize: "字体大小", + little: "小", + standard: "标准", + big: "大", + thirtySeconds: "30秒", + fiveMinutes: "5分钟", + clearAll: "清空", + clearSuccessfully: "清除成功", + groupChatSetup: "群聊设置", + viewAllGroupMembers: "查看全部群成员(%s)", + groupManage: "群管理", + myGroupMemberNickname: "我在本群的名称", + topChat: "聊天置顶", + muteAllMember: "全员禁言", + exitGroup: "退出群聊", + dismissGroup: "解散群聊", + dismissGroupHint: "所有群成员将退出此群,无法收发消息", + quitGroupHint: "退出群聊后将不再接收此群聊信息。", + joinGroupSet: "进群验证", + allowAnyoneJoinGroup: "允许任何人加群", + inviteNotVerification: "群成员邀请无需验证", + needVerification: "需要发送验证信息", + addMember: "添加", + delMember: "移除", + groupOwner: "群主", + groupAdmin: "管理员", + notAllowSeeMemberProfile: "不允许查看其他群成员资料", + notAllAddMemberToBeFriend: "不允许添加群成员为好友", + transferGroupOwnerRight: "群主管理权转让", + groupName: "群名称", + groupAcPermissionTips: "只有群主及管理员可以编辑", + plsEnterGroupAc: "请输入群公告", + edit: "编辑", + publish: "发布", + groupMember: "群聊成员", + selectedPeopleCount: "已选择(%s)", + confirmSelectedPeople: "确定(%s/%s)", + confirm: "确认", + log_in_time: "登录时间:", + log_in_time1: "登录时间", + login_IP: "登录IP:", + confirmTransferGroupToUser: "确定将群主转让给:%s?", + removeGroupMember: "移除群成员", + searchNotResult: "搜索无结果", + groupQrcode: "群二维码", + groupQrcodeHint: "扫一扫下面的二维码,立即加入群聊", + approved: "已同意", + accept: "接受", + reject: "拒绝", + friendGrouping: "好友分组", + waitingForVerification: "等待对方验证", + rejectSuccessfully: "拒绝成功", + rejectFailed: "拒绝失败", + applyJoin: "申请加入", + enterGroup: "进入群聊", + applyReason: "申请理由:%s", + invite: "邀请", + sourceFrom: "来源:%s", + byMemberInvite: "邀请", + bySearch: "通过搜索", + byScanQrcode: "扫描二维码", + iCreatedGroup: "我创建的", + iJoinedGroup: "我加入的", + nPerson: "{count}人", + searchNotFound: "未搜索到相关结果", + searchNotFoundMember: "没有找到相关用户", + organizationStructure: "组织架构", + recentConversations: "最近会话", + selectAll: "全选", + plsEnterGroupNameHint: "取个群名称方便后续搜索", + completeCreation: "完成创建", + sendCarteConfirmHint: "确定发送该联系人到本聊天吗?", + sentSeparatelyTo: "分别发送给:", + sentTo: "发送给:", + leaveMessage: "留言", + mergeForwardHint: "[合并转发]共%s条消息", + mergeForward: "合并转发", + quicklyFindChatHistory: "快速查找聊天记录", + notFoundChatHistory: '没有找到"%s"相关内容', + globalSearchAll: "综合", + globalSearchContacts: "联系人", + globalSearchGroup: "群组", + globalSearchChatHistory: "聊天记录", + globalSearchChatFile: "文件", + relatedChatHistory: "%s条相关的聊天记录", + seeMoreRelatedContacts: "查看更多相关联系人", + seeMoreRelatedGroup: "查看更多相关群组", + seeMoreRelatedChatHistory: "查看更多相关聊天记录", + seeMoreRelatedFile: "查看更多相关文档", + publishPicture: "发布图片", + publishVideo: "发布视频", + mentioned: "提到了:%s", + comment: "评论", + like: "赞", + reply: "回复", + rollUp: "收起", + fullText: "全文", + selectAssetsFromCamera: "拍摄", + selectAssetsFromAlbum: "从相册选择", + whoCanWatch: "谁可以看", + remindWhoToWatch: "提醒谁看", + public: "公开", + everyoneCanSee: "所有人可见", + partiallyVisible: "部分可见", + visibleToTheSelected: "选中的人可见", + partiallyInvisible: "不给谁看", + invisibleToTheSelected: "选中的人不可见", + private: "私密", + onlyVisibleToMe: "仅自己可见", + selectVideoLimit: "时长不能超过15秒", + selectContactsLimit: "至少选择一个联系人", + message: "消息", + commentedYou: "评论了你:%s", + likedYou: "为你点了赞", + mentionedYou: "提到了你", + replied: "回复了", + detail: "详情", + totalNPicture: "共%s张", + noDynamic: "暂无动态", + callRecords: "通话记录", + allCall: "所有通话", + missedCall: "未接来电", + incomingCall: "呼入", + outgoingCall: "呼出", + microphone: "麦克风", + speaker: "扬声器", + hangUp: "挂断", + pickUp: "接听", + waitingCallHint: "正在呼叫中…", + waitingVoiceCallHint: "正在等待对方接听…", + invitedVoiceCallHint: "邀请你进行语音通话…", + waitingVideoCallHint: "正在等待对方接受邀请", + invitedVideoCallHint: "邀请你进行视频通话…", + waitingToAnswer: "等待接听", + invitedYouToCall: "邀请你通话", + calling: "通话中", + nPeopleCalling: "%s人正在通话中", + whoInvitedVoiceCallHint: "%s邀请你进行语音通话", + whoInvitedVideoCallHint: "%s邀请你进行视频通话", + plsInputMeetingSubject: "请输入会议主题", + meetingStartTime: "开始时间", + meetingDuration: "会议时长", + enterMeeting: "进入会议", + meetingNo: "会议号", + yourMeetingName: "您的名称", + plsInputMeetingNo: "请输入会议号", + plsInputYouMeetingName: "请输入您的名称", + meetingSubjectIs: "会议主题:%s", + meetingStartTimeIs: "开始时间:%s", + meetingDurationIs: "会议时长:%s", + meetingHostIs: "主持人:%s", + meetingNoIs: "会议号:%s", + meetingMessageClickHint: "点击此消息可直接加入会议", + meetingMessage: "会议消息", + openMeeting: "未结束会议", + didNotStart: "未开始", + started: "已开始", + meetingInitiatorIs: "%s发起的视频会议", + meetingDetail: "会议详情", + meetingOrganizerIs: "发起人:%s", + updateMeetingInfo: "修改会议信息", + cancelMeeting: "取消会议", + videoMeeting: "视频会议", + joinMeeting: "加入会议", + bookAMeeting: "预约会议", + quickMeeting: "快速会议", + confirmTheChanges: "确认修改", + invitesYouToVideoConference: "%s邀请你加入视频会议", + over: "结束", + meetingMute: "静音", + meetingUnmute: "取消静音", + meetingCloseVideo: "关闭视频", + meetingOpenVideo: "开启视频", + meetingEndSharing: "结束共享", + meetingShareScreen: "共享屏幕", + meetingMembers: "成员(%s)", + settings: "设置", + leaveMeeting: "离开会议", + endMeeting: "结束会议", + leaveMeetingConfirmHint: "确定离开会议吗?", + endMeetingConfirmHit: "确定结束会议吗?", + meetingSettings: "会议设置", + allowMembersOpenMic: "允许成员自我解除静音", + allowMembersOpenVideo: "允许成员开启视频", + onlyHostShareScreen: "仅主持人可以共享屏幕", + onlyHostInviteMember: "仅主持人可邀请会议成员", + defaultMuteMembers: "成员入会禁音", + pinThisMember: "置顶该成员", + unpinThisMember: "取消置顶", + allSeeHim: "全部看他", + cancelAllSeeHim: "取消看他", + muteAll: "全员静音", + unmuteAll: "解除全员静音", + members: "成员", + screenShare: "屏幕共享", + screenShareHint: "正在共享屏幕.", + meetingClosedHint: "会议已关闭或已断开链接,确定离开吗?", + meetingIsOver: "会议已经结束!", + networkError: "网络异常请稍后再试!", + shareSuccessfully: "分享成功!", + notFoundMinP: "暂未发布小程序", + notSendMessageNotInGroup: "无法在已退出的群聊中发送消息", + whoModifyGroupName: "{name} 把群聊名称已更改为 ", + accountWarn: "警告!", + accountException: "你的账号已在其他设备登录,请及时修改密码。", + tagGroup: "标签", + issueNotice: "通知下发", + createTagGroup: "创建标签", + plsEnterTagGroupName: "请输入标签名", + tagGroupMember: "标签成员", + completeEdit: "完成编辑", + finish: "完成", + emptyTagGroup: "暂无标签分组", + confirmDelTagGroupHint: "确认移除该标签分组吗?", + editTagGroup: " 编辑标签", + newBuild: " 新建", + receiveMember: " 接收成员", + emptyNotification: "暂无通知", + notificationReceiver: "%s个接收者:%s", + sendAnother: "再发一条", + confirmDelTagNotificationHint: "确认移除该通知记录吗?", + contentNotBlank: "内容不能为空", + plsEnterDescription: "请输入描述文字", + gifNotSupported: "不支持gif图片", + register: "注册", + hk: "繁体", + RegisterSuccess: "注册成功", + LoginSuccess: "登录成功", + init_validate_1: "用户名由6-15位字母及数字组成", + init_validate_10: "手机号或密码不正确,请重新输入", + + init_validate_5: "密码必须由6-15位字母及数字组成,请重新设置密码", + init_validate_2: "两次密码不一致,请重新设置", + init_validate_3: "密码必须由6-15为字母及数字组成 请重新设置密码", + init_validate_6: "密码必须由6-15为字母及数字组成", + init_validate_4: "注册成功,即将返回登录页", + init_hint_1: "6-15位数字或英文", + init_hint_2: "确认登录密码", + init_hint_3: "登录密码", + username: "用户名", + to_modify: "去修改", + confirm_password: "确认密码", + notification_settings: "通知设置", + init_text_1: "登录注册即同意", + init_text_2: " 《用户服务协议》 ", + init_text_3: " 和 ", + init_text_4: " 《隐私政策》 ", + init_text_5: "服务协议及隐私政策", + init_text_6: "为了保护您得合法权益,请您阅读并同意以下协议", + refuse: "拒绝", + refuse_continue: "同意并继续", + forget_password: "忘记密码", + register_user: "注册账号", + gotoLogin: "登录", + gotoRegister: "创建一个新账号", + enterGroupCheck: "进群申请", + openPhotoError_1: "未读取到选择的文件", + openPhotoError_2: "文件上传失败", + openPhotoError_3: "未读取到文件信息", + agreeAll: "全部同意", + whatCanManagerDo: "群管理权限", + changeGroupName: "修改群名称", + deleteMemberMessage: "删除群员消息", + chatPrivate: "与群员加密私聊", + memberMute: "群员禁言", + deleteMember: "移除成员", + changeNotice: "发布/修改/删除群公告", + checkInGroup: "进群审核", + setAdmin: "设置管理员", + deleteAdmin: "移除管理员", + deleteAdminTitle: "确定要移除该管理员?", + deleteAdminDetail: "将此成员移除管理员,无法管理群消息", + deleteSuccess: "移除成功", + mobile_web_page: "访问手机网页", + deleteMemberTitle: "确定要移除该成员?", + deleteMemberDetail: "将此成员移出此群,无法收发消息", + deleteSelf: "不能移除自己", + handled: "已处理", + unhandle: "待处理", + noCheck: "暂无申请处理~", + success: "操作成功", + nameLenth: "请设置1-15个字符", + editNickname: "编辑群昵称", + text_password: "请设置新的登录密码", + quit: "退出", + waitAMoment: "我在看看", + editWarningTitle: "确定退出编辑群昵称吗?", + editWarningDetail: "您修改的群昵称还未保存,是否继续退出?", + editGroupAvatar: "编辑本群头像", + myGroupAvatar: "我的本群头像", + inviteLink: "邀请链接", + inviteTips: "任何已安装此APP的用户都将可以通过此链接加入您的群聊", + inviteLinkCopy: "邀请链接已复制", + shareLink: "分享链接", + manageLink: "管理邀请链接", + copyLink: "复制链接", + deleteLink: "删除链接", + deleteLinkTipsTitle: "确定要撤销该链接吗?", + deleteLinkTipsDetail: + "您确定要撤销此邀请链接吗?一旦撤销,其他人将不能通过此链接加入本群组。", + linkManage: "链接管理", + openManage: "开启后需群主或管理员确认才能进群", + timeValid: "有效时间限制", + linkInvalidTips: "邀请链接将再此日期后失效,无法继续访问", + valid: "内有效", + dayValid: "天内有效", + forever: "永久", + foreverValid: "永久有效", + attendTips: "加入请求已发送,管理员审核您的申请后,您就可以加入群组", + memberCountLimit: "此群聊人数已达上线", + linkIsOut: "邀请链接已过期", + requestAttend: "请求加入群聊", + needCheck: "此群聊仅在管理员审核后才能加入", + countLimit: "管理员人数已达上限", + openLock: "离开APP后,超时需要使用解锁方案", + closeLock: "启动app时无需验证", + checkTips: "请检查您的人脸或指纹权限是否开启~", + face: "面容解锁", + finger: "指纹解锁", + gestureLock: "手势密码解锁", + closed: "未开启", + opened: "已开启", + lanchProtect: "启动时保护", + noProtect: "无需保护", + lockSetting: "解锁设置", + drawLine: "绘制解锁图案", + drawOldLine: "绘制您的旧手势密码", + drawNewLine: "绘制您的新手势密码", + setGesturePassword: "设置手势密码", + checkGesturePassword: "验证手势密码", + needJoinPoint: "至少连接4个点,请重新绘制", + noFriendAdd: "沒有可添加好友", + noFriendDelete: "没有可移除好友", + noVerify: "添加我为好友时需要验证", + drawAgain: "请再次绘制", + checkSuccessful: "验证通过", + notInCommon: "与上次绘制不一致,请重新绘制", + changeGesturePassword: "修改手势密码", + toChange: "去修改", + change: "修改", + openSuccess: "启用成功", + closeSuccess: "关闭成功", + hasBeenMember: "已是群成员,不用再次加入", + joinGroup: "加入群聊", + checking: "管理员审核中", + noUnlock: "无法解锁?", + inputGesture: "请输入手势解锁", + faceDetect: "请进行人脸识别", + fingerDetect: "请进行指纹识别", + tryUnlocking: "请尝试:无法解锁?", + countRemain: "手势错误,您还可以输入 %s 次", + reLoginUnlock: "重新登录解锁", + selectVerityType: "请选择验证方式 ", + currentTypeNotSupport: "当前类型不支持", + //------------- + updateNewVersion: "发现新版本", + updateOkUpdate: "立即升级", + updateOpenDownloadFailed: "打开下载失败", + updateCurrentIsNew: "当前已是最新版本", + newVersionNotFound: "未找到最新版链接,您可以访问网页去下载", + openUrlFailed: "无效的链接", + qmvMsgNotExit: "[引用内容不存在]", + qmvMsgNotSupport: "[引用内容不支持展示]", + dataError: "数据解析错误", + userNotBindEmail: "用户未绑定邮箱", + userNotRegister: "用户未注册", + checkError: "检查失败", + + csvTitle: "聊天设置", + csvSetTopMsg: "置顶聊天", + csvSetOptMsg: "消息免打扰", + csvSetDisableMsg: "屏蔽消息", + csvClearMsg: "清空聊天记录", + csvClearLocal: "仅清空本地聊天记录", + csvClearUser: "清空本地和%s的聊天记录", + csvSetErrorTip: "设置失败,请检查网络", + + gmiTitle: "群昵称:", + gmiSetTalk: "加密私聊", + gmiSetMute: "设置群内禁言", + gmiDeleteUser: "移出本群", + gmiMuteTo: "禁言至 ", + gmiError1: "未找到此成员信息,可能被移出了群聊", + gmiError2: "未找到您的群成员信息,您可能被移出了群聊", + gmiError3: "确定要移除该用户?", + gmiError4: "将此成员移出此群,无法收发消息", + gmiError5: "您没有权限删除此管理员", + gmiError6: "您没有权限删除此成员", + gmiError7: "已解除禁言", + gmiError8: "您没有权限取消禁言此管理员", + gmiError9: "您没有权限取消禁言此成员", + gmiError10: "您没有权限禁言此管理员", + gmiError11: "您没有权限禁言此成员", + gmiError12: "解除禁言", + gmiGroupOwnerNotFound: "群信息加载失败", + + smmvTitle: "禁言时长", + smmvMuteTime: "选择禁言时长", + smmvMuteTime_10m: "10分钟", + smmvMuteTime_1hour: "1小时", + smmvMuteTime_12hour: "12小时", + smmvMuteTime_1day: "1天", + smmvMuteTime_30day: "30天", + smmvMuteTime_7day: "7天", + + egnpTitle: "编辑群聊名称", + egnpSetGroupName: "请设置群名称", + egnpSetTextDesc: "请设置1-15个字符", + egnpSetTitleNoPermission: "您没有修改群聊名称权限", + egnpSetFailed: "修改失败,请稍后再试", + + setGroupNameHint: "请设置群名称", + + givTitle: "编辑群聊名称", + givGroupName: "群名称", + givGroupFace: "群头像", + + gmsvTitle: "管理群", + gmsvSetManager: "设置管理员", + gmsvToSet: "去设置", + gmsvFindMsg: "查找聊天记录", + gmsvFindMsgDesc: "开启后入群成员将看到近期历史消息", + gmsvMute: "全员禁言", + gmsvMuteDesc: "开启后只允许群主和管理员发言", + gmsvApply: "进群确认", + gmsvApplyDesc: "开启后需群主或管理员确认才能进群", + + gmsvSetManagerTip: "只有群主才能设置管理员", + gmsvSetMuteTipOn: "已全员禁言", + gmsvSetNuteTipOff: "全员禁言已解除", + gmsvApplyTipOn: "进群确认已开启", + gmsvApplyTipOff: "进群确认已关闭", + gmsvFindGroupErr: "获取群信息失败", + gmsvFindMsgOn: "查找聊天记录已开启", + gmsvFindMsgOff: "查找聊天记录已关闭", + gmsvSetFailed: "设置失败", + + gsvTitle: "群详情", + gsvGroupId: "群ID", + gsvGroupMember: "群聊人员", + gsvGroupMemberValue: "共{num}人", + gsvGroupNotification: "群公告", + gsvMyName: "我的本群昵称", + gsvMyFace: "我的本群头像", + gsvGroupManager: "群管理", + gsvGroupApply: "进群确认", + gsvFindMsg: "查找聊天记录", + gsvSetTop: "置顶", + gsvSetOpt: "消息免打扰", + gsvDeleteMsg: "删除聊天记录", + gsvDeleteLocal: "仅清空本地聊天记录", + gsvDeleteAllMsg: "清空本地和所有成员的聊天记录", + gsvDeleteOtherMsg: '清空本地和对方的聊天记录', + gsvGroupDelete: "确定要解散该群聊?", + gsvOutGroup: "确定要退出该群聊?", + gsvNoNotification: "暂无公告", + gsvClearSuccess: "清除成功", + gsvClearFailed: "清除失败,请稍后再试", + + cvAllPeople: "所有人", + cvVideo: "[视频]", + cvPicture: "[图片]", + cvFile: "[文件]", + cvVoice: "[语音]", + cvNotSupportMsg: "暂不支持的消息", + cvSendMsgErrorNoPermission: "由于对方的权限设置,你无法向TA发送消息", + cvSendMsgPnMute: "禁言中,无法发送消息", + cvCopyMsg: "已复制到剪切板", + cvCopyMsgNotSupport: "暂不支持的消息", + cvdeleteLocal: "从本地删除", + cvdeleteLocalAndServer: "从本地和服务器删除", + cvdeleteLocalAndServerUser: "从本地和{name}删除", + cvClearMsgTip: "清除了会话消息", + + cvGroupCreateSuccess: "群聊创建成功", + cvGroupCreateSuccessTip1: "恭喜您建群成功,点击下方按钮邀请好友入群", + cvGroupCreateSuccessGo: "邀请好友入群", + cvGroupNotification: "群公告:", + sendMsg: "发送消息", + cvOnlineCount: "{count} 人在线", + cvUserCount: "共{count}人", + cvNotAtUser: "还没有群人员可以@哦,快去添加成员吧", + all: "全部", + + clCloseConversation: "折叠置顶聊天", + clOpenConversation: "个置顶聊天", + clNoConversation: "近期无会话~", + clTitle: "对话", + + clDelete: "此对话将删除", + clDeleteDesc: "删除此对话,将会删除本地聊天记录", + clDeleteSel: "您没有选择会话", + clDeleteSel1: "此对话将删除", + clDeleteSel2: "这些对话将删除", + clDeleteDesc2: "删除这些对话,将会删除本地聊天记录", + + homeItem0: "对话", + homeItem1: "联系人", + homeItem2: "我的", + exitTip: "再次点击返回键退出", + + mineSigEmpty: "这个用户很懒,暂无签名~", + uaProtocol: "用户服务协议", + + savSearchTip: "快速搜索聊天内容", + savMedia: "图片/视频", + searchEmptyTip: "没有找到相关的聊天记录", + savSearchMsg: "条相关聊天记录", + + savConversation: "对话", + savMsg: "聊天记录", + savConversationNotFound: "未找到对话", + savMsgNotFound: "未找到此信息", + + samTitle: "媒体", + samTimeFormat: "yyyy年M月", + notSupportType: "不支持的类型", + media: "媒体", + + openUrl: "打开链接", + lkAddGroupUrl: "加入群聊链接", + lkAddOtherUrl: "外部链接,可使用默认浏览器打开", + lkUnknowUrl: "未识别的链接", + openUrlFailed1: "打开链接失败", + + yszcTitle: "隐私政策", + slsmTitle: "法律声明", + + ctmRemove: "移除", + ctmOkRemove: "确定移除", + ctmTopMsgCount: "共{count}条", + ctmEmptyText: "没有置顶消息", + recordTimeShort: "录音时间太短", + + sendCodeExceErr: "发送验证码失败,请稍后再试", + bindEmailExecErr: "绑定邮箱失败,请稍后再试", + + ctmSuccess: "置顶成功", + ctmRemoveSuccess: "移除置顶消息成功", + ctmMsgNotFound: "此消息不存在", + + cvFirstTip: "此对话中的信息和通话已经进行端对端加密", + + clearGroupNotification: "清除了群公告", + publicGroupNotification: " 发布公告", + dialog_tip1: "温馨提示", + deleteGroups: " 分组删除后将不可恢复,是否确认删除该分组?", + dialog_tip_token_invaild: " 登录已过期,请重新登录", + + notify_new_msg_title: "您收到了一条新消息", + notify_new_msg_msg: "消息内容:.....", + dataError1: "数据错误", + groupApply: " 条进群申请", + muteLate: "后解除禁言", + groupMuteing: "群禁言中", + modifyGroupName: "修改分组名称", + groupMemberNotFound: "未找到群成员信息", + deleteGround: "删除分组", + + bindEmailTipTitle: "绑定邮箱", + bindEmailTipContent: "为了您的账户安全,请绑定您的邮箱", + bindEmailTipOk: "去绑定", + bindEmailTipCancle: "暂不绑定", + + alreadyBeOwner: "您选择的是群主,已拥有最高的管理权限", + managerApplying: "管理员审核中", + quote: "引用", + + request_exception: "请求异常", + clearMsg: "清除消息", + pressToSay: "按住说话", + slideToCancelSend: "手指上滑,取消发送", + releaseToEnd: "松开结束", + releaseToCancelSend: "松开手指,取消发送", + invalidVoiceMessage: "无效的语音消息", + hit: "打", + downloadingCache: "下载缓存中...", + unableToRecognize: "无效二维码", + scanResult: "扫码结果", + + register_fail: " 注册失败,请检查网络后再试", + skip: " 跳过", + toUse: " 立即体验", + sideToSide: " 端到端加密", + design: " 设计", + guide1: " 发送消息,加密传输,仅供自己阅读", + guide2: " 更加自由地按照自己的方式交流", + guide3: " 围绕您构建的安全通信平台", + inputGroupAc: " 请输入群公告(字符限制0-500)", + clickToEnjoy: + " 点击链接保存,或者复制本段内容,打开[球帝帶]APP,即可加入群聊。", + createOK: "创建成功", + noPravcy: "您没有创建群聊权限", + groupNumberToTop: "群组数量达到上限", + createFail: "创建失败,请稍后再试试吧", + create: "创建", + uploadGroupAvatar: "上传群头像", + useNewPassword: "密码修改成功,即将返回登录页", + + error_14: "服务器异常(14)", + error_500: "服务器异常(500)", + error_1000: "用户签名token不合法", + error_1001: "参数错误", + error_1002: "当前版已是最新版本", + error_6001: "此用户名已被注册", + error_6003: "密码必须由6-15位字母及数字组成", + error_6004: "用户名校验出错", + error_6005: "密码不正确,请重新输入", + error_6006: "今日密码错误次数超过上限", + error_6007: "用户不存在,请检查确认", + error_6008: "旧密码错误,请重新输入", + error_6009: "旧密码与新密码一致,请重新输入", + error_6010: "频繁获取验证码", + error_6011: "验证码不正确,请重试", + error_6012: "验证码过期", + error_6013: "验证码失败次数过多", + error_6014: "验证码已经使用", + error_6015: "邮箱不存在", + error_6016: "邮箱已被他人绑定,请重新录入", + error_6017: "验证码不存在", + error_6018: "该账户已被封禁", + + error_6019: "手机号已被他人绑定", + error_6020: "邮箱地址已被他人绑定", + error_6021: "邮箱地址有误,请重新输入", + error_6022: "手机号有误,请重新输入", + error_6023: "该账户已被封禁,暂不支持找回密码", + error_6024: "该账户未绑定邮箱,暂不支持找回密码", + error_6025: "该账户未绑定手机号,暂不支持找回密码", + error_6026: "手机号已被他人绑定,请重新录入", + error_6030: "服务器邮箱未配置", + error_6031: "请输入正确的手机号码", + error_6032: "请输入正确的邮箱地址", + error_100002: "置顶消息达到上限", + error_100003: "此消息已置顶", + error_api: "请求错误", + error_http: "网络请求错误", + + error_http_11: "网络连接超时", + error_http_12: "网络连接超时", + error_http_13: "网络连接超时", + error_http_14: "网络证书错误", + error_http_15: "请求数据错误", + error_http_16: "请求已取消", + error_http_17: "网络连接超时", + error_http_18: "网络请求内部出错", + + error_sdk_err_1002: "无权限操作", + error_sdk_err: "操作失败", + + search_hine_1: "请输入搜索内容", + str_no_data: "暂无数据", + str_no_net: "暂无网络", + + download_not_support_type: "不支持的格式", + download_save_at: "文件存储在: ", + download_not_url: "未识别到下载链接", + download_file_success: "保存成功,您可以到相册查看", + download_failed_file: "文件保存失败", + download_failed: "下载失败", + + loading_failed_retry: "加载失败,点击重试", + loading_ing: "加载中...", + + str_online_later_minute: "分钟前在线", + str_online_later_hour: "小时前在线", + str_online_later_llg: "很久没上线", + str_online_later_yesterday: "昨天在线", + str_yesterday: "昨天", + str_online_later_near: "近期在线", + + str_minute: "分", + + clean_notice: "清空公告", + clean_notice_1: "确定要清空群公告吗?", + publish_notice: "发布公告", + publish_notice_remind: "发布该公告将提醒所有群成员", + quit_edit: "退出编辑", + quit_the_edit: "退出本次编辑?", + continue_edit: "继续编辑", + publish_success: "发布成功", + face_success: "面容解锁开启成功", + finger_success: "指纹解锁开启成功", + groupDismissed: "该群已解散", + hasOut: "已过期", + + otherLoginStyle: "其他登录方式", + otherRegisterStyle: "其他注册方式", + hasAccount: "已有账号,去登录", + mobileCode: "手机验证码", + codeSendTo: "验证码已发送至", + + inputNumber: "请输入手机号", + + bindPhone: "绑定手机号", + bindPhoneSuccess: "手机号绑定成功", + bindPhoneTipChange: "若您的手机号已不用,请及时更换", + please_input_email: "请输入邮箱地址", + findPassByEmailTip: "用邮箱找回密码", + findPassByPhoneTip: "用手机号找回密码", + resetPass: "重设密码", + current_bind_phone: "当前绑定手机号:", + reget: "重新获取", + getVerifyCode: "获取验证码", + alreadyRegister: "此手机号已被注册", + alreadyRegister2: "此邮箱地址已被注册", + netErrorRetry: "网络出小差了,点击页面重新加载~", + newPhoneNumber: "新的手机号", + + addFriendText1: "加为好友", + + qrSaveFail: "二维码保存失败", + qrSavePhoto: "二维码已保存到相册", + userId: "用户ID:", + addMeToFriend: "加我为好友", + validTime: "有效期至", + clickRefresh: "点击刷新", + qrUseCount: "此二维码{count}次有效", + share: "分享", + savePhoto: "保存图片", + + iAm: "我是", + addFriendSuccessful: "添加好友成功", + addFriendFaceToFace: "面对面加好友", + faceToFace: "面对面加好友", + inputSameKey: "以下是已输入同样数字的好友", + sameKeyAddFriend: "和好友输入同样数字添加好友", + self: "自己", + age: "岁", + confirmAddFriend: "确认加为好友", + + inputUserId: "请输入用户ID", + myQr: "我的二维码", + back: "返回", + canNotAddSelf: "你不能添加自己到联系人", + notExsit: "该用户不存在", + searchUserId: "搜索用户ID:", + + remarkInfo: "请备注验证信息", + applyRemark: "备注", + last4Number: "请输入对方登录手机号后4位", + checkLast4Number: "验证手机号后4位", + addFriendApply: "添加好友申请", + friendNumberCatchTopLimit: "您的好友数量已达上限", + friendNumberCatchTopLimitCurrentDay: "当天添加好友已达上限", + codeInvalid: "无效密钥或失效,请重新发起", + shareFail: "分享失败", + + error_1003: "请稍后重试", + error_6033: "无效密钥或失效,请重新发起", + error_6034: "好友总数已达上限", + error_6035: "当天添加好友已达上限", + error_6036: "该用户手机号为空", + error_6037: "手机尾号验证失败", + error_6040: "分组已存在", + error_6050: "二维码已过期", + error_6051: "当日二维码使用次数已达上限", + error_6052: "二维码使用次数已达上限", + error_6053: "今日扫一扫功能次数已达上限", + error_6054: "对方已添加您为好友", + error_6055: "无效密钥或失效,请重新发起", + error_6066: "账户余额不足", + error_6067: "红包已领完", + error_6068: "红包已过期", + error_6069: "无法领取专属红包", + error_6070: "已领取红包,无法再领取", + + deletedByFriendHint: "你还不是TA朋友 ", + deletedByFriendHint1: "你还不是TA朋友,", + deletedByFriendHint2: "添加好友", + deletedByFriendHint3: "继续聊天", + gotoAddFriend: "去添加好友", + addFriendGotoGroup: "邀请好友入群", + notContacts: "暂无联系人", + notContactsFound: "没有找到{name}相关的好友", + remarkStr: "备注", + remarkStrHint: "请输入备注", + + deleteConfirm: "是否确认删除好友", + deleteFriend: "删除好友", + searchContact: "搜索联系人", + notGroupTip: "还没有加入群聊", + notAddFriend: "还没有添加新朋友", + friendApplyAdd: "好友申请", + addSuccess: "添加成功", + hasSelectFriend: "已选择好友", + + myBalance: "我的余额", + + //------------------ + revokeMsgTip: "撤回消息", + error_10050: "操作失败(未找到记录)", + set_manager_tip_1: "您选择的是群主,已拥有最高的管理权限", + envelope: "红包", + uidStr: "用户ID", + showGetDetail: "查看领取详情", + waitOtherEnter: "等待对方加入", + isFriend2SExit: "已成为好友,2秒后自动退出", + + sendRedPacket: "发红包", + redPacketNumber: "红包个数", + ge: "个", + total: "总数", + eachNumber: "单个数量", + selectUser: "选择用户", + number: "数量", + redPacketType: "红包类型", + checkVerity: "安全验证", + balance: "账户余额", + sendTo: "短信验证码已发送至", + + pin: "拼手气红包", + putong: "普通红包", + zhuanshu: "专属红包", + dajidali: "恭喜发财,大吉大利!", + + redPacketInfo: "红包详情", + best: "手气最佳", + saveToAccount: "已存入账户", + minuteOut: "分钟抢光", + hourOut: "小时抢光", + showDetail: "查看详情", + maxMemberNumber: "红包个数不可超过当前群聊人数", + sended: "发出的", + get: "已领取", + waiting: "等待用户领取", + gong: "个红包共", + + envelope_get_over: "的红包已被领完", + envelope_get: "领取了", + de: "的", + envelope_get_message: "领取红包消息", + bind_phone_please: "为了您的账户安全,请绑定手机号", + delay_bind: "暂不绑定", + send_envelope_x: "发出的红包", + send_envelope_back_all: "该红包超过24小时未领取\n已退还", + envelope_x_can_get: "仅{name}可领取", + envelope_get_completed: "手慢了,红包抢光了", + send_envelope_back_some: "该红包超过24小时未领完\n部分已退还", + envelope_x_personal: "给%s的专属红包", + envelope_normal: "普通红包", + login_out_of_date: "登录已过期,重新登录", + get_envelope_completed: "已被领完", + x_is_personal: "专属", + + error_6072: "已达到单个红包的最大金额", + error_6073: "单个红包的数量不能低于0.01", + error_6074: "红包功能已关闭", + + distroyAccount: "注销帐号", + + inComing: "收入", + outGoing: "支出", + balance_order_detail: "余额明细", + type: "类型", + + payType: "支付方式", + orderNo: "订单号", + time: "时间", + itemDataNotSupport: "当前版本暂不支持", + + manager_change_account: "管理员调账", + groupEnvelope: "群红包", + accountPay: "余额支付", + paySuccess: "支付成功", + + groupEnvelopePersonal: "群红包-专属红包", + groupEnvelopeNormal: "群红包-普通红包", + changeAccount: "调账", + groupEnvelopeRandom: "群红包-拼手气红包", + + groupEnvelopeAllBack: "群红包-全部退还", + groupEnvelopeSomeBack: "群红包-部分退还", + + distroyTips: "为了确认您的账户安全
我们将在您注销账户前进行以下验证", + safeStatus: "账户处于安全状态", + safeTips: + "请确保您的账户没有改密、换绑手机/邮箱等敏感操作,账户没有被盗风险。", + strikeBalance: "账户财产结算", + strikeTips: "注销账户后,您账户的红包余额数据都将删除、且无法恢复。", + dataDelete: "账户数据删除", + deleteTips: + "注销账户后,该账户下的所有数据都将删除、且无法恢复。数据包括您的聊天记录、加入的群聊、好友关系等内容数据。", + applyDistroy: "申请注销帐号", + distroyConform: "确定要注销账号吗?", + distroyConformTips: "注销后将退出登录,请谨慎操作!", + distroy: "确认注销", + submitSuccess: "提交成功", + submitted: "您的注销申请已提交!", + helpWord: + "平台将在{day}个工作日内处理您的申请并删除您的所有数据,如需帮助,请通过官网联系我们。", + know: "我知道了", + revoke: "撤销申请", + revokeSuccess: "撤销成功", + recover: "确定要撤销申请吗?", + toNormal: "撤销后账号状态变为正常", + beRecover: "确认恢复", + recoverSuccess: "恢复成功", + redPackageCountTip: "单个红包总数不可低于0.01", + + closeAccount: "注销账号", + recoverAccount: "恢复账户", + recoverAccountTipTitle: "确定要恢复账号吗?", + recoverAccountTipDesc: "恢复后账号状态变为正常", + recoverAccountTipOk: "确认恢复", + + closingAccount: "账户处于注销审核中", + closingAccountEndTime: ",预计将在{num}个工作日内完成删除", + error_6079: "您已不在群聊,无法领取", + + error_6075: "重复申请", + error_6076: "没有找到任何注销申请记录", + error_6077: "注销审核中", + error_6078: "用户不存在或已注销,请检查确认", + error_video_pre_err: "文件生成错误,数据加载失败。", + err_upload_file: "上传失败,请稍后再试", + err_msg_send_failed: "发送失败,请稍后再试", + error_red_package_tip1: "红包已过期", + net_error_no_net: "网络不可用,请检查你的网络设置", + upload: "上传", + error_6081: "发送失败,请稍后重试", + + atTip: "有人{'@'}你", + groupChatError: "内容加载失败,点击重试", + + legalNotice: "法律声明", + timeoutLock: "超时锁定", + reTry: "重试", + plsEnterAccount6_15: "用户名 (6-15为字母及数字)", + plsEnterPassword6_15: "密码 (6-15为字母及数字)", + sysNotify: "系统通知", + exit_group_tip: "已退出群聊", + group_mute_ing: "本群已禁言", + w_edit_group_notification: "编辑了群公告", + removeGroupNotice: "删除群公告通知", + messageNotify: "消息通知", + groupEnterLimit: "被邀好友进群数已达上限,请检查", + tipAllGroupMember: "提醒所有群成员", + unRecard: "无法录音:", + hasCreateGroup: "已创建群聊", + joinGroupForLink: "通过链接加入群聊", + be: "被", + moveoutGroup: "移出群聊", + groupOwnerChangeTo: "群主已变更为", + changeGroupNameTo: "更改群名称为", + disbandedGroup: "解散了群聊", + bannedFromSpeaking: "禁言", + gagWasLifted: "取消了禁言", + initiatedGroupGag: "开启了群禁言", + shutDownGroupGag: "关闭了群禁言", + target: "对方", + groupSizeUpperLimit: "群人数达到上限", + topUpperLimit: "置顶达到上限", + topMessageRepeate: "置顶消息已重复", + userHasReadMessage: "该用户已读过此消息", + placeholder: { + allMuted: "群主或管理员已开启全体禁言", + groupDisbanded: "该群组已被解散", + groupBanned: "该群组已被封禁", + singleBanned: "你已被禁言", + search: "搜索", + pleaseInput: "请输入", + sec: "秒", + groupNoticePlaceholder: "请输入群公告..", + inputPassword: "请输入密码", + confirmPassword: "请确认输入密码", + inputPhoneNumber: "请输入手机号", + select: "请选择", + userOrGroupIDInput: "请输入{type}", + writeMessage: "输入信息...", + inputGroupName: "请输入群名称", + typingMessage: "在这里输入消息", + inputName: "请输入你的昵称" + }, + withoutQualification: "该用户未达到添加好友条件", + pleaseBind: "为了您的账户安全,避免添加好友功能受限,请绑定", + orStr: "或", + andStr: "和", + confirmExit: "确认退出", + pleaseBindTip1: "为了您的账户安全,请绑定", + skin_item_user_name: "张娜娜", + skin_item_msg1: "你好,收到及时回复", + skin_item_msg2: "好的,收到了", + + appearance: "外观", + bubbleBgColor: "对话气泡颜色", + chatBackground: "聊天背景", + moreSettings: "更多设置", + + highPerformanceModel: "纯净模式", + highPerformanceModelDesc: "开启则不显示外观效果", + skinPreview: "效果预览", + imageAlpha: "图案透明度", + groupMask: "群聊面具", + groupMaskDescription: "设置本群昵称和头像", + maskDesc: "开启后入群成员将可设置本群昵称和头像", + + notSet: "暂不", + min: "最小", + max: "最大", + backgroundColor: "背景色", + changeSaveTip: "您已修改设置,是否保存后返回", + saveAndBack: "保存并返回", + changeAvatar: "更换头像", + appFontSize: "全局字体大小", + appFontSizeTip: + "预览字体大小,拖动下面的滑块,可设置字体大小。设置后,会改变应用内所有文本的字体大小。", + groupMaskOpenTip: " %s了群聊面具%s", + groupMaskOpenTipNew:"了群聊面具", + open: "开启", + close: "关闭", + groupMaskOpenMsgErrorTip: "群聊面具功能开关提示", + maskOpen: "群聊面具已开启", + maskClose: "群聊面具已关闭", + groupEnterLimit: "被邀好友进群数已达上限,请检查", + transmit: "转发", + multiSelect: "多选", + singleShareMsg: "逐条转发", + groupShareMsg: "合并转发", + notGroupFound: "没有找到{name}相关的群聊", + + rememberPassword: "记住密码", + mobileLogin: "手机登录", + emailLogin: "邮箱登录", + recentConversation: "最近的聊天", + selectContact: "选择联系人", + selectGroup: "选择群聊", + noGroup: "暂无群聊", + notSelectMessage: "您还没有选择消息", + hiddenMsg: "隐藏消息", + hiddenMsgTip: "是否隐藏消息来源?", + yes: "是", + no: "否", + mergeMessageList: "【合并转发】聊天记录", + singleMlCount: "【逐条转发】共{count}条消息", + notFoundUser: "未找到用户", + selectAt: "选择要{'@'}的人", + Someone: "有人{'@'}我", + welcomeToQDDChat: "欢迎来到球帝带", + choiceYourAvatar: "请选择您的头像", + enterPawAgain: "请再次输入密码", + hasSelect: "已选", + groupChat: "群聊", + groupMember1: "群成员", + groupSendMsg: "群发消息", + msgNotSupportTransmit: "消息不支持转发", + bindGoogle: "绑定Google", + bindFacebook: "绑定Facebook", + unbind: "解绑", + accountSettings: "账户设置", + thirdPartyAccountBinding: "第三方账号绑定", + otherSettings: "其他设置", + groupChatLoadFailed: "群聊信息加载失败", + addGroupSend: "发起群发", + groupSendNoData: "还没有群发消息记录", + removeBinding: "解除绑定", + unBindToastPart1: "解除绑定后,将不能再用", + unBindToastPart2: "账号登录,要继续解除吗?", + noOpPermission: "还没有可操作的权限", + bindingPhoneOrEmail: "为了您的账户数据安全,请先绑定手机号或邮箱再解除绑定。", + unbindingSuccess: "解绑成功", + unbindingFair: "解绑失败", + showAll: "查看全部", + allCount: "全部共{num}个", + bindingPhoneOrEmailSetPassword: + "为了您的账户数据安全,请先绑定手机号或邮箱再设置密码。", + recoverSuccessLoginAgain: "恢复成功,请重新登录", + createGroupSend: "发起群发", + groupSend: "群发", + notSelect: "您还没有选择", + confirmDeleteTitle: "确认删除选中的群发?", + totalGe: "共%s个", + sendTotalGe: "发送给({num}个)", + uploadFailed: "上传失败", + selectMax: "选择数量到达上限", + newFriendApply: "您有新的好友,点击查看", + notSupportLoginStyle: "暂不支持,请使用其他方式登录", + error_6091: "选择人数超过上限", + error_6092: "消息id已存在", + pay_password: "支付密码", + pleaseEnter: "请输入", + loadingPage: "页面加载中", + payTip: "您还未设置支付密码,请完成设置后再进行操作。", + deleteRedPackageErrTip: "红包消息不可撤回", + applyIconTip: "申请已提交, 审核通过即可更换头像", + applyIconProgress: "查看申请进度", + applyRecord: "申请记录", + repealWithdrawApply: "确定撤销提现申请吗?", + withdrawNotes: "提现记录", + inReview: "审核中", + passTheAudit: "审核通过", + rejectedPayment: "审核驳回", + revoked: "已撤销", + undone: "撤销", + withdrawalQuantity: "提现数量", + withdrawal: "提现", + withdrawalBack: "提现退回", + update_tip1: "若无法更新", + update_tip2: "可前往官网官网下载", + update_ing: "更新中", + update_install: "立即安装", + installFailed: "未安装,请稍后重试", + installNotFoundFile: "未找到安装文件,请稍后重试", + error_6093: "登录方式不支持", + error_6094: "头像数量已达上限", + deleteIcon: "是否删除该头像?", + balanceWithdraw: "余额提现", + canBalanceWithdraw: "可提现余额", + inputPayPassword: "请输入支付密码", + withdrawalSuccess: "提现成功", + personIcon: "个人头像", + groupIcon: "群聊头像", + error_6096: "请勿设置重复、连续数字的支付密码", + forgetPayPassword: "忘记支付密码", + downloadFailedTip: "下载失败,请稍后重试", + error_6097: "余额不足", + passed: "已通过", + headRejected: "已驳回", + readDeleteTime: "消息焚烧时长", + readDeleteTimeTitle: "焚烧时长", + error_6090: "账号已被绑定", + notSet2: "未设置", + retracted_h5: "撤回了", + message_h5: "的消息", + inputRemark: "请输入您的备注", + authFailed: "授权失败", + userNullError: "该用户账号异常", + community: "社区", + join: "加入群聊,探索更多社区活动~", + posteviews: "还没有发布活动~", + views: "次观看", + ended: "活动已结束", + configError: "配置错误,暂不能开启", + burnMsgOpen: "{name}设置了消息已读{time}后焚毁", + burnMsgClose: "{name}关闭了阅读即焚,消息不再自动焚毁", + burnMsgNotify: "阅读即焚通知", + homeItem3: "社区", + messageTimeOut: "消息已过期", + burnContentTip: "消息正在焚毁中,是否退出并一键焚毁?", + cancelLater: "我再看看", + burnConfirm: "立即焚烧", + setNickNameTimes: ",昵称在%s天内仅可修改一次", + voiceSendErrTip1: "仅支持发送%s-%s秒的语音", + textSendErrTip1: "最多输入%s个字符", + pictureSendErrTip1: "仅支持发送%s-%sM的图片", + videoSendErrTip1: "仅支持发送%s-%sM的视频", + your: "您", + setNickNameTimes: "{day}天内仅可修改一次", + immutable: "暂不可更改", + joinCommunity: "加入群聊,探索更多社区活动~", + noActivity: "还没有发布活动~", + views: "次观看", + ended: "活动已结束", + errorLink: "找不到链接", + set_payment_password: "请设置6位支付密码", + unset_payment_password: "请勿设置重复或连续数字的支付密码", + Confirm_Payment_Password: "确认支付密码", + Please_confirm_payment_password: "请再次输入确认支付密码", + error_6100: "抱歉,您只能每{day}天修改一次个人昵称", + privateSetting: "权限设置", + viewDetailed: "查看群成员详细信息", + inviter: "邀请人", + lastLoginTime: "最后登录时间", + DeleteThisUsersgroup: "删除该用户在本群发送的群聊消息", + AddGroupMembers: "添加群成员免校验同意", + error_6103: "该用户为普通成员", + error_6104: "被添加者不在群内", + error_6108: "账户异常", + group_remark: "群聊备注", + input_group_remark: "请输入备注内容", + await_accepting: "设备授权申请已发送", + apply_tips: "此设备不可信或审核未通过,是否发送设备信息进行授权申请?", + apply_send: "发送申请", + apply_ttl: "设备登录授权", + group_limit500: "每个分组人数上限为500人", + select_group: "选择好友分组", + cantfind: "没有找到{name}相关的联系人", + error_6109: "该用户数好友已达上限", + error_6106: "请等待设备审核通过后再登录", + selectGroups: '选择分组', + group_member_info: "群员信息", + join_time: "入群时间", + inviter_user: "邀请人", + last_login_time: "最后登录时间", + sync_group_avatar: '是否同时修改群聊头像?', + invite_fail: '{usernames}账号异常,邀请失败!', + enterAndCtrl:'Enter 发送 / Enter+Ctrl 换行', + enterAndCtrlIOS:'Enter 发送 / Enter+control 换行', + onlineStatus: "在线状态:", + scanLogin: '扫码登录', + formLogin: '密码登录', + scanText: '球帝带扫码登录', + scanSubText: '使用球帝带APP扫码登录', + qrcodeExpired: '二维码已过期', + expiredTime10: '此二维码10分钟内有效', + reflesh: '点击刷新', + scanSuccess: '扫描成功', + confromLogin: '请确认登录', + networkDiagnostic: "网络诊断", + "setPaymentPassword": "请设置6位支付密码", + "unSetPaymentPassword": "请勿设置重复或连续数字的支付密码", + "verifyPassword": "验证支付密码", + "confirmPaymentPassword": "确认支付密码", + "verifyCurrentPassword": "请验证当前的支付密码", + "reInsetPaymentPassword": "请再次输入确认支付密码", + "paymentPasswordDifferent": "密码不一致,请重新输入", + "getPaymentPasswordWay": "请选择找回密码的方式", + "deviceID": "设备ID:", + "groupChatRemark": "群聊备注", + "selectFriendGroup": "选择好友分组", + "selectGroups": "选择分组", + "eachGroupLimitNumber": "每个分组人数上限为{p}人", + "selectChatLimitNumber": "最多只能选择{p}个聊天", + "pleaseEnterRemarks": "请输入备注内容", + "groupMembershipInfo": "群员信息", + "error_6102": "每个分组人数上限为500人", + "error_6105": "非可信设备,请提交申请", + "error_6106": "请等待设备审核通过后再登录", + "error_6107": "设备审核中未通过", + "distroyed": "已注销", + "forbiddened": "已封号", + "addGroupMemberWithoutApply": "添加群成员免校验同意", + "error_6109": "该用户好友人数已达上限", + "apply_ttl": "设备登录授权", + "apply_tips": "此设备不可信或审核未通过,是否发送设备信息进行授权申请?", + "apply_send": "发送申请", + "device_auth_send": "设备授权申请已发送", + "using_trusted_device": "该设备登录存在风险,请使用可信设备登录", + "account_closed": "账号已注销", + "google_code": "谷歌验证码", + "input_google_code": "请输入谷歌验证码", + "hasDetailInfoPermission": "查看群成员详细信息", + "editUserHeaderWithGroupHeader": "是否同时修改群聊头像?", + "darkModel": "深色模式", + "darkModelSetDesc": "跟随系统打开或关闭深色模式", + "whiteModel": "浅色模式", + "fileNotFound": "未读取到文件", + "openFileInApp": "预览文件", + "openFileOtherApp": "其他应用打开", + "openByBrowser": "用浏览器下载", + "pauseDownload": "暂停下载", + "resumeDownload": "继续下载", + "downloadFailed": "下载失败", + "downloading": "下载中", + "inviterUser": "邀请人", + "lastLoginTime": "最后登录时间", + "notdownloaded": "未下载", + "cancelDownloading": "文件正在下载中,是否取消下载?", + "failedToDownload": "文件下载失败,是否重新下载?", + "cancelDownload": "取消下载", + "sending": "发送中", + "fileTypeNotSupported": "不支持上传该文件格式", + "fileSizeOut": "文件大小超出", + "restrict": "限制", + "downloadFailedTip1": "下载失败,请检查网络后重试", + "downloadQueueTip": "已加入下载队列,等待下载", + "notSupportTransmitTip": "消息暂不支持转发", + "getWithPhone": "通过手机号找回", + "getWithEmail": "通过邮箱地址找回", + "fileSize": "文件大小", + "whetherDownloadFile": "是否下载文件", + "groupDeleteUserTip": "同时删除该用户在本群发送的消息", + "voiceSize": "音频文件大小不可超过100MB。", + "wordSize": "文档文件大小不可超过100MB。", + "apkSize": "安装包文件大小不可超过300MB。", + "zipSize": "压缩包文件大小不可超过300MB。", + "nPersonRead": "{p}人已读", + "darkModeEnabled": "深色模式已开启,重启APP后生效", + "darkModeOff": "深色模式已关闭,重启APP后生效", + "otherUser": "对方", + "selectMsgDeleteTime": "选择保留时间", + "selectMsgDeleteTimeTip": "聊天记录将在超出保留时限后自动删除,是否继续?", + "msgSaveTime": "聊天记录保留时间", + "msgSaveTimeDesc": "自动清除超过时效的聊天记录", + "hasTimeDeleteMsgPermission": "群聊消息保存时间配置", + "accountAbnormal": "账号异常,邀请失败!", + "onlyBrowser": "仅支持浏览器查看", + "cancelSendSuccess": "已取消发送", + "allStr1": "全局", + "modelStr1": "日夜模式", + "atMaxCountTip": "@最多 {p} 人", + + "checkNet": "网络检测", + "autoChangeServer": "一键切换到最优线路", + "serverLine": "线路", + "timeOut": "超时", + "serverLineChange": "切换线路", + "netCheck": "网络诊断", + "netDelayCheck": "延迟检测", + "netDelay1Check": "互联网延迟", + "msgDelay1Check": "消息延迟", + "imgDelay1Check": "图片延迟", + "netLine": "网络线路", + "reCheck": "再测一次", + "netChecking": "网络检测中", + "locationAddr": "归属地", + "selectPerfectLineTip": "已为您选择最佳线路", + "selectPerfectLineTip2": "当前已是最佳线路,无需切换", + "selectPerfectLineTipNoNet": "未识别到最佳线路,请检查网络后重试", + "changing": "切换中", + "changeSuccess": "切换成功", + "autoChanging": "智能切换中,请稍后再试", + + "transmitTo": "转发给", + "moreActions": "更多", + "refreshing": "刷新", + "openInBrowser": "浏览器打开", + "coverFlow": "悬浮", + "confirmLogin": "确认登录", + "toolboxAudioCall": "语音通话", + "requestTimeOut": "请求超时,请稍后重试", + "error_6115": "二维码已过期", + "groupNotFound": "找不到该群聊", + "userNotFound": "找不到该用户", + "waitAnswer": "等待接听", + "callFinish": "通话结束", + "microphoneOpen": "麦克风开启", + "speakerOpen": "扬声器开启", + "voiceWithYou": "邀请您语音通话", + "fileSizeConnotZero": "文件大小不能为0", + "callInterceptor": "通话中断", + "startCallError": "启动通话错误", + "callHungUp": "通话已挂断", + "callRemoteHungUp": "对方挂断", + "callReceiverError": "接听通话错误", + "starting": "启动中", + "startTimeOut": "语音通话发起超时", + "callWaitReceiver": "等待对方接听", + "callRemoteNotAccept": "对方未接听", + "callStartSendError": "发起语音通话失败", + "notAccept": "未接听", + "callAccepting": "接听中", + "call": "通话", + "callDoing": "线路忙,已取消", + "netBad": "当前网络环境较差", + "netDown": "网络断开", + "onCalling": "正在通话中", + "error_1303": "您还不是对方的好友", + "remoteAccountError": "对方账号异常", + "userOnlineStatus": "在线状态", + "error_100008": "该用户未达到成为管理员条件", + "callTimeLimit": "每次通话最长{p}分钟", + "notLoginOnline": "从未登录", + "callRemoteDoing": "忙线中", + "exitLoginIng": "正在退出", + "usePhoneRecovery": "使用手机号找回", + "useEmailRecovery": "使用邮箱找回", + "otherRecoveryMethods": "其他找回方式", + "emailAddress": "邮箱地址", + "phoneErrorOrNotExist": "手机号错误或不存在", + "phoneNotExist": "手机号不存在", + "verificationCodeFailure": "验证码获取失败,请重试", + verificationCodeSent: "验证码已发送", + hiWelcomeLogin: "Hi~ 欢迎登录", + otherLoginMethods: "其他登录方式", + usernamePhoneEmail: "用户名/手机号/邮箱", + loginAgree: "登录注册即同意", + "error_1303": "您还不是对方的好友", + "remoteAccountError": "对方账号异常", + "userOnlineStatus": "在线状态", + "error_100008": "该用户未达到成为管理员条件", + "callTimeLimit": "每次通话最长%s分钟", + "notLoginOnline": "从未登录", + "callRemoteDoing": "忙线中", + "exitLoginIng": "正在退出", + "docReadError": "文件无法读取,请检查文件格式是否正确", + "errorDetail": "错误详情", + "searchAreaCode": "搜索国家/地区", + "fileReadError": "文件读取出错", + "otherFindStyle": "其他找回方式", + "welcome": "欢迎登录", + "docPreviewError":"文件无法预览", + "docPreviewUseOther":"如果由于网络或机型不兼容等原因,出现文件无法预览,您可以使用", + "netTimeOut":"网络超时", + ip: "IP", + imageLoadingRoute: "图片加载线路", + networkChecking: "网络检测中...", + noPermission: "没有权限,请获取权限后登录", + pageNotFound: "页面不存在", + serverError: "服务器故障", + databaseError: "数据库查询错误", + networkError: "网络连接错误" +}; diff --git a/src/lang/zh-hant-cn.js b/src/lang/zh-hant-cn.js new file mode 100644 index 0000000..c66a9c3 --- /dev/null +++ b/src/lang/zh-hant-cn.js @@ -0,0 +1,1747 @@ +export default { + checkTopMessage:'查看置頂消息', + unsupportedVideoFormat: "不支持上傳該視頻格式", + welcome: "歡迎使用OpenIM", + phoneNumber: "手機號", + plsEnterPhoneNumber: "請輸入您的手機號", + password: "密碼", + plsEnterPassword: "請輸入您的密碼", + account: "賬號", + plsEnterAccount: "請輸入您的賬號", + forgetPassword: "忘記密碼", + createAccount: "創建賬號", + verificationCodeLogin: "驗證碼登錄", + login: "登錄", + noAccountYet: "還沒有賬號?", + loginNow: "立即登錄", + registerNow: "立即註冊", + friendGrouping: "好友分組", + lockPwdErrorHint: "錯誤%s次", + newUserRegister: "新用戶註冊", + verificationCode: "驗證碼", + verificationSuccessful: "驗證成功", + sendVerificationCode: "發送驗證碼", + verification_code1: "請輸入驗證碼", + correct_email_address: "請輸入正確的郵箱地址", + Resend: "重新發送", + bind_email: "綁定郵箱", + email_verification: "郵箱驗證", + login_details: "登錄詳情", + verification_code: "請輸入郵箱驗證碼", + email_verification_code: "郵箱驗證碼", + currently_bound_email: "當前綁定郵箱:", + text_email: "如您的郵箱已不用,請及時更換:", + remove_this_device: "確定要移除該設備嗎?", + remove_this_device1: + "確定要移除該設備嗎?移除後重新使用該設備,登錄需重新驗證", + go_to_settings: "去設置", + go_to_binding: "去綁定", + date_of_birth: "出生日期", + confidential: "保密", + not_perfection: "未完善", + old_password: "舊密碼", + please_enter_old_password: "請輸入舊密碼", + trusted_device: "可信設備", + change_email_address: "換綁郵箱", + current_device: "當前設備", + remove_device: "移除設備", + current_device_records: "以下是您該設備的全部登錄記錄", + text_device: "登錄設備管理", + verification_time: "驗證時間:", + text_device1: "以下是您的全部登錄設備,點擊進入詳情,查看該設備的登錄記錄", + change_the_binding: "換綁", + resendVerificationCode: "重發驗證碼", + verificationCodeTimingReminder: "%sS後重新獲取驗證碼", + defaultVerificationCode: "(默認驗證碼:%s)", + plsEnterVerificationCode: "請輸入您的驗證碼", + invitationCode: "邀請碼", + device_list: "設備列表", + plsEnterInvitationCode: "請輸入您的邀請碼%s", + optional: "選填", + nextStep: "下一步", + plsEnterRightPhone: "請輸入正確的手機號", + enterVerificationCode: "輸入手機驗證碼", + setPassword: "設置密碼", + plsConfirmPasswordAgain: "請再次確認您的密碼", + text_password2: "請再次確認登錄密碼", + please_enter_new_password_again: "請再次輸入新密碼", + confirmPassword: "確認密碼", + wrongPasswordFormat: "密碼格式錯誤", + plsCompleteInfo: "請完善個人信息", + plsEnterYourNickname: "請輸入您的昵稱", + pleaseEnterYourUsername: "請輸入您的用戶名", + enterYourUsernameToHelpUsIdentifyYourAccount: + "輸入您的用戶名以幫助我們識別您的賬號", + text_1: "該賬戶未綁定郵箱,暫不支持找回密碼", + setInfo: "設置信息", + loginPwdFormat: "6-20位數字+字母", + passwordLogin: "密碼登錄", + contacts: "通訊錄", + workbench: "工作臺", + mine: "我的", + me: "我", + draftText: "草稿", + everyone: "所有人", + you: "你", + groupAc: "群公告", + createGroupNtf: "%s 已創建群聊 ", + editGroupInfoNtf: "%s 修改了群資料", + quitGroupNtf: "%s 退出了群聊", + invitedJoinGroupNtf: "%s 邀請 %s 加入群聊", + kickedGroupNtf: "%s 被 %s 移出群聊", + joinGroupNtf: "%s 加入群聊", + dismissGroupNtf: "%s 解散了群聊", + transferredGroupNtf: "%s 將群管理權限轉讓給了 %s", + muteMemberNtf: "%s 被 %s 禁言%s", + muteCancelMemberNtf: "%s 被 %s 取消了禁言", + muteGroupNtf: "%s 開起了群禁言", + muteCancelGroupNtf: "%s 關閉了群禁言", + friendAddedNtf: "你們已成為好友,可以開始聊天了", + openPrivateChatNtf: "已開啟閱後即焚", + closePrivateChatNtf: "已關閉閱後即焚", + memberInfoChangedNtf: "%s 編輯了自己的群成員資料", + unsupportedMessage: "暫不支持的消息類型", + picture: "圖片", + video: "視頻", + voice: "語音", + safety: "安全", + location: "位置", + file: "文件", + carte: "名片", + emoji: "自定義表情", + chatRecord: "聊天記錄", + revokeMsg: "撤回了一條消息", + aRevokeBMsg: "%s 撤回了 %s 的消息", + blockedByFriendHint: "消息已發出,但被對方拒收了", + deletedByFriendHint: "你還不是TA的好友 ", + sendFriendVerification: "驗證添加", + removedFromGroupHint: "你已被移出群聊", + groupDisbanded: "群已經解散", + search: "搜索", + synchronizing: "同步中", + syncFailed: "同步失敗", + connecting: "連接中", + connectionFailed: "連接失敗", + top: "置頂", + cancelTop: "取消置頂", + markHasRead: "標記已讀", + delete: "刪除", + nPieces: "%s條", + online: "在線", + offline: "離線", + phoneOnline: "手機", + pcOnline: "電腦", + webOnline: "Web", + webMiniOnline: "小程序", + upgradeFind: "發現新版本", + upgradeVersion: "檢測到一個新的可用版本%s,你當前版本是%s", + upgradeDescription: "更新說明:", + upgradeIgnore: "忽略", + upgradeLater: "稍後更新", + upgradeNow: "立即更新", + upgradeNever: "暫不更新", + inviteYouCall: "%s邀請你進行%s", + rejectCall: "拒絕", + acceptCall: "同意", + callVoice: "語音通話", + callVideo: "視頻通話", + sentSuccessfully: "發送成功", + copySuccessfully: "復製成功", + binding_successful: "綁定成功", + day: "天", + hour: "時", + hours: "小時", + minute: "分鐘", + seconds: "秒", + cancel: "取消", + determine: "確定", + failed_to_load: "加載失敗", + failed_to_load1: "加載失敗,請稍後再試", + removal_failed: "移除失敗", + toolboxAlbum: "相冊", + toolboxCall: "視頻通話", + toolboxCamera: "拍攝", + toolboxCard: "名片", + toolboxFile: "文件", + toolboxLocation: "位置", + send: "發送", + holdTalk: "按住 說話", + releaseToSend: "松開 發送", + releaseToSendSwipeUpToCancel: "松開發送,上滑取消", + liftFingerToCancelSend: "松開手指,取消發送", + callDuration: "通話時長 %s", + cancelled: "已取消", + cancelledByCaller: "對方已取消", + rejectedByCaller: "對方已拒絕", + callTimeout: "超時無響應", + rejected: "已拒絕", + forwardMaxCountHint: "當前僅支持轉發最多二十條消息~", + typing: "正在輸入...", + addSuccessfully: "添加成功", + addFailed: "添加失敗", + setSuccessfully: "設置成功", + callingBusy: "你已在通話中,不能進行此操作!", + groupCallHint: "當前群正在通話中,你確定加入當前通話嗎?", + joinIn: "加入", + menuCopy: "復製", + menuDel: "刪除", + menuForward: "轉發", + menuReply: "回復", + menuMulti: "多選", + menuRevoke: "撤回", + menuAdd: "添加", + nMessage: "%s條新消息", + plsSelectLocation: "請選擇一個位置", + groupAudioCallHint: "%s人正在語音通話", + groupVideoCallHint: "%s人正在視頻通話", + reEdit: "重新編輯", + playSpeed: "播放速度", + download: "下載", + download1: "立即下載", + nicknames: "用戶昵稱", + downloadAPP: "下載APP", + private_chat: "球帝帶", + googleMap: "谷歌地圖", + check_for_updates: "檢查更新", + appleMap: "Apple地圖", + baiduMap: "百度地圖", + amapMap: "高德地圖", + signature: "個性簽名", + please_enter_user_nickname: "請輸入用戶昵稱", + set_characters: "請設置4-15個字符", + personalized_signature: "請輸入個性簽名 (字符限製6-50)", + tencentMap: "騰訊地圖", + offlineMeetingMessage: "你收到了一條會議邀請消息", + offlineMessage: "你收到了一條新消息", + offlineCallMessage: "你收到了一條通話邀請消息", + logoutHint: "您確定要退出登錄嗎?", + myInfo: "我的信息", + workingCircle: "工作圈", + accountSetup: "賬號設置", + aboutUs: "關於我們", + about: "關於", + logout: "退出登錄", + qrcode: "二維碼", + qrcodeHint: "掃一掃下面的二維碼,添加我為好友", + favoriteFace: "收藏表情", + favoriteManage: "管理", + favoriteCount: "共%s個表情", + favoriteDel: "刪除(%s)", + hasRead: "已讀", + unread: "未讀", + nPersonUnRead: "%s人未讀", + allRead: "全部已讀", + messageRecipientList: "消息接收人列表", + hasReadCount: "已讀(%s)", + unreadCount: "未讀(%s)", + newFriend: "新的好友", + newGroup: "新的群聊", + myFriend: "我的好友", + myGroup: "我的群組", + addManager: "添加管理員", + add: "添加", + scan: "掃一掃", + scanHint: "掃描二維碼名片", + newGroups: "新建分組", + addFriend: "添加好友", + addFriendHint: "通過ID號搜索添加", + createGroup: "創建群聊", + createGroupHint: "創建群聊,全面使用OpenIM", + addGroup: "添加群聊", + addGroupHint: "向管理員或群聊成員詢問ID", + searchIDAddFriend: "搜索ID添加好友", + searchIDAddGroup: "搜索ID添加群聊", + noFriendAdd: "搜索ID添加群聊", + noVerify: "添加我為好友時需要驗證", + year: "歲", + myCreatedGroup: "我創建的群聊", + noFriendDelete: "沒有可移除好友", + myManageGroup: "我管理的群聊", + myJoinGroup: "我加入的群聊", + searchIDIs: "ID:%s", + searchPhoneIs: "手機號:%s", + searchEmailIs: "郵箱:%s", + searchNicknameIs: "昵稱:%s", + searchGroupNicknameIs: "群昵稱:{name}", + noFoundUser: "無法找到該用戶", + noFoundGroup: "無法找到該群聊", + joinGroupDate: "入群時間", + joinGroupMethod: "入群方式", + byInviteJoinGroup: "%s邀請進群", + byIDJoinGroup: "搜索群ID", + byQrcodeJoinGroup: "群二維碼", + groupNames: "請輸入分組名稱", + deleteGroups: "分組删除後將不可恢復,是否確認删除該分組?", + groupID: "群ID號", + setAsAdmin: "設為管理員", + deleteGround: "删除分組", + removeFriend: "移除好友", + setMute: "設置禁言", + modifyGroupName: "修改分組名稱", + noAddGround: "還沒有添加好友到分組", + deleteSuccessfully: "删除成功", + organizationInfo: "組織信息", + organization: "組織", + department: "部門", + position: "職位", + personalInfo: "個人資料", + viewDynamics: "查看動態", + audioAndVideoCall: "音視頻通話", + sendMessage: "發送消息", + avatar: "頭像", + name: "姓名", + nickname: "昵稱", + gender: "性別", + englishName: "英文名", + birthDay: "生日", + tel: "座機", + mobile: "手機號碼", + email: "郵箱", + man: "男", + woman: "女", + friendSetup: "好友設置", + setupRemark: "設置備註", + recommendToFriend: "把他推薦給朋友", + addToBlacklist: "加入黑名單", + unfriend: "解除好友關系", + areYouSureDelFriend: "確定刪除好友嗎?", + areYouSureAddBlacklist: "確定將好友加入黑名單嗎?", + remark: "備註", + save: "保存", + saveSuccessfully: "保存成功", + saveFailed: "保存失敗", + groupVerification: "群聊驗證", + friendVerification: "好友驗證", + email_address: "請輸入您的郵箱", + sendEnterGroupApplication: "發送入群申請", + sendToBeFriendApplication: "發送好友申請", + sendSuccessfully: "發送成功", + reset_successful: "重置成功", + bind_new_email: "綁定新郵箱", + Login_failed: "登錄失敗", + successful_home: "註冊成功", + sendFailed: "發送失敗", + canNotAddFriends: "該用戶已設置不可添加!", + mutedAll: "全體禁言", + tenMinutes: "10分鐘", + oneHour: "1小時", + twelveHours: "12小時", + oneDay: "1天", + custom: "自定義", + unmute: "取消禁言", + youMuted: "你已被禁言", + groupMuted: "已開啟群禁言", + notDisturbMode: "勿擾模式", + allowRing: "新消息提示音", + allowVibrate: "新消息震動", + forbidAddMeToFriend: "禁止加我為好友", + blacklist: "通訊錄黑名單", + unlockSettings: "解鎖設置", + changePassword: "修改密碼", + change: "修改", + createdGroup: "您還沒有創建分組", + clearChatHistory: "清空聊天記錄", + confirmClearChatHistory: "確認清空所有聊天記錄嗎?", + languageSetup: "語言設置", + language: "語言", + english: "英文", + setting_up: "設置中...", + chinese: "簡體中文", + traditional_chinese: "繁體中文", + followSystem: "跟隨系統", + blacklistEmpty: "暫無黑名單", + remove: "移除", + fingerprint: "指紋", + gesture: "手勢", + biometrics: "生物識別", + plsEnterPwd: "請輸入密碼", + plsEnterOldPwd: "請輸入原始密碼", + plsEnterNewPwd: "請輸入新密碼", + plsConfirmNewPwd: "請確認新密碼", + reset: "重置", + oldPwd: "原密碼", + newPwd: "新密碼", + confirmNewPwd: "確認新密碼:", + plsEnterConfirmPwd: "請輸入確認密碼", + twicePwdNoSame: "兩次輸入的密碼不一致", + twicePwd: "新密碼不一致,請重新設置", + changedSuccessfully: "修改成功", + checkNewVersion: "檢查新版本", + chatContent: "聊天內容", + topContacts: "置頂聯系人", + messageNotDisturb: "消息免打擾", + messageNotDisturbHint: "接收消息但不提醒", + burnAfterReading: "閱後即焚", + timeSet: "時間設置", + setChatBackground: "設置聊天背景", + fontFace: "字體", + fontSize: "字體大小", + little: "小", + standard: "標準", + big: "大", + thirtySeconds: "30秒", + fiveMinutes: "5分鐘", + clearAll: "清空", + clearSuccessfully: "清除成功", + groupChatSetup: "群聊設置", + viewAllGroupMembers: "查看全部群成員(%s)", + groupManage: "群管理", + myGroupMemberNickname: "我在本群的名稱", + topChat: "聊天置頂", + muteAllMember: "全員禁言", + exitGroup: "退出群聊", + dismissGroup: "解散群聊", + dismissGroupHint: "所有群成員將退出此群,無法收發消息", + quitGroupHint: "退出群聊後將不再接收此群聊信息。", + joinGroupSet: "進群驗證", + allowAnyoneJoinGroup: "允許任何人加群", + inviteNotVerification: "群成員邀請無需驗證", + needVerification: "需要發送驗證信息", + addMember: "添加", + delMember: "移除", + groupOwner: "群主", + groupAdmin: "管理員", + notAllowSeeMemberProfile: "不允許查看其他群成員資料", + notAllAddMemberToBeFriend: "不允許添加群成員為好友", + transferGroupOwnerRight: "群主管理權轉讓", + groupName: "群名稱", + groupAcPermissionTips: "只有群主及管理員可以編輯", + plsEnterGroupAc: "請輸入群公告", + edit: "編輯", + publish: "發布", + groupMember: "群聊成員", + selectedPeopleCount: "已選擇(%s)", + confirmSelectedPeople: "確定(%s/%s)", + confirm: "確認", + log_in_time: "登錄時間:", + log_in_time1: "登錄時間", + login_IP: "登錄IP:", + confirmTransferGroupToUser: "確定將群主轉讓給:%s?", + removeGroupMember: "移除群成員", + searchNotResult: "搜索無結果", + groupQrcode: "群二維碼", + groupQrcodeHint: "掃一掃下面的二維碼,立即加入群聊", + approved: "已同意", + accept: "接受", + reject: "拒絕", + waitingForVerification: "等待驗證", + rejectSuccessfully: "拒絕成功", + rejectFailed: "拒絕失敗", + applyJoin: "申請加入", + enterGroup: "進入群聊", + applyReason: "申請理由:%s", + invite: "邀請", + sourceFrom: "來源:%s", + byMemberInvite: "邀請", + bySearch: "通過搜索", + byScanQrcode: "掃描二維碼", + iCreatedGroup: "我創建的", + iJoinedGroup: "我加入的", + nPerson: "{count}人", + searchNotFound: "未搜索到相關結果", + searchNotFoundMember: "沒有找到相關用戶", + organizationStructure: "組織架構", + recentConversations: "最近會話", + selectAll: "全選", + plsEnterGroupNameHint: "取個群名稱方便後續搜索", + completeCreation: "完成創建", + sendCarteConfirmHint: "確定發送該聯系人到本聊天嗎?", + sentSeparatelyTo: "分別發送給:", + sentTo: "發送給:", + leaveMessage: "留言", + mergeForwardHint: "[合並轉發]共%s條消息", + mergeForward: "合並轉發", + quicklyFindChatHistory: "快速查找聊天記錄", + notFoundChatHistory: '沒有找到"%s"相關內容', + globalSearchAll: "綜合", + globalSearchContacts: "聯系人", + globalSearchGroup: "群組", + globalSearchChatHistory: "聊天記錄", + globalSearchChatFile: "文件", + relatedChatHistory: "%s條相關的聊天記錄", + seeMoreRelatedContacts: "查看更多相關聯系人", + seeMoreRelatedGroup: "查看更多相關群組", + seeMoreRelatedChatHistory: "查看更多相關聊天記錄", + seeMoreRelatedFile: "查看更多相關文檔", + publishPicture: "發布圖片", + publishVideo: "發布視頻", + mentioned: "提到了:%s", + comment: "評論", + like: "贊", + reply: "回復", + rollUp: "收起", + fullText: "全文", + selectAssetsFromCamera: "拍攝", + selectAssetsFromAlbum: "從相冊選擇", + whoCanWatch: "誰可以看", + remindWhoToWatch: "提醒誰看", + public: "公開", + everyoneCanSee: "所有人可見", + partiallyVisible: "部分可見", + visibleToTheSelected: "選中的人可見", + partiallyInvisible: "不給誰看", + invisibleToTheSelected: "選中的人不可見", + private: "私密", + onlyVisibleToMe: "僅自己可見", + selectVideoLimit: "時長不能超過15秒", + selectContactsLimit: "至少選擇一個聯系人", + message: "消息", + commentedYou: "評論了你:%s", + likedYou: "為你點了贊", + mentionedYou: "提到了你", + replied: "回復了", + detail: "詳情", + totalNPicture: "共%s張", + noDynamic: "暫無動態", + callRecords: "通話記錄", + allCall: "所有通話", + missedCall: "未接來電", + incomingCall: "呼入", + outgoingCall: "呼出", + microphone: "麥克風", + speaker: "揚聲器", + hangUp: "掛斷", + pickUp: "接聽", + waitingCallHint: "正在呼叫中…", + waitingVoiceCallHint: "正在等待對方接聽…", + invitedVoiceCallHint: "邀請你進行語音通話…", + waitingVideoCallHint: "正在等待對方接受邀請", + invitedVideoCallHint: "邀請你進行視頻通話…", + waitingToAnswer: "等待接聽", + invitedYouToCall: "邀請你通話", + calling: "通話中", + nPeopleCalling: "%s人正在通話中", + whoInvitedVoiceCallHint: "%s邀請你進行語音通話", + whoInvitedVideoCallHint: "%s邀請你進行視頻通話", + plsInputMeetingSubject: "請輸入會議主題", + meetingStartTime: "開始時間", + meetingDuration: "會議時長", + enterMeeting: "進入會議", + meetingNo: "會議號", + yourMeetingName: "您的名稱", + plsInputMeetingNo: "請輸入會議號", + plsInputYouMeetingName: "請輸入您的名稱", + meetingSubjectIs: "會議主題:%s", + meetingStartTimeIs: "開始時間:%s", + meetingDurationIs: "會議時長:%s", + meetingHostIs: "主持人:%s", + meetingNoIs: "會議號:%s", + meetingMessageClickHint: "點擊此消息可直接加入會議", + meetingMessage: "會議消息", + openMeeting: "未結束會議", + didNotStart: "未開始", + started: "已開始", + meetingInitiatorIs: "%s發起的視頻會議", + meetingDetail: "會議詳情", + meetingOrganizerIs: "發起人:%s", + updateMeetingInfo: "修改會議信息", + cancelMeeting: "取消會議", + videoMeeting: "視頻會議", + joinMeeting: "加入會議", + bookAMeeting: "預約會議", + quickMeeting: "快速會議", + confirmTheChanges: "確認修改", + invitesYouToVideoConference: "%s邀請你加入視頻會議", + over: "結束", + meetingMute: "靜音", + meetingUnmute: "取消靜音", + meetingCloseVideo: "關閉視頻", + meetingOpenVideo: "開啟視頻", + meetingEndSharing: "結束共享", + meetingShareScreen: "共享屏幕", + meetingMembers: "成員(%s)", + settings: "設置", + leaveMeeting: "離開會議", + endMeeting: "結束會議", + leaveMeetingConfirmHint: "確定離開會議嗎?", + endMeetingConfirmHit: "確定結束會議嗎?", + meetingSettings: "會議設置", + allowMembersOpenMic: "允許成員自我解除靜音", + allowMembersOpenVideo: "允許成員開啟視頻", + onlyHostShareScreen: "僅主持人可以共享屏幕", + onlyHostInviteMember: "僅主持人可邀請會議成員", + defaultMuteMembers: "成員入會禁音", + pinThisMember: "置頂該成員", + unpinThisMember: "取消置頂", + allSeeHim: "全部看他", + cancelAllSeeHim: "取消看他", + muteAll: "全員靜音", + unmuteAll: "解除全員靜音", + members: "成員", + screenShare: "屏幕共享", + screenShareHint: "正在共享屏幕.", + meetingClosedHint: "會議已關閉或已斷開鏈接,確定離開嗎?", + meetingIsOver: "會議已經結束!", + networkError: "網絡異常請稍後再試!", + shareSuccessfully: "分享成功!", + notFoundMinP: "暫未發布小程序", + notSendMessageNotInGroup: "無法在已退出的群聊中發送消息", + whoModifyGroupName: "{name} 把群聊名稱已更改為 ", + accountWarn: "警告!", + accountException: "你的賬號已在其他設備登錄,請及時修改密碼。", + tagGroup: "標簽", + issueNotice: "通知下發", + createTagGroup: "創建標簽", + plsEnterTagGroupName: "請輸入標簽名", + tagGroupMember: "標簽成員", + completeEdit: "完成編輯", + finish: "完成", + emptyTagGroup: "暫無標簽分組", + confirmDelTagGroupHint: "確認移除該標簽分組嗎?", + editTagGroup: " 編輯標簽", + newBuild: " 新建", + receiveMember: " 接收成員", + emptyNotification: "暫無通知", + notificationReceiver: "%s個接收者:%s", + sendAnother: "再發一條", + confirmDelTagNotificationHint: "確認移除該通知記錄嗎?", + contentNotBlank: "內容不能為空", + plsEnterDescription: "請輸入描述文字", + gifNotSupported: "不支持gif圖片", + register: "註冊", + hk: "繁體", + RegisterSuccess: "註冊成功", + LoginSuccess: "登錄成功", + init_validate_1: "用戶名由6-15位字母及數字組成", + init_validate_10: "手機號或密碼不正確,請重新輸入", + init_validate_5: "密碼必須由6-15位字母及數字組成,請重新設定密碼", + init_validate_2: "兩次密碼不一致,請重新設置", + init_validate_3: "密碼必須由6-15為字母及數字組成 請重新設置密碼", + init_validate_6: "密碼必須由6-15為字母及數字組成", + init_validate_4: "註冊成功,即將返回登錄頁", + init_hint_1: "6-15位數字或英文", + init_hint_2: "確認登錄密碼", + init_hint_3: "登錄密碼", + username: "用戶名", + to_modify: "去修改", + confirm_password: "確認密碼", + notification_settings: "通知設置", + init_text_1: "註冊登錄即同意", + init_text_2: " 《用戶服務協議》 ", + init_text_3: " 和 ", + init_text_4: " 《隱私政策》 ", + init_text_5: "服務協議及隱私政策", + init_text_6: "為了保護您得合法權益,請您閱讀並同意以下協議", + refuse: "拒絕", + refuse_continue: "同意並繼續", + forget_password: "忘記密碼", + register_user: "註冊賬號", + gotoLogin: "登錄", + gotoRegister: "創建一個新賬號", + enterGroupCheck: "進群申請", + openPhotoError_1: "未讀取到選擇的文件", + openPhotoError_2: "文件上傳失敗", + openPhotoError_3: "未讀取到文件信息", + agreeAll: "全部同意", + whatCanManagerDo: "群管理权限", + changeGroupName: "修改群名稱", + deleteMemberMessage: "刪除群員消息", + chatPrivate: "與群員加密私聊", + memberMute: "群員禁言", + deleteMember: "移除成員", + changeNotice: "發布/修改/删除群公告", + checkInGroup: "進群審核", + setAdmin: "設置管理員", + deleteAdmin: "移除管理員", + deleteAdminTitle: "確定要移除該管理員?", + deleteAdminDetail: "將此成員移除管理員,無法管理群消息", + deleteSuccess: "移除成功", + mobile_web_page: "訪問手機網頁", + deleteMemberTitle: "確定要移除該成員?", + deleteMemberDetail: "將此成員移出此群,無法收發消息", + deleteSelf: "不能移除自己", + handled: "已處理", + unhandle: "待處理", + noCheck: "暫無申請處理~", + success: "操作成功", + nameLenth: "請設置1-15個字符", + editNickname: "編輯群昵稱", + text_password: "請設置新的登錄密碼", + quit: "退出", + waitAMoment: "我在看看", + editWarningTitle: "確定退出編輯群昵稱嗎?", + editWarningDetail: "您修改的群昵稱還未保存,是否繼續退出?", + editGroupAvatar: "編輯本群頭像", + myGroupAvatar: "我的本群頭像", + inviteLink: "邀請鏈接", + inviteTips: "任何已安裝此APP的用戶都將可以通過此鏈接加入您的群聊", + inviteLinkCopy: "邀請鏈接已復製", + shareLink: "分享鏈接", + manageLink: "管理邀請鏈接", + copyLink: "復製鏈接", + deleteLink: "刪除鏈接", + deleteLinkTipsTitle: "確定要撤銷該鏈接嗎?", + deleteLinkTipsDetail: + "您確定要撤銷此邀請鏈接嗎?一旦撤銷,其他人將不能通過此鏈接加入本群組。", + linkManage: "鏈接管理", + openManage: "開啟後需群主或管理員確認才能進群", + timeValid: "有效時間限製", + linkInvalidTips: "邀請鏈接將再此日期後失效,無法繼續訪問", + valid: "內有效", + dayValid: "天內有效", + forever: "永久", + foreverValid: "永久有效", + attendTips: "加入請求已發送,管理員審核您的申請後,您就可以加入群組", + memberCountLimit: "此群聊人數已達上線", + linkIsOut: "邀請鏈接已過期", + requestAttend: "請求加入群聊", + needCheck: "此群聊僅在管理員審核後才能加入", + countLimit: "管理員人數已達上限", + openLock: "離開APP後,超時需要使用解鎖方案", + closeLock: "啟動app時無需驗證", + checkTips: "請檢查您的人臉或指紋權限是否開啟~", + face: "面容解鎖", + finger: "指紋解鎖", + gestureLock: "手勢密碼解鎖", + closed: "未開啟", + opened: "已開啟", + lanchProtect: "啟動時保護", + noProtect: "無需保護", + lockSetting: "解鎖設置", + drawLine: "繪製解鎖圖案", + drawOldLine: "繪製您的舊手勢密碼", + drawNewLine: "繪製您的新手勢密碼", + setGesturePassword: "設置手勢密碼", + checkGesturePassword: "驗證手勢密碼", + needJoinPoint: "至少連接4個點,請重新繪製", + drawAgain: "請再次繪製", + checkSuccessful: "驗證通過", + notInCommon: "與上次繪製不一致,請重新繪製", + changeGesturePassword: "修改手勢密碼", + toChange: "去修改", + openSuccess: "啟用成功", + closeSuccess: "關閉成功", + hasBeenMember: "已是群成員,不用再次加入", + joinGroup: "加入群聊", + checking: "管理員審核中", + noUnlock: "無法解鎖?", + inputGesture: "請輸入手勢解鎖", + faceDetect: "請進行人臉識別", + fingerDetect: "請進行指紋識別", + tryUnlocking: "請嘗試:無法解鎖?", + countRemain: "手勢錯誤,您還可以輸入 %s 次", + reLoginUnlock: "重新登錄解鎖", + selectVerityType: "請選擇驗證方式 ", + currentTypeNotSupport: "當前類型不支持", + //------------- + updateNewVersion: "發現新版本", + updateOkUpdate: "立即升級", + updateOpenDownloadFailed: "打開下載失敗", + updateCurrentIsNew: "當前已是最新版本", + newVersionNotFound: "未找到最新版鏈接,您可以訪問網頁去下載", + openUrlFailed: "無效的鏈接", + qmvMsgNotExit: "[引用內容不存在]", + qmvMsgNotSupport: "[引用內容不支持展示]", + dataError: "數據解析錯誤", + userNotBindEmail: "用戶未綁定郵箱", + userNotRegister: "用戶未註冊", + checkError: "檢查失敗", + + csvTitle: "聊天設置", + csvSetTopMsg: "置頂聊天", + csvSetOptMsg: "消息免打擾", + csvSetDisableMsg: "屏蔽消息", + csvClearMsg: "清空聊天記錄", + csvClearLocal: "僅清空本地聊天記錄", + csvClearUser: "清空本地和%s的聊天記錄", + csvSetErrorTip: "設置失敗,請檢查網絡", + + gmiTitle: "群昵稱:", + gmiSetTalk: "加密私聊", + gmiSetMute: "設置群內禁言", + gmiDeleteUser: "移出本群", + gmiMuteTo: "禁言至 ", + gmiError1: "未找到此成員信息,可能被移出了群聊", + gmiError2: "未找到您的群成員信息,您可能被移出了群聊", + gmiError3: "確定要移除該用戶?", + gmiError4: "將此成員移出此群,無法收發消息", + gmiError5: "您沒有權限刪除此管理員", + gmiError6: "您沒有權限刪除此成員", + gmiError7: "已解除禁言", + gmiError8: "您沒有權限取消禁言此管理員", + gmiError9: "您沒有權限取消禁言此成員", + gmiError10: "您沒有權限禁言此管理員", + gmiError11: "您沒有權限禁言此成員", + gmiError12: "解除禁言", + gmiGroupOwnerNotFound: "群信息加載失敗", + + smmvTitle: "禁言時長", + smmvMuteTime: "選擇禁言時長", + smmvMuteTime_10m: "10分鐘", + smmvMuteTime_1hour: "1小時", + smmvMuteTime_12hour: "12小時", + smmvMuteTime_1day: "1天", + smmvMuteTime_30day: "30天", + smmvMuteTime_7day: "7天", + + egnpTitle: "編輯群聊名稱", + egnpSetGroupName: "請設置群名稱", + egnpSetTextDesc: "請設置1-15個字符", + egnpSetTitleNoPermission: "您沒有修改群聊名稱權限", + egnpSetFailed: "修改失敗,請稍後再試", + + setGroupNameHint: "請設置群名稱", + + givTitle: "編輯群聊名稱", + givGroupName: "群名稱", + givGroupFace: "群頭像", + + gmsvTitle: "管理群", + gmsvSetManager: "設置管理員", + gmsvToSet: "去設置", + gmsvFindMsg: "查找聊天記錄", + gmsvFindMsgDesc: "開啟後入群成員將看到近期歷史消息", + gmsvMute: "全員禁言", + gmsvMuteDesc: "開啟後只允許群主和管理員發言", + gmsvApply: "進群確認", + gmsvApplyDesc: "開啟後需群主或管理員確認才能進群", + + gmsvSetManagerTip: "只有群主才能設置管理員", + gmsvSetMuteTipOn: "已全員禁言", + gmsvSetNuteTipOff: "全員禁言已解除", + gmsvApplyTipOn: "進群確認已開啟", + gmsvApplyTipOff: "進群確認已關閉", + gmsvFindGroupErr: "獲取群信息失敗", + gmsvFindMsgOn: "查找聊天記錄已開啟", + gmsvFindMsgOff: "查找聊天記錄已關閉", + gmsvSetFailed: "設置失敗", + + gsvTitle: "群詳情", + gsvGroupId: "群ID", + gsvGroupMember: "群聊人員", + gsvGroupMemberValue: "共{num}人", + gsvGroupNotification: "群公告", + gsvMyName: "我的本群昵稱", + gsvMyFace: "我的本群頭像", + gsvGroupManager: "群管理", + gsvGroupApply: "進群確認", + gsvFindMsg: "查找聊天記錄", + gsvSetTop: "置頂", + gsvSetOpt: "消息免打擾", + gsvDeleteMsg: "刪除聊天記錄", + gsvDeleteLocal: "僅清空本地聊天記錄", + gsvDeleteAllMsg: "清空本地和所有成員的聊天記錄", + gsvDeleteOtherMsg: '清空本地和對方的聊天記錄', + gsvGroupDelete: "確定要解散該群聊?", + gsvOutGroup: "確定要退出該群聊?", + gsvNoNotification: "暫無公告", + gsvClearSuccess: "清除成功", + gsvClearFailed: "清除失敗,請稍後再試", + alreadyBeOwner: "您選擇的是群主,已擁有最高的管理權限", + + cvAllPeople: "所有人", + cvVideo: "[視頻]", + cvPicture: "[圖片]", + cvFile: "[文件]", + cvVoice: "[語音]", + cvNotSupportMsg: "暫不支持的消息", + cvSendMsgErrorNoPermission: "由於對方的權限設置,你無法向TA發送消息", + cvSendMsgPnMute: "禁言中,無法發送消息", + cvCopyMsg: "已復製到剪切板", + cvCopyMsgNotSupport: "暫不支持的消息", + cvdeleteLocal: "從本地刪除", + cvdeleteLocalAndServer: "從本地和服務器刪除", + cvdeleteLocalAndServerUser: "從本地和{name}刪除", + cvClearMsgTip: "清除了會話消息", + + cvGroupCreateSuccess: "群聊創建成功", + cvGroupCreateSuccessTip1: "恭喜您建群成功,點擊下方按鈕邀請好友入群", + cvGroupCreateSuccessGo: "邀請好友入群", + cvGroupNotification: "群公告:", + sendMsg: "發送消息", + cvOnlineCount: "{count} 人在線", + cvUserCount: "共{count}人", + cvNotAtUser: "還沒有群人員可以@哦,快去添加成員吧", + all: "全部", + + clCloseConversation: "折疊置頂聊天", + clOpenConversation: "個置頂聊天", + clNoConversation: "近期無會話~", + clTitle: "對話", + + clDelete: "此對話將刪除", + clDeleteDesc: "刪除此對話,將會刪除本地聊天記錄", + clDeleteSel: "您沒有選擇會話", + clDeleteSel1: "此對話將刪除", + clDeleteSel2: "這些對話將刪除", + clDeleteDesc2: "刪除這些對話,將會刪除本地聊天記錄", + + homeItem0: "對話", + homeItem1: "聯系人", + homeItem2: "我的", + exitTip: "再次點擊返回鍵退出", + + mineSigEmpty: "這個用戶很懶,暫無簽名~", + uaProtocol: "用戶服務協議", + + savSearchTip: "快速搜索聊天內容", + savMedia: "圖片/視頻", + searchEmptyTip: "沒有找到相關的聊天記錄", + savSearchMsg: "條相關聊天記錄", + + savConversation: "對話", + savMsg: "聊天記錄", + savConversationNotFound: "未找到對話", + savMsgNotFound: "未找到此信息", + + samTitle: "媒體", + samTimeFormat: "yyyy年M月", + notSupportType: "不支持的類型", + media: "媒體", + + openUrl: "打開鏈接", + lkAddGroupUrl: "加入群聊鏈接", + lkAddOtherUrl: "外部鏈接,可使用默認瀏覽器打開", + lkUnknowUrl: "未識別的鏈接", + openUrlFailed1: "打開鏈接失敗", + + yszcTitle: "隱私政策", + slsmTitle: "法律聲明", + + ctmRemove: "移除", + ctmOkRemove: "確定移除", + ctmTopMsgCount: "共{count}條", + ctmEmptyText: "沒有置頂消息", + recordTimeShort: "錄音時間太短", + + sendCodeExceErr: "發送驗證碼失敗,請稍後再試", + bindEmailExecErr: "綁定郵箱失敗,請稍後再試", + + ctmSuccess: "置頂成功", + ctmRemoveSuccess: "移除置頂消息成功", + ctmMsgNotFound: "此消息不存在", + + cvFirstTip: "此對話中的信息和通話已經進行端對端加密", + + clearGroupNotification: "清除了群公告", + publicGroupNotification: " 發布公告", + dialog_tip1: " 溫馨提示", + dialog_tip_token_invaild: " 登錄已過期,請重新登錄", + + notify_new_msg_title: "您收到了一條新消息", + notify_new_msg_msg: "消息內容:.....", + dataError1: "數據錯誤", + groupApply: " 條進群申請", + muteLate: "後解除禁言", + groupMuteing: "群禁言中", + groupMemberNotFound: "未找到群成員信息", + + bindEmailTipTitle: "綁定郵箱", + bindEmailTipContent: "為了您的賬戶安全,請綁定您的郵箱", + bindEmailTipOk: "去綁定", + bindEmailTipCancle: "暫不綁定", + + managerApplying: "管理員審核中", + quote: "引用", + + request_exception: "請求異常", + clearMsg: "清除消息", + pressToSay: "按住說話", + slideToCancelSend: "手指上滑,取消發送", + releaseToEnd: "松開結束", + releaseToCancelSend: "松開手指,取消發送", + invalidVoiceMessage: "無效的語音消息", + hit: "打", + downloadingCache: "下載緩存中...", + unableToRecognize: "無效二維碼", + scanResult: "掃碼結果", + + register_fail: " 註冊失敗,請檢查網絡後再試", + skip: " 跳過", + toUse: " 立即體驗", + sideToSide: " 端到端加密", + design: " 設計", + guide1: " 發送消息,加密傳輸,僅供自己閱讀", + guide2: " 更加自由地按照自己的方式交流", + guide3: " 圍繞您構建的安全通信平臺", + inputGroupAc: " 請輸入群公告(字符限製0-500)", + clickToEnjoy: + " 點擊鏈接保存,或者復製本段內容,打開[球帝帶]APP,即可加入群聊。", + createOK: "創建成功", + noPravcy: "您沒有創建群聊權限", + groupNumberToTop: "群組數量達到上限", + createFail: "創建失敗,請稍後再試試吧", + create: "創建", + uploadGroupAvatar: "上傳群頭像", + useNewPassword: "密碼修改成功,即將返回登入頁", + + error_14: "服務器異常(14)", + error_500: "服務器異常(500)", + error_1000: "用戶簽名token不合法", + error_1001: "參數錯誤", + error_1002: "當前版已是最新版本", + error_6001: "此用戶名已被註冊", + error_6003: "密碼必須由6-15位字母及數字組成", + error_6004: "用戶名校驗出錯", + error_6005: "密碼不正確,請重新輸入", + error_6006: "今日密碼錯誤次數超過上限", + error_6007: "用戶不存在,请检查确认", + error_6008: "舊密碼錯誤,請重新輸入", + error_6009: "舊密碼與新密碼一致,請重新輸入", + error_6010: "頻繁獲取驗證碼", + error_6011: "驗證碼不正確,請重試", + error_6012: "驗證碼過期", + error_6013: "驗證碼失敗次數過多", + error_6014: "驗證碼已經使用", + error_6015: "郵箱不存在", + error_6016: "郵箱已被他人綁定,請重新錄入", + error_6017: "驗證碼不存在", + error_6018: "該賬戶已被封禁", + error_6030: "服務器郵箱未配置", + error_100002: "置頂消息達到上限", + error_100003: "此消息已置頂", + error_api: "請求錯誤", + error_http: "網絡請求錯誤", + + error_http_11: "網絡連接超時", + error_http_12: "網絡連接超時", + error_http_13: "網絡連接超時", + error_http_14: "網絡證書錯誤", + error_http_15: "請求數據錯誤", + error_http_16: "請求已取消", + error_http_17: "網絡連接超時", + error_http_18: "網絡請求內部出錯", + + error_sdk_err_1002: "無權限操作", + error_sdk_err: "操作失敗", + + search_hine_1: "請輸入搜索內容", + + str_no_data: "暂无数据", + str_no_net: "暂无网络", + + download_not_support_type: "不支持的格式", + download_save_at: "文件存儲在: ", + download_not_url: "未識別到下載鏈接", + download_file_success: "保存成功,您可以到相冊查看", + download_failed_file: "文件保存失敗", + download_failed: "下載失敗", + + loading_failed_retry: "加載失敗,點擊重試", + loading_ing: "加載中...", + + str_online_later_minute: "分鍾前在線", + str_online_later_hour: "小時前在線", + str_online_later_llg: "很久沒上線", + str_online_later_yesterday: "昨天在線", + str_yesterday: "昨天", + str_online_later_near: "近期在線", + + str_minute: "分", + clean_notice: "清空公告", + clean_notice_1: "確定要清空群公告嗎?", + publish_notice: "發布公告", + publish_notice_remind: "發布該公告將提醒所有群成員", + quit_edit: "退出編輯", + quit_the_edit: "退出本次編輯?", + continue_edit: "繼續編輯", + publish_success: "發布成功", + face_success: "面容解鎖開啟成功", + finger_success: "指紋解鎖開啟成功", + groupDismissed: "該群已解散", + hasOut: "已過期", + + otherLoginStyle: "其他登錄方式", + otherRegisterStyle: "其他註冊方式", + hasAccount: "已有賬號,去登錄", + mobileCode: "手機驗證碼", + codeSendTo: "驗證碼已發送至", + + inputNumber: "請輸入手機號", + + error_6019: "手機號已被他人綁定", + error_6020: "郵箱地址已被他人綁定", + error_6021: "郵箱地址有誤,請重新輸入", + error_6022: "手機號有誤,請重新輸入", + error_6023: "該賬戶已被封禁,暫不支持找回密碼", + error_6024: "該賬戶未綁定郵箱,暫不支持找回密碼", + error_6025: "該賬戶未綁定手機號,暫不支持找回密碼", + error_6026: "手機號已被他人綁定,請重新錄入", + error_6031: "請輸入正確的手機號碼", + error_6032: "請輸入正確的郵箱地址", + + bindPhone: "綁定手機號", + bindPhoneSuccess: "手機號綁定成功", + bindPhoneTipChange: "若您的手機號已不用,請及時更換", + please_input_email: "請輸入郵箱地址", + findPassByEmailTip: "用郵箱找回密碼", + findPassByPhoneTip: "用手機號找回密碼", + resetPass: "重設密碼", + current_bind_phone: "當前綁定手機號:", + reget: "重新獲取", + getVerifyCode: "獲取驗證碼", + alreadyRegister: "此手機號已被註冊", + alreadyRegister2: "此郵箱地址已被註冊", + netErrorRetry: "網絡出小差了,點擊頁面重新加載~", + newPhoneNumber: "新的手機號", + qrSaveFail: "二維碼保存失敗", + qrSavePhoto: "二維碼已保存到相冊", + userId: "用戶ID:", + addMeToFriend: "加我為好友", + validTime: "有效期至", + clickRefresh: "點擊刷新", + qrUseCount: "此二維碼{count}次有效", + share: "分享", + savePhoto: "保存圖片", + iAm: "我是", + addFriendSuccessful: "添加好友成功", + addFriendFaceToFace: "面對面加好友", + faceToFace: "面對面加好友", + inputSameKey: "以下是已輸入同樣數字的好友", + sameKeyAddFriend: "和好友輸入同樣數字添加好友", + self: "自己", + age: "歲", + confirmAddFriend: "確認加為好友", + + inputUserId: "請輸入用戶ID", + myQr: "我的二維碼", + back: "返回", + canNotAddSelf: "你不能添加自己到聯系人", + notExsit: "該用戶不存在", + searchUserId: "搜索用戶ID:", + + remarkInfo: "請備註驗證信息", + applyRemark: "申請備註", + last4Number: "請輸入對方登錄手機號後4位", + checkLast4Number: "驗證手機號後4位", + addFriendApply: "添加好友申請", + codeInvalid: "密鑰失效,請重新發起", + shareFail: "分享失敗", + + addFriendText1: "加為好友", + friendNumberCatchTopLimit: "您的好友數量已達上限", + friendNumberCatchTopLimitCurrentDay: "當天添加好友已達上限", + error_1003: "請稍後重試", + error_6033: "無效密鑰或失效,請重新發起", + error_6034: "好友總數上限", + error_6035: "當日好友總數上限", + error_6036: "該用戶手機號為空", + error_6037: "手機尾號驗證失敗", + error_6040: "分組已存在", + error_6050: "二維碼已過期", + error_6051: "當日二維碼使用次數已達上限", + error_6052: "二維碼使用次數已達上限", + error_6053: "今日掃一掃功能次數已達上限", + error_6054: "對方已添加您為好友", + error_6055: "無效密鑰或失效,請重新發起", + error_6066: "賬戶余額不足", + error_6067: "紅包已領完", + error_6068: "紅包已過期", + error_6069: "無法領取專屬紅包", + error_6070: "已領取紅包,無法再領取", + deletedByFriendHint1: "你還不是TA朋友,", + deletedByFriendHint2: "添加好友", + deletedByFriendHint3: "繼續聊天", + gotoAddFriend: "去添加好友", + addFriendGotoGroup: "邀請好友入群", + notContacts: "暫無聯系人", + notContactsFound: "沒有找到%s相關的好友", + remarkStr: "備註", + remarkStrHint: "請輸入備註", + deleteConfirm: "是否確認刪除好友", + deleteFriend: "刪除好友", + searchContact: "搜索聯系人", + notGroupTip: "還沒有加入群聊", + notAddFriend: "還沒有添加新朋友", + friendApplyAdd: "好友申請", + addSuccess: "添加成功", + hasSelectFriend: "已選擇好友", + + myBalance: "我的余額", + + revokeMsgTip: "撤回消息", + error_10050: "操作失敗(未找到記錄)", + set_manager_tip_1: "您選擇的是群主,已擁有最高的管理權限", + envelope: "紅包", + uidStr: "用戶ID", + showGetDetail: "查看領取詳情", + waitOtherEnter: "等待對方加入", + isFriend2SExit: "已成為好友,2秒後自動退出", + + sendRedPacket: "發紅包", + redPacketNumber: "紅包個數", + ge: "個", + total: "總數", + eachNumber: "單個數量", + selectUser: "選擇用戶", + number: "數量", + redPacketType: "紅包類型", + checkVerity: "安全驗證", + balance: "賬戶余額", + sendTo: "短信驗證碼已發送至", + + pin: "拼手氣紅包", + putong: "普通紅包", + zhuanshu: "專屬紅包", + dajidali: "恭喜發財,大吉大利!", + + redPacketInfo: "紅包詳情", + best: "手氣最佳", + saveToAccount: "已存入賬戶", + minuteOut: "分鐘搶光", + hourOut: "小時搶光", + showDetail: "查看詳情", + maxMemberNumber: "數量不應超過群成員總數", + sended: "發出的", + get: "已領取", + waiting: "等待用戶領取", + gong: "個紅包共", + + envelope_get_over: "的紅包已被領完", + envelope_get: "領取了", + de: "的", + envelope_get_message: "領取紅包消息", + bind_phone_please: "為了您的賬戶安全,請綁定手機號", + delay_bind: "暫不綁定", + send_envelope_x: "發出的紅包", + send_envelope_back_all: "該紅包超過24小時未領取\n已退還", + envelope_x_can_get: "僅{name}可領取", + envelope_get_completed: "手慢了,紅包搶光了", + send_envelope_back_some: "該紅包超過24小時未領完\n部分已退還", + envelope_x_personal: "給%s的專屬紅包", + envelope_normal: "普通紅包", + login_out_of_date: "登錄已過期,重新登錄", + get_envelope_completed: "已被領完", + x_is_personal: "專屬", + + error_6072: "已達到單個紅包的最大金額", + error_6073: "單個紅包的數量不能低於0.01", + error_6074: "紅包功能已關閉", + + distroyAccount: "註銷帳號", + + inComing: "收入", + outGoing: "支出", + balance_order_detail: "余額明細", + type: "類型", + + payType: "支付方式", + orderNo: "訂單號", + time: "時間", + itemDataNotSupport: "當前版本暫不支持", + + manager_change_account: "管理員調賬", + groupEnvelope: "群紅包", + accountPay: "余額支付", + paySuccess: "支付成功", + + groupEnvelopePersonal: "群紅包-專屬紅包", + groupEnvelopeNormal: "群紅包-普通紅包", + changeAccount: "調賬", + groupEnvelopeRandom: "群紅包-拼手氣紅包", + + groupEnvelopeAllBack: "群紅包-全部退還", + groupEnvelopeSomeBack: "群紅包-部分退還", + + distroyTips: "為了確認您的賬戶安全
我們將在您註銷賬戶前進行以下驗證", + safeStatus: "賬戶處於安全狀態", + safeTips: + "請確保您的賬戶沒有改密、換綁手機/郵箱等敏感操作,賬戶沒有被盜風險。", + strikeBalance: "賬戶財產結算", + strikeTips: "註銷賬戶後,您賬戶的紅包余額數據都將刪除、且無法恢復。", + dataDelete: "賬戶數據刪除", + deleteTips: + "註銷賬戶後,該賬戶下的所有數據都將刪除、且無法恢復。數據包括您的聊天記錄、加入的群聊、好友關系等內容數據。", + applyDistroy: "申請註銷帳號", + distroyConform: "確定要註銷賬號嗎?", + distroyConformTips: "註銷後將退出登錄,請謹慎操作!", + distroy: "確認註銷", + submitSuccess: "提交成功", + submitted: "您的註銷申請已提交!", + helpWord: + "平臺將在{day}個工作日內處理您的申請並刪除您的所有數據,如需幫助,請通過官網聯系我們。", + know: "我知道了", + revoke: "撤銷申請", + revokeSuccess: "撤銷成功", + recover: "確定要撤銷賬號嗎?", + toNormal: "撤銷後賬號狀態變為正常", + beRecover: "確認恢復", + recoverSuccess: "恢復成功", + redPackageCountTip: "單個紅包總數不可低於0.01", + closeAccount: "註銷賬號", + recoverAccount: "恢復賬戶", + recoverAccountTipTitle: "確定要恢復賬號嗎?", + recoverAccountTipDesc: "恢復後賬號狀態變為正常", + recoverAccountTipOk: "確認恢復", + + closingAccount: "賬戶處於註銷審核中", + closingAccountEndTime: ",預計將在{num}個工作日內完成刪除", + error_6079: "您已不在群聊,無法領取", + error_6075: "重複申請", + error_6076: "沒有找到任何注銷申請記錄", + error_6077: "注銷審核中", + error_6078: "用戶不存在或已注銷,請檢查確認", + error_video_pre_err: "文件生成錯誤,數據加載失敗。", + err_upload_file: "上傳失敗,請稍後再試", + newFriendList: "好友申請列表", + err_msg_send_failed: "發送失敗,請稍後再試", + error_red_package_tip1: "紅包已過期", + upload: "上傳", + error_6081: "發送失敗,請稍後重試", + privateSetting: "权限设置", + atTip: "有人{'@'}你", + timeoutLock: "超時鎖定", + reTry: "重試", + plsEnterPassword6_15: "密碼 (6-15為字母及數字)", + plsEnterAccount6_15: "用戶名 (6-15為字母及數字)", + removeGroupNotice: "删除群公告通知", + + net_error_no_net: "網絡不可用,請檢查你的網絡設置", + groupChatError: "內容加載失敗,點擊重試", + legalNotice: "法律聲明", + sysNotify: "系統通知", + exit_group_tip: "已退出群聊", + group_mute_ing: "本群已禁言", + w_edit_group_notification: "編輯了群公告", + messageNotify: "消息通知", + groupEnterLimit: "被邀好友進群數已達上限,請檢查", + unRecard: "無法錄音:", + hasCreateGroup: "已創建群聊", + joinGroupForLink: "通過鏈接加入群聊", + be: "被", + moveoutGroup: "移出群聊", + groupOwnerChangeTo: "群主已變更為", + changeGroupNameTo: "更改群名稱為", + disbandedGroup: "解散了群聊", + bannedFromSpeaking: "禁言", + gagWasLifted: "取消了禁言", + initiatedGroupGag: "開啟了群禁言", + shutDownGroupGag: "關閉了群禁言", + target: "對方", + groupSizeUpperLimit: "群人數達到上限", + topUpperLimit: "置頂達到上限", + topMessageRepeate: "置頂消息已重復", + userHasReadMessage: "該用戶已讀過此消息", + placeholder: { + allMuted: "群主或管理员已開啟全體禁言", + groupDisbanded: "該群組已被解散", + groupBanned: "該群組已被封禁", + singleBanned: "你已被禁言", + search: "搜索", + pleaseInput: "請輸入", + sec: "秒", + groupNoticePlaceholder: "請輸入群公告..", + inputPassword: "請輸入密碼", + confirmPassword: "請確認輸入密碼", + inputPhoneNumber: "請輸入手機號", + select: "請選擇", + userOrGroupIDInput: "請輸入{type}", + writeMessage: "輸入信息...", + inputGroupName: "請輸入群名稱", + typingMessage: "在這裡輸入消息", + inputName: "請輸入你的暱稱" + }, + withoutQualification: "該用戶未達到添加好友條件", + groupMask: "群聊面具", + groupMaskDescription: "設置本群昵稱和頭像", + pleaseBind: "為了您的賬戶安全,避免添加好友功能受限,請綁定", + orStr: "或", + andStr: "和", + confirmExit: "確認退出", + pleaseBindTip1: "為了您的賬戶安全,請綁定", + + skin_item_user_name: "張娜娜", + skin_item_msg1: "你好,收到及時回復", + skin_item_msg2: "好的,收到了", + + appearance: "外觀", + bubbleBgColor: "對話氣泡顏色", + chatBackground: "聊天背景", + moreSettings: "更多設置", + + highPerformanceModel: "純淨模式", + highPerformanceModelDesc: "開啟則不顯示外觀效果", + skinPreview: "效果預覽", + imageAlpha: "圖案透明度", + defaultText: "預設", + fontSizePreview: "預覽字體大小,拖動下面的滑塊,可調整字體大小。設置後,會改變所有頁面文本的字體大小。", + changedText: "好的,已經更改", + + min: "最小", + max: "最大", + backgroundColor: "背景色", + changeSaveTip: "您已修改設置,是否保存後返回", + saveAndBack: "保存並返回", + changeAvatar: "更換頭像", + appFontSize: "全局字體大小", + appFontSizeTip: + "預覽字體大小,拖動下面的滑塊,可設置字體大小。設置後,會改變應用內所有文本的字體大小。", + maskDesc: "開啟後入群成員將可設置本群昵稱和頭像", + groupMaskOpenTip: " %s了群聊面具%s", + groupMaskOpenTipNew: "了群聊面具", + open: "開啟", + close: "關閉", + groupMaskOpenMsgErrorTip: "群聊面具功能開關提示", + maskOpen: "群聊面具已開啟", + maskClose: "群聊面具已關閉", + + rememberPassword: "記住密碼", + mobileLogin: "手機登錄", + emailLogin: "郵箱登錄", + welcomeToQDDChat: "歡迎來到球帝帶", + choiceYourAvatar: "請選擇您的頭像", + + transmit: "轉發", + multiSelect: "多選", + singleShareMsg: "逐條轉發", + groupShareMsg: "合並轉發", + notGroupFound: "沒有找到{name}相關的群聊", + + recentConversation: "最近的聊天", + selectContact: "選擇聯系人", + selectGroup: "選擇群聊", + noGroup: "暫無群聊", + notSelectMessage: "您還沒有選擇消息", + hiddenMsg: "隱藏消息", + hiddenMsgTip: "是否隱藏消息來源?", + yes: "是", + no: "否", + mergeMessageList: "【合並轉發】聊天記錄", + singleMlCount: "【逐條轉發】共{count}條消息", + notFoundUser: "未找到用戶", + notFoundGroup: "找不到該群聊", + notShouldFoundUser: "找不到該用戶", + selectAt: "選擇要{'@'}的人", + enterPawAgain: "請再次輸入密碼", + Someone: "有人{'@'}我", + hasSelect: "已選", + groupChat: "群聊", + groupMember1: "群成員", + groupSendMsg: "群發消息", + msgNotSupportTransmit: "消息不支持轉發", + bindGoogle: "綁定Google", + bindFacebook: "綁定Facebook", + unbind: "解綁", + accountSettings: "賬戶設置", + thirdPartyAccountBinding: "第三方賬號綁定", + otherSettings: "其他設置", + groupChatLoadFailed: "群聊信息加載失敗", + addGroupSend: "發起群發", + groupSendNoData: "還沒有群發消息記錄", + removeBinding: "解除綁定", + unBindToastPart1: "解除綁定後,將不能再用", + unBindToastPart2: "賬號登錄,要繼續解除嗎?", + noOpPermission: "還沒有可操作的權限", + bindingPhoneOrEmail: "為了您的賬戶數據安全,請先綁定手機號或郵箱再解除綁定。", + unbindingSuccess: "解綁成功", + unbindingFair: "解綁失敗", + bindingPhoneOrEmailSetPassword: + "爲了您的賬戶數據安全,請先綁定手機號或郵箱再設置密碼。", + + showAll: "查看全部", + allCount: "全部共{num}個", + recoverSuccessLoginAgain: "恢復成功,請重新登錄", + createGroupSend: "發起群發", + groupSend: "群發", + notSelect: "您還沒有選擇", + confirmDeleteTitle: "確認刪除選中的群發?", + totalGe: "共%s個", + sendTotalGe: "發送給({num}個)", + uploadFailed: "上傳失敗", + selectMax: "選擇數量到達上限", + newFriendApply: "您有新的好友,點擊查看", + error_6091: "選擇人數超過上限", + error_6092: "消息id已存在", + notSupportLoginStyle: "暫不支持,請使用其他方式登錄", + pay_password: "支付密碼", + pleaseEnter: "請輸入", + loadingPage: "頁面加載中", + payTip: "您還未設置支付密碼,請完成設置後再進行操作。", + deleteRedPackageErrTip: "紅包消息不可撤回", + applyIconTip: "申請已提交, 審核通過即可更換頭像", + applyIconProgress: "查看申請進度", + applyRecord: "申請記錄", + repealWithdrawApply: "確定撤銷提現申請嗎?", + withdrawNotes: "提現記錄", + inReview: "審核中", + passTheAudit: "審核通過", + rejectedPayment: "審核駁回", + revoked: "已撤銷", + undone: "撤銷", + withdrawalQuantity: "提現數量", + withdrawal: "提現", + withdrawalBack: "提現退回", + update_tip1: "若無法更新", + update_tip2: "可前往官網官網下載", + update_ing: "更新中", + update_install: "立即安裝", + installFailed: "未安裝,請稍後重試", + installNotFoundFile: "未找到安裝文件,請稍後重試", + error_6093: "登錄方式不支持", + error_6094: "頭像數量已達上限", + deleteIcon: "是否刪除該頭像?", + balanceWithdraw: "余額提現", + canBalanceWithdraw: "可提現余額", + inputPayPassword: "請輸入支付密碼", + withdrawalSuccess: "提現成功", + personIcon: "個人頭像", + groupIcon: "群聊頭像", + error_6096: "請勿設置重復、連續數字的支付密碼", + forgetPayPassword: "忘記支付密碼", + downloadFailedTip: "下載失敗,請稍後重試", + error_6097: "余額不足", + notSet: "暫不", + notSet2: "未設置", + passed: "已通過", + headRejected: "已駁回", + readDeleteTime: "消息焚燒時長", + readDeleteTimeTitle: "焚燒時長", + retracted_h5: "撤回了", + message_h5: "的消息", + inputRemark: "請輸入您的備註", + authFailed: "授權失敗", + userNullError: "該用戶賬號異常", + community: "社區", + join: "加入群組聊天,探索更多社區活動~", + posteviews: "還沒有發佈活動~", + views: "次觀看", + ended: "活動已結束", + configError: "配置錯誤,暫不能開啟", + burnMsgOpen: "{name}設置了消息已讀{time}後焚毀", + burnMsgClose: "{name}關閉了閱讀即焚,消息不再自動焚毀", + burnMsgNotify: "閱讀即焚通知", + homeItem3: "社區", + messageTimeOut: "消息已過期", + burnContentTip: "消息正在焚毀中,是否退出並一鍵焚毀?", + cancelLater: "我再看看", + burnConfirm: "立即焚燒", + setNickNameTimes: ",暱稱在%s天內僅可修改一次", + voiceSendErrTip1: "僅支援發送%s-%s秒的語音", + textSendErrTip1: "最多輸入%s個字符", + pictureSendErrTip1: "僅支援發送%s-%sM的圖片", + videoSendErrTip1: "僅支援發送%s-%sM的視頻", + your: "您", + setNickNameTimes: "{day}天內僅可修改一次", + immutable: "暫不可更改", + joinCommunity: "加入群組聊天,探索更多社區活動~", + noActivity: "還沒有發佈活動~", + views: "次觀看", + ended: "活動已結束", + errorLink: "找不到連結", + set_payment_password: "請設置6位支付密碼", + unset_payment_password: "請勿設置重複或連續數字的支付密碼", + Confirm_Payment_Password: "確認支付密碼", + Please_confirm_payment_password: "請再次輸入確認支付密碼", + error_6100: "抱歉,您只能每{day}天修改一次個人暱稱", + viewDetailed: "查看群成員詳細資訊", + inviter: "邀請人", + lastLoginTime: "最後登錄時間", + DeleteThisUsersgroup: "刪除該用戶在本群發送的群聊消息", + AddGroupMembers: "添加群組成員免驗證同意", + error_6103: "該用戶為普通成員,沒有權限", + error_6104: "被添加者不在群內", + error_6108: "帳戶異常", + group_remark: "群聊備註", + input_group_remark: "請輸入備註內容", + await_accepting: "設備授權申請已發送", + apply_tips: "此設備不可信或審核未通過,是否發送設備信息進行授權申請?", + apply_send: "發送申請", + apply_ttl: "設備登錄授權", + group_limit500: "每個分組人數上限為500人", + select_group: "選擇好友分組", + cantfind: "沒有找到{name}相關的聯繫人", + error_6109: "該用戶數好友已達上限", + error_6106: "請等待設備審核通過後再登錄", + selectGroups: '選擇分組', + group_member_info: "群員資訊", + join_time: "入群時間", + inviter_user: "邀請人", + last_login_time: "最後登錄時間", + sync_group_avatar: '是否同時修改群聊頭像?', + invite_fail: '{usernames}帳號異常,邀請失敗!', + enterAndCtrl:'Enter 發送 / Enter+Ctrl 換行', + enterAndCtrlIOS:'Enter 發送 / Enter+control 換行', + scanLogin: '掃碼登錄', + formLogin: '密碼登錄', + scanText: '球帝帶掃碼登錄', + scanSubText: '使用球帝帶APP掃碼登錄', + qrcodeExpired: '二維碼已過期', + expiredTime10: '此二維碼10分鐘內有效', + reflesh: '點擊刷新', + scanSuccess: '掃描成功', + confromLogin: '請確認登錄', + onlineStatus: "在線狀態:", + networkDiagnostic: "網絡診斷", + "setPaymentPassword": "請設置6位支付密碼", + "unSetPaymentPassword": "請勿設置重複或連續數字的支付密碼", + "verifyPassword": "驗證支付密碼", + "confirmPaymentPassword": "確認支付密碼", + "verifyCurrentPassword": "請驗證當前的支付密碼", + "reInsetPaymentPassword": "請再次輸入確認支付密碼", + "paymentPasswordDifferent": "密碼不一致,請重新輸入", + "getPaymentPasswordWay": "請選擇找回密碼的方式", + "selectFriendGroup": "選擇好友分組", + "selectGroups": "選擇分組", + "eachGroupLimitNumber": "每個分組人數上限為{p}人", + "selectChatLimitNumber": "最多只能選擇{p}個聊天", + "deviceID": "設備ID:", + "groupChatRemark": "群聊備註", + "pleaseEnterRemarks": "請輸入備註內容", + "groupMembershipInfo": "群員信息", + "error_6102": "每個分組人數上限為500人", + "distroyed": "已註銷", + "forbiddened": "已封鎖", + "addGroupMemberWithoutApply": "添加群組成員免驗證同意", + "error_6109": "該用戶好友人數已達上限", + "apply_ttl": "設備登錄授權", + "apply_tips": "此設備不可信或審核未通過,是否發送設備信息進行授權申請?", + "apply_send": "發送申請", + "device_auth_send": "設備授權申請已發送", + "using_trusted_device": "該設備登錄存在風險,請使用可信設備登錄", + "account_closed": "賬號已註銷", + "google_code": "谷歌驗證碼", + "input_google_code": "請輸入谷歌驗證碼", + "inviterUser": "邀請人", + "lastLoginTime": "最後登錄時間", + "error_6105": "非可信設備,請提交申請", + "error_6106": "請等待設備審核通過後再登錄", + "error_6107": "設備審核中未通過", + "hasDetailInfoPermission": "查看群成員詳細資訊", + "editUserHeaderWithGroupHeader": "是否同時修改群聊頭像?", + "darkModel": "深色模式", + "darkModelSetDesc": "跟隨系統打開或關閉深色模式", + "whiteModel": "淺色模式", + "fileNotFound": "未讀取到文件", + "openFileInApp": "預覽文件", + "openFileOtherApp": "其他應用打開", + "openByBrowser": "用瀏覽器下載", + "pauseDownload": "暫停下載", + "resumeDownload": "繼續下載", + "downloadFailed": "下載失敗", + "downloading": "下載中", + "notdownloaded": "未下載", + "cancelDownloading": "文件正在下載中,是否取消下載?", + "failedToDownload": "文件下載失敗,是否重新下載?", + "cancelDownload": "取消下載", + "sending": "發送中", + "fileTypeNotSupported": "不支持上傳該文件格式", + "fileSizeOut": "文件大小超出", + "restrict": "限制", + "getWithPhone": "透過手機號碼找回", + "getWithEmail": "透過郵箱地址找回", + "fileSize": "文件大小", + "whetherDownloadFile": "是否下載文件", + "downloadFailedTip1": "下載失敗,請檢查網絡後重試", + "downloadQueueTip": "已加入下載隊列,等待下載", + "notSupportTransmitTip": "消息暫不支持轉發", + "voiceSize": "音訊檔案大小不可超過100MB。", + "wordSize": "文件檔案大小不可超過100MB。", + "apkSize": "安裝包檔案大小不可超過300MB。", + "zipSize": "壓縮包檔案大小不可超過300MB。", + "groupDeleteUserTip": "同時刪除該用戶在本群發送的訊息", + "nPersonRead": "{p}人已讀", + "darkModeEnabled": "深色模式已啟用,重新啟動APP後生效", + "darkModeOff": "深色模式已關閉,重新啟動APP後生效", + "otherUser": "對方", + "selectMsgDeleteTime": "選擇保留時間", + "selectMsgDeleteTimeTip": "聊天記錄將在超出保留時限後自動刪除,是否繼續?", + "msgSaveTime": "聊天記錄保留時間", + "msgSaveTimeDesc": "自動清除超過時效的聊天記錄", + "hasTimeDeleteMsgPermission": "群聊消息保存時間配置", + "accountAbnormal": "帳號異常,邀請失敗!", + "onlyBrowser": "僅支援瀏覽器查看", + "cancelSendSuccess": "已取消發送", + "allStr1": "全域", + "modelStr1": "日夜模式", + "atMaxCountTip": "最多@ {p} 人", + + "checkNet": "網絡檢測", + "autoChangeServer": "一鍵切換到最優線路", + "serverLine": "線路", + "timeOut": "超時", + "serverLineChange": "切換線路", + "netCheck": "網絡診斷", + "netDelayCheck": "延遲檢測", + "netDelay1Check": "互聯網延遲", + "msgDelay1Check": "消息延遲", + "imgDelay1Check": "圖片延遲", + "netLine": "網絡線路", + "reCheck": "再測一次", + "netChecking": "網絡檢測中", + "locationAddr": "歸屬地", + "selectPerfectLineTip": "已為您選擇最佳線路", + "selectPerfectLineTip2": "當前已是最佳線路,無需切換", + "selectPerfectLineTipNoNet": "未識別到最佳線路,請檢查網絡後重試", + "changing": "切換中", + "changeSuccess": "切換成功", + "autoChanging": "智能切換中,請稍後再試", + + "transmitTo": "轉發給", + "moreActions": "更多", + "refreshing": "刷新", + "openInBrowser": "在瀏覽器中打開", + "coverFlow": "懸浮", + "toolboxAudioCall": "語音通話", + "waitAnswer": "等待接聽", + "callFinish": "通話結束", + "confirmLogin": "確認登錄", + "requestTimeOut": "請求超時,請稍後重試", + "error_6115": "二維碼已過期", + "groupNotFound": "找不到該群組", + "userNotFound": "找不到該使用者", + "microphoneOpen": "麥克風已開啟", + "speakerOpen": "揚聲器已開啟", + "voiceWithYou": "邀請您進行語音通話", + "fileSizeConnotZero": "檔案大小不能為0", + "callInterceptor": "通話中斷", + "startCallError": "啟動通話錯誤", + "callHungUp": "通話已掛斷", + "callRemoteHungUp": "對方已掛斷", + "callReceiverError": "接聽通話錯誤", + "starting": "啟動中", + "startTimeOut": "語音通話發起超時", + "callWaitReceiver": "等待對方接聽", + "callRemoteNotAccept": "對方未接聽", + "callStartSendError": "發起語音通話失敗", + "notAccept": "未接聽", + "callAccepting": "接聽中", + "call": "通話", + "callDoing": "線路忙,已取消", + "netBad": "當前網路環境較差", + "netDown": "網路中斷", + "onCalling": "通話中", + "error_1303": "您還不是對方的好友", + "remoteAccountError": "對方帳號異常", + "userOnlineStatus": "在線狀態", + "error_100008": "該用戶未達到成為管理員條件", + "callTimeLimit": "每次通話最長{p}分鐘", + "notLoginOnline": "從未登錄", + "callRemoteDoing": "忙線中", + "exitLoginIng": "正在退出", + "usePhoneRecovery": "使用手機號找回", + "useEmailRecovery": "使用電子郵件找回", + "otherRecoveryMethods": "其他找回方式", + "emailAddress": "電子郵件地址", + "phoneErrorOrNotExist": "手機號錯誤或不存在", + "phoneNotExist": "手機號不存在", + "verificationCodeFailure": "驗證碼獲取失敗,請重試", + verificationCodeSent: "驗證碼已發送", + hiWelcomeLogin: "Hi~ 歡迎登入", + otherLoginMethods: "其他登入方式", + usernamePhoneEmail: "用戶名/手機號碼/電子郵件", + "error_1303": "您還不是對方的好友", + "remoteAccountError": "對方帳號異常", + "userOnlineStatus": "在線狀態", + "error_100008": "該用戶未達到成為管理員條件", + "callTimeLimit": "每次通話最長%s分鐘", + "notLoginOnline": "從未登錄", + "callRemoteDoing": "忙線中", + "exitLoginIng": "正在退出", + "searchAreaCode": "搜尋國家/地區", + "otherFindStyle": "其他找回方式", + "welcome": "歡迎登入", + "docReadError": "文件無法讀取,請檢查文件格式是否正確", + "errorDetail": "錯誤詳情", + "fileReadError": "文件讀取出錯", + "docPreviewError": "文件無法預覽", + "docPreviewUseOther": "如果由於網絡或機型不兼容等原因,出現文件無法預覽,您可以使用", + "netTimeOut": "網絡超時", + ip: "IP", + imageLoadingRoute: "圖片加載路線", + networkChecking: "網絡檢測中...", + noPermission: "沒有權限,請獲取權限後登入", + pageNotFound: "頁面不存在", + serverError: "伺服器故障", + databaseError: "資料庫查詢錯誤", + networkError: "網絡連接錯誤" +}; diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..81ab98d --- /dev/null +++ b/src/main.js @@ -0,0 +1,95 @@ +import { createApp } from 'vue' +import dayjs from 'dayjs' +import isToday from 'dayjs/plugin/isToday' +import isYesterday from 'dayjs/plugin/isYesterday' +// 导入pinia本地存储 +import pinia from '@/stores/index.js' +import { createRouterScroller } from 'vue-router-better-scroller' +import App from './App.vue' +import router from './router' +import api from '@/api' +import {i18n, vantLocales} from './lang/index.js' +import Avatar from "@/components/avatar/index.vue" +import vue3GoogleLogin from 'vue3-google-login' +import { Notify } from 'vant'; +// import 'virtual:uno.css' +import directive from './directive' +const { clickoutside, longPress } = directive +import './assets/quill.snow.css'; +import "ant-design-vue/dist/reset.css"; +import './app.less' +import './assets/base.css' +import './assets/reset.css' +// Vant 桌面端适配 +import '@vant/touch-emulator' +import "video.js/dist/video-js.css"; + +/* -------------------------------- +Vant 中有个别组件是以函数的形式提供的, +包括 Toast,Dialog,Notify 和 ImagePreview 组件。 +在使用函数组件时,unplugin-vue-components +无法自动引入对应的样式,因此需要手动引入样式。 +------------------------------------- */ +import 'vant/es/toast/style' +import 'vant/es/dialog/style' +import 'vant/es/notify/style' +import 'vant/es/image-preview/style' +import VConsole from 'vconsole' +import Vue3Lottie from 'vue3-lottie' +import { Image } from "ant-design-vue"; +import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' +import VueVirtualScroller from 'vue-virtual-scroller' +import cdnSource from "@/components/cdnSource/index.vue"; + +dayjs.extend(isYesterday) +dayjs.extend(isToday) +//对vant组件进行初始化语言设置 +vantLocales(i18n.global.locale.value) +new VConsole() +const app = createApp(App) +app.directive('click-outside', clickoutside) +app.directive('long-press', longPress) +app.use(router) +app.use(pinia) +app.use(i18n) +app.use(Vue3Lottie) +app.use(VueVirtualScroller) +app.component('Avatar', Avatar) +app.component('cdnSource', cdnSource) +app.provide('$api',api) +app.use(Notify); +app.use(vue3GoogleLogin, { + clientId: '18623057689-6fn6hjs6k72lt722q132pdjp3dk48q0v.apps.googleusercontent.com' +}) + +// 增强了 Vue Router v4 的滚动行为 +app.use(createRouterScroller({ + selectors: { + 'window': true, + 'body': true, + '.scrollable': true, + }, +})) +app.use(Image); + +window.addEventListener('beforeinstallprompt', (e) => { + e.preventDefault(); + window.deferredPrompt = e; +}) + +app.mount('#app') + +// 网页每次打开的时候,在 localStorage 中累加 +window.addEventListener('DOMContentLoaded', () => { + let num = Number(localStorage.getItem('pageInstanceNum')) || 0 + num += 1 + localStorage.setItem('pageInstanceNum', num) +}) + +// 刷新进来的时候,会重新storage + 1,相当于先 - 1再 + 1 +window.onbeforeunload = function (e) { + let num = Number(localStorage.getItem('pageInstanceNum')) || 0 + if (num === 0) return + num = num * 1 - 1 + localStorage.setItem('pageInstanceNum', num) +} diff --git a/src/router/banner.js b/src/router/banner.js new file mode 100644 index 0000000..5b7b89f --- /dev/null +++ b/src/router/banner.js @@ -0,0 +1,9 @@ +const route = [ + { + path: '/banner/transferPage', + name: 'transferPage', + component: () => import('@/views/banner/components/transferPage.vue'), + }, +] + +export default route \ No newline at end of file diff --git a/src/router/community.js b/src/router/community.js new file mode 100644 index 0000000..a88261d --- /dev/null +++ b/src/router/community.js @@ -0,0 +1,23 @@ +import charts from '@/views/dialogue/charts/index.vue' +import unocss from '@/views/unocss/index.vue' + +const community = () => import('@/views/community/index.vue') + +const routes = { + path: 'community', + name: 'community', + component: community, + children: [ + { + path: 'eventDetails', + name: 'eventDetails', + component: () => import('@/views/community/eventDetails.vue'), + }, + { + path: 'eventVideoDetails', + name: 'eventVideoDetails', + component: () => import('@/views/community/eventVideoDetails.vue'), + }, + ] +} +export default routes \ No newline at end of file diff --git a/src/router/contacts.js b/src/router/contacts.js new file mode 100644 index 0000000..d6ae195 --- /dev/null +++ b/src/router/contacts.js @@ -0,0 +1,321 @@ +import charts from "@/views/dialogue/charts/index.vue"; +import unocss from "@/views/unocss/index.vue"; + +const contact = () => import("@/views/contact/index.vue"); + +const routes = { + path: "contact", + name: "contact", + component: contact, + children: [ + { + path: "mySearch", + name: "mySearch", + component: () => import("@/views/contact/mySearch/index.vue") + }, + { + path: "new", + name: "newFriend", + component: () => import("@/views/contact/addFriend/index.vue"), + children: [ + { + path: "confirm", + name: "confirm", + component: () => import("@/views/contact/addFriend/confirm.vue") + } + ] + }, + { + path: "group", + name: "group", + component: () => import("@/views/contact/group/index.vue"), + children: [ + { + path: "detailed", + name: "detailed", + component: () => import("@/views/contact/group/details.vue"), + children: [ + { + path: "groupAdd", + name: "groupAdd", + component: () => import("@/views/contact/group/groupAdd.vue") + }, + { + path: "groupRemove", + name: "groupRemove", + component: () => import("@/views/contact/group/groupRemove.vue") + } + ] + } + ] + }, + { + path: "myChat", + name: "myChat", + component: () => import("@/views/contact/myChat/index.vue"), + children: [ + { + path: "mySearchs", + name: "mySearchs", + component: () => import("@/views/contact/myChat/mySearchs.vue") + } + ] + }, + { + path: "detail", + name: "detail", + component: () => import("@/views/contact/detail/index.vue"), + children: [ + { + path: "set", + name: "friendSet", + component: () => import("@/views/contact/detail/set.vue") + } + ] + }, + { + path: "scan", + name: "scan", + component: () => import("@/views/contact/scan/index.vue") + }, + { + path: "add", + name: "add", + component: () => import("@/views/contact/add/index.vue"), + children: [ + { + path: "search", + name: "friendSearch", + component: () => import("@/views/contact/add/search.vue"), + children: [ + { + path: "result", + name: "result", + component: () => import("@/views/contact/add/result.vue"), + children: [ + { + path: "apply", + name: "apply", + component: () => import("@/views/contact/add/apply.vue") + }, + { + path: "applyGroup", + name: "applyGroup", + component: () => import("@/views/contact/add/applyGroup.vue") + }, + { + path: "remark", + name: "remark", + component: () => import("@/views/contact/add/remark.vue") + } + ] + } + ] + } + ] + }, + { + path: "face", + name: "face", + component: () => import("@/views/contact/face/index.vue"), + children: [ + { + path: "next", + name: "friendNext", + component: () => import("@/views/contact/face/nextStep.vue") + } + ] + }, + { + path: "chat", + name: "chat", + component: () => import("@/views/contact/chat/index.vue") + }, + { + path: "editGroupChatAvatarUrl", + name: "editGroupChatAvatarUrl", + component: () => import("@/views/contact/chat/components/editGroupChatAvatarUrl.vue"), + }, + { + path: "code", + name: "code", + component: () => import("@/views/contact/qrcode/index.vue") + }, + { + path: "groupDetails", // 群详情 + name: "groupDetails", + component: () => import("@/views/contact/chat/groupDetails.vue"), + children: [ + { + path: "manageGrounp", // 群详情/管理群 + name: "manageGrounp", + component: () => + import("@/views/contact/chat/components/manageGrounp.vue"), + children: [ + { + path: "setAdministrator", // 群详情/管理群/设置管理员 + name: "setAdministrator", + component: () => + import("@/views/contact/chat/components/setAdministrator.vue") + }, + { + path: "addAdministrator", // 群详情/管理群/添加管理员 + name: "addAdministrator", + component: () => + import("@/views/contact/chat/components/addAdministrator.vue") + }, + { + path: "addDetails", // 群详情/管理群/添加管理员详情 + name: "addDetails", + component: () => + import("@/views/contact/chat/components/addDetails.vue") + } + ] + }, + { + path: "editNameAvatar", // 群详情/群名称和头像 + name: "editNameAvatar", + component: () => + import("@/views/contact/chat/components/editNameAvatar.vue"), + children: [ + { + path: "editNameAvatarContent", // 群详情/编辑群名称头像 + name: "editNameAvatarContent", + component: () => + import( + "@/views/contact/chat/components/editNameAvatarContent.vue" + ) + }, + { + path: "editNameAvatarUrl", // 群详情/编辑群名称头像 + name: "editNameAvatarUrl", + component: () => + import("@/views/contact/chat/components/editNameAvatarUrl.vue") + } + ] + }, + { + path: "groupNotice", // 群详情/群公告 + name: "groupNotice", + component: () => + import("@/views/contact/chat/components/groupNotice.vue") + }, + { + path: "groupchatMember", // 群详情/群聊成员 + name: "groupchatMember", + component: () => + import("@/views/contact/chat/components/groupchatMember.vue"), + children: [ + { + path: "groupUserDetail", // 群详情/群聊成员/查看群用户 + name: "groupUserDetail", + component: () => + import("@/views/contact/chat/components/groupUserDetail.vue") + }, + { + path: "setGroupBans", // 群详情/群聊成员/查看群用户/禁言 + name: "setGroupBans", + component: () => + import("@/views/contact/chat/components/setGroupBans.vue") + }, + { + path: "InviteFriends", // 群详情/群聊成员/邀请好友 + name: "InviteFriends", + component: () => + import("@/views/contact/chat/components/InviteFriends.vue") + } + ] + }, + { + path: "groupChatLogs", // 群详情/搜索群聊聊天记录 + name: "groupChatLogs", + component: () => + import("@/views/contact/chat/components/groupChatLogs.vue"), + children: [ + { + path: "groupMediaRecord", // 群聊天记录/媒体记录 + name: "groupMediaRecord", + component: () => + import("@/views/contact/chat/components/groupMediaRecord.vue") + }, + { + path: "groupFileRecord", // 群聊天记录、文件记录 + name: "groupFileRecord", + component: () => + import("@/views/contact/chat/components/groupFileRecord.vue") + } + ] + }, + { + path: "inviteLink", // 群详情/邀请链接 + name: "inviteLink", + component: () => + import("@/views/contact/chat/components/inviteLink.vue"), + children: [ + { + path: "manageLinks", // 群详情/邀请链接 + name: "manageLinks", + component: () => + import("@/views/contact/chat/components/manageLinks.vue") + } + ] + }, + { + path: "enterGroupApply", // 进群申请 + name: "enterGroupApply", + component: () => + import("@/views/contact/chat/components/enterGroupApply.vue") + } + ] + }, + { + path: "groupPrompt", + name: "groupPrompt", + component: () => import("@/views/contact/chat/components/groupPrompt.vue") + }, + { + path: "massTexting", + name: "massTexting", + component: () => import("@/views/contact/massTexting/index.vue"), + children: [ + { + path: "selectPage", + name: "selectPage", + component: () => import("@/views/contact/massTexting/selectPage.vue"), + children: [ + { + path: "selectPerson", + name: "selectPerson", + component: () => + import("@/views/contact/massTexting/selectPerson.vue") + }, + { + path: "selectGroup", + name: "selectGroup", + component: () => + import("@/views/contact/massTexting/selectGroup.vue") + }, + { + path: "friendGroup", + name: "friendGroup", + component: () => + import("@/views/contact/massTexting/friendGroup.vue") + }, + { + path: "friendDetails", + name: "friendDetails", + component: () => + import("@/views/contact/massTexting/friendDetails.vue") + } + ] + } + ] + }, + { + path: "msgSend", + name: "msgSend", + component: () => import("@/views/contact/massTexting/msgSend.vue") + } + ] +}; +export default routes; diff --git a/src/router/dialogue.js b/src/router/dialogue.js new file mode 100644 index 0000000..9385c91 --- /dev/null +++ b/src/router/dialogue.js @@ -0,0 +1,90 @@ +import charts from "@/views/dialogue/charts/index.vue"; +import unocss from "@/views/unocss/index.vue"; +const dialogue = () => import('@/views/dialogue/index.vue') +const privateChat = ()=> import('@/views/dialogue/privateChat/index.vue') +const groupChat = ()=>import('@/views/dialogue/groupChat/index.vue') +const route={ + path: 'dialogue', + name: 'dialogue', + component: dialogue, + children: [ + { + path: 'charts', + name: 'charts', + component: charts, + }, + { + path: 'privatechat/:userID', + name: 'privatechat', + component: privateChat, + }, + { + path: 'chatSetings', + name: 'chatSetings', + component: ()=> import('@/views/dialogue/privateChat/components/chatSeting.vue'), + children: [ + { + path: 'detail', + name: 'chatSetingDetail', + component: ()=> import('@/views/dialogue/privateChat/components/chatSetingDetail.vue') + } + ] + }, + { + path: 'groupChat/:groupID', + name: 'groupChat', + component: groupChat, + children: [ + { + path: 'chatSeting', + name: 'chatSeting', + component: ()=> import('@/views/dialogue/privateChat/components/chatSeting.vue') + }, + ] + }, + { + path: 'logDisplay', + name: 'logDisplay', + component: () => import('@/views/log/logDisplay.vue'), + }, + { + path: 'transmitPage', + name: 'transmitPage', + component: () => import('@/views/transmit/transmitPage.vue'), + children: [ + { + path: 'contactPerson', + name: 'contactPerson', + component: () => import('@/views/transmit/contactPerson.vue'), + }, + { + path: 'selectGroupChat', + name: 'selectGroupChat', + component: () => import('@/views/transmit/selectGroupChat.vue'), + }, + { + path: 'transmitFriendGroup', + name: 'transmitFriendGroup', + component: () => import('@/views/transmit/transmitFriendGroup.vue'), + }, + { + path: 'transmitGroupDetails', + name: 'transmitGroupDetails', + component: () => import('@/views/transmit/transmitGroupDetails.vue'), + } + ] + }, + { // 红包详情 + path: 'redpktDetail', + name: 'redpktDetail', + component: () => import('@/views/dialogue/groupChat/components/redpktDetail.vue'), + }, + { // 发红包 + path: 'sendRedpkt', + name: 'sendRedpkt', + component: () => import('@/views/dialogue/groupChat/components/sendRedpkt.vue'), + }, + ], + } + +export default route \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..0508eb2 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,134 @@ +// https://router.vuejs.org/zh/ +import { createRouter, createWebHistory } from 'vue-router' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' + +// 导入路由组件 +import home from '@/views/index.vue' + +import my from './my.js' +import dialogue from "@/router/dialogue.js"; +import contacts from "@/router/contacts.js"; +import community from "@/router/community.js"; +import loginRoute from "./login.js" +import bannerRoute from "./banner.js" +// import logRoute from "./log.js" +// import transmit from "./transmit.js" +// import { sdkInit } from "@/utils/sdkInit.js"; +import emitter from '@/utils/events'; +import { useUserStore } from "@/stores/user"; + +NProgress.configure({ showSpinner: true, parent: '#app' }) + +// 定义路由,每个路由都需要映射到一个组件 +const routes = [ + { + path: '/', + redirect: '/firstScreen', + }, + { + path: '/firstScreen', + name: 'firstScreen', + component: () => import('@/views/login/components/firstScreen.vue'), + }, + { + path: '/home', + redirect: '/home/dialogue', + }, + { + path: '/login', + component: () => import('@/views/login.vue'), + meta: { + keepAlive: true //需要被缓存的组件 + } + }, + { + path: '/home', + components: { + default: home, + }, + children: [ + dialogue, + contacts, + my, + community + ], + }, + ...loginRoute, + ...bannerRoute, + // ...logRoute +] +const notNeedInitSdk = [ + '/firstScreen', + '/signinAccount', + '/login', + '/forgotPassword', + '/otherLogin', + '/otherSignin', + '/verify', + '/setPassword', + '/forgotPassword', + '/resetPassword', + '/welcomePage', + '/restoreAccount', +] +const blackRouterList = [ + '/firstScreen', + '/login', + '/signinAccount', + '/userService', + '/setPassword', + '/forgotPassword', + '/resetPassword', + '/verify', + '/otherSignin', + '/otherLogin', + '/restoreAccount', + '/banner/transferPage', +] +const syncNewFriendsRoute = [ + '/home/dialogue', + '/home/contact', + '/home/community', + '/home/my', +] +// 创建路由实例并传递 `routes` 配置 +const router = createRouter({ + history: createWebHistory(import.meta.env.VITE_APP_PUBLIC_PATH), + routes, +}) + +router.beforeEach(async (to, from, next) => { + // sdk 初始化 + // if (localStorage.getItem('token')) { + // // NProgress.start() + // // await sdkInit() + // if (!notNeedInitSdk.includes(to.path)) { + // await sdkInit(false) + // } + // } + // 从别的页面回到首页 + if (to.path === '/firstScreen' && from.path !== '/') { + const { setFirstScreenChecked } = useUserStore(); + setFirstScreenChecked(true) + } + // 切换tab 并且不是刷新 + if (syncNewFriendsRoute.includes(to.path) && from.path !== '/') { + emitter.emit('SYNC_NEWFRIENDS_COUNT') + } + if (!blackRouterList.includes(to.path) && !localStorage.getItem('token')) { + // NProgress.start() // + // start progress bar + sessionStorage.removeItem('againLoadingSign')//跳转到登录页时删除标记 + next({ path: '/firstScreen', replace: true }) + } else { + next() + } +}) + +router.afterEach(() => { + // NProgress.done() // finish progress bar +}) + +// 导出路由实例,并在 `main.js` 挂载 +export default router diff --git a/src/router/log.js b/src/router/log.js new file mode 100644 index 0000000..eb18fda --- /dev/null +++ b/src/router/log.js @@ -0,0 +1,9 @@ +const route = [ + { + path: '/log/logDisplay', + name: 'logDisplay', + component: () => import('@/views/log/logDisplay.vue'), + }, +] + +export default route \ No newline at end of file diff --git a/src/router/login.js b/src/router/login.js new file mode 100644 index 0000000..ce8dd75 --- /dev/null +++ b/src/router/login.js @@ -0,0 +1,61 @@ +const route = [ + { + path: '/userService', + name: 'userService', + component: () => import('@/views/login/components/userService.vue'), + }, + { + path: '/otherLogin', + name: 'otherLogin', + component: () => import('@/views/login/components/otherLogin.vue'), + }, + { + path: '/signinAccount', + name: 'signinAccount', + component: () => import('@/views/login/components/signinAccount.vue'), + }, + { + path: '/otherSignin', + name: 'otherSignin', + component: () => import('@/views/login/components/otherSignin.vue'), + }, + { + path: '/verify', + name: 'verify', + component: () => import('@/views/login/components/verify.vue'), + }, + { + path: '/setPassword', + name: 'setPassword', + component: () => import('@/views/login/components/setPassword.vue'), + }, + { + path: '/forgotPassword', + name: 'forgotPassword', + component: () => import('@/views/login/components/forgotPassword.vue'), + }, + { + path: '/resetPassword', + name: 'resetPassword', + component: () => import('@/views/login/components/resetPassword.vue'), + }, + { + path: '/welcomePage', + name: 'welcomePage', + component: () => import('@/views/login/components/welcomePage.vue'), + children: [ + { + path: 'setAvatar', + name: 'setAvatar', + component: () => import('@/views/login/components/setAvatar.vue'), + } + ] + }, + { + path: '/restoreAccount', + name: 'restoreAccount', + component: () => import('@/views/my/security/logout/restoreAccount.vue'), + }, +] + +export default route \ No newline at end of file diff --git a/src/router/my.js b/src/router/my.js new file mode 100644 index 0000000..e247f74 --- /dev/null +++ b/src/router/my.js @@ -0,0 +1,253 @@ +const my = () => import('@/views/my/index.vue') + +const routes = { + path: 'my', + name: 'my', + component: my, + children: [ + { + path: 'account', + name: 'account', + component: () => import('@/views/my/account/index.vue'), + children: [ + { + path: 'info', + name: 'info', + component: () => import('@/views/my/account/info/index.vue'), + children: [ + { + path: 'modify_name', + name: 'modify_name', + component: () => import('@/views/my/account/info/modify_name/index.vue'), + children: [ + { + path: 'details', + name: 'details', + component: () => import('@/views/my/account/info/modify_name/details/index.vue'), + }, + ], + }, + { + path: 'phone', + name: 'phone', + component: () => import('@/views/my/account/info/phone/index.vue'), + }, + ], + }, + ], + }, + { + path: 'profile', + name: 'profile', + component: () => import('@/views/my/profile/index.vue'), + children: [ + { + path: 'qrcode', + name: 'qrcode', + component: () => import('@/views/contact/qrcode/index.vue'), + }, + { + path: 'user', + name: 'user', + component: () => import('@/views/my/profile/user.vue'), + }, + { + path: 'sign', + name: 'sign', + component: () => import('@/views/my/profile/sign.vue'), + }, + { + path: 'avatar', + name: 'selectAvatar', + component: () => import('@/views/my/profile/selectAvatar.vue'), + }, + { + path: 'applicationRecord', + name: 'applicationRecord', + component: () => import('@/views/my/profile/applicationRecord.vue'), + } + ] + }, + { + path: 'notDisturb', + name: 'notDisturb', + component: () => import('@/views/my/notDisturb/index.vue'), + }, + { + path: 'qrcode', + name: 'myQrcode', + component: () => import('@/views/my/qrcode/index.vue'), + }, + { + path: 'security', + name: 'security', + component: () => import('@/views/my/security/index.vue'), + children: [ + { + path: 'password', + name: 'password', + component: () => import('@/views/my/security/password.vue'), + }, + { + path: 'setPassword', + name: 'setUserPassword', + component: () => import('@/views/my/security/setPassword.vue'), + }, + { + path: 'phone', + name: 'phone', + component: () => import('@/views/my/security/phone.vue'), + children: [ + { + path: 'phoneNext', + name: 'phoneNext', + component: () => import('@/views/my/security/phoneNext.vue'), + } + ] + }, + { + path: 'bindPhone', + name: 'bindPhone', + component: () => import('@/views/my/security/bindPhone.vue'), + }, + { + path: 'email', + name: 'email', + component: () => import('@/views/my/security/email.vue'), + children: [ + { + path: 'emailNext', + name: 'emailNext', + component: () => import('@/views/my/security/emailNext.vue'), + } + ] + }, + { + path: 'bindEmail', + name: 'bindEmail', + component: () => import('@/views/my/security/bindEmail.vue'), + }, + { + path: 'device', + name: 'device', + component: () => import('@/views/my/security/device.vue'), + children: [ + { + path: 'detailId', + name: 'detailId', + component: () => import('@/views/my/security/deviceDetail.vue'), + }, + ], + }, + { + path: 'logoutAccount', + name: 'logoutAccount', + component: () => import('@/views/my/security/logout/logoutAccount.vue'), + children: [ + { + path: 'secureVerify', + name: 'secureVerify', + component: () => import('@/views/my/security/logout/secureVerify.vue'), + }, + { + path: 'submitSuccess', + name: 'submitSuccess', + component: () => import('@/views/my/security/logout/submitSuccess.vue'), + }, + ], + }, + { + path: 'phoneVerify', + name: 'phoneVerify', + component: () => import('@/views/my/security/pay/phoneVerify.vue'), + }, + { + path: 'emailVerify', + name: 'emailVerify', + component: () => import('@/views/my/security/pay/emailVerify.vue'), + }, + { + path: 'modify', + name: 'modifyPayPassword', + component: () => import('@/views/my/security/pay/modifyPassword.vue'), + }, + ] + }, + { + path: 'about', + name: 'about', + component: () => import('@/views/my/about/index.vue'), + }, + // 字体大小-配置页面 + { + path: 'typeface', + name: 'typeface', + component: () => import('@/views/my/typeface/index.vue'), + }, + // 网络诊断-配置页面 + { + path: 'network', + name: 'network', + component: () => import('@/views/my/network/diagnosis.vue'), + }, + // 网络诊断-切换线路 + { + path: 'lines', + name: 'lines', + component: () => import('@/views/my/network/circuit.vue'), + }, + { + path: 'appearance', + name: 'appearance', + component: () => import('@/views/my/appearance/index.vue'), + children: [ + { + path: 'bgconfig', + name: 'bgconfig', + component: () => import('@/views/my/appearance/appBgConf.vue'), + }, + ] + }, + // { + // path: 'download', + // name: 'download', + // component: () => import('@/views/my/download/index.vue'), + // }, + { + path: 'language', + name: 'language', + component: () => import('@/views/my/language/index.vue'), + }, + { + path: 'balance', + name: 'balance', + component: () => import('@/views/my/balance/index.vue'), + children: [ + { + path: 'detail', + name: 'balanceDetail', + component: () => import('@/views/my/balance/balanceDetail.vue'), + }, + { + path: 'packet', + name: 'redPacket', + component: () => import('@/views/my/balance/redPacket.vue'), + }, + { + path: 'withdrawal', + name: 'withdrawal', + component: () => import('@/views/my/balance/withdrawal.vue'), + children: [ + { + path: 'log', + name: 'applyLog', + component: () => import('@/views/my/balance/applyLog.vue'), + }, + ] + } + ], + }, + ], +} + +export default routes diff --git a/src/router/transmit.js b/src/router/transmit.js new file mode 100644 index 0000000..b6c6910 --- /dev/null +++ b/src/router/transmit.js @@ -0,0 +1,17 @@ +// const route = [ +// { +// path: '/transmit/transmitPage', +// name: 'transmitPage', +// component: () => import('@/views/transmit/transmitPage.vue'), +// }, { +// path: '/transmit/contactPerson', +// name: 'contactPerson', +// component: () => import('@/views/transmit/contactPerson.vue'), +// },{ +// path: '/transmit/selectGroupChat', +// name: 'selectGroupChat', +// component: () => import('@/views/transmit/selectGroupChat.vue'), +// }, +// ] + +// export default route \ No newline at end of file diff --git a/src/stores/config.js b/src/stores/config.js new file mode 100644 index 0000000..b1797fd --- /dev/null +++ b/src/stores/config.js @@ -0,0 +1,73 @@ +import { ref } from 'vue'; +import { defineStore } from 'pinia'; + +export const useConfigStore = defineStore('config', () => { + const applyUserList = ref(JSON.parse(window.localStorage.getItem('applyUserList') || '{}')) + const setApplyUserList = (data) => { + if (data === undefined) return + applyUserList.value = data + window.localStorage.setItem('applyUserList', JSON.stringify(data)) + } + const contactList = ref(JSON.parse(window.localStorage.getItem('contactList') || '[]')) + const setContactList = (data) => { + if (data === undefined) return + applyUserList.value = data + window.localStorage.setItem('contactList', JSON.stringify(data)) + } + + const isFriendList = ref(JSON.parse(window.localStorage.getItem('isFriendList') || '{}')) + const setIsFriendList = (data) => { + if (data === undefined) return + isFriendList.value = data + window.localStorage.setItem('isFriendList', JSON.stringify(data)) + } + + const chatList = ref(JSON.parse(window.localStorage.getItem('chatList') || '[]')) + const setChatList = (data) => { + if (data === undefined) return + applyUserList.value = data + window.localStorage.setItem('chatList', JSON.stringify(data)) + } + + const detailSearch = ref(JSON.parse(window.localStorage.getItem('detailSearch') || '{}')) + const setDetailSearch = (data) => { + if (data === undefined) return + detailSearch.value = data + window.localStorage.setItem('detailSearch', JSON.stringify(data)) + } + const groupId = ref('') + const setGroupId = (data) => { + if (data === undefined) return + groupId.value = data + } + const friendApplyNotice = ref(window.localStorage.getItem('friendApplyNotice') || 0) + const setFriendApplyNotice = (data) => { + if (data === undefined) return + friendApplyNotice.value = data + window.localStorage.setItem('friendApplyNotice', data) + } + const friendDetailId = ref(window.localStorage.getItem('friendDetailId') || '') + const setFriendDetailId = (data) => { + if (data === undefined) return + friendDetailId.value = data + window.localStorage.setItem('friendDetailId', data) + } + const groupSessionInfo = ref({}) + const setGroupSessionInfo = (data) => { + if (data === undefined) return + groupSessionInfo.value = data + window.localStorage.setItem('groupSessionInfo', JSON.stringify(data)) + } + const myGroupInfo = ref({}) + const setmyGroupInfo = (data) => { + if (data === undefined) return + myGroupInfo.value = data + console.log(myGroupInfo.value); + } + + + return { + friendApplyNotice, + setFriendApplyNotice + } +}); diff --git a/src/stores/contact.js b/src/stores/contact.js new file mode 100644 index 0000000..5127aa1 --- /dev/null +++ b/src/stores/contact.js @@ -0,0 +1,478 @@ +import { ref, computed } from 'vue'; +import { defineStore } from 'pinia'; +import api from '@/api' +import { getSDK } from '@/utils/openIM' +import { useUserStore } from "./user"; +import { storeToRefs } from "pinia"; +const IMSDK = getSDK(); + +export const useContactStore = defineStore('contact', () => { + const userStore = useUserStore(); + const { userInfo, groupChatConfig } = storeToRefs(userStore); + const applyUserList = ref(JSON.parse(window.sessionStorage.getItem('applyUserList') || '{}')) + const setApplyUserList = (data) => { + if (data === undefined) return + applyUserList.value = data + window.sessionStorage.setItem('applyUserList', JSON.stringify(data)) + } + // 联系人列表 + const contactList = ref(JSON.parse(window.sessionStorage.getItem('contactList') || '[]')) + const setContactList = (data) => { + if (data === undefined) return + contactList.value = data + window.sessionStorage.setItem('contactList', JSON.stringify(data)) + } + const getContactListFromReq = async () => { + try { + setTimeout(async () => { + const { data } = await IMSDK.getFriendList(); + console.log('concatListData::', data) + const list = data.map((item) => item.friendInfo) + setContactList(list) + }, 0); + } catch (error) { + console.error(error); + } + } + + // 更新联系人 + // contactItem 新的朋友 + // remove是否删除 + const updateContact = (contactItem, isRemove) => { + const idx = contactList.value.findIndex((friend) => friend.userID === contactItem.userID); + if (idx !== -1) { + if (isRemove) { + contactList.value.splice(idx, 1); + // 删除的时候要把群成员备注的信息清除掉 + setContactList(contactList.value) + return; + } + contactList.value[idx] = { ...contactItem }; + setContactList(contactList.value) + } + } + + const pushNewContact = (contactItem) => { + contactList.value.push(contactItem) + setContactList(contactList.value) + } + + const isFriendList = ref(JSON.parse(window.sessionStorage.getItem('isFriendList') || '{}')) + const setIsFriendList = (data) => { + if (data === undefined) return + isFriendList.value = data + window.sessionStorage.setItem('isFriendList', JSON.stringify(data)) + } + + const dataMap = ref({}) + + const getAllGroupOwnerOrAdmin = async (groupID) => { + const { data: allGroup } = await IMSDK.getJoinedGroupList() + dataMap.value = {} + allGroup.forEach(async (items) => { + const { data } = await IMSDK.getGroupMemberList({ + groupID: items.groupID, + filter: 2, + offset: 0, + count: 10000 + }); + data.forEach((item) => { + if (item.roleLevel !== 20) { + if (!dataMap.value[item.userID]) { + dataMap.value[item.userID] = item + } + if (Object.keys(dataMap.value).length > 0) { + if (dataMap.value[item.userID] && dataMap.value[item.userID].roleLevel === 60 && item.roleLevel == 100) { + dataMap.value[item.userID] = item + } + } + } + }) + // console.log('设置群管理员-----0000,',data) + // console.log('设置群管理员-----1111,',dataMap.value) + setAllGroupOwnerOrAdmin(dataMap.value) + }) + } + + const chatList = ref(JSON.parse(window.sessionStorage.getItem('chatList') || '[]')) + const setChatList = (data) => { + if (data === undefined) return + chatList.value = data + window.sessionStorage.setItem('chatList', JSON.stringify(data)) + } + + // 群聊列表 + const getChatGroupList = async () => { + const { data } = await IMSDK.getJoinedGroupList() + setChatList(data) + } + const updateGroupList = (item, isRemove = false) => { + const idx = chatList.value.findIndex( + (group) => group.groupID === item.groupID + ); + if (idx !== -1) { + if (isRemove) { + chatList.value.splice(idx, 1); + setChatList(chatList.value) + return; + } + chatList.value[idx] = { ...item }; + setChatList(chatList.value) + } + } + const pushNewGroup = (item) => { + chatList.value.push(item); + setChatList(chatList.value) + } + + + const detailSearch = ref(JSON.parse(window.sessionStorage.getItem('detailSearch') || '{}')) + const setDetailSearch = (data) => { + if (data === undefined) return + detailSearch.value = data + window.sessionStorage.setItem('detailSearch', JSON.stringify(data)) + } + const groupId = ref(window.sessionStorage.getItem('groupId') || '') + const setGroupId = (data) => { + if (data === undefined) return + groupId.value = data + window.sessionStorage.setItem('groupId', data) + } + const friendGroupId = ref(window.sessionStorage.getItem('friendGroupId') || '') + const setFriendGroupId = (data) => { + if (data === undefined) return + friendGroupId.value = data + window.sessionStorage.setItem('friendGroupId', data) + } + const friendDetailId = ref(window.sessionStorage.getItem('friendDetailId') || '') + const setFriendDetailId = (data) => { + if (data === undefined) return + friendDetailId.value = data + window.sessionStorage.setItem('friendDetailId', data) + } + const friendCreateCount = ref(window.sessionStorage.getItem('friendCreateCount') || 0) + const setFriendCreateCount = (data) => { + if (data === undefined) return + friendCreateCount.value = data + window.sessionStorage.setItem('friendCreateCount', data) + } + const groupSessionInfo = ref(JSON.parse(window.sessionStorage.getItem('groupSessionInfo') || '{}')) + const setGroupSessionInfo = (data) => { + if (data === undefined) return + groupSessionInfo.value = data + window.sessionStorage.setItem('groupSessionInfo', JSON.stringify(data)) + } + // 获取我在当前的群信息 + const getMyGroupInfo = async () => { + const params = { + groupID: groupChatConfig.value.groupID, + keywordList: [userInfo.value.userID], + isSearchUserID: true, + isSearchMemberNickname: false, + offset: 0, + count: 20, + } + const { data } = await IMSDK.searchGroupMembers(params) + setmyGroupInfo(data[0]) + } + + const myGroupInfo = ref(JSON.parse(window.sessionStorage.getItem('myGroupInfo') || '{}')) + const setmyGroupInfo = (data) => { + if (!data) return {} + myGroupInfo.value = data + window.sessionStorage.setItem('myGroupInfo', JSON.stringify(data)) + } + const groupInfo = ref(JSON.parse(window.sessionStorage.getItem('groupInfo')) || '') + const setGroupInfo = (data) => { + if (data === undefined) return + groupInfo.value = data + window.sessionStorage.setItem('groupInfo', JSON.stringify(data)) + } + + + // 存储群成员信息--旧方法 + // const groupMemberInfo = ref(JSON.parse(window.sessionStorage.getItem('groupMemberInfo')) || {}) + // const setGroupMemberInfo = (data) => { + // if (data === undefined) return + // groupMemberInfo.value = data + // window.sessionStorage.setItem('groupMemberInfo', JSON.stringify(data)) + // } + + // 存储群成员信息--新方法 + const groupMemberInfoObj = ref({}) + const groupMemberInfo = ref(groupMemberInfoObj.value) + const setGroupMemberInfo = (data) => { + if (data === undefined) return + groupMemberInfoObj.value = data + groupMemberInfo.value = data + // console.log('我是获取群成员接口',data) + } + + const groupMemberLoading = ref(false) + // 获取当前群的群员信息 + const getGroupMemberInfo = async (groupID) => { + const params = { + groupID: groupID, // 群 ID + filter: 0, // 群成员角色 0所有成员 1群主 2群管理员 3群普通成员 4群管理员和普通成员 5群主和群管理员 + offset: 0, + count: 10000 + } + groupMemberLoading.value = true + const res = await IMSDK.getGroupMemberList(params); + groupMemberLoading.value = false + res.data.forEach((item) => { + item.remark = '' + item.groupNickName = '' + item.isFriend = false + contactList.value.forEach((items) => { + if (item.userID === items.userID) { + item.remark = items.remark + item.groupNickName = item.nickname + item.isFriend = true + item.nickname = items.nickname + } + }) + }) + // 群成员列表 + setGroupMemberInfo({ + ...groupMemberInfo.value, + [groupID]: res.data + }) + } + + /** + * 获取群成员,按需分页加载 + * @param {*} + */ + const newGetGroupUserInfoList = async ({groupID,filter,offset,count}) =>{ + const params = { + groupID, // 群 ID + filter, // 群成员角色 0所有成员 1群主 2群管理员 3群普通成员 4群管理员和普通成员 5群主和群管理员 + offset,//这个是总数,比如说每页40条,第一页是0,第二页就是40 + count//每一页的条数 + } + groupMemberLoading.value = true + const res = await IMSDK.getGroupMemberList(params); + groupMemberLoading.value = false + res.data.forEach((item) => { + item.remark = '' + item.groupNickName = '' + item.isFriend = false + contactList.value.forEach((items) => { + if (item.userID === items.userID) { + item.remark = items.remark + item.groupNickName = item.nickname + item.isFriend = true + item.nickname = items.nickname + } + }) + }) + let list = [] + if(groupMemberInfoObj.value[groupID]?.length&&offset!=0){ + list = groupMemberInfoObj.value[groupID] + } + setGroupMemberInfo({ + [groupID]: [...list,...res.data] + }) + + } + + // 删除当前群的群员信息 + const updateGroupMemberInfo = (groupID, userID) => { + const arr = groupMemberInfo.value[groupID] + const idx = arr.findIndex( + (msg) => msg.userID === userID + ); + if (idx !== -1) { + arr.splice(idx, 1); + } + setGroupMemberInfo({ + ...groupMemberInfo.value, + [groupID]: arr + }) + } + + // 更新当前群的群员信息 + const updateGroupMemberInfos = (groupID, data) => { + const arr = groupMemberInfo.value[groupID] + const idx = arr.findIndex( + (msg) => msg.userID === data.userID + ); + if (idx !== -1) { + arr[idx] = {...arr[idx], ...data} + + setGroupMemberInfo({ + ...groupMemberInfo.value, + [groupID]: arr + }) + } + } + + // 更新当前群的群员群昵称信息 + const updateGroupMemberInfos1 = (groupID, data) => { + + const arr = groupMemberInfo.value[groupID] + if(!arr||!arr.length)return + const idx = arr.findIndex( + (msg) => msg.userID === data.userID + ); + if (idx !== -1) { + arr[idx].groupNickName = data.nickname + arr[idx].faceURL = data.faceURL + arr[idx].roleLevel = data.roleLevel + arr[idx].muteEndTime = data.muteEndTime + arr[idx].ex = data.ex || '' + setGroupMemberInfo({ + ...groupMemberInfo.value, + [groupID]: arr + }) + } + } + + + const unreadMsgCount = ref(Number(window.sessionStorage.getItem('unreadMsgCount') || 0)) + const setUnreadMsgCount = (count) => { + if (count === undefined) return + console.log('store 内部', count) + unreadMsgCount.value = count + window.sessionStorage.setItem('unreadMsgCount', count) + } + + // 是否有新好友添加 + const hasNewFriendApply = ref(window.sessionStorage.getItem('hasNewFriendApply') === 'true') + const updateNewFriendApply = (hasNewFriend) => { + if (hasNewFriend === undefined) return + hasNewFriendApply.value = hasNewFriend + window.sessionStorage.setItem('hasNewFriendApply', hasNewFriend) + } + + const toppingList = ref(JSON.parse(window.sessionStorage.getItem('toppingList') || '[]')) + const setToppingList = async () => { + toppingList.value = [] + if (groupInfo.value.conversationID) { + const { data = [] } = await api.contact.getGroupTopMsg({ + conversationID: groupInfo.value.conversationID, + groupID: groupInfo.value.groupID + }) + data.forEach(item => { + item.isDelStatus = 0 + contactList.value.forEach((items) => { + if (item.sendID === items.userID) { + item.senderNickname = items.remark || item.senderNickname + } + }) + + const groupMember = (groupMemberInfo.value[groupChatConfig.value.groupID] || []).find(member => member.userID === item.sendID) || {} + let groupRemark = JSON.parse(groupMember.ex || '{}')?.remark?.value || '' + if (myGroupInfo.value.roleLevel === 20) { + groupRemark = '' + } + item.senderNickname = groupRemark || item.senderNickname + }) + toppingList.value = data.length && data.reverse() + } + window.sessionStorage.setItem('toppingList', JSON.stringify(toppingList.value)) + } + const pinnedPopupClose = () => { + toppingList.value.forEach(item => item.isDelStatus = 0) + } + + const newFriendApplyCount = ref(Number(window.sessionStorage.getItem('newFriendApplyCount') || 0)) + const setNewFriendApplyCount = (count) => { + if (count === undefined) return + newFriendApplyCount.value = count + window.sessionStorage.setItem('newFriendApplyCount', count) + } + + // 存储所有的群, 筛选中所有的群主和管理员 + const allGroupOwnerOrAdmin = ref(JSON.parse(window.sessionStorage.getItem('groupMemberInfo')) || '{}') + const setAllGroupOwnerOrAdmin = (data) => { + if (data === undefined) return + allGroupOwnerOrAdmin.value = data + window.sessionStorage.setItem('allGroupOwnerOrAdmin', JSON.stringify(data)) + } + +// 群发消息入口展示 +const isGroupSend = ref(JSON.parse(window.sessionStorage.getItem('isGroupSend')) || false) +const setIsGroupSend = (config,userInfo)=> { + const group_role_type = config.batch_send?.group_role_type || [] + // 群发功能可选角色 + if(group_role_type.length > 0) { + let isGroupOwner = (group_role_type.includes(1) && userInfo.isGroupOwner) + let isGroupAdmin = (group_role_type.includes(2) && userInfo.isGroupAdmin) + let isNormalUser = (group_role_type.includes(3) && !userInfo.isGroupAdmin && !userInfo.isGroupOwner && !userInfo.isInternalUser) + let isInternalUser = (group_role_type.includes(4) && userInfo.isInternalUser) + isGroupSend.value = isGroupOwner || isGroupAdmin || isNormalUser || isInternalUser + }else { + isGroupSend.value = false + } + window.sessionStorage.setItem('isGroupSend', JSON.stringify(isGroupSend.value)) +} + + const clearStore = () => { + contactList.value = [] + isFriendList.value = {} + chatList.value = [] + detailSearch.value = {} + groupId.value = '' + friendGroupId.value = '' + friendDetailId.value = '' + groupMemberInfo.value = {} + } + return { + allGroupOwnerOrAdmin, + setAllGroupOwnerOrAdmin, + getAllGroupOwnerOrAdmin, + toppingList, // 群消息置顶 + setToppingList, + groupMemberInfo, + groupMemberInfoObj, + newGetGroupUserInfoList, + setGroupMemberInfo, + getGroupMemberInfo, + updateGroupMemberInfo, + updateGroupMemberInfos, + updateGroupMemberInfos1, + getContactListFromReq, + updateContact, + pushNewContact, + friendCreateCount, + setFriendCreateCount, + detailSearch, + setDetailSearch, + applyUserList, + setApplyUserList, + contactList, + setContactList, + updateGroupList, + pushNewGroup, + groupId, + groupMemberLoading, + setGroupId, + chatList, + getChatGroupList, + setChatList, + friendGroupId, + setFriendGroupId, + isFriendList, + setIsFriendList, + friendDetailId, + setFriendDetailId, + groupSessionInfo, + setGroupSessionInfo, + myGroupInfo, + setmyGroupInfo, + getMyGroupInfo, + groupInfo, + setGroupInfo, + unreadMsgCount, + setUnreadMsgCount, + hasNewFriendApply, + updateNewFriendApply, + newFriendApplyCount, + setNewFriendApplyCount, + clearStore, + setIsGroupSend, + isGroupSend + } +}); diff --git a/src/stores/index.js b/src/stores/index.js new file mode 100644 index 0000000..013723c --- /dev/null +++ b/src/stores/index.js @@ -0,0 +1,24 @@ +import { defineStore, createPinia } from 'pinia' +// 引入持久化插件 +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' +export const useStore = defineStore({ + // id: 必须的,在所有 Store 中唯一 + id: 'index', + // state: 返回对象的函数 + state: () => ({ + // light || dark + mode: '', + }), + persist: true, + // persist: { + // // 本地存储的名称 + // key: 'globalState', + // //保存的位置 + // storage: window.sessionStorage, //localstorage + // }, +}) + +const pinia = createPinia() +//pinia使用 +pinia.use(piniaPluginPersistedstate) +export default pinia diff --git a/src/stores/modules/common.js b/src/stores/modules/common.js new file mode 100644 index 0000000..d734bb9 --- /dev/null +++ b/src/stores/modules/common.js @@ -0,0 +1,21 @@ + +import { defineStore } from 'pinia' + + +export const useCommonStore = defineStore('commonStore', { + state: () => { + return { + timestamp: 0, + historyMessageList_common:[] + } + }, + actions: { + getNewTimestamp(val) { + this.timestamp = val + }, + getHistoryMessageList(val){ + this.historyMessageList_common = val + console.log(this.historyMessageList_common,'22222222222') + } + }, +}) diff --git a/src/stores/modules/contact.js b/src/stores/modules/contact.js new file mode 100644 index 0000000..b2a50bf --- /dev/null +++ b/src/stores/modules/contact.js @@ -0,0 +1,321 @@ +import { reactive, computed } from 'vue'; +import { getSDK } from '@/utils/openIM' +import { defineStore } from 'pinia'; +import { feedbackToast } from "@/utils/common"; +import router from "@/router"; +// i18n +import { i18n } from '@/lang/index' +const { t } = i18n.global +const IMSDK = getSDK() + +export const useContactStore = defineStore('contact', () => { + const state = reactive({ + friendList: [], + groupList: [], + blackList: [], + recvFriendApplicationList: [], + sendFriendApplicationList: [], + recvGroupApplicationList: [], + sendGroupApplicationList: [], + unHandleFriendApplicationNum: 0, + unHandleGroupApplicationNum: 0, + userCardData: {}, + }); + + const storeFriendList = computed(() => state.friendList); + const storeGroupList = computed(() => state.groupList); + const storeBlackList = computed(() => state.blackList); + const storeRecvFriendApplicationList = computed(() => state.recvFriendApplicationList); + const storeSendFriendApplicationList = computed(() => state.sendFriendApplicationList); + const storeRecvGroupApplicationList = computed(() => state.recvGroupApplicationList); + const storeSendGroupApplicationList = computed(() => state.sendGroupApplicationList); + const storeUnHandleFriendApplicationNum = computed(() => state.unHandleFriendApplicationNum); + const storeUnHandleGroupApplicationNum = computed(() => state.unHandleGroupApplicationNum); + const storeUserCardData = computed(() => state.userCardData); + const getFriendListFromReq = async () => { + try { + const { data } = await IMSDK.getFriendList(); + // let tmpList: FriendItem[] = []; + // data.map( + // (item: TotalUserStruct) => + // !item.blackInfo && tmpList.push(item.friendInfo!) + // ); + // state.friendList = data.map((user: FullUserItem) => user.friendInfo); + state.friendList = data.map((user) => user.friendInfo); + } catch (error) { + console.error(error); + } + } + const updateFriendList = (item, isRemove = false) => { + const idx = state.friendList.findIndex( + (friend) => friend.userID === item.userID + ); + if (idx !== -1) { + if (isRemove) { + state.friendList.splice(idx, 1); + return; + } + state.friendList[idx] = { ...item }; + } + + if (item.userID === state.userCardData.baseInfo?.userID) { + state.userCardData.baseInfo = { ...item }; + } + } + const pushNewFriend = (item) => { + state.friendList.push(item); + } + + const getGroupListFromReq = async () => { + try { + const { data } = await IMSDK.getJoinedGroupList(); + state.groupList = data; + } catch (error) { + console.error(error); + } + } + + const updateGroupList = (item, isRemove = false) => { + const idx = state.groupList.findIndex( + (group) => group.groupID === item.groupID + ); + if (idx !== -1) { + if (isRemove) { + state.groupList.splice(idx, 1); + return; + } + state.groupList[idx] = { ...item }; + } + } + const pushNewGroup = (item) => { + state.groupList.push(item); + } + const getBlackListFromReq = async () => { + try { + const { data } = await IMSDK.getBlackList(); + state.blackList = data; + } catch (error) { + console.error(error); + } + } + const updateBlackList = (item, isRemove = false) => { + const idx = state.blackList.findIndex( + (user) => user.userID === item.userID + ); + if (idx !== -1) { + if (isRemove) { + state.blackList.splice(idx, 1); + return; + } + state.blackList[idx] = { ...item }; + } + } + const pushNewBlack = (item) => { + state.blackList.push(item); + } + const getRecvFriendApplicationListFromReq = async () => { + try { + const { data } = await IMSDK.getFriendApplicationListAsRecipient(); + state.recvFriendApplicationList = data; + } catch (error) { + console.error(error); + } + } + const updateRecvFriendApplicationList = (item) => { + const idx = state.recvFriendApplicationList.findIndex( + (application) => + application.fromUserID === item.fromUserID + ); + if (idx !== -1) { + state.recvFriendApplicationList[idx] = { ...item }; + } + } + const pushNewRecvFriendApplication = (item) => { + const idx = state.recvFriendApplicationList.findIndex( + (application) => application.fromUserID === item.fromUserID + ); + if (idx > -1) { + state.recvFriendApplicationList[idx] = { ...item }; + } else { + state.recvFriendApplicationList.push(item); + } + } + const getSendFriendApplicationListFromReq = async() => { + try { + const { data } = await IMSDK.getFriendApplicationListAsApplicant(); + state.sendFriendApplicationList = data; + } catch (error) { + console.error(error); + } + } + const updateSendFriendApplicationList = (item) => { + const idx = state.sendFriendApplicationList.findIndex( + (application) => + application.toUserID === item.toUserID + ); + if (idx !== -1) { + state.sendFriendApplicationList[idx] = { ...item }; + } + } + const pushNewSendFriendApplication = (item) => { + const idx = state.sendFriendApplicationList.findIndex( + (application) => application.toUserID === item.toUserID + ); + if (idx > -1) { + state.sendFriendApplicationList[idx] = { ...item }; + } else { + state.sendFriendApplicationList.push(item); + } + } + const getRecvGroupApplicationListFromReq = async() => { + try { + const { data } = await IMSDK.getGroupApplicationListAsRecipient(); + state.recvGroupApplicationList = data; + } catch (error) { + console.error(error); + } + } + const updateRecvGroupApplicationList = (item) => { + const idx = state.recvGroupApplicationList.findIndex( + (application) => + application.userID === item.userID + ); + if (idx !== -1) { + state.recvGroupApplicationList[idx] = { ...item }; + } + } + const pushNewRecvGroupApplication = (item) => { + const idx = state.recvGroupApplicationList.findIndex( + (application) => application.userID === item.userID + ); + if (idx > -1) { + state.recvGroupApplicationList[idx] = { ...item }; + } else { + state.recvGroupApplicationList.push(item); + } + } + const getSendGroupApplicationListFromReq = async() => { + try { + const { data } = await IMSDK.getGroupApplicationListAsApplicant(); + state.sendGroupApplicationList = data; + } catch (error) { + console.error(error); + } + } + const updateSendGroupApplicationList = (item) => { + const idx = state.sendGroupApplicationList.findIndex( + (application) => + application.groupID === item.groupID + ); + if (idx !== -1) { + state.sendGroupApplicationList[idx] = { ...item }; + } + } + const pushNewSendGroupApplication = (item) => { + const idx = state.sendGroupApplicationList.findIndex( + (application) => application.groupID === item.groupID + ); + if (idx > -1) { + state.sendGroupApplicationList[idx] = { ...item }; + } else { + state.sendGroupApplicationList.push(item); + } + } + const updateUnHandleFriendApplicationNum = (num) => { + state.unHandleFriendApplicationNum = num; + } + const updateUnHandleGroupApplicationNum = (num) => { + state.unHandleGroupApplicationNum = num; + } + const setUserCardData = (data) => { + state.userCardData = { ...data }; + router.push("userCard"); + } + const updateUserCardMemberInfo = (item) => { + if ( + item.userID !== state.userCardData.groupMemberInfo?.userID || + item.groupID !== state.userCardData.groupMemberInfo?.groupID + ) + return; + state.userCardData.groupMemberInfo = { ...item }; + } + const getUserCardData = async (sourceID, groupID) => { + let baseInfo; + let groupMemberInfo; + baseInfo = state.friendList.find((item) => item.userID === sourceID); + if (!baseInfo) { + const { data } = await IMSDK.getUsersInfo([sourceID]); + baseInfo = data[0]?.friendInfo ?? data[0]?.publicInfo ?? undefined; + } + if (groupID) { + const { data } = await IMSDK.getSpecifiedGroupMembersInfo({ + groupID, + userIDList: [sourceID], + }); + groupMemberInfo = data[0]; + } + state.userCardData = { + baseInfo, + groupMemberInfo, + }; + if (!baseInfo) { + feedbackToast({ + error: t("messageTip.getUserInfoFailed"), + message: t("messageTip.getUserInfoFailed"), + }); + return; + } + router.push("userCard"); + } + const clearContactStore = () => { + state.friendList = []; + state.groupList = []; + state.blackList = []; + state.recvFriendApplicationList = []; + state.sendFriendApplicationList = []; + state.recvGroupApplicationList = []; + state.sendGroupApplicationList = []; + state.unHandleFriendApplicationNum = 0; + state.unHandleGroupApplicationNum = 0; + state.userCardData = {}; + } + return { + storeFriendList, + storeGroupList, + storeBlackList, + storeRecvFriendApplicationList, + storeSendFriendApplicationList, + storeRecvGroupApplicationList, + storeSendGroupApplicationList, + storeUnHandleFriendApplicationNum, + storeUnHandleGroupApplicationNum, + storeUserCardData, + getFriendListFromReq, + updateFriendList, + pushNewFriend, + getGroupListFromReq, + updateGroupList, + pushNewGroup, + getBlackListFromReq, + updateBlackList, + pushNewBlack, + getRecvFriendApplicationListFromReq, + updateRecvFriendApplicationList, + pushNewRecvFriendApplication, + getSendFriendApplicationListFromReq, + updateSendFriendApplicationList, + pushNewSendFriendApplication, + getRecvGroupApplicationListFromReq, + updateRecvGroupApplicationList, + pushNewRecvGroupApplication, + getSendGroupApplicationListFromReq, + updateSendGroupApplicationList, + pushNewSendGroupApplication, + updateUnHandleFriendApplicationNum, + updateUnHandleGroupApplicationNum, + setUserCardData, + updateUserCardMemberInfo, + getUserCardData, + clearContactStore + }; +}); \ No newline at end of file diff --git a/src/stores/modules/conversation.js b/src/stores/modules/conversation.js new file mode 100644 index 0000000..3662716 --- /dev/null +++ b/src/stores/modules/conversation.js @@ -0,0 +1,253 @@ +import { getSDK } from '@/utils/openIM' +import { defineStore, createPinia ,storeToRefs} from "pinia"; +import { getLatestInfo, getChatTimeline } from '@/utils/tools' +import { filter } from 'lodash'; +import { useContactStore } from "@/stores/contact"; + +const store = createPinia() +const IMSDK = getSDK() + +const useStore = defineStore("conversation", { + state: () => ({ + // 会话列表 + conversationList: [], + // 当前会话 + currentConversation: {}, + // 未读消息 + unReadCount: 0, + // 当前群组 + currentGroupInfo: {}, + // 群组对象map + currentMemberInGroup: {}, + // 引用消息 + quoteMessage: undefined, + loading: false, + // 是否折叠 + isExpand: false, + // 是否编辑 + conversationEditing: false, + // 编辑选中内容 + selectedItems: [], + // 会话所有用户 + dialogueUsers: [], + conversationOffset: 0, + }), + getters: { + storeConversationList: (state) => { + state.conversationList.map((item, idx) => { + item.latestMsgSendTimeFormat = getChatTimeline(item.latestMsgSendTime) + item.idx = idx + return item + }) + + return state.conversationList + }, + storeCurrentConversation: (state) => state.currentConversation, + storeUnReadCount: (state) => state.unReadCount, + storeCurrentGroupInfo: (state) => state.currentGroupInfo, + storeCurrentMemberInGroup: (state) => state.currentMemberInGroup, + storeQuoteMessage: (state) => state.quoteMessage, + storeLoading: (state) => state.loading, + storeIsExpand: (state) => state.isExpand, + storeConversationEditing: (state) => state.conversationEditing, + storeSelectedItems: (state) => state.selectedItems, + storeDialogueUsers: (state) => state.dialogueUsers, + storeExpandedNum: (state) => state.conversationList.filter(item => item.isPinned).length, + showExpandedIdx: (state) => state.conversationList.findIndex(item => !item.isPinned), + }, + actions: { + async getConversationListFromReq(isScrollLoad = false) { + try { + if (this.loading) return + this.loading = true + if (!isScrollLoad) { + this.conversationOffset = 0 + } + const { data } = await IMSDK.getConversationListSplit({ + offset: isScrollLoad ? Math.max(0, this.conversationOffset) : 0, + count: 20, + }); + this.conversationOffset += data.length + this.loading = false + const cves = data; + const cvesCIds = cves.map(item => item.conversationID) + // 这里的目的是要把旧数据里面相同会话id的那一条去掉 + let beforeList = [] + if (isScrollLoad) { + beforeList = this.conversationList.filter(item => !cvesCIds.includes(item.conversationID)) + } + this.conversationList = [ + ...(isScrollLoad ? beforeList : []), + ...cves, + ]; + for (let item of this.conversationList) { + if (item.conversationType === 3) { + // 最后一条消息处理逻辑 + const lastMsgParse = JSON.parse(item.latestMsg || '{}') + const groupRemark = await this.getGroupRemark(lastMsgParse.sendID, item.groupID) + item.lastGroupRemark = groupRemark + item.latestMsg = JSON.stringify({ + ...lastMsgParse, + senderNickname: groupRemark || lastMsgParse.senderNickname + }) + } + item.latestInfo = getLatestInfo(item, this.conversationList) + } + const contactUsers = this.conversationList.filter(item => item.conversationType === 1).map(item => item.userID) + this.dialogueUsers = Array.from(new Set(contactUsers)) + console.error('同步会话数据???', this.conversationList) + return cves.length === 20; + } catch (error) { + this.loading = false + console.error(error); + return false; + } + }, + async getUnReadCountFromReq() { + const { data } = await IMSDK.getTotalUnreadMsgCount(); + this.unReadCount = data; + }, + updateUnReadCount(data) { + this.unReadCount = data; + }, + updateConversationOffset(value) { + this.conversationOffset = value; + }, + async getCurrentGroupInfoFromReq(groupID) { + // const contactStore = useContactStore(); + // const sourceID = groupID ?? this.currentConversation.groupID; + // const localGroup = contactStore.storeGroupList.find( + // (group) => group.groupID === sourceID + // ); + // if (localGroup) { + // this.currentGroupInfo = localGroup; + // return; + // } + // try { + // const { data } = await IMSDK.getSpecifiedGroupsInfo([ + // sourceID, + // ]); + // this.currentGroupInfo = data[0] ?? {}; + // } catch (error) { + // console.error(error); + // } + }, + updateCurrentGroupInfo(item) { + this.currentGroupInfo = { ...item }; + }, + async getCurrentMemberInGroupFromReq(groupID) { + const userStore = useUserStore(); + + // try { + // const { data } = await IMSDK.getSpecifiedGroupMembersInfo({ + // groupID: groupID ?? this.currentConversation.groupID, + // userIDList: [userStore.storeSelfInfo.userID], + // }); + // this.currentMemberInGroup = data[0] ?? {}; + // } catch (error) { + // console.error(error); + // } + }, + updateCurrentMemberInGroup(item) { + this.currentMemberInGroup = { ...item }; + }, + updateCurrentConversation(item) { + this.currentConversation = { ...item }; + }, + // 手动更新会话列表内容 + async updateConversationList(list = []) { + const resArr = [] + for (const item of list) { + // 最后一条消息的发送者改变了或者是新消息, 重新查一下群聊备注,否则直接用之前的 + // 群聊才有下面的逻辑 + if (item.conversationType === 3) { + // 最后一条消息 + const oldItem = this.conversationList.find(cov => cov.conversationID === item.conversationID) || {} + + const latestMsgParse = JSON.parse(item.latestMsg || '{}') + const oldLatestMsgParse = JSON.parse(oldItem.latestMsg || '{}') + const { sendID: oldSendId } = oldLatestMsgParse + const { sendID, senderNickname } = latestMsgParse + if (oldSendId !== sendID) { + // 群聊 先去群聊备注里面找一下该用户的群聊备注,如果有就获取,没有就去接口获取 + const groupRemark = await this.getGroupRemark(sendID, item.groupID) + item.latestMsg = JSON.stringify({ + ...latestMsgParse, + senderNickname: groupRemark || senderNickname + }) + item.lastGroupRemark = groupRemark + } else { + // 如果没变的话还是用之前的群聊备注 + item.latestMsg = JSON.stringify({ + ...latestMsgParse, + senderNickname: oldItem.lastGroupRemark || latestMsgParse.senderNickname + }) + item.lastGroupRemark = oldItem.lastGroupRemark + } + } + + item.latestInfo = getLatestInfo(item, this.conversationList) + resArr.push(item) + } + // 更新列表的时候还是要对最后一条消息进行判断 + this.conversationList = resArr + }, + async getGroupRemark(userID, groupID){ + return this.getGroupRemarkByCache(userID, groupID) || await this.getGroupRemarkByApi(userID, groupID) + }, + // 获取缓存存中的群聊备注 + getGroupRemarkByCache(userID, groupID){ + const contactStore = useContactStore(); + const { + groupMemberInfoObj, + } = storeToRefs(contactStore); + // const groupMemberInfo = JSON.parse(sessionStorage.getItem('groupMemberInfo')||'{}') + const groupMemberInfo = groupMemberInfoObj + const groupMemberList = groupMemberInfo[groupID] || [] + const activeGroupMemberInfo = groupMemberList.find(mem => mem.userID === userID) + // 群聊备注存 + return JSON.parse(activeGroupMemberInfo?.ex || '{}').remark?.value || '' + }, + // 通过api获取群聊中的群聊备注 + async getGroupRemarkByApi(userID, groupID){ + const { data } = await IMSDK.getSpecifiedGroupMembersInfo({ + groupID: groupID, + userIDList: [userID], + }) + + return JSON.parse(data[0]?.ex || '{}')?.remark?.value || '' + }, + delConversationByCID(conversationID) { + const idx = this.conversationList.findIndex( + (cve) => cve.conversationID === conversationID + ); + if (idx !== -1) { + this.conversationList.splice(idx, 1); + } + }, + updateQuoteMessage(message) { + this.quoteMessage = message; + }, + updateConversationEditing(value) { + this.conversationEditing = value; + }, + updateExpanded(expand) { + this.isExpand = expand + }, + updateSelectItems(items) { + this.selectedItems = items + }, + clearConversationStore() { + this.conversationList = []; + this.currentConversation = {}; + this.unReadCount = 0; + this.currentGroupInfo = {}; + this.currentMemberInGroup = {}; + this.quoteMessage = undefined; + } + }, +}); + +export default function useConversationStore() { + return useStore(store); +} diff --git a/src/stores/modules/membership-info.ts b/src/stores/modules/membership-info.ts new file mode 100644 index 0000000..6c4f331 --- /dev/null +++ b/src/stores/modules/membership-info.ts @@ -0,0 +1,59 @@ +import { defineStore } from "pinia"; +import api from "@/api"; +// 所有成员信息 +export const useFriendStore = defineStore({ + id: "friendState", + state: () => ({ + memberUserIds: [], // 所有群主用户标识 + fontSize:16, // 全局字体大小 + onLineState: [], // 用户在线状态目前只包括、会话、群聊列表 + globalRequestPrefix: '', // 当前请求前缀 + }), + getters: { + memberFriendIds: (state) => { + return state.memberUserIds; + }, + // 计算在线状态 + computedOnLineState(state) { + return (userID: string | number) => { + return state.onLineState[userID]; + }; + }, + }, + actions: { + async fetchMemberIdentification() { + try { + // 调用接口获取群主和群管理员标识 + const resMemberIdentification:any= await api.user.getMemberIdentification(); + if (resMemberIdentification.code === 0) { + this.memberUserIds = resMemberIdentification.data.userIds; + return this.memberUserIds; // 返回获取的标识 + } else { + throw new Error("Failed to fetch member identification"); + } + } catch (error) { + console.error("Error fetching member identification:", error); + throw error; // 继续抛出错误,以便上层处理 + } + }, + // 设置全局字体大小 + seGlobalFontSize(fontSize:number|string) { + this.fontSize = fontSize + }, + // 设置用户在线状态 + SetOnlineStatus(state: any) { + this.onLineState = state + }, + // 设置全局请求前缀 + SetRequestPrefix(prefix : any) { + this.globalRequestPrefix = prefix + }, + }, + // 进行持久化存储 + persist: { + // 本地存储的名称 + key: 'friendState', + //保存的位置 + storage: window.sessionStorage, // 【localStorage | sessionStorage】 + }, +}); diff --git a/src/stores/modules/message.js b/src/stores/modules/message.js new file mode 100644 index 0000000..f134c55 --- /dev/null +++ b/src/stores/modules/message.js @@ -0,0 +1,294 @@ +import { storeToRefs,defineStore } from "pinia"; +import { feedbackToast } from "@/utils/common"; +import { MessageType } from "@/utils/constant"; +import { getSDK } from "@/utils/openIM"; +import { useContactStore } from "../contact"; +import { + ref, + reactive, + watchEffect, + computed, + watch +} from "vue"; +import { set } from "lodash"; +import { Data } from "emoji-mart"; + +// // 新引入 +// import {useCommonStore} from "@/stores/modules/common" + + +// //引入结束 + +const IMSDK = getSDK(); +export const useMessageStore = defineStore("message", () => { + const contactStore = useContactStore(); + const { contactList } = storeToRefs(contactStore); + const state = reactive({ + // historyMessageList: JSON.parse(sessionStorage.getItem('items')) || [], + historyMessageList: [], + hasMore: true, + previewImgList: [], + isSdkErr: false + }); + const storeHistoryMessageList = computed(() => state.historyMessageList); + const storeHistoryMessageHasMore = computed(() => state.hasMore); + const storePreviewImgList = computed(() => state.previewImgList); + + + // 联系人列表 + const quoteObj = ref({}); + const setQuoteObj = data => { + if (data === undefined) return; + quoteObj.value = data; + // window.localStorage.setItem('quoteObj', JSON.stringify(data)) + }; + + const closeQuoteObj = groupId => { + if (quoteObj.value[groupId]) { + quoteObj.value[groupId].isShow_msgQuote = false; + quoteObj.value[groupId].msg_info = {}; + } + }; + /* + * 2024-6-11 + * 把原数据返回给其他地方使用 + * 此方法用于,群聊消息中-已读和未读使用 + */ + const getNewsMessageListData = async (params) => { + try { + const { data } = await IMSDK.getAdvancedHistoryMessageList(params); + // console.error("群聊原数据-----", data); + state.historyMessageList = [...data.messageList]; + return { data }; + } catch (error) { + feedbackToast({ message: "Get news message failed", error }); + } + }; + + const getHistoryMessageListFromReq = async params => { + console.log('获取群聊天记录接口入参:',params) + const isFirstPage = + params.startClientMsgID === "" || params.lastMinSeq === 0; + try { + const { data: tmpData } = await IMSDK.getAdvancedHistoryMessageList( + params + ); + console.error('sdk返回的群聊数据',tmpData); + const newListData = [ + ...tmpData.messageList, + ...(isFirstPage ? [] : state.historyMessageList) + ]; + + newListData.forEach(msg => { + //计算未读人数 -1 计算本人 + if (msg.attachedInfoElem) { + const groupReadInfo = msg.attachedInfoElem.groupHasReadInfo; + const read = groupReadInfo.hasReadCount || 0; + const count = groupReadInfo.groupMemberCount || 0; + let unReadCount = count - 1 - read; + if (read > 0 && count > 1) { + //-1是去除自己 + if (unReadCount < 0) { + unReadCount = 0; + } + } + msg.unreadCount = unReadCount; + } + const arr = contactList.value.filter(item => item.userID == msg.sendID); + msg.senderNickname = arr[0]?.remark + ? arr[0].remark + : msg.senderNickname; + + // 针对引用已经撤回的消息进行判断 + if (msg.contentType === 114 && msg.quoteElem && msg.quoteElem.quoteMessage && msg.quoteElem.quoteMessage.clientMsgID) { + const idx = state.historyMessageList.findIndex(item => item.clientMsgID === msg.quoteElem.quoteMessage.clientMsgID) + if (idx !== -1) { + const activeRouteItem = state.historyMessageList[idx] + // 如果引用的消息是已经撤回的,那就把当前引用消息的类型也改成已撤回 + if (activeRouteItem.contentType === 2101) { + msg.quoteElem.quoteMessage.contentType = 2101 + } + } + } + if ([1507, 1511].includes(msg.contentType)) { + const notiDetail = JSON.parse(msg.notificationElem.detail) + msg.senderNickname = notiDetail.opUser.nickname + } + }); + state.historyMessageList = newListData + // console.error('本地存完的群聊数据', state.historyMessageList, storeHistoryMessageList.value) + state.isSdkErr = false; + state.hasMore = !tmpData.isEnd && tmpData.messageList.length === 30; + return { + messageIDList: tmpData.messageList.map(message => message.clientMsgID), + lastMinSeq: tmpData.lastMinSeq + }; + } catch (error) { + feedbackToast({ message: "Get history message failed", error }); + state.hasMore = false; + state.isSdkErr = true; + return { + messageIDList: [], + lastMinSeq: 0 + }; + } + }; + const pushNewMessage = message => { + // const imageUrls = filterPreviewImage([message]); + // if (imageUrls.length > 0) { + // state.previewImgList = [...state.previewImgList, ...imageUrls]; + // } + const arr = contactList.value.filter(item => item.userID == message.sendID); + message.senderNickname = arr[0]?.remark + ? arr[0].remark + : message.senderNickname; + const groupReadInfo = message.attachedInfoElem.groupHasReadInfo; + const read = groupReadInfo.hasReadCount || 0; + const count = groupReadInfo.groupMemberCount || 0; + let unReadCount = 0; + if (read > 0 && count > 1) { + //-1是去除自己 + unReadCount = count - 1 - read; + if (unReadCount < 0) { + unReadCount = 0; + } + } + // state.historyMessageList.push({...message, unreadCount: unReadCount }); + state.historyMessageList.push({...message}); + // console.error('赌东道赌东道赌东道的111',state.historyMessageList); + }; + const updateOneMessage = (message, isUpdateQuoteElem = false) => { + const idx = state.historyMessageList.findIndex( + msg => msg.clientMsgID === message.clientMsgID + ); + if (idx !== -1) { + state.historyMessageList[idx] = { + ...state.historyMessageList[idx], + ...message + }; + + // 是否需要把引用消息的类型更新掉 + if (isUpdateQuoteElem) { + state.historyMessageList.forEach((item) => { + const clientMsgID = item.quoteElem?.quoteMessage?.clientMsgID + // 原来老的引用消息 || + if (item.contentType === 114 || clientMsgID === message.clientMsgID) { + if (item.quoteElem && item.quoteElem.quoteMessage) { + item.quoteElem.quoteMessage.contentType = 2101 + } + } + }) + } + // if (isSuccessCallBack) { + // const imageUrls = filterPreviewImage([message]); + // if (imageUrls.length > 0) { + // state.previewImgList = [...state.previewImgList, ...imageUrls]; + // } + // } + } + }; + const deleteOneMessage = message => { + const idx = state.historyMessageList.findIndex( + msg => msg.clientMsgID === message.clientMsgID + ); + if (idx !== -1) { + state.historyMessageList.splice(idx, 1); + } + }; + const clearHistoryMessage = () => { + state.historyMessageList = []; + // state.previewImgList = []; + // console.error('赌东道赌东道赌东道的3333',state.historyMessageList); + }; + const resetCheckState = () => { + state.historyMessageList.forEach(message => (message.checked = false)); + // console.error('赌东道赌东道赌东道的44444',state.historyMessageList); + }; + + const updatePreviewImgList = imgs => { + state.previewImgList = [...state.previewImgList, ...imgs]; + }; + const resetHistoryMessageList = () => { + state.historyMessageList = []; + state.hasMore = true; + state.previewImgList = []; + }; + const updateMessageNicknameAndFaceUrl = ({ + sendID, + senderFaceUrl, + senderNickname + }) => { + const tmpList = [...state.historyMessageList].map(message => { + if (message.sendID === sendID) { + message.senderFaceUrl = senderFaceUrl; + message.senderNickname = senderNickname; + } + return message; + }); + state.historyMessageList = tmpList; + }; + const filterPreviewImage = messages => { + return messages + .filter(message => { + if (message.contentType === MessageType.PictureMessage) { + return true; + } + if (message.contentType === MessageType.OANotification) { + let notificationData = {}; + try { + notificationData = JSON.parse(message.notificationElem.detail); + } catch (error) {} + if (notificationData.mixType === 1) { + message.pictureElem.snapshotPicture.url = + notificationData.pictureElem.sourcePicture.url; + return true; + } + return false; + } + return false; + }) + .map(item => item.pictureElem.sourcePicture.url); + }; + + const checkedConcat = ref([]); + + const updateCheckConcat = value => { + checkedConcat.value = value; + }; + // watch(()=>state.historyMessageList,(newV,oldV)=>{ + // console.log('监控群聊数据发生变化--newV',newV.length) + // console.log('监控群聊数据发生变化--oldV',oldV.length) + // const CommonStore = useCommonStore(); + // CommonStore.getHistoryMessageList(newV) + // },{deep:true}) + + // 添加红包领取消息 + const pushRedPackageMessage = message => { + state.historyMessageList.push({ + ...message + }) + } + + return { + state, + quoteObj, + storeHistoryMessageList, + storeHistoryMessageHasMore, + storePreviewImgList, + checkedConcat, + updateCheckConcat, + setQuoteObj, + closeQuoteObj, + getNewsMessageListData, + getHistoryMessageListFromReq, + pushNewMessage, + updateOneMessage, + deleteOneMessage, + clearHistoryMessage, + resetCheckState, + updatePreviewImgList, + resetHistoryMessageList, + updateMessageNicknameAndFaceUrl, + pushRedPackageMessage + }; +}); diff --git a/src/stores/modules/user.js b/src/stores/modules/user.js new file mode 100644 index 0000000..b1a772f --- /dev/null +++ b/src/stores/modules/user.js @@ -0,0 +1,62 @@ +// import { defineStore, createPinia } from "pinia"; +// const store = createPinia() +// // import { getAppConfig, getBusinessInfo } from "@/api/user"; +// import { feedbackToast, filterEmptyValue } from "@/utils/common"; +// // import { clearIMProfile } from "@/utils/storage"; +// import useContactStore from "./contact"; +// import useConversationStore from "./conversation"; +// import { getSDK } from '@/utils/openIM' +// const IMSDK = getSDK(); + +// const useStore = defineStore("user", { +// state: () => ({ +// selfInfo: {}, +// isSyncing: false, +// appConfig: {}, +// }), +// getters: { +// storeSelfInfo: (state) => state.selfInfo, +// storeIsSyncing: (state) => state.isSyncing, +// storeAppConfig: (state) => state.appConfig, +// }, +// actions: { +// async getSelfInfoFromReq() { +// // try { +// // const { data } = await IMSDK.getSelfUserInfo(); +// // const res = await getBusinessInfo(data.userID); +// // const businessData = res.data.users[0] ?? {}; +// // filterEmptyValue(businessData); +// // this.selfInfo = { +// // ...data, +// // ...businessData, +// // }; +// // } catch (error) { +// // feedbackToast({ error, message: i18nt("messageTip.getUserInfoFailed") }); +// // this.userLogout(); +// // } +// }, +// updateSelfInfo(info) { +// this.selfInfo = { ...this.selfInfo, ...info }; +// }, +// async getAppConfigFromReq() { +// // try { +// // const { data } = await getAppConfig(); +// // this.appConfig = data.config; +// // } catch (error) {} +// }, +// async userLogout(force) { +// const useConversatione = useConversationStore(); +// const useContact = useContactStore(); + +// if (!force) await IMSDK.logout(); +// // clearIMProfile(); +// this.selfInfo = {}; +// useConversatione.clearConversationStore(); +// useContact.clearContactStore(); +// }, +// }, +// }); + +// export default function useUserStore() { +// return useStore(store); +// } diff --git a/src/stores/mutation-type.ts b/src/stores/mutation-type.ts new file mode 100644 index 0000000..679bbd5 --- /dev/null +++ b/src/stores/mutation-type.ts @@ -0,0 +1,2 @@ +export const STORAGE_TOKEN_KEY = 'access_token' +export const STORAGE_LANG_KEY = 'app_lang' diff --git a/src/stores/user.js b/src/stores/user.js new file mode 100644 index 0000000..5429159 --- /dev/null +++ b/src/stores/user.js @@ -0,0 +1,200 @@ +import { ref, computed } from 'vue'; +import { defineStore } from 'pinia'; +import api from '@/api' + +export const useUserStore = defineStore('user', () => { + const token = ref(window.sessionStorage.getItem('token') || '') + const setToken = (data) => { + if (data === undefined) return + + token.value = data + window.sessionStorage.setItem('token', data) + } + const userInfo = ref(JSON.parse(window.sessionStorage.getItem('userInfo') || '{}')) + const setUserInfo = (data) => { + if (data === undefined) return + userInfo.value = data + window.sessionStorage.setItem('userInfo', JSON.stringify(data)) + } + const firstScreenChecked = ref(false) + const setFirstScreenChecked = (data) => { + firstScreenChecked.value = data + } + + const webSocketObj = ref() + const setWebSocketObj = (ws) => { + webSocketObj.value = ws + } + + const appConfig = ref(JSON.parse(window.sessionStorage.getItem('appConfig') || '{}')) + const setAppConfig = (data) => { + if (data === undefined) return + appConfig.value = data + window.sessionStorage.setItem('appConfig', JSON.stringify(data)) + } + // 收入 + const incomeConfig = ref(JSON.parse(window.sessionStorage.getItem('incomeConfig') || '{}')) + const setIncomeConfig = (data) => { + if (data === undefined) return + incomeConfig.value = data + window.sessionStorage.setItem('incomeConfig', JSON.stringify(data)) + } + // 支出 + const payConfig = ref(JSON.parse(window.sessionStorage.getItem('payConfig') || '{}')) + const setPayConfig = (data) => { + if (data === undefined) return + payConfig.value = data + window.sessionStorage.setItem('payConfig', JSON.stringify(data)) + } + // id + const payConfigObj = ref(JSON.parse(window.sessionStorage.getItem('payConfigObj') || '{}')) + const setPayConfigObj = (data) => { + if (data === undefined) return + payConfigObj.value = data + window.sessionStorage.setItem('payConfigObj', JSON.stringify(data)) + } + + + const deviceConfig = ref(JSON.parse(window.sessionStorage.getItem('deviceConfig') || '{}')) + const setDeviceConfig = (data) => { + if (data === undefined) return + deviceConfig.value = data + window.sessionStorage.setItem('deviceConfig', JSON.stringify(data)) + } + + const areaDictList = ref(JSON.parse(window.sessionStorage.getItem('areaDictList') || '[]')) + const setAreaDictList = (data) => { + if (data === undefined) return + areaDictList.value = data + window.sessionStorage.setItem('areaDictList', JSON.stringify(data)) + } + + + const getAppConfig = async () => { + const configRes = await api.user.configGet() + + return configRes.data + } + + // 群会话 + const groupChatConfig = ref(JSON.parse(window.sessionStorage.getItem('groupChatConfig') || '{}')) + const setGroupChatConfig = (data) => { + if (data === undefined) return + if (Object.keys(data).length > 0) { + data.contactChecked = [...new Set(data.contactChecked)] + data.groupChecked = [...new Set(data.groupChecked)] + } + groupChatConfig.value = data + window.sessionStorage.setItem('groupChatConfig', JSON.stringify(data)) + } + // 群消息推送 + const groupChatConfigs = ref(JSON.parse(window.sessionStorage.getItem('groupChatConfigs') || '{}')) + const setGroupChatConfigs = (data) => { + if (data === undefined) return + groupChatConfigs.value = data + window.sessionStorage.setItem('groupChatConfigs', JSON.stringify(data)) + } + // 私聊消息推送 + const privateChatConfigs = ref(JSON.parse(window.sessionStorage.getItem('privateChatConfigs') || '{}')) + const setPrivateChatConfigs = (data) => { + if (data === undefined) return + privateChatConfigs.value = data + window.sessionStorage.setItem('privateChatConfigs', JSON.stringify(data)) + } + + const friendApplication = ref(JSON.parse(window.sessionStorage.getItem('friendApplication') || '{}')) + const setFriendApplication = (data) => { + if (data === undefined) return + friendApplication.value = data + window.sessionStorage.setItem('friendApplication', JSON.stringify(data)) + } + + + // 私聊会话 + const privateChatConfig = ref(JSON.parse(window.sessionStorage.getItem('privateChatConfig') || '{}')) + const setPrivateChatConfig = (data) => { + if (data === undefined) return + if (Object.keys(data).length > 0) { + data.contactChecked = [...new Set(data.contactChecked)] + data.groupChecked = [...new Set(data.groupChecked)] + } + privateChatConfig.value = data + window.sessionStorage.setItem('privateChatConfig', JSON.stringify(data)) + } + + // 群聊消息 + const groupChatList = ref(JSON.parse(window.sessionStorage.getItem('groupChatList') || '[]')) + const setGroupChatList = (data) => { + if (data === undefined) return + groupChatList.value = data + window.sessionStorage.setItem('groupChatList', JSON.stringify(data)) + } + + // 用户背景配置 + const userBgSettings = ref(null) + const setUserBgSettings = (data) => { + userBgSettings.value = data + } + + // 记住手机号 + const rememberPhone = ref(window.localStorage.getItem('rememberPhone') || "") + const setRememberPhone = (val) => { + window.localStorage.setItem('rememberPhone',val) + } + // 记住邮箱 + const rememberEmail = ref(window.localStorage.getItem('rememberEmail') || "") + const setRememberEmail = (val) => { + window.localStorage.setItem('rememberEmail',val) + } + + // 群发选择会话 + const massTextDataChecked = ref(JSON.parse(window.sessionStorage.getItem('MassTextDataChecked') || '[]')) + const setMassTextDataChecked = (data) => { + massTextDataChecked.value = data + window.sessionStorage.setItem('MassTextDataChecked', JSON.stringify(data)) + } + + return { + friendApplication, + setFriendApplication, + groupChatConfigs, + setGroupChatConfigs, + privateChatConfigs, + setPrivateChatConfigs, + groupChatList, + setGroupChatList, + privateChatConfig, + setPrivateChatConfig, + groupChatConfig, + setGroupChatConfig, + incomeConfig, + setIncomeConfig, + payConfig, + payConfigObj, + setPayConfigObj, + setPayConfig, + token, + setToken, + userInfo, + setUserInfo, + webSocketObj, + userBgSettings, + setUserBgSettings, + setWebSocketObj, + appConfig, + setAppConfig, + getAppConfig, + areaDictList, + setAreaDictList, + deviceConfig, + setDeviceConfig, + rememberPhone, + setRememberPhone, + rememberEmail, + setRememberEmail, + massTextDataChecked, + setMassTextDataChecked, + firstScreenChecked, + setFirstScreenChecked, + } +}); diff --git a/src/style/common-styles.less b/src/style/common-styles.less new file mode 100644 index 0000000..24977d2 --- /dev/null +++ b/src/style/common-styles.less @@ -0,0 +1,597 @@ +// 1、定义公共样式文件 common-styles.less +// 2、这里可以定义一些公共的样式问题然后在其他文件中导入使用 +// 3、导入公共样式文件 @import '@/style/common-styles.less'; +.footer-box-content { + // background-color: #fff; + // min-width: 375px; + // max-width: 750px; + // box-sizing: border-box; + // margin: 0 auto; + background-color: #fff; + min-width: 375px; + box-sizing: border-box; + position: absolute; + left: 0; + bottom: 16px; + width: 100vw; + display: flex; + justify-content: center; +} +.public-wrap-text { + white-space: pre-wrap; +} +// flex相关 +// 所有元素水平居中,垂直居中 +.public-fcc { + display: flex; + justify-content: center; + align-items: center; +} +// 行内所有元素居中 +.public-ifcc { + display: inline-flex; + justify-content: center; + align-items: center; +} +// 垂直居中,两端对齐 +.public-flex-sb { + display: flex; + justify-content: space-between; + align-items: center; +} +// 垂直居中,分散对齐 +.public-flex-sa { + display: flex; + justify-content: space-around; + align-items: center; +} +// 剩余空间分配 +.public-fg1 { + flex-grow: 1; +} +.public-fg2 { + flex-grow: 2; +} +// 纵向排列 +// 所有元素垂直居中 +.public-fdcc { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +// 单个组合模式: +// 进入弹性布局模式: +.public-flex { + display: flex; +} +// 默认情况下就是这个垂直靠左 +.public-fdc { + display: flex; + flex-direction: column; +} +// 文本超出就隐藏并且显示省略号 +.public-hen{ + //超出的文本隐藏 + overflow:hidden; + //溢出用省略号显示 + text-overflow:ellipsis; + //溢出不换行 + white-space:nowrap; +} +// 用于文本溢出两行显示省略号的样式 +.public-text-overflow-2-lines { + white-space: pre-wrap; + display: -webkit-box; + overflow: hidden; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + + + +/************************************************************ +** 请将全局样式拷贝到项目的全局 CSS 文件或者当前页面的顶部 ** +** 否则页面将无法正常显示 ** +************************************************************/ +.flex-row { + display: flex; + flex-direction: row; +} + +.flex-col { + display: flex; + flex-direction: column; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.justify-around { + justify-content: space-around; +} + +.justify-evenly { + justify-content: space-evenly; +} + +.items-start { + align-items: flex-start; +} + +.items-end { + align-items: flex-end; +} + +.items-center { + align-items: center; +} + +.items-baseline { + align-items: baseline; +} + +.items-stretch { + align-items: stretch; +} + +.self-start { + align-self: flex-start; +} + +.self-end { + align-self: flex-end; +} + +.self-center { + align-self: center; +} + +.self-baseline { + align-self: baseline; +} + +.self-stretch { + align-self: stretch; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-auto { + flex: 1 1 auto; +} + +.grow { + flex-grow: 1; +} + +.grow-0 { + flex-grow: 0; +} + +.shrink { + flex-shrink: 1; +} + +.shrink-0 { + flex-shrink: 0; +} + +.relative { + position: relative; +} + +.ml-2 { + margin-left: 2px; +} + +.mt-2 { + margin-top: 2px; +} + +.ml-4 { + margin-left: 4px; +} + +.mt-4 { + margin-top: 4px; +} + +.ml-6 { + margin-left: 6px; +} + +.mt-6 { + margin-top: 6px; +} + +.ml-8 { + margin-left: 8px; +} + +.mt-8 { + margin-top: 8px; +} + +.ml-10 { + margin-left: 10px; +} + +.mt-10 { + margin-top: 10px; +} + +.ml-12 { + margin-left: 12px; +} + +.mt-12 { + margin-top: 12px; +} + +.ml-14 { + margin-left: 14px; +} + +.mt-14 { + margin-top: 14px; +} + +.ml-16 { + margin-left: 16px; +} + +.mt-16 { + margin-top: 16px; +} + +.ml-18 { + margin-left: 18px; +} + +.mt-18 { + margin-top: 18px; +} + +.ml-20 { + margin-left: 20px; +} + +.mt-20 { + margin-top: 20px; +} + +.ml-22 { + margin-left: 22px; +} + +.mt-22 { + margin-top: 22px; +} + +.ml-24 { + margin-left: 24px; +} + +.mt-24 { + margin-top: 24px; +} + +.ml-26 { + margin-left: 26px; +} + +.mt-26 { + margin-top: 26px; +} + +.ml-28 { + margin-left: 28px; +} + +.mt-28 { + margin-top: 28px; +} + +.ml-30 { + margin-left: 30px; +} + +.mt-30 { + margin-top: 30px; +} + +.ml-32 { + margin-left: 32px; +} + +.mt-32 { + margin-top: 32px; +} + +.ml-34 { + margin-left: 34px; +} + +.mt-34 { + margin-top: 34px; +} + +.ml-36 { + margin-left: 36px; +} + +.mt-36 { + margin-top: 36px; +} + +.ml-38 { + margin-left: 38px; +} + +.mt-38 { + margin-top: 38px; +} + +.ml-40 { + margin-left: 40px; +} + +.mt-40 { + margin-top: 40px; +} + +.ml-42 { + margin-left: 42px; +} + +.mt-42 { + margin-top: 42px; +} + +.ml-44 { + margin-left: 44px; +} + +.mt-44 { + margin-top: 44px; +} + +.ml-46 { + margin-left: 46px; +} + +.mt-46 { + margin-top: 46px; +} + +.ml-48 { + margin-left: 48px; +} + +.mt-48 { + margin-top: 48px; +} + +.ml-50 { + margin-left: 50px; +} + +.mt-50 { + margin-top: 50px; +} + +.ml-52 { + margin-left: 52px; +} + +.mt-52 { + margin-top: 52px; +} + +.ml-54 { + margin-left: 54px; +} + +.mt-54 { + margin-top: 54px; +} + +.ml-56 { + margin-left: 56px; +} + +.mt-56 { + margin-top: 56px; +} + +.ml-58 { + margin-left: 58px; +} + +.mt-58 { + margin-top: 58px; +} + +.ml-60 { + margin-left: 60px; +} + +.mt-60 { + margin-top: 60px; +} + +.ml-62 { + margin-left: 62px; +} + +.mt-62 { + margin-top: 62px; +} + +.ml-64 { + margin-left: 64px; +} + +.mt-64 { + margin-top: 64px; +} + +.ml-66 { + margin-left: 66px; +} + +.mt-66 { + margin-top: 66px; +} + +.ml-68 { + margin-left: 68px; +} + +.mt-68 { + margin-top: 68px; +} + +.ml-70 { + margin-left: 70px; +} + +.mt-70 { + margin-top: 70px; +} + +.ml-72 { + margin-left: 72px; +} + +.mt-72 { + margin-top: 72px; +} + +.ml-74 { + margin-left: 74px; +} + +.mt-74 { + margin-top: 74px; +} + +.ml-76 { + margin-left: 76px; +} + +.mt-76 { + margin-top: 76px; +} + +.ml-78 { + margin-left: 78px; +} + +.mt-78 { + margin-top: 78px; +} + +.ml-80 { + margin-left: 80px; +} + +.mt-80 { + margin-top: 80px; +} + +.ml-82 { + margin-left: 82px; +} + +.mt-82 { + margin-top: 82px; +} + +.ml-84 { + margin-left: 84px; +} + +.mt-84 { + margin-top: 84px; +} + +.ml-86 { + margin-left: 86px; +} + +.mt-86 { + margin-top: 86px; +} + +.ml-88 { + margin-left: 88px; +} + +.mt-88 { + margin-top: 88px; +} + +.ml-90 { + margin-left: 90px; +} + +.mt-90 { + margin-top: 90px; +} + +.ml-92 { + margin-left: 92px; +} + +.mt-92 { + margin-top: 92px; +} + +.ml-94 { + margin-left: 94px; +} + +.mt-94 { + margin-top: 94px; +} + +.ml-96 { + margin-left: 96px; +} + +.mt-96 { + margin-top: 96px; +} + +.ml-98 { + margin-left: 98px; +} + +.mt-98 { + margin-top: 98px; +} + +.ml-100 { + margin-left: 100px; +} + +.mt-100 { + margin-top: 100px; +} \ No newline at end of file diff --git a/src/typing.ts b/src/typing.ts new file mode 100644 index 0000000..2a319f9 --- /dev/null +++ b/src/typing.ts @@ -0,0 +1,3 @@ +import type { Ref } from 'vue' + +export type MaybeRef = T | Ref diff --git a/src/use/index.js b/src/use/index.js new file mode 100644 index 0000000..65beece --- /dev/null +++ b/src/use/index.js @@ -0,0 +1,22 @@ +import { useEncryption, useUid, useCoinId,useEmailLang, useSystem } from "./useTool" +import { useUpload, useUploadOss } from "./useFile" +import useLongPress from "./useLongPress" + +let use = { + // useClone, + useEncryption, + // useAWSC, + useUid, + // useDecimal, + // useCustomer, + // useIsPhone, + // useTimezone, + useCoinId, + useLongPress, + useUpload, + useUploadOss, + useEmailLang, + useSystem, +} + +export default use diff --git a/src/use/useFile.js b/src/use/useFile.js new file mode 100644 index 0000000..32e29a7 --- /dev/null +++ b/src/use/useFile.js @@ -0,0 +1,58 @@ +import api from '@/api'; +import axios from 'axios'; + +export const useUpload = (access_permissions, callback) => { + return async (fileObj) => { + const file = fileObj.file; + const ossData = { + access_permissions: access_permissions, + content_type: file.type, + file_name: file.name + }; + const ossRes = await api.identity.ossPutUrl(ossData); + const { access_url, put_url } = ossRes.data; + const fileRes = await axios({ + method: 'put', + url: put_url, + data: file, + headers: { + 'Content-Type': file.type + } + }); + + callback && callback(access_url, file); + }; +}; + + + + +/* +* +* oss上传文件 +* @param {File } file 文件对象 +*@param {Number} file_from 1.非聊天场景:管理后台头像上传等; 2.聊天场景 +* +* ***/ +export const useUploadOss = async (file_from, callback) => { + return async (fileObj) => { + const file = fileObj.file; + const ossData = { + content_type: file.type, + file_name: file.name, + file_from: file_from + }; + const ossRes = await api.identity.ossPutUrl(ossData); + const { access_url, put_url } = ossRes.data; + const fileRes = await axios({ + method: 'put', + url: put_url, + data: file, + headers: { + 'Content-Type': file.type + } + }); + + callback && callback(access_url); + }; +} diff --git a/src/use/useLongPress.js b/src/use/useLongPress.js new file mode 100644 index 0000000..d548d80 --- /dev/null +++ b/src/use/useLongPress.js @@ -0,0 +1,42 @@ +import { ref, onMounted, onUnmounted } from 'vue' + +export default function useLongPress(callback = () => {}, ms = 500) { + const pressing = ref(false) + let timeoutId = null + + const start = () => { + timeoutId = setTimeout(() => { + callback() + start() + }, ms) + } + + const stop = () => { + if (timeoutId !== null) { + clearTimeout(timeoutId) + timeoutId = null + } + } + + const onPressDown = () => { + pressing.value = true + start() + } + + const onPressUp = () => { + pressing.value = false + stop() + } + + onMounted(() => { + document.addEventListener('touchstart', onPressDown) + document.addEventListener('touchend', onPressUp) + }) + + onUnmounted(() => { + document.removeEventListener('touchstart', onPressDown) + document.removeEventListener('touchend', onPressUp) + }) + + return { pressing } +} \ No newline at end of file diff --git a/src/use/usePasteImg.js b/src/use/usePasteImg.js new file mode 100644 index 0000000..a4ee047 --- /dev/null +++ b/src/use/usePasteImg.js @@ -0,0 +1,67 @@ +import { ref, onMounted, onUnmounted } from 'vue' +import { isMobileBrowser } from "@/utils/tools" +import pasteImgDialog from '@/components/pasteImgDialog/index' + +/** + * @param {String} targetId 监听粘贴事件的dom ID #号可写也可以不写 + * @param {Function} parseOk 粘贴弹窗 点击确定后的回调,一般用来去上传并处理消息列表, 返回base64格式字符串的图片内容 + */ +export default function usePasteImg({ + parseOk, + targetId, +}) { + const previewUrl = ref('') + + // 粘贴事件回调 + const handlePasteImg = (e) => { + const clp = e.clipboardData; + if (clp?.items[0].type.includes("text/plain")) { + e.preventDefault() + const text = clp.getData("text/plain") || ""; + document.execCommand('inserttext', false, text) + } + const imageItems = [...clp.items].filter((item) => + item.type.includes("image"), + ); + // 如果是贴图片,不让它贴进去 + if (!imageItems.length || isMobileBrowser()) { + // 如果是手机就不走下面的逻辑,手机没有截图 + e.preventDefault(); + return + } + e.preventDefault(); + const [ imgItem ] = imageItems + // 贴的是图片 + // 这里一般只会有一张 + const blob = imgItem.getAsFile(); + const reader = new FileReader(); + reader.onload = () => { + previewUrl.value = reader.result + pasteImgDialog.show({ + previewUrl: reader.result, + ok: () => { + parseOk && parseOk(reader.result) + } + }) + }; + reader.readAsDataURL(blob); + } + + onMounted(() => { + const target = document.querySelector(`#${targetId.replace(/#/, '')}`) + if (target) { + target.addEventListener('paste', handlePasteImg) + } + }) + + onUnmounted(() => { + const target = document.querySelector(`#${targetId}`) + if (target) { + target.removeEventListener('paste', handlePasteImg) + } + }) + + return { + previewUrl, + } +} \ No newline at end of file diff --git a/src/use/usePolling.js b/src/use/usePolling.js new file mode 100644 index 0000000..78a41bf --- /dev/null +++ b/src/use/usePolling.js @@ -0,0 +1,60 @@ +import { ref, onMounted, onUnmounted } from 'vue' + +const DEFAULT_TIME_INTERVAL = 5 * 60 * 1000 +export default function usePolling(pollingFunction = () => {}, timeInterval = DEFAULT_TIME_INTERVAL) { + const intervalId = ref(null) + const isPolling = ref(false) + + const startInterval = () => { + isPolling.value = true + // 每隔指定时间执行一次轮询函数 + intervalId.value = setInterval(() => { + pollingFunction() + }, timeInterval) + } + + const stopInterval = () => { + isPolling.value = false + // 取消定时器 + clearInterval(intervalId.value) + intervalId.value = null // 将定时器ID重置为null + } + + // 手动开始定时器 + const manualStartInterval = () => { + if (!intervalId.value) { + startInterval() + } + } + + // 手动停止定时器 + const manualStopInterval = () => { + if (intervalId.value) { + stopInterval() + } + } + + // 手动调用轮询函数,并重新计时 + const manualCallPollingFunction = () => { + // 停止定时器 + manualStopInterval() + + // 调用轮询函数 + pollingFunction() + + // 重新开始定时器 + manualStartInterval() + } + + // 在组件销毁时停止定时器,避免内存泄漏 + onUnmounted(() => { + stopInterval() + }) + + return { + isPolling, + manualStartInterval, + manualStopInterval, + manualCallPollingFunction + } +} \ No newline at end of file diff --git a/src/use/useTool.js b/src/use/useTool.js new file mode 100644 index 0000000..e9e47d1 --- /dev/null +++ b/src/use/useTool.js @@ -0,0 +1,261 @@ +import JSEncrypt from 'jsencrypt' +import { Base64 } from 'js-base64' +import api from '@/api' +// import dayjs from 'dayjs' +// import utc from 'dayjs/plugin/utc' +// import timezone from 'dayjs/plugin/timezone' +// // import { useRouter } from 'vue-router' +// import router from '../router/index' +// import { storeToRefs } from 'pinia' +// import { useUserStore } from '@/stores/user' + +// dayjs.extend(utc) +// dayjs.extend(timezone) + +// const userStore = useUserStore() +// const { userInfo } = storeToRefs(userStore) + +export const useEncryption = async () => { + const res = await api.login.getPubKey() + const { pub_key, pub_key_id } = res.data + const encryptor = new JSEncrypt() // 创建加密对象实例 + + encryptor.setPublicKey('-----BEGIN PUBLIC KEY-----' + Base64.decode(pub_key) + '-----END PUBLIC KEY-----') //设置公钥 + + return { encryptor, pub_key_id } +} + +// export const useClone = (obj) => { +// return JSON.parse(JSON.stringify(obj)) +// } + +// export const useAWSC = async (callback) => { +// let NCObj = null +// const res = await api.user.configGet() +// const { captcha } = res.data || {} + +// window.AWSC.use("nc", function (state, module) { +// // 初始化 +// NCObj = module.init({ +// // 应用类型标识。它和使用场景标识(scene字段)一起决定了滑动验证的业务场景与后端对应使用的策略模型。您可以在阿里云验证码控制台的配置管理页签找到对应的appkey字段值,请务必正确填写。 +// appkey: captcha.app_key, +// //使用场景标识。它和应用类型标识(appkey字段)一起决定了滑动验证的业务场景与后端对应使用的策略模型。您可以在阿里云验证码控制台的配置管理页签找到对应的scene值,请务必正确填写。 +// scene: captcha.scene_id, +// // 声明滑动验证需要渲染的目标ID。 +// renderTo: "nc", +// //前端滑动验证通过时会触发该回调参数。您可以在该回调参数中将会话ID(sessionId)、签名串(sig)、请求唯一标识(token)字段记录下来,随业务请求一同发送至您的服务端调用验签。 +// success: function (data) { +// callback && callback(JSON.stringify(data), NCObj) +// }, +// // 滑动验证失败时触发该回调参数。 +// fail: function (failCode) { +// window.console && console.log(failCode) +// }, +// // 验证码加载出现异常时触发该回调参数。 +// error: function (errorCode) { +// window.console && console.log(errorCode) +// } +// }); +// }) +// } + +export const useUid = () => { + let navigator_info = window.navigator + let screen_info = window.screen + let uid = navigator_info.mimeTypes.length + + uid += navigator_info.userAgent.replace(/\D+/g, '') + uid += navigator_info.plugins.length + uid += screen_info.height || '' + uid += screen_info.width || '' + uid += screen_info.pixelDepth || '' + + return uid +} + +// // 截取两位小数,补0 +// export const useDecimal = (num, decimalPlaces) => { +// if (num === undefined) return + +// num += '' +// num = num.indexOf(".") < 0 ? num+'.' : num + +// let newNum = num.substr(0, num.indexOf(".")+decimalPlaces+1) +// let rs = newNum.indexOf('.') + +// if (rs<0) { +// newNum += '.00' +// } + +// while (newNum.length <= rs + decimalPlaces) { +// newNum += '0' +// } + +// return newNum +// } + +// export const useCustomer = async (backPath) => { +// // const router = useRouter() +// const res = await api.user.configGet() +// const { customer_support } = res.data || {} +// const { domains } = customer_support || {} +// let customerDomain = domains.split(',')[0] + +// customerDomain = customerDomain.startsWith('http') ? customerDomain : 'https://' + customerDomain + +// // window.open(customerDomain) + +// // console.log(router); + +// customerDomain = customerDomain + '/#/index?deviceId='+useUid() + +// router.push({ +// path: '/Webview', +// query: { +// url: customerDomain, +// isTitle: false, +// backPath +// } +// }) +// } + +// export const useIsPhone = (phone) => { +// // return /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1})|(16[0-9]{1})|(17[0-9]{1})|(19[0-9]{1}))+\d{8})$/.test(phone) +// return !isNaN(phone) && phone.length >= 7 && phone.length <= 11 +// } + +// export const useTimezone = (time) => { +// if (time && userInfo.value.timezone) { +// // time = time+'Z' +// // if (time.endsWith('Z')) { +// // time = time.replace('Z', '') +// // } + +// return dayjs(time).tz(userInfo.value.timezone).format('YYYY-MM-DD HH:mm:ss') +// } else { +// return time +// } +// } +export const useCoinId = async(coin_id) => { + if (coin_id !== undefined) { + await api.user.userUpdate({ currency_id: coin_id }) + } else { + const { data } = await api.user.userInfo() + return data.currency_id + } +} + +// 转换当前语言-邮箱发送 +export const useEmailLang = () => { + let navLang = 'us' + if (navigator.language === 'zh-CN') { + navLang = 'zh-cn' + } else if (navigator.language === 'en') { + navLang = 'us' + } else if (navigator.language === 'zh-TW') { + navLang = 'zh-hant-cn' + } else if (navigator.language === 'vi') { + navLang = 'vi' // 越南 + } else if (navigator.language === 'th') { + navLang = 'th' // 泰语 + } else if (navigator.language === 'id') { + navLang = 'id' // 印尼 + } else if (navigator.language === 'pt-BR') { + navLang = 'pt-br' // 巴西 + } + let configLang = '' + if (localStorage.getItem('language') === 'zh-cn') { + configLang = 'zh-cn' + } else if (localStorage.getItem('language') === 'us') { + configLang = 'us' + } else if (localStorage.getItem('language') === 'zh-hant-cn') { + configLang = 'zh-hant-cn' + } else if (localStorage.getItem('language') === 'vi-VN') { + configLang = 'vi' // 越南 + } else if (localStorage.getItem('language') === 'th') { + configLang = 'th' // 泰语 + } else if (localStorage.getItem('language') === 'id') { + configLang = 'id' // 印尼 + } else if (localStorage.getItem('language') === 'pt-br') { + configLang = 'pt-br' // 巴西 + } + const language = configLang || navLang; + console.log('navigator.language',language); + return language +} + +// // 设备判断 +// export const usePhoneOs = () => { +// let u = navigator.userAgent + +// let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1 + +// let isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) + +// console.log('usePhoneOs',navigator.userAgent); +// if (isAndroid) { +// return 'Android' + +// } +// if (isiOS) { + +// return 'IOS' + +// } +// return '' +// } +export const usePhoneOs = () => { + // 获取用户代理字符串 + var userAgent = navigator.userAgent || navigator.vendor || window.opera; + // 判断是否为iOS设备 + if (/iPad|iPhone|iPod|Mac/.test(userAgent) && !window.MSStream) { + return 'IOS' + } + // 判断是否为安卓设备 + else if (/android/i.test(userAgent)) { + return 'Android' + } + // 其他设备 + else { + return '' + } +} + +// 判断当前系统 +export const useSystem = () => { + /**Platform ID + IOSPlatformID = 1 + AndroidPlatformID = 2 + WindowsPlatformID = 3 + OSXPlatformID = 4 + WebPlatformID = 5 + MiniWebPlatformID = 6 + LinuxPlatformID = 7 + AndroidPadPlatformID = 8 + IPadPlatformID = 9 + AdminPlatformID = 10 + AndroidWebPlatformID = 11 + IOSWebPlatformID = 12 + WindowWebPlatformID = 13 + MacWebPlatformID = 14 + */ + const userAgent = navigator.userAgent || navigator.vendor || window.opera; + console.log(userAgent,'useSystem'); + if (/android/i.test(userAgent)) { + return 11; + } + + if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { + return 12; + } + + if (/Mac/i.test(userAgent)) { + return 14; + } + + if (/Windows/i.test(userAgent)) { + return 13; + } + // 默认情况下,可能是一个不支持的或未知的设备 + return 11; +} \ No newline at end of file diff --git a/src/utils/cdnUtils.js b/src/utils/cdnUtils.js new file mode 100644 index 0000000..7c836dc --- /dev/null +++ b/src/utils/cdnUtils.js @@ -0,0 +1,112 @@ +import axios from 'axios'; +import { showToast } from 'vant'; +import { i18n } from "@/lang/index"; +import { getSDK } from '@/utils/openIM/sdk/index.js' +const IMSDK = getSDK(); +const { t } = i18n.global +const { DEV, VITE_APP_PUB_API, VITE_APP_MSG_API,VITE_APP_PROXY_TARGET } = import.meta.env;; +// 获取环境变量 +// const apiUrl = import.meta.env.VITE_APP_PROXY_TARGET; // 或 process.env.VUE_APP_API_URL +/** + * 查找最快的 CDN 线路 + * @returns {Promise} 最快的 CDN 线路 URL + */ +export const findFastestCdn = async () => { + if (DEV===false) { + // 从 sessionStorage 获取配置 + const appConfig = sessionStorage.getItem("appConfig") + ? JSON.parse(sessionStorage.getItem("appConfig")) + : {}; + // 获取 CDN 线路列表 + const cdnList = appConfig.site_acceleration?.filter( + item => item.back_end_url && item.back_end_url.trim() !== "" + ) || []; + // 没有配置 CDN 线路,返回默认线路 + if (cdnList.length === 0) { + console.error('没有配置 CDN 线路,使用默认线路'); + return window.WEB_CONFIG.proxy_target; + } + // 在 CDN 线路列表中添加默认线路 || 当线路存在的时候才去检测,默认线路已经生成了,就不需要在发送检测请求了 + cdnList.unshift({ + back_end_url: window.WEB_CONFIG.proxy_target || VITE_APP_PROXY_TARGET, + }); + console.error('线路列表', cdnList); + console.error('当前的默认线路', window.WEB_CONFIG); + // 创建请求的 URL 列表 + const requests = cdnList.map(cdn => { + const url = cdn.back_end_url ? `${cdn.back_end_url}/chat-api/health` : undefined; + if (!url) { + // 直接返回一个 Promise 解决的结果,表示 URL 为 undefined 时的情况 + return Promise.resolve({ originalUrl: cdn.back_end_url, time: Infinity }); + } + const start = performance.now(); + return axios.get(url,{timeout:3000}) + .then(response => { + // 只有在状态码为 200 时,才返回 originalUrl 和 time + console.error('response.status======',response); + if (response.status === 200) { + return { originalUrl: cdn.back_end_url, time: performance.now() - start }; + } else { + return { originalUrl: cdn.back_end_url, time: Infinity }; + } + }) + .catch(() => ({ originalUrl: cdn.back_end_url, time: Infinity })); + }); + // 等待所有请求完成并筛选出成功的请求 + const responses = await Promise.all(requests); + const availableResponses = responses.filter(response => response.time < Infinity); + // 找到最快的可用 CDN + const fastest = availableResponses.length + ? availableResponses.reduce((prev, curr) => (prev.time < curr.time ? prev : curr)) + : null; + if (fastest) { + sessionStorage.setItem("globalRequestPrefix", fastest.originalUrl); + // const wsUrl = fastest.originalUrl.replace("https", "wss"); + // await IMSDK.updateConfig({ + // apiAddr:fastest.originalUrl, + // wsAddr:wsUrl, + // reConn:true, + // backupHttpAddrs:availableResponses.map(item => `${item.originalUrl}/chat-server-api` ) + // }); + console.error('最快的 CDN:', fastest.originalUrl); + return fastest.originalUrl; + } else { + console.error('所有 CDN 线路均不可用,使用默认线路'); + showToast(t("error_http_11")); + return -1; + } + } +}; + +// 处理应用配置和参数更新 +export const updateAppConfig = async () => { + try { + const { VITE_APP_MSG_API,VITE_APP_PROXY_TARGET } = import.meta.env;; + // 获取并解析 appConfig + const appConfig = sessionStorage.getItem("appConfig") + ? JSON.parse(sessionStorage.getItem("appConfig")) + : {}; + // 过滤有效的 cdnList + const cdnList = appConfig.site_acceleration?.filter( + item => item.back_end_url && item.back_end_url.trim() !== "" + ) || []; + // 调用 IMSDK.updateConfig 更新配置 + await IMSDK.updateConfig({ + apiAddr: VITE_APP_PROXY_TARGET, + wsAddr: VITE_APP_PROXY_TARGET.replace("https", "wss"), + reConn: true, + backupHttpAddrs: cdnList.map(item => `${item.back_end_url}/chat-server-api`) + }); + } catch (error) { + console.error('Error updating app config:', error); + } +} +// 1-页面入口:启动APP后自动进入; +// 2-初始化&切换线路功能: +// 2.1-功能说明: +// 进入该页面即自动进行初始化并ping线路选择最优线路; +// 线路正常:跳转登录页或会话页(依据登录状态判定) +// 线路异常:自动切换线路 +// 全部线路异常:Toast显示“网络连接超时”并展示[网络诊断]按钮; +// 2.2-交互说明: +// 点击[网络诊断],跳转网络诊断页面; diff --git a/src/utils/chatRule.js b/src/utils/chatRule.js new file mode 100644 index 0000000..f8eaad8 --- /dev/null +++ b/src/utils/chatRule.js @@ -0,0 +1,114 @@ +import { useUserStore } from "@/stores/user"; +import { storeToRefs } from 'pinia'; +const userStore = useUserStore() +const { userInfo, appConfig } = storeToRefs(userStore) +// 获取发言频率配置 +const ChatSendRegularData = ref({}) +//当前规则是否有效 +const chatSendRegular = () => { + console.log('chatSendRegular',appConfig.value); + let config = {} + if(appConfig.value && appConfig.value.conversation_rules && appConfig.value.conversation_rules.length > 0) { + config = appConfig.value?.conversation_rules[0] + } + ChatSendRegularData.value = { + seconds: config.duration || 0, // 几秒内 + continueCount: config.threshold || 0,// 连续发送多少条 + maxCheckCount: 100, //检查最近的条数-最多100条 + enable: config.status === 1 ? true : false, // 是否开启 + }; + if(ChatSendRegularData.value.seconds > 0 && ChatSendRegularData.value.continueCount > 0 && ChatSendRegularData.value.enable){ + return ChatSendRegularData.value; + } + return +} + +// 获取消息文本 +const getMessageText = (message) => { + let type = message.contentType; + let str = '' + if(type === 101 ) { // 文本 + return message.textElem.content + } else if (type === 106) { // @消息 + return message.atTextElem.text + } else if (type === 114) { // 引用 + return message.quoteElem.text + } else if (type === 105) { // 文件 + if(message.fileElem?.sourceUrl){ + str = stringMark(message.fileElem.sourceUrl) + } + return str + } else if (type === 102) { // 图片 + if(message.pictureElem?.sourcePicture){ + str = stringMark(message.pictureElem?.sourcePicture?.url) + } + // return message.pictureElem?.sourcePath + return str + } else if (type === 104) { // 视频 + if(message.videoElem?.videoUrl){ + str = stringMark(message.videoElem?.videoUrl) + } + // return message.videoElem?.videoPath; + return str + } + return null; +} +const stringMark = (str) => { + // 查找第一个问号的位置 + const index = str.indexOf('?'); + // 如果找到了问号,返回问号之前的子字符串 + if (index !== -1) { + return str.substring(0, index); + } + console.log(str,'stringMark2'); + // 如果没有找到问号,返回原始字符串 + return str; +} + +// 传入消息列表,新消息,发送人ID +export const checkMessage = (messageList, newMsg) => { + console.log('messageList',messageList, newMsg,userInfo.value.userID); + return new Promise((resolve, reject) => { + // chatSendRegular为当前的规则 + const regular = chatSendRegular(); + if(regular){ + const curTime = new Date().getTime(); // 当前时间戳秒数 + const preTime = (curTime - (regular.seconds*1000)); // 最近多少秒内 + //检测的总消息条数 + let msgCount = 0; + //我发送的消息条数 + let mySendCount = 0; + //相同的消息条数 + let eqCount = 0; + const data = messageList.slice().reverse(); + for (const element of data) { + // 发送人 + if (element.sendID === userInfo.value.userID) { + if ((element.createTime) < preTime) { + break; + } + const msgText = getMessageText(element); + const text = getMessageText(newMsg) + if (msgText === text) { + eqCount++; + if (eqCount >= regular.continueCount) { + resolve({code:101,msg:'发送失败,请勿连续发送重复内容'}) + } + } + // 检查消息内容的连续性 + // mySendCount++; + // if (mySendCount >= regular.continueCount) { + // break; + // } + } + msgCount++; + if (msgCount >= regular.maxCheckCount) { + break; + } + } + resolve({code:0,msg:'发送成功'}) + } + resolve({code:0,msg:'发送成功'}) + }) +}; + diff --git a/src/utils/common.js b/src/utils/common.js new file mode 100644 index 0000000..de14797 --- /dev/null +++ b/src/utils/common.js @@ -0,0 +1,174 @@ +import { showToast } from "vant"; +// i18n +import { i18n } from '@/lang/index' +import api from '@/api' +const { t } = i18n.global + +export const feedbackToast = (config) => { + const { message, error, duration, onClose = () => {} } = config ?? {}; + showToast({ + message: + message ?? + t(error ? "messageTip.nomalFailed" : "messageTip.nomalSuccess"), + type: error ? "fail" : "success", + onClose, + duration, + }); + if (error) { + console.error(message, error); + } +}; + +export const filterEmptyValue = (obj) => { + for (let key in obj) { + if (obj[key] === "") { + delete obj[key]; + } + } +}; + +export const isBase64 = (str) => { + if (typeof str !== 'string') return false + if(str === '' || str.trim() === ''){ + return false; + } + // 检查字符串是否以 "data:image/png;base64," 开头 + return /^data:image\/(jpeg|png|gif|bmp);base64,/.test(str) +} + +export const getPicInfo = (file) => { + return new Promise((resolve, reject) => { + const _URL = window.URL || window.webkitURL; + const img = new Image(); + img.onload = function () { + resolve(img); + }; + img.src = isBase64(file) ? file : _URL.createObjectURL(file); + }); +}; +export const getMediaDuration = (path) => { + return new Promise((resolve, reject) => { + const vel = new Audio(path); + vel.onloadedmetadata = async function () { + // resolve(vel.duration); + resolve(Number(vel.duration.toFixed())); + }; + }); +}; + +export const getVideoSnshot = (item) => { + return new Promise((reslove, reject) => { + var video = document.createElement("VIDEO"); + video.setAttribute("autoplay", "autoplay"); + video.setAttribute("muted", "muted"); + video.innerHTML = "'; + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + video.addEventListener("canplay", function () { + var anw = document.createAttribute("width"); + //@ts-ignore + anw.nodeValue = video.videoWidth; + var anh = document.createAttribute("height"); + //@ts-ignore + anh.nodeValue = video.videoHeight; + canvas.setAttributeNode(anw); + canvas.setAttributeNode(anh); + //@ts-ignore + ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); + var base64 = canvas.toDataURL("image/png"); + //@ts-ignore + video.pause(); + const file = base64toFile(base64); + reslove(file); + }); + }); +}; + +export const getFileType = (name) => { + const idx = name.lastIndexOf("."); + return name.slice(idx + 1); +}; + +///不是红包类型,且发送成功的消息 +export const canDeleteFromServer = (msg, showFalseTip = false) => { + if (msg.contentType == 110 && JSON.parse(msg.customElem.data).customType === 1003) { + if (showFalseTip) { + showToast({ + message: t('deleteRedPackageErrTip') + }); + } + return false; + } + return msg.status == 2; +} + +/** + * 判断当前是移动端还是pc端 + * @returns + */ +export const isMobile= ()=>{ + const userAgent = navigator.userAgent; + const mobileKeywords = [ + "Android", + "webOS", + "iPhone", + "iPad", + "iPod", + "BlackBerry", + "Windows Phone" + ]; + for (let i = 0; i < mobileKeywords.length; i++) { + if (userAgent.indexOf(mobileKeywords[i]) > -1) { + return true; + } + } + return false; +} + +export const burnMsgByClearMsg = async (conversationID, endTime = 0) => { + const res = await api.contact.burnMsgByClearMsg({ + conversationID, + endTime + }); + // 接口调成功后才删本地消息 + if (res.errCode !== 0) { + throw new Error(res.errCode) + } else { + return true + } +} + +// 判断是否是 http 或 https 链接 +export function isHttpLink(content) { + // 允许以 http://, https:// 或 www. 开头的链接 + const regexHttpLink = /^(https?:\/\/|www\.)/i; + return regexHttpLink.test(content); +} + +// 判断链接的后缀是否是常见图片类型 +export function isImageLink(url) { + const extension = getFileExtensionFromUrl(url); + return ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension.toLowerCase()); +} + +// 从链接中获取文件扩展名 +export function getFileExtensionFromUrl(url) { + let extension = ''; + const queryStringIndex = url.indexOf('?'); // 查找查询参数的位置 + let urlWithoutQuery = url; + if (queryStringIndex !== -1) { + urlWithoutQuery = url.substring(0, queryStringIndex); // 获取不包含查询参数的url + } + const lastDotIndex = urlWithoutQuery.lastIndexOf('.'); + if (lastDotIndex !== -1) { + extension = urlWithoutQuery.substring(lastDotIndex + 1).toLowerCase(); + } + return extension; +} + +// 用于存储请求耗时的全局 Map 对象 +export let requestTimesMap = new Map(); +// 定义 Map 来存储线路数据 +export let linesMap = new Map(); +// 创建一个 Map 用于存储延迟数据 +export let delayMap = new Map(); \ No newline at end of file diff --git a/src/utils/constant.js b/src/utils/constant.js new file mode 100644 index 0000000..a0b5cec --- /dev/null +++ b/src/utils/constant.js @@ -0,0 +1,345 @@ +export const CustomMessageType = { + call: 901, + emoji: 902, + tag: 903, + moments: 904, + meeting: 905, + blockedByFriend: 910, + deletedByFriend: 911, + removedFromGroup: 912, + groupDisbanded: 913, + ///清除会话信息 + clearConversationMsg: 1001, + ///红包消息 + redPackageMsg: 1003, + ///接收红包的消息 + receiverRedPackageMsg: 1004, +} + + +export const SDKErrorCode = { + /// 网络请求错误 + networkRequestError: 10000, + + /// 网络等待超时错误 + networkWaitTimeoutError: 10001, + + /// 参数错误 + parameterError: 10002, + + /// 上下文超时错误,通常为用户已经退出 + contextTimeoutError: 10003, + + /// 资源未加载完毕,通常为未初始化,或者登录接口还未成功返回 + resourceNotLoaded: 10004, + + /// 未知错误,需要根据 errmsg 确认原因 + unknownError: 10005, + + /// sdk 内部错误,需要根据 errmsg 确认原因 + sdkInternalError: 10006, + + /// 该用户已设置不可添加 + refuseToAddFriends: 10013, + + /// 用户不存在或未注册 + userNotExistOrNotRegistered: 10100, + + /// 用户已经退出登录 + userHasLoggedOut: 10101, + + /// 用户重复登录,可以通过 getloginstatus 确认登录状态,避免重复登录 + repeatLogin: 10102, + + /// 需要上传的文件不存在 + uploadFileNotExist: 10200, + + /// 消息解压失败 + messageDecompressionFailed: 10201, + + /// 消息解码失败 + messageDecodingFailed: 10202, + + /// 不支持的长连接二进制协议 + unsupportedLongConnection: 10203, + + /// 消息重复发送 + messageRepeated: 10204, + + /// 消息内容类型不支持 + messageContentTypeNotSupported: 10205, + + /// 不支持的会话操作 + unsupportedSessionOperation: 10301, + + /// 群 ID 不存在 + groupIDNotExist: 10400, + + /// 群组类型错误 + wrongGroupType: 10401, + + /// 服务器内部错误,通常为内部网络错误,需要检查服务器各节点运行是否正常 + serverInternalError: 500, + + /// 参数错误,需要检查 body 参数以及 header 参数是否正确 + serverParameterError: 1001, + + /// 权限不足,一般为 header 参数中携带 token 不正确,或者权限越级操作 + insufficientPermissions: 1002, + + /// 数据库主键重复 + duplicateDatabasePrimaryKey: 1003, + + /// 数据库记录未找到 + databaseRecordNotFound: 1004, + + /// 用户 ID 不存在 + userIDNotExist: 1101, + + /// 用户已经注册 + userAlreadyRegistered: 1102, + + /// 群不存在 + groupNotExis: 1201, + + /// 群已存在 + groupAlreadyExists: 1202, + + /// 用户不在群组中 + userIsNotInGroup: 1203, + + /// 群组已解散 + groupDisbanded: 1204, + + /// 群申请已经被处理,不需要重复处理 + groupApplicationHasBeenProcessed: 1206, + + /// 不能添加自己为好友 + notAddMyselfAsAFriend: 1301, + + /// 已被对方拉黑 + hasBeenBlocked: 1302, + + /// 对方不是自己的好友 + notFriend: 1303, + + /// 已经是好友关系,不需要重复申请 + alreadyAFriendRelationship: 1304, + + /// 消息已读功能被关闭 + messageReadFunctionIsTurnedOff: 1401, + + /// 你已被禁言,不能在群里发言 + youHaveBeenBanned: 1402, + + /// 群已被禁言,不能发言 + groupHasBeenBanned: 1403, + + /// 该消息已被撤回 + messageHasBeenRetracted: 1404, + + /// 授权过期 + licenseExpired: 1405, + + /// token 已经过期 + tokenHasExpired: 1501, + + /// token 无效 + tokenInvalid: 1502, + + /// token 格式错误 + tokenFormatError: 1503, + + /// token 还未生效 + tokenHasNotYetTakenEffect: 1504, + + /// 未知 token 错误 + unknownTokenError: 1505, + + /// 被踢出的 token,无效 + thekickedOutTokenIsInvalid: 1506, + + /// token 不存在 + tokenNotExist: 1507, + + /// 连接数超过网关最大限制 + connectionsExceedsMaximumLimit: 1601, + + /// 连接握手参数错误 + handshakeParameterError: 1602, + + /// 文件上传过期 + fileUploadExpired: 1701, +} + +export const MessageReceiveOptType = { + Nomal: 0, + NotReceive: 1, + NotNotify: 2 +} +export const AllowType = { + Allowed: 0, + NotAllowed: 1 +} +export const GroupType = { + Group: 2, + WorkingGroup: 2 +} +export const GroupJoinSource = { + Invitation: 2, + Search: 3, + QrCode: 4 +} +export const GroupMemberRole = { + Nomal: 20, + Admin: 60, + Owner: 100 +} +export const GroupVerificationType = { + ApplyNeedInviteNot: 0, + AllNeed: 1, + AllNot: 2 +} +export const MessageStatus = { + Sending: 1, + Succeed: 2, + Failed: 3 +} +export const Platform = { + iOS: 1, + Android: 2, + Windows: 3, + MacOSX: 4, + Web: 5, + Linux: 7, + AndroidPad: 8, + iPad: 9 +} +export const LogLevel = { + Debug: 5, + Info: 4, + Warn: 3, + Error: 2, + Fatal: 1, + Panic: 0 +} +export const ApplicationHandleResult = { + Unprocessed: 0, + Agree: 1, + Reject: -1 +} +export const MessageType = { + TextMessage: 101, + PictureMessage: 102, + VoiceMessage: 103, + VideoMessage: 104, + FileMessage: 105, + AtTextMessage: 106, + MergeMessage: 107, + CardMessage: 108, + LocationMessage: 109, + CustomMessage: 110, + TypingMessage: 113, + QuoteMessage: 114, + FaceMessage: 115, + FriendAdded: 1201, + OANotification: 1400, + GroupCreated: 1501, + GroupInfoUpdated: 1502, + MemberQuit: 1504, + GroupOwnerTransferred: 1507, + MemberKicked: 1508, + MemberInvited: 1509, + MemberEnter: 1510, + GroupDismissed: 1511, + GroupMemberMuted: 1512, + GroupMemberCancelMuted: 1513, + GroupMuted: 1514, + GroupCancelMuted: 1515, + GroupAnnouncementUpdated: 1519, + GroupNameUpdated: 1520, + BurnMessageChange: 1701, + RevokeMessage: 2101 +} +export const SessionType = { + Single: 1, + Group: 3, + WorkingGroup: 3, + Notification: 4 +} +export const GroupStatus = { + Nomal: 0, + Baned: 1, + Dismissed: 2, + Muted: 3 +} +export const GroupAtType = { + AtNormal: 0, + AtMe: 1, + AtAll: 2, + AtAllAtMe: 3, + AtGroupNotice: 4 +} +export const GroupMemberFilter = { + All: 0, + Owner: 1, + Admin: 2, + Nomal: 3, + AdminAndNomal: 4, + AdminAndOwner: 5 +} +export const Relationship = { + isBlack: 0, + isFriend: 1 +} +export const LoginStatus = { + Logout: 1, + Logging: 2, + Logged: 3 +} +export const OnlineState = { + Online: 1, + Offline: 0 +} +export const GroupMessageReaderFilter = { + Readed: 0, + UnRead: 1 +} + +// export const CustomMessageTypes = { +// Call: 100, +// MassMsg: 903, +// MeetingInvitation: 905, +// }; + +export const GroupSessionTypes = [SessionType.Group, SessionType.WorkingGroup]; + +export const GroupSystemMessageTypes = [ + MessageType.GroupCreated, + MessageType.GroupInfoUpdated, + MessageType.MemberQuit, + MessageType.GroupOwnerTransferred, + MessageType.MemberKicked, + MessageType.MemberInvited, + MessageType.MemberEnter, + MessageType.GroupDismissed, + MessageType.GroupMemberMuted, + MessageType.GroupMuted, + MessageType.GroupCancelMuted, + MessageType.GroupMemberCancelMuted, + MessageType.GroupNameUpdated +]; + +export const TipTypes = [ + MessageType.RevokeMessage, + MessageType.FriendAdded, + MessageType.BurnMessageChange, + ...GroupSystemMessageTypes +]; + +export const FileMessageTypes = [ + MessageType.PictureMessage, + MessageType.VideoMessage, + MessageType.VoiceMessage, + MessageType.FileMessage, +]; \ No newline at end of file diff --git a/src/utils/events.js b/src/utils/events.js new file mode 100644 index 0000000..a8bbdd6 --- /dev/null +++ b/src/utils/events.js @@ -0,0 +1,15 @@ +import mitt from "mitt"; + +// type Events = { +// CHAT_MAIN_SCROLL_TO_BOTTOM: boolean; +// CHAT_MAIN_SCROLL_TO_CLIENTMSGID: string; +// TYPING_UPDATE: void; +// ONLINE_STATE_CHECK: void; +// UPDATE_MULTIPLE_CHECK_STATE: boolean; +// KEYBOARD_UPDATE: void; +// AT_SOMEONE: any; +// }; + +const emitter = mitt(); + +export default emitter; diff --git a/src/utils/fileUtils.js b/src/utils/fileUtils.js new file mode 100644 index 0000000..864d5d8 --- /dev/null +++ b/src/utils/fileUtils.js @@ -0,0 +1,147 @@ +import { computed,onMounted } from "vue"; +import { i18n } from '@/lang/index' +import { showImagePreview, showToast, showConfirmDialog ,showLoadingToast,showFailToast,showSuccessToast} from "vant"; +import apkIcon from "@/assets/filesvg/apk.svg"; +import pdfIcon from "@/assets/filesvg/pdf.svg"; +import exeIcon from "@/assets/filesvg/exe.svg"; +import txtIcon from "@/assets/filesvg/txt.svg"; +import docIcon from "@/assets/filesvg/doc.svg"; +import rarIcon from "@/assets/filesvg/rar.svg"; +import zipIcon from "@/assets/filesvg/rar.svg"; +import ipaIcon from "@/assets/filesvg/ipa.svg"; +import dmgIcon from "@/assets/filesvg/dmg.svg"; +import debIcon from "@/assets/filesvg/deb.svg"; +import epubIcon from "@/assets/filesvg/epub.svg"; +import pagesIcon from "@/assets/filesvg/pages.svg"; +import pptIcon from "@/assets/filesvg/ppt.svg"; +import numbersIcon from "@/assets/filesvg/numbers.svg"; +import keyIcon from "@/assets/filesvg/key.svg"; +import xlsxIcon from "@/assets/filesvg/xls.svg"; +import htmlIcon from "@/assets/filesvg/html.svg"; +import codeIcon from "@/assets/filesvg/code.svg"; +import mp3Icon from "@/assets/filesvg/mp3.svg"; +import unknownIcon from "@/assets/filesvg/unknown.svg"; + +const { t } = i18n.global + +/// 公共方法-计算文件大小 +export const formatFileSize = (bytes) => { + if (bytes === 0) return "0 Bytes"; + const k = 1024; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; +}; + +// 公共方法-文件上传错误的状态集合 +export const fileErrorsToast = async (error) => { + const { errCode } = error; + switch (errCode) { + case 13: + // 这个是对方已经屏蔽你 + showToast(t("cvSendMsgErrorNoPermission")); + break; + case 10005: + showToast(t("sendMessageFailed")); + break; + } +}; + +// 公共方法-创建文件对象 +export async function createFileObject(message) { + const { sourceUrl, fileName, fileSize, fileType } = message; + const response = await fetch(sourceUrl); + const data = await response.blob(); + const file = new File([data], fileName, { type: fileType }); + return file; +} +// 公共方法-根据文件名获取文件后缀,并根据文件后缀返回对应的图标路径 +export const fileIcon = computed(() => { + // 定义两个参数的函数 + return (val) => { + const fileName = val.fileElem?.fileName || val.cloneFileName; + const extension = fileName.split(".").pop().toLowerCase(); + // 定义扩展名和对应的图标路径 + const iconMap = { + apk: apkIcon, + apk1: apkIcon, + pdf: pdfIcon, + exe: exeIcon, + txt: txtIcon, + doc: docIcon, + docx: docIcon, + rar: rarIcon, + zip: zipIcon, + ipa: ipaIcon, + dmg: dmgIcon, + deb: debIcon, + epub: epubIcon, + pages: pagesIcon, + ppt: pptIcon, + pptx: pptIcon, + numbers: numbersIcon, + key: keyIcon, + xls: xlsxIcon, + xlsx: xlsxIcon, + html: htmlIcon, + mp3: mp3Icon, + wav: mp3Icon, + flac: mp3Icon, + wma: mp3Icon, + m4a: mp3Icon, + alff: mp3Icon, + alac: mp3Icon, + midi: mp3Icon, + ogg: mp3Icon, + css: codeIcon, + java: codeIcon, + cpp: codeIcon, + js: codeIcon, + php: codeIcon, + pyc: codeIcon, + json: codeIcon, + xml: codeIcon, + sql: codeIcon, + sqlite: codeIcon, + csv: codeIcon, + }; + // 返回对应的图标路径,如果没有匹配到则返回默认图标路径 + return iconMap[extension] || unknownIcon; + }; +}); +// 公共方法-根据url 下载文件 +export const publicDownloadFile = (item, showMultiple) => { + // 当开启了多选的时候不能点击下载 + if (showMultiple) { + return false; + } + showConfirmDialog({ + title: "温馨提示", + message: `文件大小${formatFileSize(item.fileElem.fileSize)}是否下载文件?`, + confirmButtonText: "下载", + cancelButtonText: "取消", + }) + .then(() => { + // 获取文件的 URL + const fileUrl = item.fileElem.sourceUrl; + // 创建一个 元素 + const link = document.createElement("a"); + link.href = fileUrl; + // 设置下载的文件名 + link.download = item.fileName; + // 将链接添加到页面中,这样它就可以被点击 + document.body.appendChild(link); + // 模拟用户点击下载链接 + link.click(); + // 清理创建的链接 + document.body.removeChild(link); + }) + .catch(() => { + // on cancel + }); +}; + +// 私聊文件:定义为全局变量 +export let privateChatFileMap = new Map(); +// 群聊文件:定义为全局变量 +export let groupChatFileMap = new Map(); diff --git a/src/utils/hooks/useFetchData.ts b/src/utils/hooks/useFetchData.ts new file mode 100644 index 0000000..44b2365 --- /dev/null +++ b/src/utils/hooks/useFetchData.ts @@ -0,0 +1,192 @@ +import type { UnwrapRef } from 'vue' +import { isReactive, isRef, reactive, ref, unref, watch } from 'vue' +import { isEqual } from 'lodash-es' +import type { MaybeRef } from '@/typing' + +export interface PageInfo { + current: number + pageSize: number + total: number + [key: string]: any +} + +export interface ReponseData { + data: T[] + success?: boolean + total?: number + [key: string]: any +} + +export type RequestParams = + | { + pageSize: number + current: number + [key: string]: any + } + | undefined + +export interface UseFetchDataAction> { + stripe: (record: any, index: number) => string | undefined + cancel: () => void + reload: () => Promise + resetPageIndex: () => void + reset: () => void + context: Context + setPageInfo: (pageInfo: Partial) => void +} + +export interface Context> { + current: number + pageSize: number + dataSource: T['data'] + loading: boolean + total: number + requestParams?: MaybeRef<{ + [key: string]: any + }> + [key: string]: any +} + +export const defaultContext: Context = { + stripe: false, + loading: false, + current: 1, + pageSize: 20, + total: 0, + dataSource: [], +} + +// 如果请求数据中,没有分页,仅单列表数据,可以使用该方法进行包装,免去重复写请求方法 +export function wrap(req: () => Promise): (() => Promise>) { + return () => + req().then((res) => { + const data = res + return { + data, + total: data.length, + success: data !== null && data !== undefined, + } + }) +} + +function filterNoValidValue(obj: Record = {}) { + const newObj = {} + Object.keys(obj).forEach((k) => { + if (obj[k] !== undefined && obj[k] !== '' && obj[k] !== null) + newObj[k] = obj[k] + }) + + return newObj +} + +export function useFetchData>(getData: (params?: RequestParams) => Promise, context: MaybeRef<{ + stripe?: boolean + current?: number + pageSize?: number + dataSource?: T['data'] + loading?: boolean + requestParams?: MaybeRef<{ + [key: string]: any + }> + [key: string]: any +}> = reactive({ ...defaultContext }), options?: { + current?: number + pageSize?: number + onLoad?: (dataSource: T['data']) => void + onRequestError?: (e: Error) => void + pagination?: boolean +}): UseFetchDataAction { + const state = reactive({ ...defaultContext } as Context) + const mergeContext = isReactive(context) || isRef(context) ? context : ref(context) + watch( + mergeContext, + () => { + Object.assign(state, unref(context)) + }, + { immediate: true }, + ) + + const fetchList = async () => { + // 请求中禁止重复请求 + if (state.loading) + return + + state.loading = true + const { pageSize = 20, current = 1 } = state + try { + const params: RequestParams + = options?.pagination !== false + ? { + current, + pageSize, + ...filterNoValidValue(unref(mergeContext).requestParams), + } + : undefined + const { data, success, total: dataTotal = 0 } = await getData(params) + state.loading = false + if (success !== false) { + state.dataSource = data as UnwrapRef + state.total = dataTotal + } + } + catch (e: any) { + state.loading = false + // 如果没有传递这个方法的话,需要把错误抛出去,以免吞掉错误 + if (options?.onRequestError === undefined) + throw new Error(e) + + else + options.onRequestError(e) + } + } + + const cancel = () => {} + + const reset = () => {} + + const reload = (): Promise => { + return new Promise((resolve) => { + resolve(fetchList()) + }) + } + + const setPageInfo = (pageInfo: Partial) => { + console.warn('setPageInfo 废弃,请直接使用响应式 context') + pageInfo.current && (state.current = pageInfo.current) + pageInfo.pageSize && (state.pageSize = pageInfo.pageSize) + } + + const resetPageIndex = (): void => { + console.warn('resetPageIndex 废弃,请直接使用响应式 context') + // state.current = 1; + } + watch( + [() => state.current, () => state.pageSize, () => ({ ...state.requestParams })], + (nextValue, preValue) => { + if (!isEqual(nextValue, preValue)) { + fetchList().catch((e) => { + throw new Error(e) + }) + } + }, + { immediate: true, deep: true }, + ) + + const stripe = (_: any, index: number) => index % 2 === 1 && state.stripe && 'ant-pro-table-row-striped' + + return { + stripe, + cancel, + reset, + reload, + resetPageIndex, + setPageInfo: (info) => { + setPageInfo({ + current: state.current, + pageSize: state.pageSize, + ...info, + }) + }, + context: state, + } +} diff --git a/src/utils/hooks/useSendMessage.js b/src/utils/hooks/useSendMessage.js new file mode 100644 index 0000000..1a7f59c --- /dev/null +++ b/src/utils/hooks/useSendMessage.js @@ -0,0 +1,95 @@ +import useConversationStore from "@/stores/modules/conversation"; +import { useMessageStore } from "@/stores/modules/message"; +import { useUserStore } from "@/stores/user"; +import emitter from "@/utils/events"; +import { getSDK } from '@/utils/openIM' +import { MessageStatus } from '@/utils/constant'; +const IMSDK = getSDK() + +const messageStore = useMessageStore(); +const conversationStore = useConversationStore(); +const userStore = useUserStore(); + +export default function useSendMessage() { + const sendMessage = async ({ + recvID, + groupID, + message, + // fileArrayBuffer, + // snpFileArrayBuffer, + needOpreateMessage, + }) => { + needOpreateMessage = + needOpreateMessage ?? inCurrentConversation(recvID || groupID); + if (needOpreateMessage) { + messageStore.pushNewMessage(message); + emitter.emit("CHAT_MAIN_SCROLL_TO_BOTTOM", false); + updateFrequentContacts(); + } + + // let funcName = "sendMessage"; + // if (FileMessageTypes.includes(message.contentType)) { + // funcName = fileArrayBuffer ? "sendMessageByBuffer" : "sendMessageNotOss"; + // } + const options = { + recvID: recvID ?? conversationStore.storeCurrentConversation.userID ?? "", + groupID: + groupID ?? conversationStore.storeCurrentConversation.groupID ?? "", + message, + // fileArrayBuffer, + // snpFileArrayBuffer, + }; + try { + // @ts-ignore + const { data: successMessage } = await IMSDK.sendMessage(options); + messageStore.updateOneMessage(successMessage, true); + } catch (error) { + console.error(error); + + messageStore.updateOneMessage({ + ...message, + status: MessageStatus.Failed, + }); + } + }; + + const updateFrequentContacts = () => { + if (!conversationStore.storeCurrentConversation.userID) { + return; + } + let myFrequentContacts = []; + let totalFrequentContacts = {}; + const item = { + userID: conversationStore.storeCurrentConversation.userID, + nickname: conversationStore.storeCurrentConversation.showName, + faceURL: conversationStore.storeCurrentConversation.faceURL, + }; + try { + totalFrequentContacts = JSON.parse( + localStorage.getItem("IMFrequentContacts_H5") || "{}" + ); + myFrequentContacts = + totalFrequentContacts[userStore.storeSelfInfo.userID] ?? []; + } catch (error) {} + const user = myFrequentContacts.find( + (contact) => contact.userID === item.userID + ); + if (user) return; + myFrequentContacts.unshift({ ...item }); + totalFrequentContacts[userStore.storeSelfInfo.userID] = myFrequentContacts; + localStorage.setItem( + "IMFrequentContacts_H5", + JSON.stringify(totalFrequentContacts) + ); + }; + + const inCurrentConversation = (sourceID) => + sourceID + ? conversationStore.storeCurrentConversation.userID === sourceID || + conversationStore.storeCurrentConversation.groupID === sourceID + : true; + + return { + sendMessage, + }; +} diff --git a/src/utils/imCommon.js b/src/utils/imCommon.js new file mode 100644 index 0000000..b05a368 --- /dev/null +++ b/src/utils/imCommon.js @@ -0,0 +1,457 @@ +import { getSDK } from "./openIM/index.js"; +export const IMSDK = getSDK('./openIM.wasm.gzip') +// export const IMSDK = getSDK('https://openim-priv-tmp.oss-cn-shenzhen.aliyuncs.com/openim/SDK/PROD/862ac9f5179f4173de3fc4952cb0ddf2.gzip') +import { GroupSystemMessageTypes } from "@/utils/constant"; +import dayjs from "dayjs"; +// import { useContactStore } from "@/stores/modules/contact"; +import { useContactStore } from "@/stores/contact"; +import useConversationStore from "@/stores/modules/conversation"; +import { useUserStore } from "@/stores/user"; +import { showToast,showConfirmDialog } from "vant"; +import { useRoute } from 'vue-router'; // 导入 useRoute 钩子 +import { i18n } from "@/lang/index"; +const { t } = i18n.global + +import calendar from "dayjs/plugin/calendar"; +import relativeTime from "dayjs/plugin/relativeTime"; +import updateLocale from "dayjs/plugin/updateLocale"; + +import api from '@/api' + +export const AddFriendQrCodePrefix = "io.openim.app/addFriend/"; +export const AddGroupQrCodePrefix = "io.openim.app/joinGroup/"; + +export const tipMessaggeFormat = (msg) => { + const userStore = useUserStore(); + + const selfID = userStore.selfInfo.userID; + + const getName = (user) => { + return user.userID === selfID ? t("you") : user.nickname; + }; + + switch (msg.contentType) { + case MessageType.FriendAdded: + return t("notificationTipMessage.alreadyFriendMessage"); + case MessageType.GroupCreated: + const groupCreatedDetail = JSON.parse(msg.notificationElem.detail); + const groupCreatedUser = groupCreatedDetail.opUser; + return t("notificationTipMessage.createGroupMessage", { + creator: linkWrap({ + userID: groupCreatedUser.userID, + groupID: msg.groupID, + name: getName(groupCreatedUser), + }), + }); + case MessageType.GroupInfoUpdated: + const groupUpdateDetail = JSON.parse(msg.notificationElem.detail); + const groupUpdateUser = groupUpdateDetail.opUser; + return t("notificationTipMessage.updateGroupAnnouncementMessage", { + operator: linkWrap({ + userID: groupCreatedUser.userID, + groupID: msg.groupID, + name: getName(groupUpdateUser), + }), + }); + case MessageType.GroupOwnerTransferred: + const transferDetails = JSON.parse(msg.notificationElem.detail); + const transferOpUser = transferDetails.opUser; + const newOwner = transferDetails.newGroupOwner; + return t("notificationTipMessage.transferGroupMessage", { + owner: linkWrap({ + userID: transferOpUser.userID, + groupID: msg.groupID, + name: getName(transferOpUser), + }), + newOwner: linkWrap({ + userID: newOwner.userID, + groupID: msg.groupID, + name: getName(newOwner), + }), + }); + case MessageType.MemberQuit: + const quitDetails = JSON.parse(msg.notificationElem.detail); + const quitUser = quitDetails.quitUser; + return t("notificationTipMessage.quitGroupMessage", { + name: linkWrap({ + userID: quitUser.userID, + groupID: msg.groupID, + name: getName(quitUser), + }), + }); + case MessageType.MemberInvited: + const inviteDetails = JSON.parse(msg.notificationElem.detail); + const inviteOpUser = inviteDetails.opUser; + const invitedUserList = inviteDetails.invitedUserList ?? []; + let inviteStr = ""; + invitedUserList.slice(0, 3).map( + (user) => + (inviteStr += `${linkWrap({ + userID: user.userID, + groupID: msg.groupID, + name: getName(user), + })}、`) + ); + inviteStr = inviteStr.slice(0, -1); + return t("notificationTipMessage.invitedToGroupMessage", { + operator: linkWrap({ + userID: inviteOpUser.userID, + groupID: msg.groupID, + name: getName(inviteOpUser), + }), + invitedUser: `${inviteStr}${ + invitedUserList.length > 3 + ? `${t("messageDescription.somePerson", { + num: invitedUserList.length, + })}` + : "" + }`, + }); + case MessageType.MemberKicked: + const kickDetails = JSON.parse(msg.notificationElem.detail); + const kickOpUser = kickDetails.opUser; + const kickdUserList = kickDetails.kickedUserList ?? []; + let kickStr = ""; + kickdUserList.slice(0, 3).map( + (user) => + (kickStr += `${linkWrap({ + userID: user.userID, + groupID: msg.groupID, + name: getName(user), + })}、`) + ); + kickStr = kickStr.slice(0, -1); + return t("messageDescription.kickInGroupMessage", { + operator: linkWrap({ + userID: kickOpUser.userID, + groupID: msg.groupID, + name: getName(kickOpUser), + }), + kickedUser: `${kickStr}${ + kickdUserList.length > 3 + ? `${t("messageDescription.somePerson", { + num: kickdUserList.length, + })}` + : "" + }`, + }); + case MessageType.MemberEnter: + const enterDetails = JSON.parse(msg.notificationElem.detail); + const enterUser = enterDetails.entrantUser; + return t("notificationTipMessage.joinGroupMessage", { + name: linkWrap({ + userID: enterUser.userID, + groupID: msg.groupID, + name: getName(enterUser), + }), + }); + case MessageType.GroupDismissed: + const dismissDetails = JSON.parse(msg.notificationElem.detail); + const dismissUser = dismissDetails.opUser; + return t("notificationTipMessage.disbanedGroupMessage", { + operator: linkWrap({ + userID: dismissUser.userID, + groupID: msg.groupID, + name: getName(dismissUser), + }), + }); + case MessageType.GroupNameUpdated: + const groupNameDetails = JSON.parse(msg.notificationElem.detail); + return t("messageDescription.updateGroupNameMessage", { + operator: linkWrap({ + userID: groupNameDetails.opUser.userID, + groupID: msg.groupID, + name: getName(groupNameDetails.opUser), + }), + name: groupNameDetails.group.groupName, + }); + default: + return ""; + } +}; + +export const formatConversionTime = (timestemp) => { + if (!timestemp) return ""; + + const fromNowStr = dayjs(timestemp).fromNow(); + + if (fromNowStr.includes(t("date.seconds"))) { + return t("date.justNow"); + } + + if ( + !fromNowStr.includes(t("date.seconds")) && + !fromNowStr.includes(t("date.minutes")) + ) { + return dayjs(timestemp).calendar(); + } + + return fromNowStr; +}; + +const linkWrap = ({ + userID, + name, + groupID, +}) => { + return `${name}`; +}; + +export const parseBr = (text) => { + return text + .replace(new RegExp("\\n", "g"), "
") + .replace(new RegExp("\n", "g"), "
"); +}; + +export const getCleanText = (text) => { + return text.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, ""); +}; + +export const formatMessageByType = (message) => { + const userStore = useUserStore(); + const selfUserID = userStore.storeSelfInfo.userID; + + const isSelf = (id) => id === userStore.storeSelfInfo.userID; + const getName = (user) => { + return user.userID === selfUserID ? t("you") : user.nickname; + }; + + switch (message.contentType) { + case MessageType.TextMessage: + return message.textElem?.content; + case MessageType.PictureMessage: + return t("messageDescription.imageMessage"); + case MessageType.VideoMessage: + return t("messageDescription.videoMessage"); + case MessageType.FriendAdded: + return t("messageDescription.alreadyFriendMessage"); + case MessageType.MemberEnter: + const enterDetails = JSON.parse(message.notificationElem.detail); + const enterUser = enterDetails.entrantUser; + return t("messageDescription.joinGroupMessage", { + name: getName(enterUser), + }); + case MessageType.GroupCreated: + const groupCreatedDetail = JSON.parse(message.notificationElem.detail); + const groupCreatedUser = groupCreatedDetail.opUser; + return t("messageDescription.createGroupMessage", { + creator: getName(groupCreatedUser), + }); + case MessageType.MemberInvited: + const inviteDetails = JSON.parse(message.notificationElem.detail); + const inviteOpUser = inviteDetails.opUser; + const invitedUserList = inviteDetails.invitedUserList ?? []; + let inviteStr = ""; + invitedUserList + .slice(0, 3) + .map((user) => (inviteStr += `${getName(user)}、`)); + inviteStr = inviteStr.slice(0, -1); + return t("notificationTipMessage.invitedToGroupMessage", { + operator: getName(inviteOpUser), + invitedUser: `${inviteStr}${ + invitedUserList.length > 3 + ? `${t("messageDescription.somePerson", { + num: invitedUserList.length, + })}` + : "" + }`, + }); + case MessageType.MemberKicked: + const kickDetails = JSON.parse(message.notificationElem.detail); + const kickOpUser = kickDetails.opUser; + const kickdUserList = kickDetails.kickedUserList ?? []; + let kickStr = ""; + kickdUserList + .slice(0, 3) + .map((user) => (kickStr += `${getName(user)}、`)); + kickStr = kickStr.slice(0, -1); + return t("messageDescription.kickInGroupMessage", { + operator: getName(kickOpUser), + kickedUser: `${kickStr}${ + kickdUserList.length > 3 + ? `${t("messageDescription.somePerson", { + num: kickdUserList.length, + })}` + : "" + }`, + }); + case MessageType.MemberQuit: + const quitDetails = JSON.parse(message.notificationElem.detail); + const quitUser = quitDetails.quitUser; + return t("messageDescription.quitGroupMessage", { + name: getName(quitUser), + }); + case MessageType.GroupInfoUpdated: + const groupUpdateDetail = JSON.parse(message.notificationElem.detail); + const groupUpdateUser = groupUpdateDetail.opUser; + return t("messageDescription.updateGroupInfoMessage", { + operator: getName(groupUpdateUser), + }); + case MessageType.GroupOwnerTransferred: + const transferDetails = JSON.parse(message.notificationElem.detail); + const transferOpUser = transferDetails.opUser; + const newOwner = transferDetails.newGroupOwner; + return t("messageDescription.transferGroupMessage", { + owner: getName(transferOpUser), + newOwner: getName(newOwner), + }); + case MessageType.GroupDismissed: + const dismissDetails = JSON.parse(message.notificationElem.detail); + const dismissUser = dismissDetails.opUser; + return t("messageDescription.disbanedGroupMessage", { + operator: getName(dismissUser), + }); + case MessageType.GroupNameUpdated: + const groupNameDetails = JSON.parse(message.notificationElem.detail); + return t("messageDescription.updateGroupNameMessage", { + operator: getName(groupNameDetails.opUser), + name: groupNameDetails.group.groupName, + }); + default: + return ""; + } +}; + +export const initStore = async () => { + const userStore = useUserStore(); + const conversationStore = useConversationStore(); + const contactStore = useContactStore(); + // userStore.getSelfInfoFromReq(); + conversationStore.getConversationListFromReq(); + conversationStore.getUnReadCountFromReq(); + contactStore.getContactListFromReq(); + // await getUserInfo() +// contactStore.getBlackListFromReq(); +// contactStore.getGroupListFromReq(); +// contactStore.getRecvFriendApplicationListFromReq(); +// contactStore.getSendFriendApplicationListFromReq(); +// contactStore.getRecvGroupApplicationListFromReq(); +// contactStore.getSendGroupApplicationListFromReq(); +}; + +export const conversationSort = (conversationList) => { + const arr = []; + const filterArr = conversationList.filter( + (c) => !arr.includes(c.conversationID) && arr.push(c.conversationID) + ); + filterArr.sort((a, b) => { + if (a.isPinned === b.isPinned) { + const aCompare = + a.draftTextTime > a.latestMsgSendTime + ? a.draftTextTime + : a.latestMsgSendTime; + const bCompare = + b.draftTextTime > b.latestMsgSendTime + ? b.draftTextTime + : b.latestMsgSendTime; + if (aCompare > bCompare) { + return -1; + } else if (aCompare < bCompare) { + return 1; + } else { + return 0; + } + } else if (a.isPinned && !b.isPinned) { + return -1; + } else { + return 1; + } + }); + return filterArr; +}; + +export const getFrequentContacts = () => { + const userStore = useUserStore(); + const currentUser = userStore.storeSelfInfo.userID; + let myFrequentContacts = []; + try { + const totalFrequentContacts = JSON.parse( + localStorage.getItem("IMFrequentContacts_H5") + ); + myFrequentContacts = totalFrequentContacts[currentUser] ?? []; + } catch (error) {} + return myFrequentContacts; +}; + +export const getConversationContent = (message) => { + const userStore = useUserStore(); + if ( + !message.groupID || + GroupSystemMessageTypes.includes(message.contentType) || + message.sendID === userStore.storeSelfInfo.userID || + message.contentType === MessageType.GroupAnnouncementUpdated + ) { + return formatMessageByType(message); + } + return `${message.senderNickname}:${formatMessageByType(message)}`; +}; + +const regex = + /\b(https?:\/\/)?((?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9](?:\.[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]+)*\.[a-zA-Z]{2,})(\/[^\s]*)?\b/g; + +export const formatLink = (content) => + content.replace(regex, (match) => { + let href = match; + if (!match.match(/^https?:\/\//)) { + href = "https://" + match; + } + return `
${match}`; + }); + +export const getUserInfo = () => new Promise(async (resolve, reject) => { + const userStore = useUserStore(); + const { setUserInfo } = userStore; + const userRes = await api.login.userInfo(); + if (userRes.code !== 0) reject(userRes.msg); + setUserInfo(userRes.data); + resolve(userRes.data); +}) + +// export const getConfigInfo = async () => { +// const userStore = useUserStore(); +// const { setAppConfig,appConfig } = userStore; +// const configRes = await api.login.getConfigInfo(); +// console.error('==========configRes',configRes); +// if (configRes.code !== 0) return +// setAppConfig({ +// ...appConfig, +// ...configRes.data, +// }); +// } +export const getConfigInfo = async () => { + const userStore = useUserStore(); + const route = useRoute(); // 获取当前路由信息 + const { setAppConfig, appConfig } = userStore; + try { + const configRes = await api.login.getConfigInfo(); + if (configRes.code !== 0) return + setAppConfig({ + ...appConfig, + ...configRes.data, + }); + return configRes + } catch (error) { + // 捕捉和处理错误 + console.error('Error getConfigInfo:', error); + // 这里是异步所以需要添加定时器 + setTimeout(() => { + console.error('rou================',route.path); + // 仅在路由是 /firstScreen 或 /login 时弹框 + if (route.path === '/firstScreen' || route.path === '/login') { + showConfirmDialog({ + title: t("dialog_tip1"), + message: t("error_http_17"), + confirmButtonText: t("refreshing"), + }) + .then(() => { + location.reload(); + }) + .catch(() => { + console.error('取消'); + }); + } + }, 300); + } +}; diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..88331c2 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,95 @@ +import CryptoJS from "crypto-js"; +export const phoneReg = (phone) => { + let reg = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/ + return reg.test(phone) +} +export const emailReg = (email) => { + let reg = /^([a-zA-Z0-9_\.\-])+\@([a-zA-Z0-9\-])+\.[a-zA-Z]{2,6}$/ + return reg.test(email) +} + +//加密方法 +export const encrypt = (word) => { + var key = CryptoJS.enc.Utf8.parse("thisis32bitlongpassphraseimgsda8"); //十六位十六进制数作为秘钥 + var iv = CryptoJS.enc.Utf8.parse("0123456789abcdef");//十六位十六进制数作为秘钥偏移量 + var srcs = CryptoJS.enc.Utf8.parse(word); + var encrypted = CryptoJS.AES.encrypt(srcs, key, { + iv: iv, + }); + return CryptoJS.enc.Base64.stringify(encrypted.ciphertext); +} + +export const checkIsSafari = () => + /^((?!chrome|android).)*safari/i.test(navigator.userAgent) && + /iPad|iPhone|iPod/.test(navigator.userAgent); + +export const getCleanText = (text) => { + return text.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, ""); +}; + + +/* +* +* 处理禁言时间格式化 +* @param {Number} seconds 秒 +* +* **/ +export const handle_Prohibition_time = seconds => { + if (!seconds) return '' + const toMinutes = num => num / 60 + const toHours = num => toMinutes(num) / 60 + const toDays = num => toHours(num) / 60 + const day = toDays(seconds) + const hour = toHours(seconds) + const minutes = toMinutes(seconds) + if (day >= 1) { + return day + t('day') + } else if (hour >= 1) { + return hour % 24 === 0 ? `${hour / 24}${t('day')}` : hour + t('hours') + } else if (minutes >= 1) { + return minutes + t('minute') + } + } + +/** + * 将颜色值转换为 CSS 背景色样式 + * + * @param color 颜色值,多个颜色值之间用分号隔开 + * @param onlyValue 是否只返回颜色值(不包括 CSS 属性),默认为 false + * @returns 返回 CSS 背景色样式 + */ +export const color2bgCCss = (color, onlyValue = false) => { + if (!color) { + return onlyValue ? '' : 'background-color: transparent' + } + const cls = color.split(';').filter(item => !!item).map(item => item.startsWith('#') ? item : `#${item}`) + if (cls.length === 1) { + const c = cls[0] + return onlyValue ? c : `background-color: ${c}` + } + const val = `linear-gradient(135deg, ${cls.join(',')})` + return onlyValue ? val : `background: linear-gradient(135deg, ${cls.join(',')})` +} + +export const isValidCssColor = (colorString) => { + if (!colorString) return false + const cssColorRegex = /^(#([A-Fa-f0-9]{3}){1,2}|(rgb|rgba)\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*([01]|0?\.\d+))?\s*\)|[a-zA-Z]+)$/; + + return cssColorRegex.test(colorString); +} + +// 设置全局变量 +export const setGlobalProperty = (path, alpha) => { + if (path) { + document.documentElement.style.setProperty('--preview-bg', `url(${path})`) + } else { + document.documentElement.style.removeProperty('--preview-bg') + } + document.documentElement.style.setProperty('--preview-bg-alpha', `opacity(${alpha || 100}%)`) +} + +// 删除全局变量 +export const clearGlobalProperty = () => { + document.documentElement.style.removeProperty('--preview-bg') + document.documentElement.style.removeProperty('--preview-bg-alpha') +} diff --git a/src/utils/levelConfig.js b/src/utils/levelConfig.js new file mode 100644 index 0000000..41060fb --- /dev/null +++ b/src/utils/levelConfig.js @@ -0,0 +1,96 @@ +import { onMounted, ref,nextTick ,onUnmounted} from 'vue' +import BScroll from 'better-scroll' +import { useRouter } from 'vue-router' +// import path from "node:path"; +// import Router from "@/router/index.js"; +/** + *设置路由级别 + * @param {any} el_ref 当前组件根节点ref + * @param {Number} level 当前页面级别 ,有:1 2 3 4 5 + * @param {Boolean} isScroll 当前页面是否滚动(未开发) + */ +export function useGrade(el_ref, level,isScroll=false,callback) { + const router = useRouter() + const el_overlay = document.createElement('div') + + onMounted(async ()=>{ + //设置不同级别 + switch (level) { + case 1: + el_ref.value.classList.add('firstly_view') + break + case 2: + el_ref.value.classList.add('secondary_view') + break + case 3: + console.log('我是三级页面') + el_ref.value.classList.add('thirdly_view') + el_ref.value.parentNode.insertBefore(el_overlay,el_ref.value) + el_overlay.classList.add('thirdly_view_overlay')//添加背景模态框 + await nextTick() + //点击返回二级菜单 + el_overlay.addEventListener('click',e=>{ + let path = router.currentRoute.value.path.split('/').slice(0,4).join('/') + //判断是否开启过四级页面 + if(el_ref.value.querySelector('.fourth_view')){ + router.push(path) + }else{ + if(callback){ + callback() + }else{ + router.push(path) + } + el_overlay.remove() + } + }) + break + case 4: + if (el_ref.value)el_ref.value.classList.add('fourth_view') + break + case 5: + el_ref.value.classList.add('fifth_view') + break + default: + throw new Error(`${level}级别不存在`) + } + await nextTick() + // if (isScroll){ + // new BScroll(el_ref.value, { + // scrollY: true, + // click: true + // }) + // } + }) + onUnmounted(()=>{ + //层级为3 去掉遮掩层 + if (level === 3){ + el_overlay.remove() + } + }) +} + +/** + * 根据不同屏幕宽度 pc同时展示三级与四级页面 移动跳转三级页面 + * @param {String} thirdly_view_path 三级页面对应地址 格式: /home/my/account/info + * @Param {String} fourth_view_path 4级页面对应地址 格式:/home/my/account/info/modify_name + * @return {function} + * **/ +export function useToBelow(){ + + const router = useRouter() + return (thirdly_view_path,fourth_view_path)=>{ + + if (!fourth_view_path){ + fourth_view_path = thirdly_view_path + } + + const current_width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; + + if (current_width>= 750){ + router.push(fourth_view_path) + }else { + router.push(thirdly_view_path) + } + } + +} diff --git a/src/utils/local-storage.js b/src/utils/local-storage.js new file mode 100644 index 0000000..2490082 --- /dev/null +++ b/src/utils/local-storage.js @@ -0,0 +1,9 @@ +import store from 'store' +import expirePlugin from 'store/plugins/expire' + +// plugin usage: +store.addPlugin(expirePlugin) + +export { store as localStorage } + +export default store diff --git a/src/utils/messageTimeDeleteHelper.js b/src/utils/messageTimeDeleteHelper.js new file mode 100644 index 0000000..a005371 --- /dev/null +++ b/src/utils/messageTimeDeleteHelper.js @@ -0,0 +1,141 @@ +import api from '@/api'; +import { getSDK } from '@/utils/openIM/sdk/index.js'; + +class MessageTimeDeleteHelper { + constructor() { + this.OpenIM = getSDK(); + + + // 定时删除会话处理 + + // 配置保存时长,单位:毫秒 + this.msgSaveTime = 0; + // 0 表示不限制消息的时间,单位:毫秒 + this.showMsgEndTime = 0; + //可以显示的消息最后时长,单位:毫秒 + this.lastMsgShowTime = 0; + this.conversationID = ""; + this.singleChat = false; + this.doing = false; + } + + // 单聊-检测删除消息-计算最后一条能显示消息的时间 + updateSingleCheck(privateChatConfig, deleteLocal = false, currentServerTime) { + // console.log('updateSingleCheck', privateChatConfig, deleteLocal, currentServerTime); + // 获取单聊会话的扩展字段 ex + let conversationEx = JSON.parse(privateChatConfig.ex || '{}') + // 上次删除时间点lastMsgDestructTimeNote + let newLastMsgShowTime = conversationEx?.lastMsgDestructTimeNote * 1000; + // 判断是否需要删除 + if(newLastMsgShowTime == this.lastMsgShowTime) return 0 + // 标记为单聊 + this.singleChat = true; + // 获取会话的保存时间 + this.msgSaveTime = (conversationEx?.msgDestructTime || 0) * 1000 + // 上次删除时间点lastMsgDestructTimeNote + this.lastMsgShowTime = (conversationEx?.lastMsgDestructTimeNote || 0) * 1000 + // 获取会话ID + this.conversationID = privateChatConfig?.conversationID || '' + // 获取当前时间 + const curTime = new Date().getTime(); + // 计算最后一条消息的展示时间点 + this.showMsgEndTime = this.getEndTime(curTime) + // 删除本地消息 + if (deleteLocal) { + let code = this.deleteLocalMessage(currentServerTime); + return code; + } + return 0; + } + + // 群聊-检测删除消息-计算最后一条能显示消息的时间 + updateGroupCheck(groupChatConfig, deleteLocal = false, currentServerTime) { + // console.log('updateGroupCheck', groupChatConfig, deleteLocal, currentServerTime); + // 获取群聊会话的扩展字段 ex + let conversationEx = JSON.parse(groupChatConfig.ex || '{}') + // 上次删除时间点lastMsgDestructTimeNote + let newLastMsgShowTime = conversationEx?.lastMsgDestructTimeNote * 1000; + // 判断是否需要删除 + if(newLastMsgShowTime == this.lastMsgShowTime) return 0 + // 标记为群聊 + this.singleChat = false + // 获取会话的保存时间 + this.msgSaveTime = (conversationEx?.msgDestructTime || 0) * 1000 + // 上次删除时间点lastMsgDestructTimeNote + this.lastMsgShowTime = (conversationEx?.lastMsgDestructTimeNote || 0) * 1000 + // 获取会话ID + this.conversationID = groupChatConfig?.conversationID || '' + // 获取当前时间 + const curTime = new Date().getTime(); + // 计算最后一条消息的展示时间点 + this.showMsgEndTime = this.getEndTime(curTime) + // 删除本地消息 + if (deleteLocal) { + let code = this.deleteLocalMessage(currentServerTime); + return code; + } + return 0; + } + + // 检查消息列表是否有过期的消息,并且会移除list里面的过期消息 + refactorMessageList(list) { + const result = list.filter((item) => { + if (item.sendTime != null && item.sendTime <= this.showMsgEndTime) { + return false + } + return true + }) + return result; + } + + // 删除本地数据库过期的消息。需要取服务器时间为准取计算最后一条可以显示消息的时间 + async deleteLocalMessage(currentServerTime) { + if(!this.conversationID) return -1 + if (this.msgSaveTime <= 0 && this.lastMsgShowTime <= 0) return -2 + if (this.doing) return -3; + this.doing = true + try { + let curTime = currentServerTime || 0 + if (curTime <= 0) { + // 获取当前服务器时间 + const resp = await api.contact.getCurrentTime(); + if (resp.code !== 0) return -4 + curTime = resp.data?.currentTime || 0 + if (curTime <= 0) return -5 + } + // 最后一条消息的保留时间点 + const endTime = this.getEndTime(curTime) + if (!this.conversationID || endTime <= 0) return -6; + // 单聊删除阅读即焚消息 + if(this.singleChat) { + //删除阅读即焚消息 + const burnMsgRes = await api.contact.burnMsgByClearMsg({ + conversationID: this.conversationID, + endTime + }); + if(burnMsgRes.errCode !== 0) return -7 + } + //删除sendTime< endTime的所有消息 + console.log('clearConversationAndDeleteAllMsgByEndTime-----------------------',endTime,this.conversationID); + await this.OpenIM.clearConversationAndDeleteAllMsgByEndTime({ + conversationID: this.conversationID, + endTime + }); + //通知消息清除 + // MsgTimeDeleteMessage(conversationID!, endTime) + } catch (e) { + console.log("deleteLocalMessageError", e); + } finally { + this.doing = false; + } + return 0; + } + + // 计算最后一条可以显示消息的时间 + getEndTime(curMillis) { + let endTime = this.msgSaveTime <= 0 ? this.lastMsgShowTime : Math.max(curMillis - this.msgSaveTime, this.lastMsgShowTime); + return endTime; + } +} + +export default MessageTimeDeleteHelper; \ No newline at end of file diff --git a/src/utils/msgTimeDeleteManager.js b/src/utils/msgTimeDeleteManager.js new file mode 100644 index 0000000..e2bcbd0 --- /dev/null +++ b/src/utils/msgTimeDeleteManager.js @@ -0,0 +1,121 @@ +import api from '@/api' +import { getSDK } from '@/utils/openIM/sdk/index.js' +// import { updateSingleCheck, updateGroupCheck } from './messageTimeDeleteHelper' +import MessageTimeDeleteHelper from "@/utils/messageTimeDeleteHelper"; + +const OpenIM = getSDK() + +///定时删除消息处理逻辑 +///(1) 打开APP 5秒后检查一次(如果当时正在同步中,则再等待5秒,直到同步完成),搜索全部会话信息,删除会话里面的过期消息。 +///(网络异常不处理,app运行期间,非主动设置聊天信息保存时间变化不处理。等待下一次启动APP) +///(2)打开会话时,检查一次当前打开的会话过期信息,具体看:[MessageTimeDeleteHelper]的处理逻辑,不显示过期的消息信息。 + +const IDLE = 0; +const DOING = 1; +const FINISH = 2; +let timer = null +let state = IDLE +///当前服务器时间 +let curServerTime = 0; + +// 同步所有会话信息 +export const syncAllConversation = () => { + if (state == IDLE) { + state = DOING; + if (timer != null) return; + timer = setInterval(() => { + if (isFinished()) return; + // console.log("AppMessageTimeDeleteManager", 'checking') + const token = localStorage.getItem('token'); + // 退出登录 取消定时器 + if(!token){ + cancelMsgTimeDelete() + } + // 同步所有会话信息 根据会话保留时间进行消息删除 + loadCurrentServerTime() + }, 5000); + } +}; +const isFinished = () => state == FINISH +let loading = false; + +const loadCurrentServerTime = async () => { + if(loading || curServerTime > 0) return + // console.log("AppMessageTimeDeleteManager", "loadCurrentServerTime"); + loading = true + //1.获取当前服务器时间 + const resp = await api.contact.getCurrentTime(); + if (resp.code == 0) { + curServerTime = resp.data?.currentTime || 0 + } + console.log("AppMessageTimeDeleteManagercurServerTime", curServerTime); + if (curServerTime > 0) { + cancelTimer() + console.log("AppMessageTimeDeleteManager", "START"); + await checkConversation(); + console.log("AppMessageTimeDeleteManager", "END"); + } + loading = false +} +const checkConversation = async () => { + if (isFinished() || curServerTime <= 0) return; + // console.log("AppMessageTimeDeleteManagerdelete", `delete curTime:${curServerTime}`); + let running = true; + let offset = 0; + let pageSize = 20; + let conversationList = [] + console.log(); + // 拉取所有会话 + while (running) { + const conversationRes = await OpenIM.getConversationListSplit({ + count: pageSize, + offset: offset + }); + offset += pageSize; + // console.log('ConversationRes',conversationRes); + const resList = conversationRes.data || []; + conversationList = [...conversationList, ...resList] + if (resList.length <= 0) { + running = false; + } + } + if(!running){ + if (isFinished()) return; + conversationList.forEach(async (element) => { + if (isFinished()) return; + // 根据userID或groupID判断是单聊还是群聊 + if (element.userID) { + const messageTimeDeleteHelper = new MessageTimeDeleteHelper(); + // 检测单聊消息 删除过期消息 + await messageTimeDeleteHelper.updateSingleCheck(element, true ,curServerTime); + // console.log("AppMessageTimeDeleteManagerupdateSingleCheck",`clear result:${element.conversationID},${element.showName}===${code}`); + } else if (element.groupID) { + // 拉取群聊信息 + const groupsInfoRes = await OpenIM.getSpecifiedGroupsInfo([element.groupID]) + // console.log('groupsInfoRes',groupsInfoRes); + let groupInfo = null; + if(groupsInfoRes && groupsInfoRes.data && groupsInfoRes.data.length > 0) { + groupInfo = groupsInfoRes?.data[0]; + } + if (groupInfo == null || isFinished()) return; + // console.log('updateGroupCheck',groupInfo,element); + const messageTimeDeleteHelper = new MessageTimeDeleteHelper(); + // 检测群聊消息 删除过期消息 + await messageTimeDeleteHelper.updateGroupCheck({...element,...groupInfo}, true, curServerTime); + // console.log("AppMessageTimeDeleteManagerupdateGroupCheck",`clear result:${element.conversationID},${element.showName}===${code}`); + } + }) + } +} +const cancelTimer = () => { + // console.log("AppMessageTimeDeleteManager", '_cancelTimer') + clearInterval(timer); + timer = null; +}; + +export const cancelMsgTimeDelete = () => { + if(timer){ + cancelTimer(timer); + } + state = FINISH; +} \ No newline at end of file diff --git a/src/utils/openIM/api/database/black.d.ts b/src/utils/openIM/api/database/black.d.ts new file mode 100644 index 0000000..698becc --- /dev/null +++ b/src/utils/openIM/api/database/black.d.ts @@ -0,0 +1,7 @@ +export declare function getBlackList(): Promise; +export declare function getBlackListUserID(): Promise; +export declare function getBlackInfoByBlockUserID(blockUserID: string, loginUserID: string): Promise; +export declare function getBlackInfoList(blockUserIDListStr: string): Promise; +export declare function insertBlack(localBlackStr: string): Promise; +export declare function deleteBlack(blockUserID: string, loginUserID: string): Promise; +export declare function updateBlack(localBlackStr: string): Promise; diff --git a/src/utils/openIM/api/database/black.js b/src/utils/openIM/api/database/black.js new file mode 100644 index 0000000..6021a32 --- /dev/null +++ b/src/utils/openIM/api/database/black.js @@ -0,0 +1,88 @@ +import { DatabaseErrorCode } from '../../constant'; +import { getBlackList as databaseGetBlackList, getBlackListUserID as databaseGetBlackListUserID, getBlackInfoByBlockUserID as databaseGetBlackInfoByBlockUserID, getBlackInfoList as databaseGetBlackInfoList, insertBlack as databaseInsertBlack, deleteBlack as databasedeleteBlack, updateBlack as databaseupdateBlack, } from '../../sqls'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { getInstance } from './instance'; +export async function getBlackList() { + try { + const db = await getInstance(); + const execResult = databaseGetBlackList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + block_user_id: 'userID', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getBlackListUserID() { + try { + const db = await getInstance(); + const execResult = databaseGetBlackListUserID(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getBlackInfoByBlockUserID(blockUserID, loginUserID) { + try { + const db = await getInstance(); + const execResult = databaseGetBlackInfoByBlockUserID(db, blockUserID, loginUserID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getBlackInfoList(blockUserIDListStr) { + try { + const db = await getInstance(); + const execResult = databaseGetBlackInfoList(db, JSON.parse(blockUserIDListStr)); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function insertBlack(localBlackStr) { + try { + const db = await getInstance(); + const localBlack = convertToSnakeCaseObject(convertObjectField(JSON.parse(localBlackStr), { + userID: 'block_user_id', + name: 'nickname', + })); + databaseInsertBlack(db, localBlack); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteBlack(blockUserID, loginUserID) { + try { + const db = await getInstance(); + databasedeleteBlack(db, blockUserID, loginUserID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateBlack(localBlackStr) { + try { + const db = await getInstance(); + const localBlack = convertToSnakeCaseObject(convertObjectField(JSON.parse(localBlackStr))); + databaseupdateBlack(db, localBlack); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/conversation.d.ts b/src/utils/openIM/api/database/conversation.d.ts new file mode 100644 index 0000000..05ae413 --- /dev/null +++ b/src/utils/openIM/api/database/conversation.d.ts @@ -0,0 +1,33 @@ +import { ClientConversation } from '../../sqls'; +export declare function getAllConversationList(): Promise; +export declare function getAllConversationListToSync(): Promise; +export declare function getAllSingleConversationIDList(): Promise; +export declare function getAllConversationIDList(): Promise; +export declare function getHiddenConversationList(): Promise; +export declare function getConversation(conversationID: string): Promise; +export declare function getMultipleConversation(conversationIDList: string): Promise; +export declare function updateColumnsConversation(conversationID: string, conversation: ClientConversation | string): Promise; +export declare function decrConversationUnreadCount(conversationID: string, count: number): Promise; +export declare function batchInsertConversationList(conversationListStr: string): Promise; +export declare function insertConversation(conversationStr: string): Promise; +export declare function getTotalUnreadMsgCount(): Promise; +export declare function getConversationByUserID(userID: string): Promise; +export declare function getConversationListSplit(offset: number, count: number): Promise; +export declare function deleteConversation(conversationID: string): Promise; +export declare function updateConversation(conversationStr: string): Promise; +export declare function batchUpdateConversationList(conversationListStr: string): Promise; +export declare function conversationIfExists(conversationID: string): Promise; +export declare function resetConversation(conversationID: string): Promise; +export declare function resetAllConversation(): Promise; +export declare function clearConversation(conversationID: string): Promise; +export declare function clearConversationByTime(conversationID: string, endTime: number): Promise; +export declare function clearAllConversation(): Promise; +export declare function setConversationDraft(conversationID: string, draftText: string): Promise; +export declare function removeConversationDraft(conversationID: string, draftText: string): Promise; +export declare function unPinConversation(conversationID: string, isPinned: number): Promise; +export declare function incrConversationUnreadCount(conversationID: string): Promise; +export declare function setMultipleConversationRecvMsgOpt(conversationIDListStr: string, opt: number): Promise; +export declare function getAllConversations(): Promise; +export declare function batchUpdateConversationsByList(conversationListStr: string): Promise; +export declare function batchUpdateConversationsByMap(conversationsStr: string): Promise; +export declare function batchDeleteConversations(conversationIdsStr: string): Promise; diff --git a/src/utils/openIM/api/database/conversation.js b/src/utils/openIM/api/database/conversation.js new file mode 100644 index 0000000..9f23f6e --- /dev/null +++ b/src/utils/openIM/api/database/conversation.js @@ -0,0 +1,456 @@ +import { DatabaseErrorCode } from '../../constant'; +import { batchInsertConversationList as databaseBatchInsertConversationList, decrConversationUnreadCount as databaseDecrConversationUnreadCount, + getAllConversationList as databaseGetAllConversationList, getAllConversationListToSync as databaseGetAllConversationListToSync, + getConversation as databaseGetConversation, getHiddenConversationList as databaseGetHiddenConversationList, + getAllSingleConversationIDList as databaseGetAllSingleConversationIDList, getAllConversationIDList as databaseGetAllConversationIDList, + updateColumnsConversation as databaseUpdateColumnsConversation, getTotalUnreadMsgCount as databaseGetTotalUnreadMsgCount, + getMultipleConversation as databaseGetMultipleConversation, getConversationByUserID as databaseGetConversationByUserID, + getConversationListSplit as databaseGetConversationListSplit, incrConversationUnreadCount as databaseIncrConversationUnreadCount, + updateConversation as databaseUpdateConversation, deleteConversation as databaseDeleteConversation, batchDeleteConversations as databaseBatchDeleteConversations, + conversationIfExists as databaseConversationIfExists, resetConversation as databaseResetConversation, resetAllConversation as databaseResetAllConversation, + clearConversation as databaseClearConversation, clearConversationByTime as databaseClearConversationByTime, + clearAllConversation as databaseClearAllConversation, setConversationDraft as databaseSetConversationDraft, + removeConversationDraft as databaseRemoveConversationDraft, unPinConversation as databaseUnPinConversation, + batchUpdateConversationsByMap as databaseBatchUpdateConversationsByMap, batchUpdateConversationsByList as databaseBatchUpdateConversationsByList, + setMultipleConversationRecvMsgOpt as databaseSetMultipleConversationRecvMsgOpt, getAllConversations as databaseGetAllConversations, } from '../../sqls'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { getInstance } from './instance'; +export async function getAllConversationList() { + try { + const db = await getInstance(); + const execResult = databaseGetAllConversationList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllConversationListToSync() { + try { + const db = await getInstance(); + const execResult = databaseGetAllConversationListToSync(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllSingleConversationIDList() { + try { + const db = await getInstance(); + const execResult = databaseGetAllSingleConversationIDList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase').map(item => item.conversationID)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllConversationIDList() { + try { + const db = await getInstance(); + const execResult = databaseGetAllConversationIDList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase').map(item => item.conversationID)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getHiddenConversationList() { + try { + const db = await getInstance(); + const execResult = databaseGetHiddenConversationList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getConversation(conversationID) { + try { + const db = await getInstance(); + const execResult = databaseGetConversation(db, conversationID); + if (execResult.length === 0) { + return formatResponse('', DatabaseErrorCode.ErrorNoRecord, `no conversation with id ${conversationID}`); + } + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getMultipleConversation(conversationIDList) { + try { + if(conversationIDList === "null"||conversationIDList === null || conversationIDList === undefined){ + return + } + const db = await getInstance(); + const idList = JSON.parse(conversationIDList); + const execResult = databaseGetMultipleConversation(db, idList); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateColumnsConversation(conversationID, conversation) { + try { + const db = await getInstance(); + let parsedConversation = conversation; + if (typeof conversation === 'string') { + parsedConversation = convertToSnakeCaseObject(convertObjectField(JSON.parse(conversation))); + } + const execResult = databaseUpdateColumnsConversation(db, conversationID, parsedConversation); + const modifed = db.getRowsModified(); + if (modifed === 0) { + throw 'updateColumnsConversation no record updated'; + } + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function decrConversationUnreadCount(conversationID, count) { + try { + const db = await getInstance(); + const execResult = databaseDecrConversationUnreadCount(db, conversationID, count); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchInsertConversationList(conversationListStr) { + try { + const db = await getInstance(); + const conversationList = (JSON.parse(conversationListStr) || []).map((v) => convertToSnakeCaseObject(v)); + if (conversationList.length === 0) { + return formatResponse(''); + } + const execResult = databaseBatchInsertConversationList(db, conversationList); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function insertConversation(conversationStr) { + return batchInsertConversationList(`[${conversationStr}]`); +} +export async function getTotalUnreadMsgCount() { + try { + const db = await getInstance(); + const execResult = databaseGetTotalUnreadMsgCount(db); + return formatResponse(execResult[0]?.values[0]?.[0] ?? 0); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getConversationByUserID(userID) { + try { + const db = await getInstance(); + const execResult = databaseGetConversationByUserID(db, userID); + if (execResult.length === 0) { + return formatResponse('', DatabaseErrorCode.ErrorNoRecord, `no conversation with userID ${userID}`); + } + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getConversationListSplit(offset, count) { + try { + const db = await getInstance(); + const execResult = databaseGetConversationListSplit(db, offset, count); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteConversation(conversationID) { + try { + const db = await getInstance(); + databaseDeleteConversation(db, conversationID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateConversation(conversationStr) { + try { + const db = await getInstance(); + const localConversation = convertToSnakeCaseObject(convertObjectField(JSON.parse(conversationStr))); + databaseUpdateConversation(db, localConversation); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchUpdateConversationList(conversationListStr) { + try { + const db = await getInstance(); + const conversationList = (JSON.parse(conversationListStr) || []).map((v) => convertToSnakeCaseObject(v)); + if (conversationList.length === 0) { + return formatResponse(''); + } + conversationList.forEach(conversation => { + databaseUpdateConversation(db, conversation); + }); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function conversationIfExists(conversationID) { + try { + const db = await getInstance(); + const execResult = databaseConversationIfExists(db, conversationID); + return formatResponse(execResult.length !== 0); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function resetConversation(conversationID) { + try { + const db = await getInstance(); + databaseResetConversation(db, conversationID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function resetAllConversation() { + try { + const db = await getInstance(); + databaseResetAllConversation(db); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function clearConversation(conversationID) { + try { + const db = await getInstance(); + databaseClearConversation(db, conversationID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function clearConversationByTime(conversationID, endTime) { + try { + const db = await getInstance(); + databaseClearConversationByTime(db, conversationID, endTime); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function clearAllConversation() { + try { + const db = await getInstance(); + databaseClearAllConversation(db); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function setConversationDraft(conversationID, draftText) { + try { + const db = await getInstance(); + databaseSetConversationDraft(db, conversationID, draftText); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function removeConversationDraft(conversationID, draftText) { + try { + const db = await getInstance(); + databaseRemoveConversationDraft(db, conversationID, draftText); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function unPinConversation(conversationID, isPinned) { + try { + const db = await getInstance(); + databaseUnPinConversation(db, conversationID, isPinned); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +// export async function updateAllConversation( +// conversationID: string, +// conversation: ClientConversation | string +// ): Promise { +// try { +// const db = await getInstance(); +// let parsedConversation = conversation as ClientConversation; +// if (typeof conversation === 'string') { +// parsedConversation = convertToSnakeCaseObject( +// convertObjectField(JSON.parse(conversation)) +// ) as ClientConversation; +// } +// const execResult = databaseUpdateColumnsConversation( +// db, +// conversationID, +// parsedConversation +// ); +// const modifed = db.getRowsModified(); +// if (modifed === 0) { +// throw 'updateColumnsConversation no record updated'; +// } +// return formatResponse(execResult); +// } catch (e) { +// console.error(e); +// return formatResponse( +// undefined, +// DatabaseErrorCode.ErrorInit, +// JSON.stringify(e) +// ); +// } +// } +export async function incrConversationUnreadCount(conversationID) { + try { + const db = await getInstance(); + const execResult = databaseIncrConversationUnreadCount(db, conversationID); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function setMultipleConversationRecvMsgOpt(conversationIDListStr, opt) { + try { + const db = await getInstance(); + databaseSetMultipleConversationRecvMsgOpt(db, JSON.parse(conversationIDListStr), opt); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllConversations() { + try { + const db = await getInstance(); + const execResult = databaseGetAllConversations(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [ + 'isPinned', + 'isPrivateChat', + 'isNotInGroup', + 'isMsgDestruct', + ])); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} + +export async function batchUpdateConversationsByMap(conversationsJsonStr) { + try { + const db = await getInstance(); + let conversations = JSON.parse(conversationsJsonStr); + const execResult = databaseBatchUpdateConversationsByMap(db, conversations); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} + +export async function batchUpdateConversationsByList(conversationsJsonStr) { + return batchUpdateConversationList(conversationsJsonStr) +} +export async function batchDeleteConversations(conversationsIdsStr) { + try { + const db = await getInstance(); + databaseBatchDeleteConversations(db, JSON.parse(conversationsIdsStr)); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} \ No newline at end of file diff --git a/src/utils/openIM/api/database/friend.d.ts b/src/utils/openIM/api/database/friend.d.ts new file mode 100644 index 0000000..76cf642 --- /dev/null +++ b/src/utils/openIM/api/database/friend.d.ts @@ -0,0 +1,12 @@ +export declare function insertFriend(localFriendStr: string): Promise; +export declare function deleteFriend(friendUserID: string, loginUserID: string): Promise; +export declare function batchDeleteFriends(loginUserID: string, friendUserIds: string): Promise; +export declare function updateFriend(localFriendStr: string): Promise; +export declare function batchUpdateFriends(localFriendsStr: string): Promise; +export declare function getAllFriendList(loginUserID: string): Promise; +export declare function getPageFriendList(offset: number, count: number, loginUserID: string): Promise; +export declare function searchFriendList(key: string, isSearchUserID: boolean, isSearchNickname: boolean, isSearchRemark: boolean): Promise; +export declare function getFriendInfoByFriendUserID(friendUserID: string, loginUserID: string): Promise; +export declare function getFriendInfoList(friendUserIDListStr: string): Promise; +export declare function getFriendInfos(ownerUserID: string, friendUserIds: string): Promise; +export declare function batchInsertFriends(friends: string): Promise; diff --git a/src/utils/openIM/api/database/friend.js b/src/utils/openIM/api/database/friend.js new file mode 100644 index 0000000..db607b7 --- /dev/null +++ b/src/utils/openIM/api/database/friend.js @@ -0,0 +1,188 @@ +import { DatabaseErrorCode } from '../../constant'; +import { insertFriend as databaseInsertFriend, deleteFriend as databasedeleteFriend, + batchDeleteFriends as databaseBatchDeleteFriends, updateFriend as databaseupdateFriend, + getAllFriendList as databaseGetAllFriendList, getPageFriendList as databaseGetPageFriendList, searchFriendList as databasesearchFriendList, + getFriendInfoByFriendUserID as databaseGetFriendInfoByFriendUserID, getFriendInfoList as databaseGetFriendInfoList, + getFriendInfos as databaseGetFriendInfos, batchInsertFriends as databaseBatchInsertFriends + } from '../../sqls'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { getInstance } from './instance'; +export async function insertFriend(localFriendStr) { + try { + const db = await getInstance(); + const localFriend = convertToSnakeCaseObject(convertObjectField(JSON.parse(localFriendStr), { + userID: 'friend_user_id', + nickname: 'name', + })); + databaseInsertFriend(db, localFriend); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteFriend(friendUserID, loginUserID) { + try { + const db = await getInstance(); + databasedeleteFriend(db, friendUserID, loginUserID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchDeleteFriends(loginUserId, friendUserIds) { + try { + const db = await getInstance(); + databaseBatchDeleteFriends(db, loginUserId,JSON.parse(friendUserIds)); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateFriend(localFriendStr) { + try { + const db = await getInstance(); + const localFriend = convertToSnakeCaseObject(convertObjectField(JSON.parse(localFriendStr), { + userID: 'friend_user_id', + nickname: 'name', + })); + databaseupdateFriend(db, localFriend); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchUpdateFriends(localFriendsStr) { + try { + const db = await getInstance(); + const localFriends = convertToSnakeCaseObject(convertObjectField(JSON.parse(localFriendsStr), { + userID: 'friend_user_id', + nickname: 'name', + })); + localFriends.forEach(function (item){ + databaseupdateFriend(db, item); + }) + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllFriendList(loginUserID) { + try { + const db = await getInstance(); + const execResult = databaseGetAllFriendList(db, loginUserID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + friend_user_id: 'userID', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getPageFriendList(offset, count, loginUserID) { + try { + const db = await getInstance(); + const execResult = databaseGetPageFriendList(db, offset, count, loginUserID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + friend_user_id: 'userID', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function searchFriendList(key, isSearchUserID, isSearchNickname, isSearchRemark) { + try { + const db = await getInstance(); + const execResult = databasesearchFriendList(db, key, isSearchUserID, isSearchNickname, isSearchRemark); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + friend_user_id: 'userID', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getFriendInfoByFriendUserID(friendUserID, loginUserID) { + try { + const db = await getInstance(); + const execResult = databaseGetFriendInfoByFriendUserID(db, friendUserID, loginUserID); + if (execResult.length === 0) { + return formatResponse('', DatabaseErrorCode.ErrorNoRecord, `no friend with id ${friendUserID}`); + } + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + friend_user_id: 'userID', + })[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getFriendInfoList(friendUserIDListStr) { + try { + const db = await getInstance(); + const execResult = databaseGetFriendInfoList(db, JSON.parse(friendUserIDListStr)); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + friend_user_id: 'userID', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} + +export async function getFriendInfos(ownerUserID,friendUserIds) { + try { + const db = await getInstance(); + const execResult = databaseGetFriendInfos(db, ownerUserID, JSON.parse(friendUserIds)); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + friend_user_id: 'userID', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} + +export async function batchInsertFriends(friendJsonStr) { + try { + const db = await getInstance(); + let arr = JSON.parse(friendJsonStr) + let friends = new Array() + arr.forEach(element => { + const localFriend = convertToSnakeCaseObject(convertObjectField(element, { + userID: 'friend_user_id', + nickname: 'name', + })); + friends.push(localFriend) + }); + + databaseBatchInsertFriends(db, friends); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} \ No newline at end of file diff --git a/src/utils/openIM/api/database/friendRequest.d.ts b/src/utils/openIM/api/database/friendRequest.d.ts new file mode 100644 index 0000000..39551e7 --- /dev/null +++ b/src/utils/openIM/api/database/friendRequest.d.ts @@ -0,0 +1,10 @@ +export declare function insertFriendRequest(localFriendRequestStr: string): Promise; +export declare function batchInsertFriendRequests(localFriendRequestsStr: string): Promise; +export declare function deleteFriendRequestBothUserID(fromUserID: string, toUserID: string): Promise; +export declare function batchDeleteFriendRequests(toUserID: string, fromUserIds: string): Promise; +export declare function updateFriendRequest(localFriendRequestStr: string): Promise; +export declare function batchUpdateFriendRequests(localFriendRequestsStr: string): Promise; +export declare function getRecvFriendApplication(loginUserID: string): Promise; +export declare function getSendFriendApplication(fromUserId: string): Promise; +export declare function getFriendApplicationByBothID(fromUserID: string, toUserID: boolean): Promise; +export declare function getBothFriendReq(fromUserID: string, toUserID: boolean): Promise; diff --git a/src/utils/openIM/api/database/friendRequest.js b/src/utils/openIM/api/database/friendRequest.js new file mode 100644 index 0000000..dfcbf1b --- /dev/null +++ b/src/utils/openIM/api/database/friendRequest.js @@ -0,0 +1,134 @@ +import { DatabaseErrorCode } from '../../constant'; +import { insertFriendRequest as databaseInsertFriendRequest, deleteFriendRequestBothUserID as databasedeleteFriendRequestBothUserID, + updateFriendRequest as databaseupdateFriendRequest, getRecvFriendApplication as databaseGetRecvFriendApplication, + getSendFriendApplication as databaseGetSendFriendApplication, getFriendApplicationByBothID as databaseGetFriendApplicationByBothID, + getBothFriendReq as databaseGetBothFriendReq,batchDeleteFriendRequests as databaseBatchDeleteFriendRequests, + batchInsertFriendRequests as databaseBatchInsertFriendRequests } from '../../sqls'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { getInstance } from './instance'; +export async function insertFriendRequest(localFriendRequestStr) { + try { + const db = await getInstance(); + const localFriendRequest = convertToSnakeCaseObject(convertObjectField(JSON.parse(localFriendRequestStr))); + databaseInsertFriendRequest(db, localFriendRequest); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchInsertFriendRequests(localFriendRequestsStr) { + try { + const db = await getInstance(); + var arr = JSON.parse(localFriendRequestsStr); + var list = [] + arr.forEach(function (item){ + const localFriendRequest = convertToSnakeCaseObject(convertObjectField(item)); + list.push(localFriendRequest) + }) + databaseBatchInsertFriendRequests(db, list); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteFriendRequestBothUserID(fromUserID, toUserID) { + try { + const db = await getInstance(); + databasedeleteFriendRequestBothUserID(db, fromUserID, toUserID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchDeleteFriendRequests(toUserID,fromUserIds) { + try { + const db = await getInstance(); + databaseBatchDeleteFriendRequests(db, toUserID,JSON.parse(fromUserIds)); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchUpdateFriendRequests(localFriendRequestsStr) { + try { + const db = await getInstance(); + const arr = JSON.parse(localFriendRequestsStr) + const list = new Array() + arr.forEach(function(item){ + const obj = convertToSnakeCaseObject(convertObjectField(item)); + list.push(obj) + }) + list.forEach(function (item){ + databaseupdateFriendRequest(db, item); + }) + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateFriendRequest(localFriendRequestStr) { + try { + const db = await getInstance(); + const localFriendRequest = convertToSnakeCaseObject(convertObjectField(JSON.parse(localFriendRequestStr))); + databaseupdateFriendRequest(db, localFriendRequest); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getRecvFriendApplication(loginUserID) { + try { + const db = await getInstance(); + const execResult = databaseGetRecvFriendApplication(db, loginUserID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getSendFriendApplication(fromUserId) { + try { + const db = await getInstance(); + const execResult = databaseGetSendFriendApplication(db, fromUserId); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getFriendApplicationByBothID(fromUserID, toUserID) { + try { + const db = await getInstance(); + const execResult = databaseGetFriendApplicationByBothID(db, fromUserID, toUserID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getBothFriendReq(fromUserID, toUserID) { + try { + const db = await getInstance(); + const execResult = databaseGetBothFriendReq(db, fromUserID, toUserID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/groupMember.d.ts b/src/utils/openIM/api/database/groupMember.d.ts new file mode 100644 index 0000000..763e4b1 --- /dev/null +++ b/src/utils/openIM/api/database/groupMember.d.ts @@ -0,0 +1,25 @@ +export declare function getGroupMemberInfoByGroupIDUserID(groupID: string, userID: string): Promise; +export declare function getAllGroupMemberList(): Promise; +export declare function getAllGroupMemberUserIDList(): Promise; +export declare function getAllGroupMemberCount(): Promise; +export declare function getGroupMemberCount(groupID: string): Promise; +export declare function getGroupSomeMemberInfo(groupID: string, userIDListStr: string): Promise; +export declare function getGroupAdminID(groupID: string): Promise; +export declare function getGroupMemberListByGroupID(groupID: string): Promise; +export declare function getGroupMemberListSplit(groupID: string, filter: number, offset: number, count: number): Promise; +export declare function getGroupMemberOwnerAndAdmin(groupID: string): Promise; +export declare function getGroupMemberOwner(groupID: string): Promise; +export declare function getGroupMemberListSplitByJoinTimeFilter(groupID: string, offset: number, count: number, joinTimeBegin: number | undefined, joinTimeEnd: number | undefined, userIDListStr: string): Promise; +export declare function getGroupOwnerAndAdminByGroupID(groupID: string): Promise; +export declare function getGroupMemberUIDListByGroupID(groupID: string): Promise; +export declare function insertGroupMember(localGroupMemberStr: string): Promise; +export declare function batchInsertGroupMember(localGroupMemberStr: string): Promise; +export declare function deleteGroupMember(groupID: string, userID: string): Promise; +export declare function batchDeleteGroupMembers(groupID: string, userIds: string): Promise; +export declare function deleteGroupAllMembers(groupID: string): Promise; +export declare function updateGroupMember(localGroupMemberStr: string): Promise; +export declare function batchUpdateGroupMembers(localGroupMembersStr: string): Promise; +export declare function updateGroupMemberField(groupID: string, userID: string, localGroupMemberStr: string): Promise; +export declare function searchGroupMembers(keyword: string, groupID: string, isSearchMemberNickname: boolean, isSearchUserID: boolean, offset: number, count: number): Promise; +export declare function getUserJoinedGroupIDs(userID: string): Promise; + diff --git a/src/utils/openIM/api/database/groupMember.js b/src/utils/openIM/api/database/groupMember.js new file mode 100644 index 0000000..72c6f45 --- /dev/null +++ b/src/utils/openIM/api/database/groupMember.js @@ -0,0 +1,316 @@ +import { DatabaseErrorCode } from '../../constant'; +import { getGroupMemberInfoByGroupIDUserID as databaseGetGroupMemberInfoByGroupIDUserID, batchDeleteGroupMembers as databaseBatchDeleteGroupMembers, + getAllGroupMemberList as databaseGetAllGroupMemberList, getAllGroupMemberCount as databaseGetAllGroupMemberCount, getAllGroupMemberUserIDList as databaseGetAllGroupMemberUserIDList, + getGroupMemberCount as databaseGetGroupMemberCount, getGroupSomeMemberInfo as databaseGetGroupSomeMemberInfo, + getGroupAdminID as databaseGetGroupAdminID, getGroupMemberListByGroupID as databaseGetGroupMemberListByGroupID, + getGroupMemberListSplit as databaseGetGroupMemberListSplit, getGroupMemberOwnerAndAdmin as databaseGetGroupMemberOwnerAndAdmin, + getGroupMemberOwner as databaseGetGroupMemberOwner, getGroupMemberListSplitByJoinTimeFilter as databaseGetGroupMemberListSplitByJoinTimeFilter, + getGroupOwnerAndAdminByGroupID as databaseGetGroupOwnerAndAdminByGroupID, getGroupMemberUIDListByGroupID as databaseGetGroupMemberUIDListByGroupID, + insertGroupMember as databaseInsertGroupMember, batchInsertGroupMember as databaseBatchInsertGroupMember, + deleteGroupMember as databaseDeleteGroupMember, deleteGroupAllMembers as databaseDeleteGroupAllMembers, + updateGroupMember as databaseUpdateGroupMember, updateGroupMemberField as databaseUpdateGroupMemberField, + searchGroupMembers as databaseSearchGroupMembers, getUserJoinedGroupIDs as databaseGetUserJoinedGroupIDs, } from '../../sqls'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { getInstance } from './instance'; +export async function getGroupMemberInfoByGroupIDUserID(groupID, userID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberInfoByGroupIDUserID(db, groupID, userID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllGroupMemberList() { + try { + const db = await getInstance(); + const execResult = databaseGetAllGroupMemberList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllGroupMemberCount() { + try { + const db = await getInstance(); + const execResult = databaseGetAllGroupMemberCount(db); + return formatResponse(execResult[0]?.values[0]?.[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllGroupMemberUserIDList() { + try { + const db = await getInstance(); + const execResult = databaseGetAllGroupMemberUserIDList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberCount(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberCount(db, groupID); + return formatResponse(execResult[0]?.values[0]?.[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupSomeMemberInfo(groupID, userIDListStr) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupSomeMemberInfo(db, groupID, JSON.parse(userIDListStr)); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupAdminID(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupAdminID(db, groupID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase').map(member => member.userID)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberListByGroupID(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberListByGroupID(db, groupID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberListSplit(groupID, filter, offset, count) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberListSplit(db, groupID, filter, offset, count); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberOwnerAndAdmin(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberOwnerAndAdmin(db, groupID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberOwner(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberOwner(db, groupID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberListSplitByJoinTimeFilter(groupID, offset, count, joinTimeBegin = 0, joinTimeEnd = 100000000000, userIDListStr) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberListSplitByJoinTimeFilter(db, groupID, offset, count, joinTimeBegin, joinTimeEnd, JSON.parse(userIDListStr)); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupOwnerAndAdminByGroupID(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupOwnerAndAdminByGroupID(db, groupID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberUIDListByGroupID(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberUIDListByGroupID(db, groupID); + const userIDList = converSqlExecResult(execResult[0], 'CamelCase'); + return formatResponse(userIDList.map(item => item.userID)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function insertGroupMember(localGroupMemberStr) { + try { + const db = await getInstance(); + const localGroup = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupMemberStr), { + faceURL: 'user_group_face_url', + })); + databaseInsertGroupMember(db, localGroup); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchInsertGroupMember(localGroupMemberStr) { + try { + const db = await getInstance(); + const localGroupList = (JSON.parse(localGroupMemberStr) || []).map((v) => convertToSnakeCaseObject(convertObjectField(v, { + faceURL: 'user_group_face_url', + }))); + databaseBatchInsertGroupMember(db, localGroupList); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteGroupMember(groupID, userID) { + try { + const db = await getInstance(); + databaseDeleteGroupMember(db, groupID, userID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchDeleteGroupMembers(groupID, userIds) { + try { + const db = await getInstance(); + databaseBatchDeleteGroupMembers(db, groupID, JSON.parse(userIds)); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteGroupAllMembers(groupID) { + try { + const db = await getInstance(); + databaseDeleteGroupAllMembers(db, groupID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateGroupMember(localGroupMemberStr) { + try { + const db = await getInstance(); + const localGroupMember = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupMemberStr), { + faceURL: 'user_group_face_url', + })); + databaseUpdateGroupMember(db, localGroupMember); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchUpdateGroupMembers(localGroupMembersStr) { + try { + const db = await getInstance(); + const localGroupMembers = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupMembersStr), { + faceURL: 'user_group_face_url', + })); + localGroupMembers.forEach(function (item){ + databaseUpdateGroupMember(db, item); + }) + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateGroupMemberField(groupID, userID, localGroupMemberStr) { + try { + const db = await getInstance(); + const localGroupMember = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupMemberStr), { + faceURL: 'user_group_face_url', + })); + databaseUpdateGroupMemberField(db, groupID, userID, localGroupMember); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function searchGroupMembers(keyword, groupID, isSearchMemberNickname, isSearchUserID, offset, count) { + try { + const db = await getInstance(); + const execResult = databaseSearchGroupMembers(db, keyword, groupID, isSearchMemberNickname, isSearchUserID, offset, count); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + user_group_face_url: 'faceURL', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getUserJoinedGroupIDs(userID) { + try { + const db = await getInstance(); + const execResult = databaseGetUserJoinedGroupIDs(db, userID); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase').map(item => item.groupID)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/groupRequest.d.ts b/src/utils/openIM/api/database/groupRequest.d.ts new file mode 100644 index 0000000..85f4038 --- /dev/null +++ b/src/utils/openIM/api/database/groupRequest.d.ts @@ -0,0 +1,8 @@ +export declare function insertGroupRequest(localGroupRequestStr: string): Promise; +export declare function deleteGroupRequest(groupID: string, userID: string): Promise; +export declare function updateGroupRequest(localGroupRequestStr: string): Promise; +export declare function getSendGroupApplication(): Promise; +export declare function insertAdminGroupRequest(localAdminGroupRequestStr: string): Promise; +export declare function deleteAdminGroupRequest(groupID: string, userID: string): Promise; +export declare function updateAdminGroupRequest(localGroupRequestStr: string): Promise; +export declare function getAdminGroupApplication(): Promise; diff --git a/src/utils/openIM/api/database/groupRequest.js b/src/utils/openIM/api/database/groupRequest.js new file mode 100644 index 0000000..eea15ac --- /dev/null +++ b/src/utils/openIM/api/database/groupRequest.js @@ -0,0 +1,126 @@ +import { DatabaseErrorCode } from '../../constant'; +import { insertGroupRequest as databaseInsertGroupRequest, deleteGroupRequest as databaseDeleteGroupRequest, updateGroupRequest as databaseUpdateGroupRequest, getSendGroupApplication as databaseGetSendGroupApplication, insertAdminGroupRequest as databaseInsertAdminGroupRequest, deleteAdminGroupRequest as databaseDeleteAdminGroupRequest, updateAdminGroupRequest as databaseUpdateAdminGroupRequest, getAdminGroupApplication as databaseGetAdminGroupApplication, } from '../../sqls'; +import { convertToSnakeCaseObject, convertObjectField, formatResponse, converSqlExecResult, } from '../../utils'; +import { getInstance } from './instance'; +export async function insertGroupRequest(localGroupRequestStr) { + try { + const db = await getInstance(); + const localGroupRequest = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupRequestStr), { + groupFaceURL: 'face_url', + userFaceURL: 'user_face_url', + handledMsg: 'handle_msg', + handledTime: 'handle_time', + })); + databaseInsertGroupRequest(db, localGroupRequest); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteGroupRequest(groupID, userID) { + try { + const db = await getInstance(); + databaseDeleteGroupRequest(db, groupID, userID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateGroupRequest(localGroupRequestStr) { + try { + const db = await getInstance(); + const localGroupRequest = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupRequestStr), { + groupFaceURL: 'face_url', + userFaceURL: 'user_face_url', + handledMsg: 'handle_msg', + handledTime: 'handle_time', + })); + databaseUpdateGroupRequest(db, localGroupRequest); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getSendGroupApplication() { + try { + const db = await getInstance(); + const execResult = databaseGetSendGroupApplication(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + face_url: 'groupFaceURL', + user_face_url: 'userFaceURL', + handle_msg: 'handledMsg', + handle_time: 'handledTime', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function insertAdminGroupRequest(localAdminGroupRequestStr) { + try { + const db = await getInstance(); + const localAminGroupRequest = convertToSnakeCaseObject(convertObjectField(JSON.parse(localAdminGroupRequestStr), { + groupFaceURL: 'face_url', + userFaceURL: 'user_face_url', + handledMsg: 'handle_msg', + handledTime: 'handle_time', + })); + databaseInsertAdminGroupRequest(db, localAminGroupRequest); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteAdminGroupRequest(groupID, userID) { + try { + const db = await getInstance(); + databaseDeleteAdminGroupRequest(db, groupID, userID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateAdminGroupRequest(localGroupRequestStr) { + try { + const db = await getInstance(); + const localGroupRequest = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupRequestStr), { + groupFaceURL: 'face_url', + userFaceURL: 'user_face_url', + handledMsg: 'handle_msg', + handledTime: 'handle_time', + })); + databaseUpdateAdminGroupRequest(db, localGroupRequest); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAdminGroupApplication() { + try { + const db = await getInstance(); + const execResult = databaseGetAdminGroupApplication(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + face_url: 'groupFaceURL', + user_face_url: 'userFaceURL', + handle_msg: 'handledMsg', + handle_time: 'handledTime', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/groups.d.ts b/src/utils/openIM/api/database/groups.d.ts new file mode 100644 index 0000000..45e1835 --- /dev/null +++ b/src/utils/openIM/api/database/groups.d.ts @@ -0,0 +1,15 @@ +export declare function insertGroup(localGroupStr: string): Promise; +export declare function batchInsertGroups(localGroupsStr: string): Promise; +export declare function deleteGroup(groupID: string): Promise; +export declare function batchDeleteGroups(groupIds: string): Promise; +export declare function updateGroup(groupID: string, localGroupStr: string): Promise; +export declare function batchUpdateGroups(localGroupsStr: string): Promise; +export declare function getJoinedGroupList(): Promise; +export declare function getGroupInfoByGroupID(groupID: string): Promise; +export declare function getAllGroupInfoByGroupIDOrGroupName(keyword: string, isSearchGroupID: boolean, isSearchGroupName: boolean): Promise; +export declare function subtractMemberCount(groupID: string): Promise; +export declare function addMemberCount(groupID: string): Promise; +export declare function getJoinedWorkingGroupIDList(): Promise; +export declare function getJoinedWorkingGroupList(): Promise; +export declare function getGroupMemberAllGroupIDs(): Promise; +export declare function getGroups(groupIDListStr: string): Promise; diff --git a/src/utils/openIM/api/database/groups.js b/src/utils/openIM/api/database/groups.js new file mode 100644 index 0000000..242124e --- /dev/null +++ b/src/utils/openIM/api/database/groups.js @@ -0,0 +1,205 @@ +import { DatabaseErrorCode } from '../../constant'; +import { insertGroup as databaseInsertGroup, deleteGroup as databasedeleteGroup, batchInsertGroups as databaseBatchInsertGroups, + updateGroup as databaseupdateGroup, batchDeleteGroups as databaseBatchDeleteGroups, + getJoinedGroupList as databaseGetJoinedGroupList, getGroupInfoByGroupID as databaseGetGroupInfoByGroupID, getGroupMemberAllGroupIDs as databaseGetGroupMemberAllGroupIDs, getAllGroupInfoByGroupIDOrGroupName as databaseGetAllGroupInfoByGroupIDOrGroupName, subtractMemberCount as databaseSubtractMemberCount, addMemberCount as databaseAddMemberCount, getGroups as databaseGetGroups, } from '../../sqls'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { getInstance } from './instance'; +export async function insertGroup(localGroupStr) { + try { + const db = await getInstance(); + const localGroup = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupStr), { groupName: 'name' })); + databaseInsertGroup(db, localGroup); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchInsertGroups(localGroupsStr) { + try { + const db = await getInstance(); + let arr = JSON.parse(localGroupsStr) + let groups = new Array() + arr.forEach(element => { + const localFriend = convertToSnakeCaseObject(convertObjectField(element, { + groupName: 'name' + })); + groups.push(localFriend) + }); + databaseBatchInsertGroups(db, groups); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteGroup(groupID) { + try { + const db = await getInstance(); + databasedeleteGroup(db, groupID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchDeleteGroups(groupIds) { + try { + const db = await getInstance(); + databaseBatchDeleteGroups(db, JSON.parse(groupIds)); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateGroup(groupID, localGroupStr) { + try { + const db = await getInstance(); + const localGroup = convertToSnakeCaseObject(convertObjectField(JSON.parse(localGroupStr), { groupName: 'name' })); + databaseupdateGroup(db, groupID, localGroup); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} + +export async function batchUpdateGroups(localGroupsStr) { + try { + const db = await getInstance(); + let localGroups = new Array() + const arr = JSON.parse(localGroupsStr) + Object.values(arr).forEach(function(item){ + var group = convertToSnakeCaseObject(convertObjectField(item, { groupName: 'name' })) + localGroups.push(group) + }) + localGroups.forEach(function (item){ + databaseupdateGroup(db, item.groupID, item); + }) + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getJoinedGroupList() { + try { + const db = await getInstance(); + const execResult = databaseGetJoinedGroupList(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { name: 'groupName' })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupInfoByGroupID(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetGroupInfoByGroupID(db, groupID); + if (execResult.length === 0) { + return formatResponse('', DatabaseErrorCode.ErrorNoRecord, `no group with id ${groupID}`); + } + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'groupName', + })[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getAllGroupInfoByGroupIDOrGroupName(keyword, isSearchGroupID, isSearchGroupName) { + try { + const db = await getInstance(); + const execResult = databaseGetAllGroupInfoByGroupIDOrGroupName(db, keyword, isSearchGroupID, isSearchGroupName); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { name: 'groupName' })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function subtractMemberCount(groupID) { + try { + const db = await getInstance(); + databaseSubtractMemberCount(db, groupID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function addMemberCount(groupID) { + try { + const db = await getInstance(); + databaseAddMemberCount(db, groupID); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getJoinedWorkingGroupIDList() { + try { + const db = await getInstance(); + const execResult = databaseGetJoinedGroupList(db); + const allJoinedGroupList = converSqlExecResult(execResult[0], 'CamelCase'); + const filterIDList = []; + allJoinedGroupList.forEach(group => { + if (group.groupType === 2) { + filterIDList.push(group.groupID); + } + }); + return formatResponse(JSON.stringify(filterIDList)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getJoinedWorkingGroupList() { + try { + const db = await getInstance(); + const execResult = databaseGetJoinedGroupList(db); + const allJoinedGroupList = converSqlExecResult(execResult[0], 'CamelCase', [], { name: 'groupName' }); + const filterList = allJoinedGroupList.filter(group => group.groupType === 2); + return formatResponse(JSON.stringify(filterList)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroupMemberAllGroupIDs() { + try { + const db = await getInstance(); + const execResult = databaseGetGroupMemberAllGroupIDs(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase').map(item => item.groupID)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getGroups(groupIDListStr) { + try { + const db = await getInstance(); + const execResult = databaseGetGroups(db, JSON.parse(groupIDListStr)); + const allJoinedGroupList = converSqlExecResult(execResult[0], 'CamelCase', [], { name: 'groupName' }); + return formatResponse(JSON.stringify(allJoinedGroupList)); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/index.d.ts b/src/utils/openIM/api/database/index.d.ts new file mode 100644 index 0000000..9cf1471 --- /dev/null +++ b/src/utils/openIM/api/database/index.d.ts @@ -0,0 +1,15 @@ +export * from './init'; +export * from './message'; +export * from './conversation'; +export * from './users'; +export * from './superGroup'; +export * from './unreadMessage'; +export * from './black'; +export * from './friend'; +export * from './friendRequest'; +export * from './groups'; +export * from './groupRequest'; +export * from './groupMember'; +export * from './tempCacheChatLogs'; +export * from './upload'; +export * from './stranger'; diff --git a/src/utils/openIM/api/database/index.js b/src/utils/openIM/api/database/index.js new file mode 100644 index 0000000..9cf1471 --- /dev/null +++ b/src/utils/openIM/api/database/index.js @@ -0,0 +1,15 @@ +export * from './init'; +export * from './message'; +export * from './conversation'; +export * from './users'; +export * from './superGroup'; +export * from './unreadMessage'; +export * from './black'; +export * from './friend'; +export * from './friendRequest'; +export * from './groups'; +export * from './groupRequest'; +export * from './groupMember'; +export * from './tempCacheChatLogs'; +export * from './upload'; +export * from './stranger'; diff --git a/src/utils/openIM/api/database/init.d.ts b/src/utils/openIM/api/database/init.d.ts new file mode 100644 index 0000000..83c4275 --- /dev/null +++ b/src/utils/openIM/api/database/init.d.ts @@ -0,0 +1,2 @@ +export declare function init(userId: string, dir: string): Promise; +export declare function close(): Promise; diff --git a/src/utils/openIM/api/database/init.js b/src/utils/openIM/api/database/init.js new file mode 100644 index 0000000..8555afa --- /dev/null +++ b/src/utils/openIM/api/database/init.js @@ -0,0 +1,66 @@ +import { DatabaseErrorCode } from '../../constant'; +import { locaBlacks, localFriends, localGroups, localFriendRequests, localGroupRequests, localAdminGroupRequests, localConversations, localUsers, localSuperGroups, localConversationUnreadMessages, localGroupMembers, tempCacheLocalChatLogs, localNotification, localUploads, localStranger, } from '../../sqls'; +import { formatResponse } from '../../utils'; +import { getInstance, resetInstance } from './instance'; +export async function init(userId, dir) { + console.info(`=> (database api) invoke init with args ${JSON.stringify({ + userId, + dir, + })}`); + try { + console.time('SDK => (performance measure) init database used '); + const db = await getInstance(`${dir}${userId}.sqlite`); + const results = []; + const execResultLocalUploads = localUploads(db); + const execResultLocalStrangers = localStranger(db); + const execResultLocalConversations = localConversations(db); + const execResultLocalUsers = localUsers(db); + const execResultLocalBlack = locaBlacks(db); + const execResultLocalFriend = localFriends(db); + const execResuLocalGroup = localGroups(db); + const execResuLocalGroupRequest = localGroupRequests(db); + const execResuLocalGroupMembers = localGroupMembers(db); + const execResuLocalAdminGroupRequest = localAdminGroupRequests(db); + const execResultlocaFendRequest = localFriendRequests(db); + const execResultLocalSuperGroups = localSuperGroups(db); + const execResultTempCacheLocalChatLogs = tempCacheLocalChatLogs(db); + const execResultlocalNotification = localNotification(db); + const execResultLocalConversationUnreadMessages = localConversationUnreadMessages(db); + results.push(...[ + execResultLocalUploads, + execResultLocalStrangers, + execResultLocalConversations, + execResultLocalUsers, + execResultLocalSuperGroups, + execResultLocalConversationUnreadMessages, + execResultLocalBlack, + execResultLocalFriend, + execResuLocalGroup, + execResuLocalGroupMembers, + execResultlocaFendRequest, + execResuLocalGroupRequest, + execResuLocalAdminGroupRequest, + execResultTempCacheLocalChatLogs, + execResultlocalNotification, + ]); + return formatResponse(results); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } + finally { + console.timeEnd('SDK => (performance measure) init database used '); + } +} +export async function close() { + console.info('=> (database api) invoke close'); + try { + await resetInstance(); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/instance.d.ts b/src/utils/openIM/api/database/instance.d.ts new file mode 100644 index 0000000..38bfc13 --- /dev/null +++ b/src/utils/openIM/api/database/instance.d.ts @@ -0,0 +1,3 @@ +import { Database } from '@jlongster/sql.js'; +export declare function getInstance(filePath?: string): Promise; +export declare function resetInstance(): Promise; diff --git a/src/utils/openIM/api/database/instance.js b/src/utils/openIM/api/database/instance.js new file mode 100644 index 0000000..d9ed26e --- /dev/null +++ b/src/utils/openIM/api/database/instance.js @@ -0,0 +1,53 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +import initSqlJs from '@jlongster/sql.js'; +import { SQLiteFS } from 'absurd-sql-optimized'; +import IndexedDBBackend from 'absurd-sql-optimized/dist/indexeddb-backend'; +self.$RefreshReg$ = () => { }; +self.$RefreshSig$ = () => () => { }; +let instance; +let SQL; +async function InitializeDB(filePath) { + if (!SQL) { + SQL = await initSqlJs({ locateFile: () => '/sql-wasm.wasm' }); + const sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend()); + SQL.register_for_idb(sqlFS); + SQL.FS.mkdir('/sql'); + SQL.FS.mount(sqlFS, {}, '/sql'); + } + const path = `/sql/${filePath}`; + const db = new SQL.Database(path, { filename: true }); + if (typeof SharedArrayBuffer === 'undefined') { + // @ts-ignore + const stream = SQL.FS.open(path, 'a+'); + await stream.node.contents.readIfFallback(); + // @ts-ignore + SQL.FS.close(stream); + } + db.exec(` + PRAGMA page_size=8192; + PRAGMA journal_mode=MEMORY; + `); + return db; +} +export function getInstance(filePath) { + if (instance) { + return instance; + } + if (!filePath) { + throw new Error('must speciefic database file'); + } + instance = new Promise((resolve, reject) => { + const db = InitializeDB(filePath); + db.then(res => resolve(res)).catch(err => reject(err)); + }); + return instance; +} +export async function resetInstance() { + if (!instance) { + return; + } + const db = await instance; + db.close(); + instance = undefined; +} diff --git a/src/utils/openIM/api/database/message.d.ts b/src/utils/openIM/api/database/message.d.ts new file mode 100644 index 0000000..f581805 --- /dev/null +++ b/src/utils/openIM/api/database/message.d.ts @@ -0,0 +1,169 @@ +export declare function getMessage(conversationID: string, clientMsgID: string): Promise +export declare function getAlreadyExistSeqList( + conversationID: string, + lostSeqListStr: string +): Promise +export declare function getMessageList( + conversationID: string, + count: number, + startTime: number, + isReverse?: boolean +): Promise +export declare function getMessageBySeq(conversationID: string, seq: number): Promise +export declare function getMessagesByClientMsgIDs( + conversationID: string, + clientMsgIDListStr: string +): Promise +export declare function getMessagesBySeqs( + conversationID: string, + seqListStr: string +): Promise +export declare function getMessageListNoTime( + conversationID: string, + count: number, + isReverse?: boolean +): Promise +export declare function getMessageListNotDel( + conversationID: string, + count: number, + isReverse?: boolean +): Promise +export declare function getLastMessageByTime( + conversationID: string, + count: number, + startTime: number, + isReverse: boolean +): Promise + +export declare function getConversationNormalMsgSeq(conversationID: string, init: boolean): Promise +export declare function getConversationPeerNormalMsgSeq( + conversationID: string, + loginUserID: string +): Promise +export declare function getSendingMessageList(conversationID: string): Promise +export declare function updateMessageTimeAndStatus( + conversationID: string, + clientMsgID: string, + serverMsgID: string, + sendTime: number, + status: number +): Promise +export declare function updateMessageStatus( + conversationID: string, + clientMsgID: string, + serverMsgID: string, + sendTime: number, + status: number +): Promise +export declare function updateMessage( + conversationID: string, + clientMsgID: string, + messageStr: string +): Promise +export declare function updateMessageBySeq( + conversationID: string, + seq: number, + messageStr: string +): Promise +export declare function batchInsertMessageList( + conversationID: string, + messageListStr: string +): Promise +export declare function insertMessage(conversationID: string, messageStr: string): Promise +export declare function getMultipleMessage( + conversationID: string, + messageIDStr: string +): Promise +export declare function searchMessageByKeyword( + conversationID: string, + contentTypeStr: string, + keywordListStr: string, + keywordListMatchType: number, + startTime: number, + endTime: number, + offset: number, + count: number +): Promise +export declare function searchMessageByContentType( + conversationID: string, + contentTypeStr: string, + startTime: number, + endTime: number, + offset: number, + count: number +): Promise +export declare function searchMessageByContentTypeAndKeyword( + conversationID: string, + contentTypeStr: string, + keywordListStr: string, + keywordListMatchType: number, + startTime: number, + endTime: number +): Promise +export declare function messageIfExists( + conversationID: string, + clientMsgID: string +): Promise +export declare function updateMsgSenderFaceURLAndSenderNickname( + conversationID: string, + sendID: string, + faceURL: string, + nickname: string +): Promise +export declare function deleteConversationAllMessages(conversationID: string): Promise +export declare function deleteConversationAllMessagesBySeq(conversationID: string, maxSeq: number): Promise +export declare function markDeleteConversationAllMessages(conversationID: string): Promise +export declare function getUnreadMessage( + conversationID: string, + loginUserID: string +): Promise +export declare function markConversationMessageAsReadBySeqs( + conversationID: string, + seqListStr: string, + loginUserID: string +): Promise +export declare function markConversationMessageAsRead( + conversationID: string, + clientMsgIDListStr: string, + loginUserID: string +): Promise +export declare function updateColumnsMessage( + conversationID: string, + clientMsgID: string, + messageStr: string +): Promise +export declare function deleteConversationMsgs( + conversationID: string, + clientMsgIDListStr: string +): Promise +export declare function markConversationAllMessageAsRead( + conversationID: string, + clientMsgIDListStr: string +): Promise +export declare function searchAllMessageByContentType( + conversationID: string, + clientMsgIDListStr: string +): Promise +export declare function updateMessageBySeqEx( + conversationID: string, + clientMsgID: string, + seq: number, + localChatLogs: string +): Promise +type ClientMessage = { + [key: string]: any +} +export declare function updateMessageBySeqs( + conversationID: string, + seqs: number[], + localChatLogs: ClientMessage, +): Promise + + +export declare function updateMessageByUserID( + conversationID: string, + userID: string, + localChatLogs: ClientMessage, +): Promise + + diff --git a/src/utils/openIM/api/database/message.js b/src/utils/openIM/api/database/message.js new file mode 100644 index 0000000..d051639 --- /dev/null +++ b/src/utils/openIM/api/database/message.js @@ -0,0 +1,627 @@ +import { DatabaseErrorCode } from '../../constant' +import { + getMessage as databaseGetMessage, + getAlreadyExistSeqList as databaseGetAlreadyExistSeqList, + getMessageBySeq as databaseGetMessageBySeq, + getMessagesByClientMsgIDs as databaseGetMessagesByClientMsgIDs, + getMessagesBySeqs as databaseGetMessagesBySeqs, + getMessageListNoTime as databaseGetMessageListNoTime, + getMessageListNotDel as databaseGetMessageListNotDel, + getConversationNormalMsgSeq as databaseGetConversationNormalMsgSeq, + getConversationPeerNormalMsgSeq as databaseGetConversationPeerNormalMsgSeq, + getMultipleMessage as databaseGetMultipleMessage, + getSendingMessageList as databaseGetSendingMessageList, + updateMessageTimeAndStatus as databaseUpdateMessageTimeAndStatus, + updateMessageStatus as databaseUpdateMessageStatus, + updateMessage as databaseUpdateMessage, + updateMessageBySeq as databaseUpdateMessageBySeq, + updateColumnsMessage as databaseUpdateColumnsMessage, + deleteConversationMsgs as databaseDeleteConversationMsgs, + markConversationAllMessageAsRead as databaseMarkConversationAllMessageAsRead, + searchAllMessageByContentType as databaseSearchAllMessageByContentType, + insertMessage as databaseInsertMessage, + batchInsertMessageList as databaseBatchInsertMessageList, + getMessageList as databaseGetMesageList, + getLastMessageByTime as databaseGetLastMessageByTime, + messageIfExists as databaseMessageIfExists, + searchMessageByKeyword as databaseSearchMessageByKeyword, + searchMessageByContentType as databaseSearchMessageByContentType, + searchMessageByContentTypeAndKeyword as databaseSearchMessageByContentTypeAndKeyword, + updateMsgSenderFaceURLAndSenderNickname as databaseUpdateMsgSenderFaceURLAndSenderNickname, + deleteConversationAllMessages as databaseDeleteConversationAllMessages, + deleteConversationAllMessagesBySeq as databaseDeleteConversationAllMessagesBySeq, + markDeleteConversationAllMessages as databaseMarkDeleteConversationAllMessages, + getUnreadMessage as databaseGetUnreadMessage, + markConversationMessageAsReadBySeqs as databaseMarkConversationMessageAsReadBySeqs, + markConversationMessageAsRead as databaseMarkConversationMessageAsRead, + updateMessageBySeqEx as databaseupdateMessageBySeqEx, + updateMessageBySeqs as databaseUpdateMessageBySeqs, + updateMessageByUserID as databaseUpdateMessageByUserID, +} from '../../sqls' +import { + converSqlExecResult, + convertObjectField, + convertToSnakeCaseObject, + formatResponse +} from '../../utils' +import { getInstance } from './instance' +export async function getMessage(conversationID, clientMsgID) { + try { + const db = await getInstance() + const execResult = databaseGetMessage(db, conversationID, clientMsgID) + if (execResult.length === 0) { + return formatResponse( + '', + DatabaseErrorCode.ErrorNoRecord, + `no message with id ${clientMsgID}` + ) + } + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', [ + 'isRead', + 'isReact', + 'isExternalExtensions' + ])[0] + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getAlreadyExistSeqList(conversationID, lostSeqListStr) { + try { + const db = await getInstance() + const execResult = databaseGetAlreadyExistSeqList( + db, + conversationID, + JSON.parse(lostSeqListStr) + ) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', [ + 'isRead', + 'isReact', + 'isExternalExtensions' + ]).map((item) => item.seq) ?? [] + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getMessageList(conversationID, count, startTime, isReverse = false) { + try { + const db = await getInstance() + const execResult = databaseGetMesageList(db, conversationID, count, startTime, isReverse) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getLastMessageByTime(conversationID, count, startTime, isReverse = false) { + try { + const db = await getInstance() + const execResult = databaseGetLastMessageByTime(db, conversationID, count, startTime, isReverse) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getMessageBySeq(conversationID, seq) { + try { + const db = await getInstance() + const execResult = databaseGetMessageBySeq(db, conversationID, seq) + if (execResult.length === 0) { + return formatResponse('', DatabaseErrorCode.ErrorNoRecord, `no message with seq ${seq}`) + } + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', [ + 'isRead', + 'isReact', + 'isExternalExtensions' + ])[0] + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getMessagesByClientMsgIDs(conversationID, clientMsgIDListStr) { + try { + const db = await getInstance() + const execResult = databaseGetMessagesByClientMsgIDs( + db, + conversationID, + JSON.parse(clientMsgIDListStr) + ) + if (execResult.length === 0) { + return formatResponse( + '', + DatabaseErrorCode.ErrorNoRecord, + `no message with clientMsgIDListStr ${clientMsgIDListStr}` + ) + } + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getMessagesBySeqs(conversationID, seqListStr) { + try { + const db = await getInstance() + const execResult = databaseGetMessagesBySeqs(db, conversationID, JSON.parse(seqListStr)) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getMessageListNoTime(conversationID, count, isReverse = false) { + try { + const db = await getInstance() + const execResult = databaseGetMessageListNoTime(db, conversationID, count, isReverse) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getMessageListNotDel(conversationID, count, isReverse = false) { + try { + const db = await getInstance() + const execResult = databaseGetMessageListNotDel(db, conversationID, count, isReverse) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getConversationNormalMsgSeq(conversationID, init) { + try { + const db = await getInstance() + const execResult = databaseGetConversationNormalMsgSeq(db, conversationID, init) + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')[0]?.seq ?? 0) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getConversationPeerNormalMsgSeq(conversationID, loginUserID) { + try { + const db = await getInstance() + const execResult = databaseGetConversationPeerNormalMsgSeq(db, conversationID, loginUserID) + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')[0]?.seq ?? 0) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getSendingMessageList(conversationID) { + try { + const db = await getInstance() + const execResult = databaseGetSendingMessageList(db, conversationID) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateMessageTimeAndStatus( + conversationID, + clientMsgID, + serverMsgID, + sendTime, + status +) { + try { + const db = await getInstance() + const execResult = databaseUpdateMessageTimeAndStatus( + db, + conversationID, + clientMsgID, + serverMsgID, + sendTime, + status + ) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessageTimeAndStatus no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateMessageStatus( + conversationID, + clientMsgID, + status +) { + try { + const db = await getInstance() + const execResult = databaseUpdateMessageStatus( + db, + conversationID, + clientMsgID, + status + ) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessageStatus no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateMessage(conversationID, clientMsgID, messageStr) { + try { + const db = await getInstance() + const message = convertToSnakeCaseObject(convertObjectField(JSON.parse(messageStr))) + const execResult = databaseUpdateMessage(db, conversationID, clientMsgID, message) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessage no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateMessageBySeq(conversationID, seq, messageStr) { + try { + const db = await getInstance() + const message = convertToSnakeCaseObject(convertObjectField(JSON.parse(messageStr))) + const execResult = databaseUpdateMessageBySeq(db, conversationID, seq, message) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessageBySeq no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function batchInsertMessageList(conversationID, messageListStr) { + try { + const db = await getInstance() + const messageList = JSON.parse(messageListStr).map((v) => convertToSnakeCaseObject(v)) + const execResult = databaseBatchInsertMessageList(db, conversationID, messageList) + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function insertMessage(conversationID, messageStr) { + try { + const db = await getInstance() + const message = convertToSnakeCaseObject(JSON.parse(messageStr)) + const execResult = databaseInsertMessage(db, conversationID, message) + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getMultipleMessage(conversationID, messageIDStr) { + try { + const db = await getInstance() + const execResult = databaseGetMultipleMessage(db, conversationID, JSON.parse(messageIDStr)) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function searchMessageByKeyword( + conversationID, + contentTypeStr, + keywordListStr, + keywordListMatchType, + startTime, + endTime, + offset, + count +) { + try { + const db = await getInstance() + const execResult = databaseSearchMessageByKeyword( + db, + conversationID, + JSON.parse(contentTypeStr), + JSON.parse(keywordListStr), + keywordListMatchType, + startTime, + endTime, + offset, + count + ) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function searchMessageByContentType( + conversationID, + contentTypeStr, + startTime, + endTime, + offset, + count +) { + try { + const db = await getInstance() + const execResult = databaseSearchMessageByContentType( + db, + conversationID, + JSON.parse(contentTypeStr), + startTime, + endTime, + offset, + count + ) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function searchMessageByContentTypeAndKeyword( + conversationID, + contentTypeStr, + keywordListStr, + keywordListMatchType, + startTime, + endTime +) { + try { + const db = await getInstance() + const execResult = databaseSearchMessageByContentTypeAndKeyword( + db, + conversationID, + JSON.parse(contentTypeStr), + JSON.parse(keywordListStr), + keywordListMatchType, + startTime, + endTime + ) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function messageIfExists(conversationID, clientMsgID) { + try { + const db = await getInstance() + const execResult = databaseMessageIfExists(db, conversationID, clientMsgID) + return formatResponse(execResult.length !== 0) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateMsgSenderFaceURLAndSenderNickname( + conversationID, + sendID, + faceURL, + nickname +) { + try { + const db = await getInstance() + databaseUpdateMsgSenderFaceURLAndSenderNickname(db, conversationID, sendID, faceURL, nickname) + return formatResponse('') + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function deleteConversationAllMessages(conversationID) { + try { + const db = await getInstance() + databaseDeleteConversationAllMessages(db, conversationID) + return formatResponse('') + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function deleteConversationAllMessagesBySeq(conversationID, maxSeq) { + try { + const db = await getInstance() + databaseDeleteConversationAllMessagesBySeq(db, conversationID, maxSeq) + return formatResponse('') + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function markDeleteConversationAllMessages(conversationID) { + try { + const db = await getInstance() + databaseMarkDeleteConversationAllMessages(db, conversationID) + return formatResponse('') + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function getUnreadMessage(conversationID, loginUserID) { + try { + const db = await getInstance() + const execResult = databaseGetUnreadMessage(db, conversationID, loginUserID) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function markConversationMessageAsReadBySeqs(conversationID, seqListStr, loginUserID) { + try { + const db = await getInstance() + databaseMarkConversationMessageAsReadBySeqs( + db, + conversationID, + JSON.parse(seqListStr), + loginUserID + ) + return formatResponse(db.getRowsModified()) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function markConversationMessageAsRead( + conversationID, + clientMsgIDListStr, + loginUserID +) { + try { + const db = await getInstance() + databaseMarkConversationMessageAsRead( + db, + conversationID, + JSON.parse(clientMsgIDListStr), + loginUserID + ) + return formatResponse(db.getRowsModified()) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateColumnsMessage(conversationID, clientMsgID, messageStr) { + try { + const db = await getInstance() + const message = convertToSnakeCaseObject(convertObjectField(JSON.parse(messageStr))) + const execResult = databaseUpdateColumnsMessage(db, conversationID, clientMsgID, message) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessage no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function deleteConversationMsgs(conversationID, clientMsgIDListStr) { + try { + const db = await getInstance() + const execResult = databaseDeleteConversationMsgs( + db, + conversationID, + JSON.parse(clientMsgIDListStr) + ) + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function markConversationAllMessageAsRead(conversationID, clientMsgIDListStr) { + try { + const db = await getInstance() + databaseMarkConversationAllMessageAsRead(db, conversationID, JSON.parse(clientMsgIDListStr)) + return formatResponse(db.getRowsModified()) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function searchAllMessageByContentType(conversationID, clientMsgIDListStr) { + try { + const db = await getInstance() + const execResult = databaseSearchAllMessageByContentType( + db, + conversationID, + JSON.parse(clientMsgIDListStr) + ) + return formatResponse( + converSqlExecResult(execResult[0], 'CamelCase', ['isRead', 'isReact', 'isExternalExtensions']) + ) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateMessageBySeqEx(conversationID, clientMsgID, localChatLogs) { + try { + const db = await getInstance() + const message = convertToSnakeCaseObject(convertObjectField(JSON.parse(localChatLogs))) + const execResult = databaseupdateMessageBySeqEx(db, conversationID, clientMsgID, message) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessage no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} +export async function updateMessageBySeqs(conversationID, seqs, localChatLogs) { + try { + const db = await getInstance() + const message = convertToSnakeCaseObject(convertObjectField(JSON.parse(localChatLogs))) + const execResult = databaseUpdateMessageBySeqs(db, conversationID, JSON.parse(seqs), message) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessage no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} + +export async function updateMessageByUserID(conversationID, userID, localChatLogs) { + try { + const db = await getInstance() + const message = convertToSnakeCaseObject(convertObjectField(JSON.parse(localChatLogs))) + const execResult = databaseUpdateMessageByUserID(db, conversationID, userID, message) + const modifed = db.getRowsModified() + if (modifed === 0) { + throw 'updateMessage no record updated' + } + return formatResponse(execResult) + } catch (e) { + console.error(e) + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)) + } +} + + diff --git a/src/utils/openIM/api/database/notification.d.ts b/src/utils/openIM/api/database/notification.d.ts new file mode 100644 index 0000000..4187386 --- /dev/null +++ b/src/utils/openIM/api/database/notification.d.ts @@ -0,0 +1,2 @@ +export declare function setNotificationSeq(conversationID: string, seq: number): Promise; +export declare function getNotificationAllSeqs(): Promise; diff --git a/src/utils/openIM/api/database/notification.js b/src/utils/openIM/api/database/notification.js new file mode 100644 index 0000000..a604261 --- /dev/null +++ b/src/utils/openIM/api/database/notification.js @@ -0,0 +1,30 @@ +import { DatabaseErrorCode } from '../../constant'; +import { insertNotificationSeq as databaseInsertNotificationSeq, setNotificationSeq as databaseSetNotificationSeq, getNotificationAllSeqs as databaseGetNotificationAllSeqs, } from '../../sqls'; +import { converSqlExecResult, formatResponse } from '../../utils'; +import { getInstance } from './instance'; +export async function setNotificationSeq(conversationID, seq) { + try { + const db = await getInstance(); + let execResult = databaseSetNotificationSeq(db, conversationID, seq); + const modified = db.getRowsModified(); + if (modified === 0) { + execResult = databaseInsertNotificationSeq(db, conversationID, seq); + } + return formatResponse(execResult[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getNotificationAllSeqs() { + try { + const db = await getInstance(); + const execResult = databaseGetNotificationAllSeqs(db); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase')); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/stranger.d.ts b/src/utils/openIM/api/database/stranger.d.ts new file mode 100644 index 0000000..a05fab8 --- /dev/null +++ b/src/utils/openIM/api/database/stranger.d.ts @@ -0,0 +1,4 @@ +import { LocalStranger } from '../../sqls'; +export declare function getStrangerInfo(userIDListStr: string): Promise; +export declare function setStrangerInfo(localStrangerInfoListStr: string): Promise; +export declare function setSingleStrangerInfo(localStrangerInfo: LocalStranger): Promise; diff --git a/src/utils/openIM/api/database/stranger.js b/src/utils/openIM/api/database/stranger.js new file mode 100644 index 0000000..3d7db94 --- /dev/null +++ b/src/utils/openIM/api/database/stranger.js @@ -0,0 +1,48 @@ +import { getStrangerInfo as databaseGetStrangerInfo, insertStrangerInfo as databseInsertStrangerInfo, updateStrangerInfo as databaseUpdateStrangerInfo, } from '../../sqls'; +import { getInstance } from './instance'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { DatabaseErrorCode } from '../../constant'; +export async function getStrangerInfo(userIDListStr) { + try { + const db = await getInstance(); + const execResult = databaseGetStrangerInfo(db, JSON.parse(userIDListStr)); + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + })); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function setStrangerInfo(localStrangerInfoListStr) { + try { + const localStrangerInfoList = JSON.parse(localStrangerInfoListStr).map((item) => convertToSnakeCaseObject(convertObjectField(item, { + nickname: 'name', + }))); + localStrangerInfoList.map((localStrangerInfo) => setSingleStrangerInfo(localStrangerInfo)); + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function setSingleStrangerInfo(localStrangerInfo) { + try { + const db = await getInstance(); + const execResult = databaseGetStrangerInfo(db, [localStrangerInfo.user_id]); + const result = converSqlExecResult(execResult[0]); + if (result.length) { + databaseUpdateStrangerInfo(db, localStrangerInfo); + } + else { + databseInsertStrangerInfo(db, localStrangerInfo); + } + return formatResponse(''); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/superGroup.d.ts b/src/utils/openIM/api/database/superGroup.d.ts new file mode 100644 index 0000000..5a32d40 --- /dev/null +++ b/src/utils/openIM/api/database/superGroup.d.ts @@ -0,0 +1,6 @@ +export declare function getJoinedSuperGroupList(): Promise; +export declare function getJoinedSuperGroupIDList(): Promise; +export declare function getSuperGroupInfoByGroupID(groupID: string): Promise; +export declare function deleteSuperGroup(groupID: string): Promise; +export declare function insertSuperGroup(groupStr: string): Promise; +export declare function updateSuperGroup(groupID: string, groupStr: string): Promise; diff --git a/src/utils/openIM/api/database/superGroup.js b/src/utils/openIM/api/database/superGroup.js new file mode 100644 index 0000000..c4a0578 --- /dev/null +++ b/src/utils/openIM/api/database/superGroup.js @@ -0,0 +1,81 @@ +import { DatabaseErrorCode } from '../../constant'; +import { getJoinedSuperGroupList as databaseGetJoinedSuperGroupList, insertSuperGroup as databaseInsertSuperGroup, updateSuperGroup as databaseUpdateSuperGroup, deleteSuperGroup as databaseDeleteSuperGroup, getSuperGroupInfoByGroupID as databaseGetSuperGroupInfoByGroupID, } from '../../sqls'; +import { formatResponse, converSqlExecResult, convertToSnakeCaseObject, convertObjectField, } from '../../utils'; +import { getInstance } from './instance'; +export async function getJoinedSuperGroupList() { + try { + const db = await getInstance(); + const execResult = databaseGetJoinedSuperGroupList(db); + return formatResponse(converSqlExecResult(execResult[0])); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getJoinedSuperGroupIDList() { + try { + const db = await getInstance(); + const execResult = databaseGetJoinedSuperGroupList(db); + const records = converSqlExecResult(execResult[0]); + const groupIds = records.map(r => r.groupID); + return formatResponse(groupIds); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function getSuperGroupInfoByGroupID(groupID) { + try { + const db = await getInstance(); + const execResult = databaseGetSuperGroupInfoByGroupID(db, groupID); + if (execResult.length === 0) { + return formatResponse('', DatabaseErrorCode.ErrorNoRecord, `no super group with id ${groupID}`); + } + return formatResponse(converSqlExecResult(execResult[0])[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteSuperGroup(groupID) { + try { + const db = await getInstance(); + const execResult = databaseDeleteSuperGroup(db, groupID); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function insertSuperGroup(groupStr) { + try { + const db = await getInstance(); + const group = convertToSnakeCaseObject(convertObjectField(JSON.parse(groupStr), { groupName: 'name' })); + const execResult = databaseInsertSuperGroup(db, group); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateSuperGroup(groupID, groupStr) { + try { + const db = await getInstance(); + const group = convertToSnakeCaseObject(convertObjectField(JSON.parse(groupStr), { groupName: 'name' })); + const execResult = databaseUpdateSuperGroup(db, groupID, group); + const modifed = db.getRowsModified(); + if (modifed === 0) { + throw 'updateSuperGroup no record updated'; + } + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/tempCacheChatLogs.d.ts b/src/utils/openIM/api/database/tempCacheChatLogs.d.ts new file mode 100644 index 0000000..459c4b2 --- /dev/null +++ b/src/utils/openIM/api/database/tempCacheChatLogs.d.ts @@ -0,0 +1,2 @@ +export declare function batchInsertTempCacheMessageList(messageListStr: string): Promise; +export declare function InsertTempCacheMessage(messageStr: string): Promise; diff --git a/src/utils/openIM/api/database/tempCacheChatLogs.js b/src/utils/openIM/api/database/tempCacheChatLogs.js new file mode 100644 index 0000000..be56cf2 --- /dev/null +++ b/src/utils/openIM/api/database/tempCacheChatLogs.js @@ -0,0 +1,19 @@ +import { DatabaseErrorCode } from '../../constant'; +import { batchInsertTempCacheMessageList as databseBatchInsertTempCacheMessageList, } from '../../sqls'; +import { convertToSnakeCaseObject, formatResponse } from '../../utils'; +import { getInstance } from './instance'; +export async function batchInsertTempCacheMessageList(messageListStr) { + try { + const db = await getInstance(); + const messageList = JSON.parse(messageListStr).map((v) => convertToSnakeCaseObject(v)); + const execResult = databseBatchInsertTempCacheMessageList(db, messageList); + return formatResponse(execResult[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function InsertTempCacheMessage(messageStr) { + return batchInsertTempCacheMessageList(`[${messageStr}]`); +} diff --git a/src/utils/openIM/api/database/unreadMessage.d.ts b/src/utils/openIM/api/database/unreadMessage.d.ts new file mode 100644 index 0000000..2f46f67 --- /dev/null +++ b/src/utils/openIM/api/database/unreadMessage.d.ts @@ -0,0 +1,2 @@ +export declare function deleteConversationUnreadMessageList(conversationID: string, sendTime: number): Promise; +export declare function batchInsertConversationUnreadMessageList(messageListStr: string): Promise; diff --git a/src/utils/openIM/api/database/unreadMessage.js b/src/utils/openIM/api/database/unreadMessage.js new file mode 100644 index 0000000..ab9b1e3 --- /dev/null +++ b/src/utils/openIM/api/database/unreadMessage.js @@ -0,0 +1,28 @@ +import { DatabaseErrorCode } from '../../constant'; +import { batchInsertConversationUnreadMessageList as databaseBatchInsertConversationUnreadMessageList, deleteConversationUnreadMessageList as databaseDeleteConversationUnreadMessageList, } from '../../sqls'; +import { convertToSnakeCaseObject, formatResponse } from '../../utils'; +import { getInstance } from './instance'; +export async function deleteConversationUnreadMessageList(conversationID, sendTime) { + try { + const db = await getInstance(); + databaseDeleteConversationUnreadMessageList(db, conversationID, sendTime); + const modifed = db.getRowsModified(); + return formatResponse(modifed); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function batchInsertConversationUnreadMessageList(messageListStr) { + try { + const db = await getInstance(); + const messageList = JSON.parse(messageListStr).map((v) => convertToSnakeCaseObject(v)); + const execResult = databaseBatchInsertConversationUnreadMessageList(db, messageList); + return formatResponse(execResult[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/upload.d.ts b/src/utils/openIM/api/database/upload.d.ts new file mode 100644 index 0000000..ba8de85 --- /dev/null +++ b/src/utils/openIM/api/database/upload.d.ts @@ -0,0 +1,4 @@ +export declare function getUpload(partHash: string): Promise; +export declare function insertUpload(uploadStr: string): Promise; +export declare function updateUpload(uploadStr: string): Promise; +export declare function deleteUpload(partHash: string): Promise; diff --git a/src/utils/openIM/api/database/upload.js b/src/utils/openIM/api/database/upload.js new file mode 100644 index 0000000..4b9a75b --- /dev/null +++ b/src/utils/openIM/api/database/upload.js @@ -0,0 +1,54 @@ +import { DatabaseErrorCode } from '../../constant'; +import { getUpload as databaseGetUpload, insertUpload as databaseInsertUpload, updateUpload as databaseUpdateUpload, deleteUpload as databaseDeleteUpload, } from '../../sqls'; +import { converSqlExecResult, convertObjectField, convertToSnakeCaseObject, formatResponse, } from '../../utils'; +import { getInstance } from './instance'; +export async function getUpload(partHash) { + try { + const db = await getInstance(); + const execResult = databaseGetUpload(db, partHash); + const upload = converSqlExecResult(execResult[0], 'CamelCase'); + if (upload.length === 0) { + throw `no upload with partHash = ${partHash}`; + } + return formatResponse(upload[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function insertUpload(uploadStr) { + try { + const db = await getInstance(); + const upload = convertToSnakeCaseObject(convertObjectField(JSON.parse(uploadStr))); + const execResult = databaseInsertUpload(db, upload); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateUpload(uploadStr) { + try { + const db = await getInstance(); + const upload = convertToSnakeCaseObject(convertObjectField(JSON.parse(uploadStr))); + const execResult = databaseUpdateUpload(db, upload); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function deleteUpload(partHash) { + try { + const db = await getInstance(); + const execResult = databaseDeleteUpload(db, partHash); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/database/users.d.ts b/src/utils/openIM/api/database/users.d.ts new file mode 100644 index 0000000..a4a544b --- /dev/null +++ b/src/utils/openIM/api/database/users.d.ts @@ -0,0 +1,3 @@ +export declare function getLoginUser(userID: string): Promise; +export declare function insertLoginUser(userStr: string): Promise; +export declare function updateLoginUser(userStr: string): Promise; diff --git a/src/utils/openIM/api/database/users.js b/src/utils/openIM/api/database/users.js new file mode 100644 index 0000000..53e3011 --- /dev/null +++ b/src/utils/openIM/api/database/users.js @@ -0,0 +1,48 @@ +import { DatabaseErrorCode } from '../../constant'; +import { getLoginUser as databaseGetLoginUser, insertLoginUser as databaseInsertLoginUser, updateLoginUser as databaseUpdateLoginUser, } from '../../sqls'; +import { formatResponse, converSqlExecResult, convertToSnakeCaseObject, convertObjectField, } from '../../utils'; +import { getInstance } from './instance'; +export async function getLoginUser(userID) { + try { + const db = await getInstance(); + const execResult = databaseGetLoginUser(db, userID); + if (execResult.length === 0) { + return formatResponse('', DatabaseErrorCode.ErrorNoRecord, `no login user with id ${userID}`); + } + return formatResponse(converSqlExecResult(execResult[0], 'CamelCase', [], { + name: 'nickname', + })[0]); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function insertLoginUser(userStr) { + try { + const db = await getInstance(); + const user = convertToSnakeCaseObject(convertObjectField(JSON.parse(userStr), { nickname: 'name' })); + const execResult = databaseInsertLoginUser(db, user); + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} +export async function updateLoginUser(userStr) { + try { + const db = await getInstance(); + const user = convertToSnakeCaseObject(convertObjectField(JSON.parse(userStr), { nickname: 'name' })); + const execResult = databaseUpdateLoginUser(db, user); + const modifed = db.getRowsModified(); + if (modifed === 0) { + throw 'updateLoginUser no record updated'; + } + return formatResponse(execResult); + } + catch (e) { + console.error(e); + return formatResponse(undefined, DatabaseErrorCode.ErrorInit, JSON.stringify(e)); + } +} diff --git a/src/utils/openIM/api/index.d.ts b/src/utils/openIM/api/index.d.ts new file mode 100644 index 0000000..0e4e418 --- /dev/null +++ b/src/utils/openIM/api/index.d.ts @@ -0,0 +1,4 @@ +export declare const fileMapSet: (...args: unknown[]) => Promise; +export declare const fileMapClear: (...args: unknown[]) => Promise; +export declare function initDatabaseAPI(): void; +export declare const workerPromise: Promise | undefined; diff --git a/src/utils/openIM/api/index.js b/src/utils/openIM/api/index.js new file mode 100644 index 0000000..4b23daa --- /dev/null +++ b/src/utils/openIM/api/index.js @@ -0,0 +1,395 @@ +import { initBackend } from 'absurd-sql-optimized/dist/indexeddb-main-thread' +import { RPCMessageEvent, RPC } from 'rpc-shooter' +import { DatabaseErrorCode } from '../constant' +// @ts-ignore +// for vite +import IMWorker from './worker?worker' +import {batchUpdateGroupMembers} from "./database/groupMember.js"; +import {batchDeleteGroups} from "../sqls/index.js"; +import {batchInsertGroups} from "./database/groups.js"; +import { + batchDeleteConversations, + batchDeleteFriendRequests, batchDeleteGroupMembers, + batchInsertFriendRequests, + batchUpdateFriendRequests, getAllGroupMemberCount +} from "./database/index.js"; +// @ts-ignore +// for webpack4 +// import IMWorker from 'worker-loader!./worker.js'; +let rpc +let worker +function initWorker() { + if (typeof window === 'undefined') { + return + } + // for webpack4 or vite + worker = new IMWorker() + // for webpack5 + // worker = new Worker(new URL('./worker.js', import.meta.url)); + // This is only required because Safari doesn't support nested + // workers. This installs a handler that will proxy creating web + // workers through the main thread + initBackend(worker) + rpc = new RPC({ + event: new RPCMessageEvent({ + currentEndpoint: worker, + targetEndpoint: worker + }) + }) +} +function resetWorker() { + if (rpc) { + rpc.destroy() + rpc = undefined + } + if (worker) { + worker.terminate() + worker = undefined + } +} +initWorker() +function catchErrorHandle(error) { + // defined in rpc-shooter + if (error.code === -32300) { + resetWorker() + return JSON.stringify({ + data: '', + errCode: DatabaseErrorCode.ErrorDBTimeout, + errMsg: 'database maybe damaged' + }) + } + throw error +} +function registeMethodOnWindow(name, realName, needStringify = true) { + // console.info(`=> (database api) registe ${realName ?? name}`); + return async (...args) => { + if (!rpc || !worker) { + initWorker() + } + if (!rpc) { + return + } + try { + // console.info( + // `=> (invoked by go wasm) run ${realName ?? name} method with args ${JSON.stringify(args)}` + // ) + const response = await rpc.invoke(name, ...args, { timeout: 5000000 }) + // console.info( + // `=> (invoked by go wasm) run ${realName ?? name} method with response `, + // JSON.stringify(response) + // ) + if (!needStringify) { + return response + } + return JSON.stringify(response) + } catch (error) { + // defined in rpc-shooter + catchErrorHandle(error) + } + } +} +export const fileMapSet = registeMethodOnWindow('fileMapSet') +export const fileMapClear = registeMethodOnWindow('fileMapClear') +// register method on window for go wasm invoke +export function initDatabaseAPI() { + if (!rpc) { + return + } + // upload + window.wasmOpen = registeMethodOnWindow('wasmOpen') + window.wasmClose = registeMethodOnWindow('wasmClose') + window.wasmRead = registeMethodOnWindow('wasmRead', 'wasmRead', false) + window.getUpload = registeMethodOnWindow('getUpload') + window.insertUpload = registeMethodOnWindow('insertUpload') + window.updateUpload = registeMethodOnWindow('updateUpload') + window.deleteUpload = registeMethodOnWindow('deleteUpload') + window.initDB = registeMethodOnWindow('initDB') + window.close = registeMethodOnWindow('close') + // message + window.updateMessageBySeqs = registeMethodOnWindow('updateMessageBySeqs') + window.updateMessageByUserID = registeMethodOnWindow('updateMessageByUserID') + window.updateMessageBySeqEx = registeMethodOnWindow('updateMessageBySeqEx') + window.getMessage = registeMethodOnWindow('getMessage') + window.getMultipleMessage = registeMethodOnWindow('getMultipleMessage') + window.getSendingMessageList = registeMethodOnWindow('getSendingMessageList') + window.getNormalMsgSeq = registeMethodOnWindow('getNormalMsgSeq') + window.updateMessageTimeAndStatus = registeMethodOnWindow('updateMessageTimeAndStatus') + window.updateMessage = registeMethodOnWindow('updateMessage') + window.updateMessageBySeq = registeMethodOnWindow('updateMessageBySeq') + window.updateColumnsMessage = registeMethodOnWindow('updateColumnsMessage') + window.insertMessage = registeMethodOnWindow('insertMessage') + window.batchInsertMessageList = registeMethodOnWindow('batchInsertMessageList') + window.getMessageList = registeMethodOnWindow('getMessageList') + window.getMessageListNoTime = registeMethodOnWindow('getMessageListNoTime') + window.getMessageListNotDel = registeMethodOnWindow('getMessageListNotDel') + window.getLastMessageByTime = registeMethodOnWindow('getLastMessageByTime') + window.messageIfExists = registeMethodOnWindow('messageIfExists') + window.messageIfExistsBySeq = registeMethodOnWindow('messageIfExistsBySeq') + window.getAbnormalMsgSeq = registeMethodOnWindow('getAbnormalMsgSeq') + window.getAbnormalMsgSeqList = registeMethodOnWindow('getAbnormalMsgSeqList') + window.batchInsertExceptionMsg = registeMethodOnWindow('batchInsertExceptionMsg') + window.searchMessageByKeyword = registeMethodOnWindow('searchMessageByKeyword') + window.searchMessageByContentType = registeMethodOnWindow('searchMessageByContentType') + window.searchMessageByContentTypeAndKeyword = registeMethodOnWindow( + 'searchMessageByContentTypeAndKeyword' + ) + window.updateMsgSenderNickname = registeMethodOnWindow('updateMsgSenderNickname') + window.updateMsgSenderFaceURL = registeMethodOnWindow('updateMsgSenderFaceURL') + window.updateMsgSenderFaceURLAndSenderNickname = registeMethodOnWindow( + 'updateMsgSenderFaceURLAndSenderNickname' + ) + window.getMsgSeqByClientMsgID = registeMethodOnWindow('getMsgSeqByClientMsgID') + window.getMsgSeqListByGroupID = registeMethodOnWindow('getMsgSeqListByGroupID') + window.getMsgSeqListByPeerUserID = registeMethodOnWindow('getMsgSeqListByPeerUserID') + window.getMsgSeqListBySelfUserID = registeMethodOnWindow('getMsgSeqListBySelfUserID') + window.deleteAllMessage = registeMethodOnWindow('deleteAllMessage') + window.getAllUnDeleteMessageSeqList = registeMethodOnWindow('getAllUnDeleteMessageSeqList') + window.updateSingleMessageHasRead = registeMethodOnWindow('updateSingleMessageHasRead') + window.updateGroupMessageHasRead = registeMethodOnWindow('updateGroupMessageHasRead') + window.updateMessageStatusBySourceID = registeMethodOnWindow('updateMessageStatusBySourceID') + window.getAlreadyExistSeqList = registeMethodOnWindow('getAlreadyExistSeqList') + window.getMessageBySeq = registeMethodOnWindow('getMessageBySeq') + window.getMessagesByClientMsgIDs = registeMethodOnWindow('getMessagesByClientMsgIDs') + window.getMessagesBySeqs = registeMethodOnWindow('getMessagesBySeqs') + window.getConversationNormalMsgSeq = registeMethodOnWindow('getConversationNormalMsgSeq') + window.getConversationPeerNormalMsgSeq = registeMethodOnWindow('getConversationPeerNormalMsgSeq') + window.deleteConversationAllMessages = registeMethodOnWindow('deleteConversationAllMessages') + window.deleteConversationAllMessagesBySeq = registeMethodOnWindow('deleteConversationAllMessagesBySeq') + window.markDeleteConversationAllMessages = registeMethodOnWindow( + 'markDeleteConversationAllMessages' + ) + window.getUnreadMessage = registeMethodOnWindow('getUnreadMessage') + window.markConversationMessageAsReadBySeqs = registeMethodOnWindow( + 'markConversationMessageAsReadBySeqs' + ) + window.markConversationMessageAsReadDB = registeMethodOnWindow('markConversationMessageAsRead') + window.deleteConversationMsgs = registeMethodOnWindow('deleteConversationMsgs') + window.markConversationAllMessageAsRead = registeMethodOnWindow( + 'markConversationAllMessageAsRead' + ) + window.searchAllMessageByContentType = registeMethodOnWindow('searchAllMessageByContentType') + // conversation + window.getAllConversationListDB = registeMethodOnWindow('getAllConversationList') + window.getAllConversationListToSync = registeMethodOnWindow('getAllConversationListToSync') + window.getHiddenConversationList = registeMethodOnWindow('getHiddenConversationList') + window.getConversation = registeMethodOnWindow('getConversation') + window.getMultipleConversationDB = registeMethodOnWindow('getMultipleConversation') + window.updateColumnsConversation = registeMethodOnWindow('updateColumnsConversation') + window.updateConversation = registeMethodOnWindow( + 'updateColumnsConversation', + 'updateConversation' + ) + window.updateConversationForSync = registeMethodOnWindow( + 'updateColumnsConversation', + 'updateConversationForSync' + ) + window.decrConversationUnreadCount = registeMethodOnWindow('decrConversationUnreadCount') + window.batchInsertConversationList = registeMethodOnWindow('batchInsertConversationList') + window.batchUpdateConversationsByList = registeMethodOnWindow('batchUpdateConversationsByList') + window.batchUpdateConversationsByMap = registeMethodOnWindow('batchUpdateConversationsByMap') + window.insertConversation = registeMethodOnWindow('insertConversation') + window.getTotalUnreadMsgCountDB = registeMethodOnWindow('getTotalUnreadMsgCount') + window.getConversationByUserID = registeMethodOnWindow('getConversationByUserID') + window.getConversationListSplitDB = registeMethodOnWindow('getConversationListSplit') + window.deleteConversation = registeMethodOnWindow('deleteConversation') + window.batchDeleteConversations = registeMethodOnWindow('batchDeleteConversations') + window.batchUpdateConversationList = registeMethodOnWindow('batchUpdateConversationList') + window.conversationIfExists = registeMethodOnWindow('conversationIfExists') + window.resetConversation = registeMethodOnWindow('resetConversation') + window.resetAllConversation = registeMethodOnWindow('resetAllConversation') + window.clearConversation = registeMethodOnWindow('clearConversation') + window.clearConversationByTime = registeMethodOnWindow('clearConversationByTime') + window.clearAllConversation = registeMethodOnWindow('clearAllConversation') + window.setConversationDraftDB = registeMethodOnWindow('setConversationDraft') + window.removeConversationDraft = registeMethodOnWindow('removeConversationDraft') + window.unPinConversation = registeMethodOnWindow('unPinConversation') + // window.updateAllConversation = registeMethodOnWindow('updateAllConversation'); + window.incrConversationUnreadCount = registeMethodOnWindow('incrConversationUnreadCount') + window.setMultipleConversationRecvMsgOpt = registeMethodOnWindow( + 'setMultipleConversationRecvMsgOpt' + ) + window.getAllSingleConversationIDList = registeMethodOnWindow('getAllSingleConversationIDList') + window.getAllConversationIDList = registeMethodOnWindow('getAllConversationIDList') + window.getAllConversations = registeMethodOnWindow('getAllConversations') + // users + window.getLoginUser = registeMethodOnWindow('getLoginUser') + window.insertLoginUser = registeMethodOnWindow('insertLoginUser') + window.updateLoginUser = registeMethodOnWindow('updateLoginUser') + window.getStrangerInfo = registeMethodOnWindow('getStrangerInfo') + window.setStrangerInfo = registeMethodOnWindow('setStrangerInfo') + // super groups + window.getJoinedSuperGroupList = registeMethodOnWindow('getJoinedSuperGroupList') + window.getJoinedSuperGroupIDList = registeMethodOnWindow('getJoinedSuperGroupIDList') + window.getSuperGroupInfoByGroupID = registeMethodOnWindow('getSuperGroupInfoByGroupID') + window.deleteSuperGroup = registeMethodOnWindow('deleteSuperGroup') + window.insertSuperGroup = registeMethodOnWindow('insertSuperGroup') + window.updateSuperGroup = registeMethodOnWindow('updateSuperGroup') + // unread messages + window.deleteConversationUnreadMessageList = registeMethodOnWindow( + 'deleteConversationUnreadMessageList' + ) + window.batchInsertConversationUnreadMessageList = registeMethodOnWindow( + 'batchInsertConversationUnreadMessageList' + ) + // super group messages + window.superGroupGetMessage = registeMethodOnWindow('superGroupGetMessage') + window.superGroupGetMultipleMessage = registeMethodOnWindow('superGroupGetMultipleMessage') + window.superGroupGetNormalMinSeq = registeMethodOnWindow('superGroupGetNormalMinSeq') + window.getSuperGroupNormalMsgSeq = registeMethodOnWindow('getSuperGroupNormalMsgSeq') + window.superGroupUpdateMessageTimeAndStatus = registeMethodOnWindow( + 'superGroupUpdateMessageTimeAndStatus' + ) + window.superGroupUpdateMessage = registeMethodOnWindow('superGroupUpdateMessage') + window.superGroupInsertMessage = registeMethodOnWindow('superGroupInsertMessage') + window.superGroupBatchInsertMessageList = registeMethodOnWindow( + 'superGroupBatchInsertMessageList' + ) + window.superGroupGetMessageListNoTime = registeMethodOnWindow('superGroupGetMessageListNoTime') + window.superGroupGetMessageList = registeMethodOnWindow('superGroupGetMessageList') + window.superGroupUpdateColumnsMessage = registeMethodOnWindow('superGroupUpdateColumnsMessage') + window.superGroupDeleteAllMessage = registeMethodOnWindow('superGroupDeleteAllMessage') + window.superGroupSearchMessageByKeyword = registeMethodOnWindow( + 'superGroupSearchMessageByKeyword' + ) + window.superGroupSearchMessageByContentType = registeMethodOnWindow( + 'superGroupSearchMessageByContentType' + ) + window.superGroupSearchMessageByContentTypeAndKeyword = registeMethodOnWindow( + 'superGroupSearchMessageByContentTypeAndKeyword' + ) + window.superGroupUpdateMessageStatusBySourceID = registeMethodOnWindow( + 'superGroupUpdateMessageStatusBySourceID' + ) + window.updateMessageStatus = registeMethodOnWindow( + 'updateMessageStatus' + ) + + window.superGroupGetSendingMessageList = registeMethodOnWindow('superGroupGetSendingMessageList') + window.superGroupUpdateGroupMessageHasRead = registeMethodOnWindow( + 'superGroupUpdateGroupMessageHasRead' + ) + window.superGroupGetMsgSeqByClientMsgID = registeMethodOnWindow( + 'superGroupGetMsgSeqByClientMsgID' + ) + window.superGroupUpdateMsgSenderFaceURLAndSenderNickname = registeMethodOnWindow( + 'superGroupUpdateMsgSenderFaceURLAndSenderNickname' + ) + window.superGroupSearchAllMessageByContentType = registeMethodOnWindow( + 'superGroupSearchAllMessageByContentType' + ) + // debug + window.exec = registeMethodOnWindow('exec') + window.getRowsModified = registeMethodOnWindow('getRowsModified') + window.exportDB = async () => { + if (!rpc || !worker) { + initWorker() + } + if (!rpc) { + return + } + try { + console.info('=> (invoked by go wasm) run exportDB method ') + const result = await rpc.invoke('exportDB', undefined, { timeout: 5000 }) + console.info( + '=> (invoked by go wasm) run exportDB method with response ', + JSON.stringify(result) + ) + return result + } catch (error) { + catchErrorHandle(error) + } + } + // black + window.getBlackListDB = registeMethodOnWindow('getBlackList') + window.getBlackListUserID = registeMethodOnWindow('getBlackListUserID') + window.getBlackInfoByBlockUserID = registeMethodOnWindow('getBlackInfoByBlockUserID') + window.getBlackInfoList = registeMethodOnWindow('getBlackInfoList') + window.insertBlack = registeMethodOnWindow('insertBlack') + window.deleteBlack = registeMethodOnWindow('deleteBlack') + window.updateBlack = registeMethodOnWindow('updateBlack') + // friendRequest + window.insertFriendRequest = registeMethodOnWindow('insertFriendRequest') + window.batchInsertFriendRequests = registeMethodOnWindow('batchInsertFriendRequests') + window.batchDeleteFriendRequests = registeMethodOnWindow('batchDeleteFriendRequests') + window.batchUpdateFriendRequests = registeMethodOnWindow('batchUpdateFriendRequests') + window.deleteFriendRequestBothUserID = registeMethodOnWindow('deleteFriendRequestBothUserID') + window.updateFriendRequest = registeMethodOnWindow('updateFriendRequest') + window.getRecvFriendApplication = registeMethodOnWindow('getRecvFriendApplication') + window.getSendFriendApplication = registeMethodOnWindow('getSendFriendApplication') + window.getFriendApplicationByBothID = registeMethodOnWindow('getFriendApplicationByBothID') + window.getBothFriendReq = registeMethodOnWindow('getBothFriendReq') + // friend + window.insertFriend = registeMethodOnWindow('insertFriend') + window.deleteFriendDB = registeMethodOnWindow('deleteFriend') + window.updateFriend = registeMethodOnWindow('updateFriend') + window.getAllFriendList = registeMethodOnWindow('getAllFriendList') + window.searchFriendList = registeMethodOnWindow('searchFriendList') + window.getFriendInfoByFriendUserID = registeMethodOnWindow('getFriendInfoByFriendUserID') + window.getFriendInfoList = registeMethodOnWindow('getFriendInfoList') + window.getPageFriendList = registeMethodOnWindow('getPageFriendList') + window.getFriendInfos = registeMethodOnWindow('getFriendInfos') + window.batchInsertFriends = registeMethodOnWindow('batchInsertFriends') + // groups + window.insertGroup = registeMethodOnWindow('insertGroup') + window.batchInsertGroups = registeMethodOnWindow('batchInsertGroups') + window.deleteGroup = registeMethodOnWindow('deleteGroup') + window.batchDeleteGroups = registeMethodOnWindow('batchDeleteGroups') + window.updateGroup = registeMethodOnWindow('updateGroup') + window.batchUpdateGroups = registeMethodOnWindow('batchUpdateGroups') + window.getJoinedGroupListDB = registeMethodOnWindow('getJoinedGroupList') + window.getGroupInfoByGroupID = registeMethodOnWindow('getGroupInfoByGroupID') + window.getAllGroupInfoByGroupIDOrGroupName = registeMethodOnWindow( + 'getAllGroupInfoByGroupIDOrGroupName' + ) + window.subtractMemberCount = registeMethodOnWindow('subtractMemberCount') + window.addMemberCount = registeMethodOnWindow('addMemberCount') + window.getJoinedWorkingGroupIDList = registeMethodOnWindow('getJoinedWorkingGroupIDList') + window.getJoinedWorkingGroupList = registeMethodOnWindow('getJoinedWorkingGroupList') + window.getGroupMemberAllGroupIDs = registeMethodOnWindow('getGroupMemberAllGroupIDs') + window.getUserJoinedGroupIDs = registeMethodOnWindow('getUserJoinedGroupIDs') + window.getGroups = registeMethodOnWindow('getGroups') + // groupRequest + window.insertGroupRequest = registeMethodOnWindow('insertGroupRequest') + window.deleteGroupRequest = registeMethodOnWindow('deleteGroupRequest') + window.updateGroupRequest = registeMethodOnWindow('updateGroupRequest') + window.getSendGroupApplication = registeMethodOnWindow('getSendGroupApplication') + window.insertAdminGroupRequest = registeMethodOnWindow('insertAdminGroupRequest') + window.deleteAdminGroupRequest = registeMethodOnWindow('deleteAdminGroupRequest') + window.updateAdminGroupRequest = registeMethodOnWindow('updateAdminGroupRequest') + window.getAdminGroupApplication = registeMethodOnWindow('getAdminGroupApplication') + // groupMember + window.getGroupMemberInfoByGroupIDUserID = registeMethodOnWindow( + 'getGroupMemberInfoByGroupIDUserID' + ) + window.getAllGroupMemberList = registeMethodOnWindow('getAllGroupMemberList') + window.getAllGroupMemberUserIDList = registeMethodOnWindow('getAllGroupMemberUserIDList') + window.getGroupMemberCount = registeMethodOnWindow('getGroupMemberCount') + window.getAllGroupMemberCount = registeMethodOnWindow('getAllGroupMemberCount') + window.getGroupSomeMemberInfo = registeMethodOnWindow('getGroupSomeMemberInfo') + window.getGroupAdminID = registeMethodOnWindow('getGroupAdminID') + window.getGroupMemberListByGroupID = registeMethodOnWindow('getGroupMemberListByGroupID') + window.getGroupMemberListSplit = registeMethodOnWindow('getGroupMemberListSplit') + window.getGroupMemberOwnerAndAdminDB = registeMethodOnWindow('getGroupMemberOwnerAndAdmin') + window.getGroupMemberOwner = registeMethodOnWindow('getGroupMemberOwner') + window.getGroupMemberListSplitByJoinTimeFilter = registeMethodOnWindow( + 'getGroupMemberListSplitByJoinTimeFilter' + ) + window.getGroupOwnerAndAdminByGroupID = registeMethodOnWindow('getGroupOwnerAndAdminByGroupID') + window.getGroupMemberUIDListByGroupID = registeMethodOnWindow('getGroupMemberUIDListByGroupID') + window.insertGroupMember = registeMethodOnWindow('insertGroupMember') + window.batchInsertGroupMember = registeMethodOnWindow('batchInsertGroupMember') + window.deleteGroupMember = registeMethodOnWindow('deleteGroupMember') + window.deleteGroupAllMembers = registeMethodOnWindow('deleteGroupAllMembers') + window.batchDeleteGroupMembers = registeMethodOnWindow('batchDeleteGroupMembers') + window.updateGroupMember = registeMethodOnWindow('updateGroupMember') + window.batchUpdateGroupMembers = registeMethodOnWindow('batchUpdateGroupMembers') + window.updateGroupMemberField = registeMethodOnWindow('updateGroupMemberField') + window.searchGroupMembersDB = registeMethodOnWindow('searchGroupMembers', 'searchGroupMembersDB') + // temp cache chat logs + window.batchInsertTempCacheMessageList = registeMethodOnWindow('batchInsertTempCacheMessageList') + window.InsertTempCacheMessage = registeMethodOnWindow('InsertTempCacheMessage') + // notification + window.getNotificationAllSeqs = registeMethodOnWindow('getNotificationAllSeqs') + window.setNotificationSeq = registeMethodOnWindow('setNotificationSeq') +} +export const workerPromise = rpc?.connect(5000) diff --git a/src/utils/openIM/api/upload.d.ts b/src/utils/openIM/api/upload.d.ts new file mode 100644 index 0000000..185bda0 --- /dev/null +++ b/src/utils/openIM/api/upload.d.ts @@ -0,0 +1,5 @@ +export declare const fileMapSet: (uuid: string, file: File) => any; +export declare const fileMapClear: () => any; +export declare const wasmOpen: (uuid: string) => Promise; +export declare const wasmClose: (uuid: string) => Promise; +export declare const wasmRead: (uuid: string, offset: number, length: number) => Promise; diff --git a/src/utils/openIM/api/upload.js b/src/utils/openIM/api/upload.js new file mode 100644 index 0000000..6137cfd --- /dev/null +++ b/src/utils/openIM/api/upload.js @@ -0,0 +1,44 @@ +import { formatResponse } from '../utils'; +const fileMap = new Map(); +export const fileMapSet = (uuid, file) => { + fileMap.set(uuid, file); + return formatResponse(uuid); +}; +export const fileMapClear = () => { + fileMap.clear(); + return formatResponse(''); +}; +export const wasmOpen = (uuid) => { + return new Promise((resolve, reject) => { + const file = fileMap.get(uuid); + if (!file) { + reject('file not found'); + } + else { + resolve(formatResponse(file.size)); + } + }); +}; +export const wasmClose = async (uuid) => { + return new Promise(resolve => { + fileMap.delete(uuid); + resolve(formatResponse(uuid)); + }); +}; +export const wasmRead = (uuid, offset, length) => { + const file = fileMap.get(uuid); + if (!file) { + throw 'file not found'; + } + const blob = file.slice(offset, offset + length); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.onerror = () => { + reject(reader.error); + }; + reader.readAsArrayBuffer(blob); + }); +}; diff --git a/src/utils/openIM/api/worker.d.ts b/src/utils/openIM/api/worker.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/utils/openIM/api/worker.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/src/utils/openIM/api/worker.js b/src/utils/openIM/api/worker.js new file mode 100644 index 0000000..1043544 --- /dev/null +++ b/src/utils/openIM/api/worker.js @@ -0,0 +1,414 @@ +import { RPC, RPCMessageEvent } from 'rpc-shooter' +import { + init, + close, + // message + getMessage, + getMultipleMessage, + getSendingMessageList, + updateMessageTimeAndStatus, + updateMessageStatus, + updateMessage, + updateMessageBySeq, + updateColumnsMessage, + insertMessage, + batchInsertMessageList, + getMessageList, + getMessageListNoTime, + getMessageListNotDel, + getLastMessageByTime, + messageIfExists, + searchMessageByKeyword, + searchMessageByContentType, + searchMessageByContentTypeAndKeyword, + updateMsgSenderFaceURLAndSenderNickname, + updateMessageBySeqEx, + updateMessageBySeqs, + updateMessageByUserID, + // conversation + getAllConversationList, + getAllConversationListToSync, + getHiddenConversationList, + getConversation, + batchDeleteConversations, + getMultipleConversation, + updateColumnsConversation, + decrConversationUnreadCount, + batchInsertConversationList, + batchUpdateConversationsByList, + batchUpdateConversationsByMap, + insertConversation, + getTotalUnreadMsgCount, + batchUpdateConversationList, + clearAllConversation, + clearConversation, + clearConversationByTime, + conversationIfExists, + deleteConversation, + getConversationByUserID, + getConversationListSplit, + incrConversationUnreadCount, + removeConversationDraft, + resetAllConversation, + resetConversation, + setConversationDraft, + setMultipleConversationRecvMsgOpt, + unPinConversation, + // users + getLoginUser, + insertLoginUser, + updateLoginUser, + getStrangerInfo, + setStrangerInfo, + // super group + getJoinedSuperGroupList, + getJoinedSuperGroupIDList, + getSuperGroupInfoByGroupID, + deleteSuperGroup, + insertSuperGroup, + updateSuperGroup, + // unread messages + deleteConversationUnreadMessageList, + batchInsertConversationUnreadMessageList, + // black + getBlackList, + getBlackListUserID, + getBlackInfoByBlockUserID, + getBlackInfoList, + insertBlack, + deleteBlack, + updateBlack, + // friendRequest + insertFriendRequest, + batchInsertFriendRequests, + batchUpdateFriendRequests, + batchDeleteFriendRequests, + deleteFriendRequestBothUserID, + updateFriendRequest, + getRecvFriendApplication, + getSendFriendApplication, + getFriendApplicationByBothID, + getBothFriendReq, + // groups + insertGroup, + batchInsertGroups, + deleteGroup, + batchDeleteGroups, + updateGroup, + batchUpdateGroups, + getJoinedGroupList, + getGroupInfoByGroupID, + getAllGroupInfoByGroupIDOrGroupName, + subtractMemberCount, + addMemberCount, + getJoinedWorkingGroupIDList, + getJoinedWorkingGroupList, + getGroups, + // groupRequest + insertGroupRequest, + deleteGroupRequest, + updateGroupRequest, + getSendGroupApplication, + insertAdminGroupRequest, + deleteAdminGroupRequest, + updateAdminGroupRequest, + getAdminGroupApplication, + // friend + insertFriend, + deleteFriend, + updateFriend, + getAllFriendList, + searchFriendList, + getFriendInfoList, + getFriendInfoByFriendUserID, + getFriendInfos, + batchInsertFriends, + batchUpdateFriends, + batchDeleteFriends, + // groupMember + batchInsertGroupMember, + deleteGroupAllMembers, + batchDeleteGroupMembers, + deleteGroupMember, + getAllGroupMemberList, + getAllGroupMemberUserIDList, + getGroupAdminID, + getGroupMemberCount, + getAllGroupMemberCount, + getGroupMemberInfoByGroupIDUserID, + getGroupMemberListByGroupID, + getGroupMemberListSplit, + getGroupMemberListSplitByJoinTimeFilter, + getGroupMemberOwner, + getGroupMemberOwnerAndAdmin, + getGroupMemberUIDListByGroupID, + getGroupOwnerAndAdminByGroupID, + getGroupSomeMemberInfo, + insertGroupMember, + searchGroupMembers, + updateGroupMember, + batchUpdateGroupMembers, + updateGroupMemberField, + getUserJoinedGroupIDs, + // temp cache chatlogs + batchInsertTempCacheMessageList, + InsertTempCacheMessage, + getAllSingleConversationIDList, + getAllConversationIDList, + getPageFriendList, + getGroupMemberAllGroupIDs, + getAlreadyExistSeqList, + markConversationAllMessageAsRead, + searchAllMessageByContentType, + deleteConversationMsgs, + markConversationMessageAsRead, + markConversationMessageAsReadBySeqs, + markDeleteConversationAllMessages, + getUnreadMessage, + getConversationPeerNormalMsgSeq, + getConversationNormalMsgSeq, + deleteConversationAllMessages, + deleteConversationAllMessagesBySeq, + getMessagesByClientMsgIDs, + getMessagesBySeqs, + getMessageBySeq, + getAllConversations, + // upload + updateUpload, + deleteUpload, + getUpload, + insertUpload, +} from '../api/database' +import { getInstance } from './database/instance' +import { setNotificationSeq, getNotificationAllSeqs } from './database/notification' +import { fileMapClear, fileMapSet, wasmClose, wasmOpen, wasmRead } from './upload' +// import {batchInsertGroups} from "./database/groups.js"; +const ctx = self +const rpc = new RPC({ + event: new RPCMessageEvent({ + currentEndpoint: ctx, + targetEndpoint: ctx + }) +}) +// upload +rpc.registerMethod('fileMapSet', fileMapSet) +rpc.registerMethod('fileMapClear', fileMapClear) +rpc.registerMethod('wasmOpen', wasmOpen) +rpc.registerMethod('wasmClose', wasmClose) +rpc.registerMethod('wasmRead', wasmRead) +rpc.registerMethod('getUpload', getUpload) +rpc.registerMethod('insertUpload', insertUpload) +rpc.registerMethod('updateUpload', updateUpload) +rpc.registerMethod('deleteUpload', deleteUpload) +rpc.registerMethod('initDB', init) +rpc.registerMethod('close', close) +// message +rpc.registerMethod('updateMessageBySeqs', updateMessageBySeqs) +rpc.registerMethod('updateMessageByUserID', updateMessageByUserID) +rpc.registerMethod('updateMessageBySeqEx', updateMessageBySeqEx) +rpc.registerMethod('getMessage', getMessage) +rpc.registerMethod('getMultipleMessage', getMultipleMessage) +rpc.registerMethod('getSendingMessageList', getSendingMessageList) +rpc.registerMethod('updateMessageTimeAndStatus', updateMessageTimeAndStatus) +rpc.registerMethod('updateMessageStatus', updateMessageStatus) +rpc.registerMethod('updateMessage', updateMessage) +rpc.registerMethod('updateMessageBySeq', updateMessageBySeq) +rpc.registerMethod('updateColumnsMessage', updateColumnsMessage) +rpc.registerMethod('insertMessage', insertMessage) +rpc.registerMethod('batchInsertMessageList', batchInsertMessageList) +rpc.registerMethod('getMessageList', getMessageList) +rpc.registerMethod('getMessageListNoTime', getMessageListNoTime) +rpc.registerMethod('getMessageListNotDel', getMessageListNotDel) +rpc.registerMethod('getLastMessageByTime', getLastMessageByTime) + +rpc.registerMethod('messageIfExists', messageIfExists) +rpc.registerMethod('searchMessageByKeyword', searchMessageByKeyword) +rpc.registerMethod('searchMessageByContentType', searchMessageByContentType) +rpc.registerMethod('searchMessageByContentTypeAndKeyword', searchMessageByContentTypeAndKeyword) +rpc.registerMethod( + 'updateMsgSenderFaceURLAndSenderNickname', + updateMsgSenderFaceURLAndSenderNickname +) +rpc.registerMethod('getAlreadyExistSeqList', getAlreadyExistSeqList) +rpc.registerMethod('getMessageBySeq', getMessageBySeq) +rpc.registerMethod('getMessagesByClientMsgIDs', getMessagesByClientMsgIDs) +rpc.registerMethod('getMessagesBySeqs', getMessagesBySeqs) +rpc.registerMethod('getConversationNormalMsgSeq', getConversationNormalMsgSeq) +rpc.registerMethod('getConversationPeerNormalMsgSeq', getConversationPeerNormalMsgSeq) +rpc.registerMethod('deleteConversationAllMessages', deleteConversationAllMessages) +rpc.registerMethod('deleteConversationAllMessagesBySeq', deleteConversationAllMessagesBySeq) +rpc.registerMethod('markDeleteConversationAllMessages', markDeleteConversationAllMessages) +rpc.registerMethod('getUnreadMessage', getUnreadMessage) +rpc.registerMethod('markConversationMessageAsReadBySeqs', markConversationMessageAsReadBySeqs) +rpc.registerMethod('markConversationMessageAsRead', markConversationMessageAsRead) +rpc.registerMethod('deleteConversationMsgs', deleteConversationMsgs) +rpc.registerMethod('markConversationAllMessageAsRead', markConversationAllMessageAsRead) +rpc.registerMethod('searchAllMessageByContentType', searchAllMessageByContentType) +// conversation +rpc.registerMethod('getAllConversationList', getAllConversationList) +rpc.registerMethod('getAllConversationListToSync', getAllConversationListToSync) +rpc.registerMethod('getHiddenConversationList', getHiddenConversationList) +rpc.registerMethod('getConversation', getConversation) +rpc.registerMethod('getMultipleConversation', getMultipleConversation) +rpc.registerMethod('updateColumnsConversation', updateColumnsConversation) +rpc.registerMethod('decrConversationUnreadCount', decrConversationUnreadCount) +rpc.registerMethod('batchInsertConversationList', batchInsertConversationList) +rpc.registerMethod('batchUpdateConversationsByList', batchUpdateConversationsByList) +rpc.registerMethod('batchUpdateConversationsByMap', batchUpdateConversationsByMap) +rpc.registerMethod('getTotalUnreadMsgCount', getTotalUnreadMsgCount) +rpc.registerMethod('insertConversation', insertConversation) +rpc.registerMethod('getConversationByUserID', getConversationByUserID) +rpc.registerMethod('getConversationListSplit', getConversationListSplit) +rpc.registerMethod('deleteConversation', deleteConversation) +rpc.registerMethod('batchDeleteConversations', batchDeleteConversations) +rpc.registerMethod('batchUpdateConversationList', batchUpdateConversationList) +rpc.registerMethod('conversationIfExists', conversationIfExists) +rpc.registerMethod('resetConversation', resetConversation) +rpc.registerMethod('resetAllConversation', resetAllConversation) +rpc.registerMethod('clearConversation', clearConversation) +rpc.registerMethod('clearConversationByTime', clearConversationByTime) +rpc.registerMethod('clearAllConversation', clearAllConversation) +rpc.registerMethod('setConversationDraft', setConversationDraft) +rpc.registerMethod('removeConversationDraft', removeConversationDraft) +rpc.registerMethod('unPinConversation', unPinConversation) +rpc.registerMethod('incrConversationUnreadCount', incrConversationUnreadCount) +rpc.registerMethod('setMultipleConversationRecvMsgOpt', setMultipleConversationRecvMsgOpt) +rpc.registerMethod('getAllSingleConversationIDList', getAllSingleConversationIDList) +rpc.registerMethod('getAllConversationIDList', getAllConversationIDList) +rpc.registerMethod('getAllConversations', getAllConversations) +rpc.registerMethod('getLoginUser', getLoginUser) +rpc.registerMethod('insertLoginUser', insertLoginUser) +rpc.registerMethod('updateLoginUser', updateLoginUser) +rpc.registerMethod('getStrangerInfo', getStrangerInfo) +rpc.registerMethod('setStrangerInfo', setStrangerInfo) +rpc.registerMethod('getJoinedSuperGroupList', getJoinedSuperGroupList) +rpc.registerMethod('getJoinedSuperGroupIDList', getJoinedSuperGroupIDList) +rpc.registerMethod('getSuperGroupInfoByGroupID', getSuperGroupInfoByGroupID) +rpc.registerMethod('deleteSuperGroup', deleteSuperGroup) +rpc.registerMethod('insertSuperGroup', insertSuperGroup) +rpc.registerMethod('updateSuperGroup', updateSuperGroup) +rpc.registerMethod('deleteConversationUnreadMessageList', deleteConversationUnreadMessageList) +rpc.registerMethod( + 'batchInsertConversationUnreadMessageList', + batchInsertConversationUnreadMessageList +) +// black +rpc.registerMethod('getBlackList', getBlackList) +rpc.registerMethod('getBlackListUserID', getBlackListUserID) +rpc.registerMethod('getBlackInfoByBlockUserID', getBlackInfoByBlockUserID) +rpc.registerMethod('getBlackInfoList', getBlackInfoList) +rpc.registerMethod('insertBlack', insertBlack) +rpc.registerMethod('deleteBlack', deleteBlack) +rpc.registerMethod('updateBlack', updateBlack) +// friendRequest +rpc.registerMethod('insertFriendRequest', insertFriendRequest) +rpc.registerMethod('batchInsertFriendRequests', batchInsertFriendRequests) +rpc.registerMethod('batchUpdateFriendRequests', batchUpdateFriendRequests) +rpc.registerMethod('batchDeleteFriendRequests', batchDeleteFriendRequests) +rpc.registerMethod('deleteFriendRequestBothUserID', deleteFriendRequestBothUserID) +rpc.registerMethod('updateFriendRequest', updateFriendRequest) +rpc.registerMethod('getRecvFriendApplication', getRecvFriendApplication) +rpc.registerMethod('getSendFriendApplication', getSendFriendApplication) +rpc.registerMethod('getFriendApplicationByBothID', getFriendApplicationByBothID) +rpc.registerMethod('getBothFriendReq', getBothFriendReq) +// groups +rpc.registerMethod('insertGroup', insertGroup) +rpc.registerMethod('batchInsertGroups', batchInsertGroups) +rpc.registerMethod('deleteGroup', deleteGroup) +rpc.registerMethod('batchDeleteGroups', batchDeleteGroups) +rpc.registerMethod('updateGroup', updateGroup) +rpc.registerMethod('batchUpdateGroups', batchUpdateGroups) +rpc.registerMethod('getJoinedGroupList', getJoinedGroupList) +rpc.registerMethod('getGroupInfoByGroupID', getGroupInfoByGroupID) +rpc.registerMethod('getAllGroupInfoByGroupIDOrGroupName', getAllGroupInfoByGroupIDOrGroupName) +rpc.registerMethod('subtractMemberCount', subtractMemberCount) +rpc.registerMethod('addMemberCount', addMemberCount) +rpc.registerMethod('getJoinedWorkingGroupIDList', getJoinedWorkingGroupIDList) +rpc.registerMethod('getJoinedWorkingGroupList', getJoinedWorkingGroupList) +rpc.registerMethod('getGroupMemberAllGroupIDs', getGroupMemberAllGroupIDs) +rpc.registerMethod('getGroups', getGroups) +// groupMembers +rpc.registerMethod('getGroupMemberInfoByGroupIDUserID', getGroupMemberInfoByGroupIDUserID) +rpc.registerMethod('getAllGroupMemberList', getAllGroupMemberList) +rpc.registerMethod('getAllGroupMemberUserIDList', getAllGroupMemberUserIDList) +rpc.registerMethod('getGroupMemberCount', getGroupMemberCount) +rpc.registerMethod('getAllGroupMemberCount', getAllGroupMemberCount) +rpc.registerMethod('getGroupSomeMemberInfo', getGroupSomeMemberInfo) +rpc.registerMethod('getGroupAdminID', getGroupAdminID) +rpc.registerMethod('getGroupMemberListByGroupID', getGroupMemberListByGroupID) +rpc.registerMethod('getGroupMemberListSplit', getGroupMemberListSplit) +rpc.registerMethod('getGroupMemberOwnerAndAdmin', getGroupMemberOwnerAndAdmin) +rpc.registerMethod('getGroupMemberOwner', getGroupMemberOwner) +rpc.registerMethod( + 'getGroupMemberListSplitByJoinTimeFilter', + getGroupMemberListSplitByJoinTimeFilter +) +rpc.registerMethod('getGroupOwnerAndAdminByGroupID', getGroupOwnerAndAdminByGroupID) +rpc.registerMethod('getGroupMemberUIDListByGroupID', getGroupMemberUIDListByGroupID) +rpc.registerMethod('insertGroupMember', insertGroupMember) +rpc.registerMethod('batchInsertGroupMember', batchInsertGroupMember) +rpc.registerMethod('deleteGroupMember', deleteGroupMember) +rpc.registerMethod('deleteGroupAllMembers', deleteGroupAllMembers) +rpc.registerMethod('batchDeleteGroupMembers', batchDeleteGroupMembers) +rpc.registerMethod('updateGroupMember', updateGroupMember) +rpc.registerMethod('batchUpdateGroupMembers', batchUpdateGroupMembers) +rpc.registerMethod('updateGroupMemberField', updateGroupMemberField) +rpc.registerMethod('searchGroupMembers', searchGroupMembers) +rpc.registerMethod('getUserJoinedGroupIDs', getUserJoinedGroupIDs) +// groupRequest +rpc.registerMethod('insertGroupRequest', insertGroupRequest) +rpc.registerMethod('deleteGroupRequest', deleteGroupRequest) +rpc.registerMethod('updateGroupRequest', updateGroupRequest) +rpc.registerMethod('getSendGroupApplication', getSendGroupApplication) +rpc.registerMethod('insertAdminGroupRequest', insertAdminGroupRequest) +rpc.registerMethod('deleteAdminGroupRequest', deleteAdminGroupRequest) +rpc.registerMethod('updateAdminGroupRequest', updateAdminGroupRequest) +rpc.registerMethod('getAdminGroupApplication', getAdminGroupApplication) +// friend +rpc.registerMethod('insertFriend', insertFriend) +rpc.registerMethod('deleteFriend', deleteFriend) +rpc.registerMethod('updateFriend', updateFriend) +rpc.registerMethod('getAllFriendList', getAllFriendList) +rpc.registerMethod('searchFriendList', searchFriendList) +rpc.registerMethod('getFriendInfoByFriendUserID', getFriendInfoByFriendUserID) +rpc.registerMethod('getFriendInfoList', getFriendInfoList) +rpc.registerMethod('getPageFriendList', getPageFriendList) +rpc.registerMethod('getFriendInfos', getFriendInfos) +rpc.registerMethod('batchInsertFriends', batchInsertFriends) +rpc.registerMethod('batchUpdateFriends', batchUpdateFriends) +rpc.registerMethod('batchDeleteFriends', batchDeleteFriends) +// temp cache chatlogs +rpc.registerMethod('batchInsertTempCacheMessageList', batchInsertTempCacheMessageList) +rpc.registerMethod('InsertTempCacheMessage', InsertTempCacheMessage) +// notification +rpc.registerMethod('getNotificationAllSeqs', getNotificationAllSeqs) +rpc.registerMethod('setNotificationSeq', setNotificationSeq) +rpc.registerMethod('exec', async (sql) => { + const db = await getInstance() + try { + const result = db.exec(sql) + console.info(`sql debug with exec sql = ${sql.trim()} , return `, result) + } catch (error) { + console.info(`sql debug with exec sql = ${sql} , return `, error) + } +}) +rpc.registerMethod('getRowsModified', async () => { + const db = await getInstance() + try { + const result = db.getRowsModified() + console.info('sql debug with getRowsModified return ', result) + } catch (error) { + console.info('sql debug with getRowsModified return ', error) + } +}) +rpc.registerMethod('exportDB', async () => { + const db = await getInstance() + try { + const data = db.export() + const blob = new Blob([data]) + const blobHref = URL.createObjectURL(blob) + return blobHref + } catch (error) { + console.info('sql export error, return ', error) + } +}) diff --git a/src/utils/openIM/constant/index.d.ts b/src/utils/openIM/constant/index.d.ts new file mode 100644 index 0000000..6199ef6 --- /dev/null +++ b/src/utils/openIM/constant/index.d.ts @@ -0,0 +1,69 @@ +export declare const DatabaseErrorCode: { + ErrorInit: number; + ErrorNoRecord: number; + ErrorDBTimeout: number; +}; +export declare enum CbEvents { + Login = "Login", + OnConnectFailed = "OnConnectFailed", + OnConnectSuccess = "OnConnectSuccess", + OnConnecting = "OnConnecting", + OnKickedOffline = "OnKickedOffline", + OnKickedOfflineWithExData = "OnKickedOfflineWithExData", + OnPingTimeout = "OnPingTimeout", + OnSelfInfoUpdated = "OnSelfInfoUpdated", + OnUserTokenExpired = "OnUserTokenExpired", + OnProgress = "OnProgress", + OnNewRecvMessageBurnedBatch = "OnNewRecvMessageBurnedBatch", + OnNewRecvMessageBurned = "OnNewRecvMessageBurned", + OnRecvNewMessage = "OnRecvNewMessage", + OnRecvNewMessages = "OnRecvNewMessages", + OnRecvMessageRevoked = "OnRecvMessageRevoked", + OnNewRecvMessageRevoked = "OnNewRecvMessageRevoked", + OnRecvC2CReadReceipt = "OnRecvC2CReadReceipt", + OnRecvGroupReadReceipt = "OnRecvGroupReadReceipt", + OnConversationChanged = "OnConversationChanged", + OnNewConversation = "OnNewConversation", + OnSyncServerFailed = "OnSyncServerFailed", + OnSyncServerFinish = "OnSyncServerFinish", + OnSyncServerStart = "OnSyncServerStart", + OnTotalUnreadMessageCountChanged = "OnTotalUnreadMessageCountChanged", + OnBlackAdded = "OnBlackAdded", + OnBlackDeleted = "OnBlackDeleted", + OnFriendApplicationAccepted = "OnFriendApplicationAccepted", + OnFriendApplicationAdded = "OnFriendApplicationAdded", + OnFriendApplicationDeleted = "OnFriendApplicationDeleted", + OnFriendApplicationRejected = "OnFriendApplicationRejected", + OnFriendInfoChanged = "OnFriendInfoChanged", + OnFriendAdded = "OnFriendAdded", + OnFriendDeleted = "OnFriendDeleted", + OnJoinedGroupAdded = "OnJoinedGroupAdded", + OnJoinedGroupDeleted = "OnJoinedGroupDeleted", + OnGroupDismissed = "OnGroupDismissed", + OnGroupMemberAdded = "OnGroupMemberAdded", + OnGroupMemberDeleted = "OnGroupMemberDeleted", + OnGroupApplicationAdded = "OnGroupApplicationAdded", + OnGroupApplicationDeleted = "OnGroupApplicationDeleted", + OnGroupInfoChanged = "OnGroupInfoChanged", + OnGroupMemberInfoChanged = "OnGroupMemberInfoChanged", + OnGroupApplicationAccepted = "OnGroupApplicationAccepted", + OnGroupApplicationRejected = "OnGroupApplicationRejected", + UploadComplete = "UploadComplete", + OnRecvCustomBusinessMessage = "OnRecvCustomBusinessMessage", + OnUserStatusChanged = "OnUserStatusChanged", + OnReceiveNewInvitation = "OnReceiveNewInvitation", + OnInviteeAccepted = "OnInviteeAccepted", + OnInviteeRejected = "OnInviteeRejected", + OnInvitationCancelled = "OnInvitationCancelled", + OnHangUp = "OnHangUp", + OnInvitationTimeout = "OnInvitationTimeout", + OnInviteeAcceptedByOtherDevice = "OnInviteeAcceptedByOtherDevice", + OnInviteeRejectedByOtherDevice = "OnInviteeRejectedByOtherDevice", + OnStreamChange = "OnStreamChange", + OnRoomParticipantConnected = "OnRoomParticipantConnected", + OnRoomParticipantDisconnected = "OnRoomParticipantDisconnected", + OnDeleteUserMsg = "OnDeleteUserMsg", + OnCustomNotifications = "OnCustomNotifications", + OnReceiveCustomSignal = "OnReceiveCustomSignal", + CancelSuccess = "CancelSuccess", +} diff --git a/src/utils/openIM/constant/index.js b/src/utils/openIM/constant/index.js new file mode 100644 index 0000000..ff20a72 --- /dev/null +++ b/src/utils/openIM/constant/index.js @@ -0,0 +1,73 @@ +export const DatabaseErrorCode = { + ErrorInit: 10001, + ErrorNoRecord: 10002, + ErrorDBTimeout: 10003, +}; +export var CbEvents; +(function (CbEvents) { + CbEvents["Login"] = "Login"; + CbEvents["OnConnectFailed"] = "OnConnectFailed"; + CbEvents["OnConnectSuccess"] = "OnConnectSuccess"; + CbEvents["OnConnecting"] = "OnConnecting"; + CbEvents["OnKickedOffline"] = "OnKickedOffline"; + CbEvents["OnKickedOfflineWithExData"] = "OnKickedOfflineWithExData"; + CbEvents["OnPingTimeout"] = "OnPingTimeout"; + CbEvents["OnSelfInfoUpdated"] = "OnSelfInfoUpdated"; + CbEvents["OnUserTokenExpired"] = "OnUserTokenExpired"; + CbEvents["OnProgress"] = "OnProgress"; + CbEvents["OnNewRecvMessageBurnedBatch"] = "OnNewRecvMessageBurnedBatch"; + CbEvents["OnNewRecvMessageBurned"] = "OnNewRecvMessageBurned"; + CbEvents["OnRecvNewMessage"] = "OnRecvNewMessage"; + CbEvents["OnRecvNewMessages"] = "OnRecvNewMessages"; + CbEvents["OnRecvMessageRevoked"] = "OnRecvMessageRevoked"; + CbEvents["OnNewRecvMessageRevoked"] = "OnNewRecvMessageRevoked"; + CbEvents["OnRecvC2CReadReceipt"] = "OnRecvC2CReadReceipt"; + CbEvents["OnRecvGroupReadReceipt"] = "OnRecvGroupReadReceipt"; + CbEvents["OnConversationChanged"] = "OnConversationChanged"; + CbEvents["OnNewConversation"] = "OnNewConversation"; + CbEvents["OnSyncServerFailed"] = "OnSyncServerFailed"; + CbEvents["OnSyncServerFinish"] = "OnSyncServerFinish"; + CbEvents["OnSyncServerStart"] = "OnSyncServerStart"; + CbEvents["OnTotalUnreadMessageCountChanged"] = "OnTotalUnreadMessageCountChanged"; + CbEvents["OnBlackAdded"] = "OnBlackAdded"; + CbEvents["OnBlackDeleted"] = "OnBlackDeleted"; + CbEvents["OnFriendApplicationAccepted"] = "OnFriendApplicationAccepted"; + CbEvents["OnFriendApplicationAdded"] = "OnFriendApplicationAdded"; + CbEvents["OnFriendApplicationDeleted"] = "OnFriendApplicationDeleted"; + CbEvents["OnFriendApplicationRejected"] = "OnFriendApplicationRejected"; + CbEvents["OnFriendInfoChanged"] = "OnFriendInfoChanged"; + CbEvents["OnFriendAdded"] = "OnFriendAdded"; + CbEvents["OnFriendDeleted"] = "OnFriendDeleted"; + CbEvents["OnJoinedGroupAdded"] = "OnJoinedGroupAdded"; + CbEvents["OnJoinedGroupDeleted"] = "OnJoinedGroupDeleted"; + CbEvents["OnGroupDismissed"] = "OnGroupDismissed"; + CbEvents["OnGroupMemberAdded"] = "OnGroupMemberAdded"; + CbEvents["OnGroupMemberDeleted"] = "OnGroupMemberDeleted"; + CbEvents["OnGroupApplicationAdded"] = "OnGroupApplicationAdded"; + CbEvents["OnGroupApplicationDeleted"] = "OnGroupApplicationDeleted"; + CbEvents["OnGroupInfoChanged"] = "OnGroupInfoChanged"; + CbEvents["OnGroupMemberInfoChanged"] = "OnGroupMemberInfoChanged"; + CbEvents["OnGroupApplicationAccepted"] = "OnGroupApplicationAccepted"; + CbEvents["OnGroupApplicationRejected"] = "OnGroupApplicationRejected"; + CbEvents["UploadComplete"] = "UploadComplete"; + CbEvents["OnRecvCustomBusinessMessage"] = "OnRecvCustomBusinessMessage"; + CbEvents["OnUserStatusChanged"] = "OnUserStatusChanged"; + CbEvents["OnIsJoinGroup"] = "OnIsJoinGroup"; + // rtc + CbEvents["OnReceiveNewInvitation"] = "OnReceiveNewInvitation"; + CbEvents["OnInviteeAccepted"] = "OnInviteeAccepted"; + CbEvents["OnInviteeRejected"] = "OnInviteeRejected"; + CbEvents["OnInvitationCancelled"] = "OnInvitationCancelled"; + CbEvents["OnHangUp"] = "OnHangUp"; + CbEvents["OnInvitationTimeout"] = "OnInvitationTimeout"; + CbEvents["OnInviteeAcceptedByOtherDevice"] = "OnInviteeAcceptedByOtherDevice"; + CbEvents["OnInviteeRejectedByOtherDevice"] = "OnInviteeRejectedByOtherDevice"; + // meeting + CbEvents["OnStreamChange"] = "OnStreamChange"; + CbEvents["OnRoomParticipantConnected"] = "OnRoomParticipantConnected"; + CbEvents["OnRoomParticipantDisconnected"] = "OnRoomParticipantDisconnected"; + CbEvents["OnReceiveCustomSignal"] = "OnReceiveCustomSignal"; + CbEvents["OnDeleteUserMsg"] = "OnDeleteUserMsg"; + CbEvents["OnCustomNotifications"] = "OnCustomNotifications"; + CbEvents["CancelSuccess"] = "CancelSuccess"; +})(CbEvents || (CbEvents = {})); diff --git a/src/utils/openIM/index.d.ts b/src/utils/openIM/index.d.ts new file mode 100644 index 0000000..632ef97 --- /dev/null +++ b/src/utils/openIM/index.d.ts @@ -0,0 +1 @@ +export { getSDK } from './sdk'; diff --git a/src/utils/openIM/index.js b/src/utils/openIM/index.js new file mode 100644 index 0000000..632ef97 --- /dev/null +++ b/src/utils/openIM/index.js @@ -0,0 +1 @@ +export { getSDK } from './sdk'; diff --git a/src/utils/openIM/sdk/index.d.ts b/src/utils/openIM/sdk/index.d.ts new file mode 100644 index 0000000..725cf94 --- /dev/null +++ b/src/utils/openIM/sdk/index.d.ts @@ -0,0 +1,180 @@ +import Emitter from '../utils/emitter'; +import { AccessFriendParams, AccessGroupParams, AccessMessageParams, AccessMessagesParams, AddFriendParams, AdvancedMsgParams, AdvancedQuoteMsgParams, AtMsgParams, ChangeGroupMemberMuteParams, ChangeGroupMuteParams, CreateGroupParams, CreateMeetingParams, CustomMsgParams, CustomSignalParams, FaceMessageParams, FileMsgFullParams, FileMsgParams, FindMessageParams, GetAdvancedHistoryMsgParams, GetGroupMemberByTimeParams, GetGroupMemberParams, GetHistoryMsgParams, GetOneConversationParams, GetOneCveParams, GetUserInfoWithCacheParams, GroupBaseInfo, ImageMsgParams, InitAndLoginConfig, InsertGroupMsgParams, InsertSingleMsgParams, InviteGroupParams, isRecvParams, JoinGroupParams, LocationMsgParams, MarkNotiParams, MeetingOperateStreamParams, MemberExParams, MemberNameParams, MergerMsgParams, PartialUserItem, PinCveParams, QuoteMsgParams, RemarkFriendParams, RtcActionParams, SearchFriendParams, SearchGroupMemberParams, SearchGroupParams, SearchLocalParams, SendMsgParams, SetBurnDurationParams, SetConversationMsgDestructParams, SetConversationMsgDestructTimeParams, SetDraftParams, SetGroupRoleParams, SetGroupVerificationParams, SetMemberAuthParams, SetMessageLocalExParams, SetPrvParams, SignalingInviteParams, SoundMsgParams, SouondMsgFullParams, SplitParams, TransferGroupParams, TypingUpdateParams, UpdateMeetingParams, UploadFileParams, VideoMsgFullParams, VideoMsgParams,ConfigParams } from '../types/params'; +import { AdvancedGetMessageResult, BlackUserItem, CardElem, ConversationItem, FriendApplicationItem, FriendshipInfo, FriendUserItem, FullUserItem, FullUserItemWithCache, GroupApplicationItem, GroupItem, GroupMemberItem, MessageItem, SearchedFriendsInfo, SearchMessageResult, SelfUserInfo, UserOnlineState, WsResponse } from '../types/entity'; +import { LoginStatus, MessageReceiveOptType } from '../types/enum'; + +declare class SDK extends Emitter { + private wasmInitializedPromise; + private goExitPromise; + private goExisted; + private tryParse; + private isLogStandardOutput; + + constructor(url?: string); + + _logWrap(...args: any[]): void; + + _invoker(functionName: string, func: (...args: any[]) => Promise, args: any[], processor?: (data: string) => string): Promise>; + + login: (params: InitAndLoginConfig, operationID?: string) => Promise; + logout: (operationID?: string) => Promise>; + getAllConversationList: (operationID?: string) => Promise>; + getOneConversation: (params: GetOneConversationParams, operationID?: string) => Promise>; + batchGetConversationsBySessionTypeAndSourceId: (params: GetOneConversationParams[], operationID?: string) => Promise>; + updateMsgStatus: (conversationID: string, clientMsgID: string, status: number, operationID?: string) => Promise>; + getAdvancedHistoryMessageList: (params: GetAdvancedHistoryMsgParams, operationID?: string) => Promise>; + getAdvancedHistoryMessageListReverse: (params: GetAdvancedHistoryMsgParams, operationID?: string) => Promise>; + getSpecifiedGroupsInfo: (params: string[], operationID?: string) => Promise>; + deleteConversationAndDeleteAllMsg: (conversationID: string, operationID?: string) => Promise>; + markConversationMessageAsRead: (data: string, operationID?: string) => Promise>; + markMessagesAsReadByMsgID: (params: MarkNotiParams, operationID?: string) => Promise>; + getGroupMemberList: (params: GetGroupMemberParams, operationID?: string) => Promise>; + createTextMessage: (text: string, operationID?: string) => Promise>; + createImageMessage: (params: ImageMsgParams, operationID?: string) => Promise>; + createImageMessageByFile: (params: ImageMsgParams & { + file: File; + }, operationID?: string) => Promise>; + createCustomMessage: (params: CustomMsgParams, operationID?: string) => Promise>; + createQuoteMessage: (params: QuoteMsgParams, operationID?: string) => Promise>; + createAdvancedQuoteMessage: (params: AdvancedQuoteMsgParams, operationID?: string) => Promise>; + createAdvancedTextMessage: (params: AdvancedMsgParams, operationID?: string) => Promise>; + sendMessage: (params: SendMsgParams, operationID?: string) => Promise>; + sendMessageNotOss: (params: SendMsgParams, operationID?: string) => Promise>; + sendMessageByBuffer: (params: SendMsgParams, operationID?: string) => Promise>; + setMessageLocalEx: (params: SetMessageLocalExParams, operationID?: string) => Promise>; + + exportDB(operationID?: string): Promise>; + + getHistoryMessageListReverse: (params: GetHistoryMsgParams, operationID?: string) => Promise>; + revokeMessage: (data: AccessMessageParams, operationID?: string) => Promise>; + burnMessage: (data: AccessMessageParams, operationID?: string) => Promise>; + burnMessageBatch: (data: AccessMessagesParams, operationID?: string) => Promise>; + setConversationPrivateChat: (params: SetPrvParams, operationID?: string) => Promise>; + setConversationBurnDuration: (params: SetBurnDurationParams, operationID?: string) => Promise>; + getLoginStatus: (operationID?: string) => Promise>; + setAppBackgroundStatus: (data: boolean, operationID?: string) => Promise>; + networkStatusChanged: (operationID?: string) => Promise>; + getLoginUserID: (operationID?: string) => Promise>; + getSelfUserInfo: (operationID?: string) => Promise>; + getUsersInfo: (data: string[], operationID?: string) => Promise>; + getUsersInfoWithCache: (data: GetUserInfoWithCacheParams, operationID?: string) => Promise>; + setSelfInfo: (data: PartialUserItem, operationID?: string) => Promise>; + createTextAtMessage: (data: AtMsgParams, operationID?: string) => Promise>; + createSoundMessage: (data: SoundMsgParams, operationID?: string) => Promise>; + createSoundMessageByFile: (data: SoundMsgParams & { + file: File; + }, operationID?: string) => Promise>; + createVideoMessage: (data: VideoMsgParams, operationID?: string) => Promise>; + createVideoMessageByFile: (data: VideoMsgParams & { + videoFile: File; + snapshotFile: File; + }, operationID?: string) => Promise>; + createFileMessage: (data: FileMsgParams, operationID?: string) => Promise>; + createFileMessageByFile: (data: FileMsgParams & { + file: File; + }, operationID?: string) => Promise>; + createFileMessageFromFullPath: (data: FileMsgFullParams, operationID?: string) => Promise>; + createImageMessageFromFullPath: (data: string, operationID?: string) => Promise>; + createSoundMessageFromFullPath: (data: SouondMsgFullParams, operationID?: string) => Promise>; + createVideoMessageFromFullPath: (data: VideoMsgFullParams, operationID?: string) => Promise>; + createMergerMessage: (data: MergerMsgParams, operationID?: string) => Promise>; + createForwardMessage: (data: MessageItem, operationID?: string) => Promise>; + createFaceMessage: (data: FaceMessageParams, operationID?: string) => Promise>; + createLocationMessage: (data: LocationMsgParams, operationID?: string) => Promise>; + createCardMessage: (data: CardElem, operationID?: string) => Promise>; + deleteMessageFromLocalStorage: (data: AccessMessageParams, operationID?: string) => Promise>; + deleteMessage: (data: AccessMessageParams, operationID?: string) => Promise>; + deleteAllConversationFromLocal: (operationID?: string) => Promise>; + deleteAllMsgFromLocal: (operationID?: string) => Promise>; + deleteAllMsgFromLocalAndSvr: (operationID?: string) => Promise>; + insertSingleMessageToLocalStorage: (data: InsertSingleMsgParams, operationID?: string) => Promise>; + insertGroupMessageToLocalStorage: (data: InsertGroupMsgParams, operationID?: string) => Promise>; + typingStatusUpdate: (data: TypingUpdateParams, operationID?: string) => Promise>; + clearConversationAndDeleteAllMsg: (data: string, operationID?: string) => Promise>; + clearConversationAndDeleteAllMsgByEndTime: (data: string, operationID?: string, endTime?: number) => Promise>; + hideConversation: (data: string, operationID?: string) => Promise>; + getConversationListSplit: (data: SplitParams, operationID?: string) => Promise>; + getConversationIDBySessionType: (data: GetOneCveParams, operationID?: string) => Promise>; + getMultipleConversation: (data: string[], operationID?: string) => Promise>; + deleteConversation: (data: string, operationID?: string) => Promise>; + setConversationDraft: (data: SetDraftParams, operationID?: string) => Promise>; + pinConversation: (data: PinCveParams, operationID?: string) => Promise>; + getTotalUnreadMsgCount: (operationID?: string) => Promise>; + getConversationRecvMessageOpt: (data: string[], operationID?: string) => Promise>; + setConversationRecvMessageOpt: (data: isRecvParams, operationID?: string) => Promise>; + searchLocalMessages: (data: SearchLocalParams, operationID?: string) => Promise>; + addFriend: (data: AddFriendParams, operationID?: string) => Promise>; + searchFriends: (data: SearchFriendParams, operationID?: string) => Promise>; + getSpecifiedFriendsInfo: (data: string[], operationID?: string) => Promise>; + getFriendApplicationListAsRecipient: (operationID?: string) => Promise>; + getFriendApplicationListAsApplicant: (operationID?: string) => Promise>; + getFriendList: (operationID?: string) => Promise>; + setFriendRemark: (data: RemarkFriendParams, operationID?: string) => Promise>; + checkFriend: (data: string[], operationID?: string) => Promise>; + acceptFriendApplication: (data: AccessFriendParams, operationID?: string) => Promise>; + refuseFriendApplication: (data: AccessFriendParams, operationID?: string) => Promise>; + deleteFriend: (data: string, operationID?: string) => Promise>; + addBlack: (data: string, operationID?: string) => Promise>; + removeBlack: (data: string, operationID?: string) => Promise>; + getBlackList: (operationID?: string) => Promise>; + inviteUserToGroup: (data: InviteGroupParams, operationID?: string) => Promise>; + kickGroupMember: (data: InviteGroupParams, operationID?: string) => Promise>; + isJoinGroup: (data: string, operationID?: string) => Promise>; + getSpecifiedGroupMembersInfo: (data: Omit, operationID?: string) => Promise>; + getGroupMemberListByJoinTimeFilter: (data: GetGroupMemberByTimeParams, operationID?: string) => Promise>; + searchGroupMembers: (data: SearchGroupMemberParams, operationID?: string) => Promise>; + setGroupApplyMemberFriend: (data: SetMemberAuthParams, operationID?: string) => Promise>; + setGroupLookMemberInfo: (data: SetMemberAuthParams, operationID?: string) => Promise>; + getJoinedGroupList: (operationID?: string) => Promise>; + createGroup: (data: CreateGroupParams, operationID?: string) => Promise>; + setGroupInfo: (data: GroupBaseInfo, operationID?: string) => Promise>; + setGroupMemberNickname: (data: MemberNameParams, operationID?: string) => Promise>; + setGroupMemberInfo: (data: MemberExParams, operationID?: string) => Promise>; + joinGroup: (data: JoinGroupParams, operationID?: string) => Promise>; + searchGroups: (data: SearchGroupParams, operationID?: string) => Promise>; + quitGroup: (data: string, operationID?: string) => Promise>; + dismissGroup: (data: string, operationID?: string) => Promise>; + changeGroupMute: (data: ChangeGroupMuteParams, operationID?: string) => Promise>; + changeGroupMemberMute: (data: ChangeGroupMemberMuteParams, operationID?: string) => Promise>; + transferGroupOwner: (data: TransferGroupParams, operationID?: string) => Promise>; + getGroupApplicationListAsApplicant: (operationID?: string) => Promise>; + getGroupApplicationListAsRecipient: (operationID?: string) => Promise>; + acceptGroupApplication: (data: AccessGroupParams, operationID?: string) => Promise>; + refuseGroupApplication: (data: AccessGroupParams, operationID?: string) => Promise>; + resetConversationGroupAtType: (data: string, operationID?: string) => Promise>; + setGroupMemberRoleLevel: (data: SetGroupRoleParams, operationID?: string) => Promise>; + setGroupVerification: (data: SetGroupVerificationParams, operationID?: string) => Promise>; + getGroupMemberOwnerAndAdmin: (data: string, operationID?: string) => Promise>; + setGlobalRecvMessageOpt: (opt: MessageReceiveOptType, operationID?: string) => Promise>; + findMessageList: (data: FindMessageParams[], operationID?: string) => Promise>; + uploadFile: (data: UploadFileParams, operationID?: string) => Promise>; + cancelUploadFile: (data: string, operationID?: string) => Promise>; + updateConfig: (data: ConfigParams, operationID?: string) => Promise>; + subscribeUsersStatus: (data: string[], operationID?: string) => Promise>; + unsubscribeUsersStatus: (data: string[], operationID?: string) => Promise>; + getUserStatus: (operationID?: string) => Promise>; + getSubscribeUsersStatus: (operationID?: string) => Promise>; + signalingInvite: (data: SignalingInviteParams, operationID?: string) => Promise>; + signalingInviteInGroup: (data: SignalingInviteParams, operationID?: string) => Promise>; + signalingAccept: (data: RtcActionParams, operationID?: string) => Promise>; + signalingReject: (data: RtcActionParams, operationID?: string) => Promise>; + signalingCancel: (data: RtcActionParams, operationID?: string) => Promise>; + signalingHungUp: (data: RtcActionParams, operationID?: string) => Promise>; + signalingGetRoomByGroupID: (groupID: string, operationID?: string) => Promise>; + signalingGetTokenByRoomID: (roomID: string, operationID?: string) => Promise>; + signalingSendCustomSignal: (data: CustomSignalParams, operationID?: string) => Promise>; + signalingCreateMeeting: (data: CreateMeetingParams, operationID?: string) => Promise>; + signalingJoinMeeting: (data: string, operationID?: string) => Promise>; + signalingUpdateMeetingInfo: (data: UpdateMeetingParams, operationID?: string) => Promise>; + signalingCloseRoom: (roomID: string, operationID?: string) => Promise>; + signalingGetMeetings: (operationID?: string) => Promise>; + signalingOperateStream: (data: MeetingOperateStreamParams, operationID?: string) => Promise>; + setConversationIsMsgDestruct: (data: SetConversationMsgDestructParams, operationID?: string) => Promise>; + setConversationMsgDestructTime: (data: SetConversationMsgDestructTimeParams, operationID?: string) => Promise>; +} +export declare function getSDK(url?: string): SDK; +export {}; diff --git a/src/utils/openIM/sdk/index.js b/src/utils/openIM/sdk/index.js new file mode 100644 index 0000000..766f8fe --- /dev/null +++ b/src/utils/openIM/sdk/index.js @@ -0,0 +1,899 @@ +import { fileMapClear, fileMapSet, initDatabaseAPI, workerPromise, } from '../api'; +import Emitter from '../utils/emitter'; +import { v4 as uuidv4 } from 'uuid'; +import { getGO, initializeWasm, getGoExitPromsie } from './initialize'; +import { logBoxStyleValue } from '../utils'; +class SDK extends Emitter { + wasmInitializedPromise; + goExitPromise; + goExisted = false; + tryParse = true; + isLogStandardOutput = true; + constructor(url='./openIM.wasm.gzip') { + super(); + initDatabaseAPI(); + this.wasmInitializedPromise = initializeWasm(url); + this.goExitPromise = getGoExitPromsie(); + if (this.goExitPromise) { + this.goExitPromise + .then(() => { + console.info('SDK => wasm exist'); + }) + .catch(err => { + console.info('SDK => wasm with error ', err); + }) + .finally(() => { + this.goExisted = true; + }); + } + } + _logWrap(...args) { + if (this.isLogStandardOutput) { + // console.info(...args); + } + } + _invoker(functionName, func, args, processor) { + return new Promise(async (resolve, reject) => { + this._logWrap(`%cSDK =>%c [OperationID:${args[0]}] (invoked by js) run ${functionName} with args ${JSON.stringify(args)}`, 'font-size:0.875rem; background:#7CAEFF; border-radius:4px; padding-inline:4px;', ''); + let response = { + operationID: args[0], + event: (functionName.slice(0, 1).toUpperCase() + + functionName.slice(1).toLowerCase()), + }; + try { + if (!getGO() || getGO().exited || this.goExisted) { + throw 'wasm exist already, fail to run'; + } + if (!func){ + // console.error('window not register function :'+functionName); + reject('window not register function :'+functionName) + return + } + let data = await func(...args); + if (processor) { + this._logWrap(`%cSDK =>%c [OperationID:${args[0]}] (invoked by js) run ${functionName} with response before processor ${JSON.stringify(data)}`, logBoxStyleValue('#FFDC19'), ''); + data = processor(data); + } + if (this.tryParse) { + try { + data = JSON.parse(data); + } + catch (error) { + // console.log('SDK => parse error ', error); + } + } + response.data = data; + resolve(response); + } + catch (error) { + console.error(error) + this._logWrap(`%cSDK =>%c [OperationID:${args[0]}] (invoked by js) run ${functionName} with error ${JSON.stringify(error)}`, logBoxStyleValue('#EE4245'), ''); + response = { + ...response, + ...error, + }; + reject(response); + } + }); + } + login = async (params, operationID = uuidv4()) => { + this._logWrap(`SDK => (invoked by js) run login with args ${JSON.stringify({ + params, + operationID, + })}`); + await workerPromise; + await this.wasmInitializedPromise; + window.commonEventFunc(event => { + try { + // console.info(`%cSDK =>%c received event %c${event}%c `, logBoxStyleValue('#282828', '#ffffff'), '', 'color: #4f2398;', ''); + const parsed = JSON.parse(event); + if (this.tryParse) { + try { + parsed.data = JSON.parse(parsed.data); + } + catch (error) { + // console.log('SDK => parse error ', error); + } + } + this.emit(parsed.event, parsed); + } + catch (error) { + // console.error(error); + } + }); + const config = { + platformID: params.platformID, + apiAddr: params.apiAddr, + wsAddr: params.wsAddr, + dataDir: './', + logLevel: params.logLevel || 5, + isLogStandardOutput: params.isLogStandardOutput ?? true, + logFilePath: './', + isExternalExtensions: params.isExternalExtensions || false, + deviceID: params.deviceID, + backupHttpAddrs: params.backupHttpAddrs, + }; + console.log('SDK => initSDK', JSON.stringify(config)) + this.tryParse = params.tryParse ?? true; + this.isLogStandardOutput = config.isLogStandardOutput; + window.initSDK(operationID, JSON.stringify(config)); + return await window.login(operationID, params.userID, params.token); + }; + logout = (operationID = uuidv4()) => { + fileMapClear(); + return this._invoker('logout', window.logout, [operationID]); + }; + getAllConversationList = (operationID = uuidv4()) => { + return this._invoker('getAllConversationList', window.getAllConversationList, [operationID]); + }; + getOneConversation = (params, operationID = uuidv4()) => { + return this._invoker('getOneConversation', window.getOneConversation, [operationID, params.sessionType, params.sourceID]); + }; + batchGetConversationsBySessionTypeAndSourceId = (params, operationID = uuidv4()) => { + return this._invoker('batchGetConversationsBySessionTypeAndSourceId', window.batchGetConversationsBySessionTypeAndSourceId, [operationID, JSON.stringify(params)]); + }; + getAdvancedHistoryMessageList = (params, operationID = uuidv4()) => { + return this._invoker('getAdvancedHistoryMessageList', window.getAdvancedHistoryMessageList, [operationID, JSON.stringify(params)]); + }; + getAdvancedHistoryMessageListReverse = (params, operationID = uuidv4()) => { + return this._invoker('getAdvancedHistoryMessageListReverse', window.getAdvancedHistoryMessageListReverse, [operationID, JSON.stringify(params)]); + }; + getSpecifiedGroupsInfo = (params, operationID = uuidv4()) => { + return this._invoker('getSpecifiedGroupsInfo', window.getSpecifiedGroupsInfo, [operationID, JSON.stringify(params)]); + }; + deleteConversationAndDeleteAllMsg = (conversationID, operationID = uuidv4()) => { + return this._invoker('deleteConversationAndDeleteAllMsg', window.deleteConversationAndDeleteAllMsg, [operationID, conversationID]); + }; + markConversationMessageAsRead = (data, operationID = uuidv4()) => { + return this._invoker('markConversationMessageAsRead', window.markConversationMessageAsRead, [operationID, data]); + }; + markMessagesAsReadByMsgID = (params, operationID = uuidv4()) => { + return this._invoker('markMessagesAsReadByMsgID', window.markMessagesAsReadByMsgID, [ + operationID, + params.conversationID, + JSON.stringify(params.clientMsgIDList), + ]); + }; + getGroupMemberList = (params, operationID = uuidv4()) => { + return this._invoker('getGroupMemberList', window.getGroupMemberList, [operationID, params.groupID, params.filter, params.offset, params.count]); + }; + createTextMessage = (text, operationID = uuidv4()) => { + return this._invoker('createTextMessage', window.createTextMessage, [operationID, text], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createImageMessage = (params, operationID = uuidv4()) => { + return this._invoker('createImageMessage', window.createImageMessageByURL, [ + operationID, + JSON.stringify(params.sourcePicture), + JSON.stringify(params.bigPicture), + JSON.stringify(params.snapshotPicture), + ], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createImageMessageByFile = (params, operationID = uuidv4()) => { + params.sourcePicture.uuid = `${params.sourcePicture.uuid}/${params.file.name}`; + fileMapSet(params.sourcePicture.uuid, params.file); + return this._invoker('createImageMessageByFile', window.createImageMessageByURL, [ + operationID, + JSON.stringify(params.sourcePicture), + JSON.stringify(params.bigPicture), + JSON.stringify(params.snapshotPicture), + ], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createCustomMessage = (params, operationID = uuidv4()) => { + return this._invoker('createCustomMessage', window.createCustomMessage, [operationID, params.data, params.extension, params.description], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createQuoteMessage = (params, operationID = uuidv4()) => { + return this._invoker('createQuoteMessage', window.createQuoteMessage, [operationID, params.text, params.message], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createAdvancedQuoteMessage = (params, operationID = uuidv4()) => { + return this._invoker('createAdvancedQuoteMessage', window.createAdvancedQuoteMessage, [ + operationID, + params.text, + JSON.stringify(params.message), + JSON.stringify(params.messageEntityList), + ], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createAdvancedTextMessage = (params, operationID = uuidv4()) => { + return this._invoker('createAdvancedTextMessage', window.createAdvancedTextMessage, [operationID, params.text, JSON.stringify(params.messageEntityList)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + sendMessage = (params, operationID = uuidv4()) => { + const offlinePushInfo = params.offlinePushInfo ?? { + title: '你有一条新消息', + desc: '', + ex: '', + iOSPushSound: '+1', + iOSBadgeCount: true, + }; + return this._invoker('sendMessage', window.sendMessage, [ + operationID, + JSON.stringify(params.message), + params.recvID, + params.groupID, + JSON.stringify(offlinePushInfo), + ]); + }; + sendMessageNotOss = (params, operationID = uuidv4()) => { + const offlinePushInfo = params.offlinePushInfo ?? { + title: '你有一条新消息', + desc: '', + ex: '', + iOSPushSound: '+1', + iOSBadgeCount: true, + }; + return this._invoker('sendMessageNotOss', window.sendMessageNotOss, [ + operationID, + JSON.stringify(params.message), + params.recvID, + params.groupID, + JSON.stringify(offlinePushInfo), + ]); + }; + sendMessageByBuffer = (params, operationID = uuidv4()) => { + const offlinePushInfo = params.offlinePushInfo ?? { + title: '你有一条新消息', + desc: '', + ex: '', + iOSPushSound: '+1', + iOSBadgeCount: true, + }; + return this._invoker('sendMessageByBuffer', window.sendMessageByBuffer, [ + operationID, + JSON.stringify(params.message), + params.recvID, + params.groupID, + JSON.stringify(offlinePushInfo), + params.fileArrayBuffer, + params.snpFileArrayBuffer, + ]); + }; + setMessageLocalEx = (params, operationID = uuidv4()) => { + return this._invoker('setMessageLocalEx', window.setMessageLocalEx, [ + operationID, + params.conversationID, + params.clientMsgID, + params.localEx, + ]); + }; + exportDB(operationID = uuidv4()) { + return this._invoker('exportDB', window.exportDB, [operationID]); + } + getHistoryMessageListReverse = (params, operationID = uuidv4()) => { + return this._invoker('getHistoryMessageListReverse', window.getHistoryMessageListReverse, [operationID, JSON.stringify(params)]); + }; + revokeMessage = (data, operationID = uuidv4()) => { + return this._invoker('revokeMessage', window.revokeMessage, [ + operationID, + data.conversationID, + data.clientMsgID, + ]); + }; + burnMessage = (data, operationID = uuidv4()) => { + return this._invoker('burnMessage', window.burnMessage, [ + operationID, + data.conversationID, + data.clientMsgID, + ]); + }; + burnMessageBatch = (data, operationID = uuidv4()) => { + return this._invoker('burnMessageBatch', window.burnMessageBatch, [ + operationID, + data.conversationID, + data.seqs, + ]); + }; + setConversationPrivateChat = (params, operationID = uuidv4()) => { + return this._invoker('setConversationPrivateChat', window.setConversationPrivateChat, [operationID, params.conversationID, params.isPrivate]); + }; + setConversationBurnDuration = (params, operationID = uuidv4()) => { + return this._invoker('setConversationBurnDuration', window.setConversationBurnDuration, [operationID, params.conversationID, params.burnDuration]); + }; + getLoginStatus = (operationID = uuidv4()) => { + return this._invoker('getLoginStatus', window.getLoginStatus, [operationID], data => { + // compitable with old version sdk + return data[0]; + }); + }; + setAppBackgroundStatus = (data, operationID = uuidv4()) => { + return this._invoker('setAppBackgroundStatus', window.setAppBackgroundStatus, [operationID, data]); + }; + networkStatusChanged = (operationID = uuidv4()) => { + return this._invoker('networkStatusChanged ', window.networkStatusChanged, [operationID]); + }; + getLoginUserID = (operationID = uuidv4()) => { + return this._invoker('getLoginUserID', window.getLoginUserID, [ + operationID, + ]); + }; + getSelfUserInfo = (operationID = uuidv4()) => { + return this._invoker('getSelfUserInfo', window.getSelfUserInfo, [operationID]); + }; + getUsersInfo = (data, operationID = uuidv4()) => { + return this._invoker('getUsersInfo', window.getUsersInfo, [ + operationID, + JSON.stringify(data), + ]); + }; + getUsersInfoWithCache = (data, operationID = uuidv4()) => { + return this._invoker('getUsersInfoWithCache', window.getUsersInfoWithCache, [operationID, JSON.stringify(data.userIDList), data.groupID]); + }; + setSelfInfo = (data, operationID = uuidv4()) => { + return this._invoker('setSelfInfo', window.setSelfInfo, [ + operationID, + JSON.stringify(data), + ]); + }; + createTextAtMessage = (data, operationID = uuidv4()) => { + return this._invoker('createTextAtMessage', window.createTextAtMessage, [ + operationID, + data.text, + JSON.stringify(data.atUserIDList), + JSON.stringify(data.atUsersInfo), + JSON.stringify(data.message) ?? '', + ], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createSoundMessage = (data, operationID = uuidv4()) => { + return this._invoker('createSoundMessage', window.createSoundMessageByURL, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createSoundMessageByFile = (data, operationID = uuidv4()) => { + data.uuid = `${data.uuid}/${data.file.name}`; + fileMapSet(data.uuid, data.file); + return this._invoker('createSoundMessageByFile', window.createSoundMessageByURL, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createVideoMessage = (data, operationID = uuidv4()) => { + return this._invoker('createVideoMessage', window.createVideoMessageByURL, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createVideoMessageByFile = (data, operationID = uuidv4()) => { + data.videoUUID = `${data.videoUUID}/${data.videoFile.name}`; + data.snapshotUUID = `${data.snapshotUUID}/${data.snapshotFile.name}`; + fileMapSet(data.videoUUID, data.videoFile); + fileMapSet(data.snapshotUUID, data.snapshotFile); + return this._invoker('createVideoMessageByFile', window.createVideoMessageByURL, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createFileMessage = (data, operationID = uuidv4()) => { + return this._invoker('createFileMessage', window.createFileMessageByURL, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createFileMessageByFile = (data, operationID = uuidv4()) => { + data.uuid = `${data.uuid}/${data.file.name}`; + fileMapSet(data.uuid, data.file); + return this._invoker('createFileMessageByFile', window.createFileMessageByURL, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createFileMessageFromFullPath = (data, operationID = uuidv4()) => { + return this._invoker('createFileMessageFromFullPath', window.createFileMessageFromFullPath, [operationID, data.fileFullPath, data.fileName], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createImageMessageFromFullPath = (data, operationID = uuidv4()) => { + return this._invoker('createImageMessageFromFullPath ', window.createImageMessageFromFullPath, [operationID, data], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createSoundMessageFromFullPath = (data, operationID = uuidv4()) => { + return this._invoker('createSoundMessageFromFullPath ', window.createSoundMessageFromFullPath, [operationID, data.soundPath, data.duration], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createVideoMessageFromFullPath = (data, operationID = uuidv4()) => { + return this._invoker('createVideoMessageFromFullPath ', window.createVideoMessageFromFullPath, [ + operationID, + data.videoFullPath, + data.videoType, + data.duration, + data.snapshotFullPath, + ], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createMergerMessage = (data, operationID = uuidv4()) => { + return this._invoker('createMergerMessage ', window.createMergerMessage, [ + operationID, + JSON.stringify(data.messageList), + data.title, + JSON.stringify(data.summaryList), + ], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createForwardMessage = (data, operationID = uuidv4()) => { + return this._invoker('createForwardMessage ', window.createForwardMessage, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createFaceMessage = (data, operationID = uuidv4()) => { + return this._invoker('createFaceMessage ', window.createFaceMessage, [operationID, data.index, data.data], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createLocationMessage = (data, operationID = uuidv4()) => { + return this._invoker('createLocationMessage ', window.createLocationMessage, [operationID, data.description, data.longitude, data.latitude], data => { + // compitable with old version sdk + return data[0]; + }); + }; + createCardMessage = (data, operationID = uuidv4()) => { + return this._invoker('createCardMessage ', window.createCardMessage, [operationID, JSON.stringify(data)], data => { + // compitable with old version sdk + return data[0]; + }); + }; + deleteMessageFromLocalStorage = (data, operationID = uuidv4()) => { + return this._invoker('deleteMessageFromLocalStorage ', window.deleteMessageFromLocalStorage, [operationID, data.conversationID, data.clientMsgID]); + }; + deleteMessage = (data, operationID = uuidv4()) => { + return this._invoker('deleteMessage ', window.deleteMessage, [ + operationID, + data.conversationID, + data.clientMsgID, + ]); + }; + deleteAllConversationFromLocal = (operationID = uuidv4()) => { + return this._invoker('deleteAllConversationFromLocal ', window.deleteAllConversationFromLocal, [operationID]); + }; + deleteAllMsgFromLocal = (operationID = uuidv4()) => { + return this._invoker('deleteAllMsgFromLocal ', window.deleteAllMsgFromLocal, [operationID]); + }; + deleteAllMsgFromLocalAndSvr = (operationID = uuidv4()) => { + return this._invoker('deleteAllMsgFromLocalAndSvr ', window.deleteAllMsgFromLocalAndSvr, [operationID]); + }; + insertSingleMessageToLocalStorage = (data, operationID = uuidv4()) => { + return this._invoker('insertSingleMessageToLocalStorage ', window.insertSingleMessageToLocalStorage, [operationID, JSON.stringify(data.message), data.recvID, data.sendID]); + }; + insertGroupMessageToLocalStorage = (data, operationID = uuidv4()) => { + return this._invoker('insertGroupMessageToLocalStorage ', window.insertGroupMessageToLocalStorage, [operationID, JSON.stringify(data.message), data.groupID, data.sendID]); + }; + typingStatusUpdate = (data, operationID = uuidv4()) => { + return this._invoker('typingStatusUpdate ', window.typingStatusUpdate, [ + operationID, + data.recvID, + data.msgTip, + ]); + }; + clearConversationAndDeleteAllMsg = (data, operationID = uuidv4()) => { + return this._invoker('clearConversationAndDeleteAllMsg ', window.clearConversationAndDeleteAllMsg, [operationID, data]); + }; + + clearConversationAndDeleteAllMsgByEndTime = async (data, operationID = uuidv4()) => { + this._invoker('clearConversationAndDeleteAllMsgByEndTime ', window.clearConversationAndDeleteAllMsgByEndTime, [operationID, data.conversationID, data.endTime]); + }; + + hideConversation = (data, operationID = uuidv4()) => { + return this._invoker('hideConversation ', window.hideConversation, [ + operationID, + data, + ]); + }; + getConversationListSplit = (data, operationID = uuidv4()) => { + return this._invoker('getConversationListSplit ', window.getConversationListSplit, [operationID, data.offset, data.count]); + }; + getConversationIDBySessionType = (data, operationID = uuidv4()) => { + return this._invoker('getConversationIDBySessionType ', window.getConversationIDBySessionType, [operationID, data.sourceID, data.sessionType]); + }; + getMultipleConversation = (data, operationID = uuidv4()) => { + return this._invoker('getMultipleConversation ', window.getMultipleConversation, [operationID, JSON.stringify(data)]); + }; + deleteConversation = (data, operationID = uuidv4()) => { + return this._invoker('deleteConversation ', window.deleteConversation, [ + operationID, + data, + ]); + }; + setConversationDraft = (data, operationID = uuidv4()) => { + return this._invoker('setConversationDraft ', window.setConversationDraft, [operationID, data.conversationID, data.draftText]); + }; + pinConversation = (data, operationID = uuidv4()) => { + return this._invoker('pinConversation ', window.pinConversation, [ + operationID, + data.conversationID, + data.isPinned, + ]); + }; + getTotalUnreadMsgCount = (operationID = uuidv4()) => { + return this._invoker('getTotalUnreadMsgCount ', window.getTotalUnreadMsgCount, [operationID]); + }; + getConversationRecvMessageOpt = (data, operationID = uuidv4()) => { + return this._invoker('getConversationRecvMessageOpt ', window.getConversationRecvMessageOpt, [operationID, JSON.stringify(data)]); + }; + setConversationRecvMessageOpt = (data, operationID = uuidv4()) => { + return this._invoker('setConversationRecvMessageOpt ', window.setConversationRecvMessageOpt, [operationID, data.conversationID, data.opt]); + }; + searchLocalMessages = (data, operationID = uuidv4()) => { + return this._invoker('searchLocalMessages ', window.searchLocalMessages, [operationID, JSON.stringify(data)]); + }; + addFriend = (data, operationID = uuidv4()) => { + return this._invoker('addFriend ', window.addFriend, [ + operationID, + JSON.stringify(data), + ]); + }; + searchFriends = (data, operationID = uuidv4()) => { + return this._invoker('searchFriends ', window.searchFriends, [operationID, JSON.stringify(data)]); + }; + getSpecifiedFriendsInfo = (data, operationID = uuidv4()) => { + return this._invoker('getSpecifiedFriendsInfo ', window.getSpecifiedFriendsInfo, [operationID, JSON.stringify(data)]); + }; + getFriendApplicationListAsRecipient = (operationID = uuidv4()) => { + return this._invoker('getFriendApplicationListAsRecipient ', window.getFriendApplicationListAsRecipient, [operationID]); + }; + getFriendApplicationListAsApplicant = (operationID = uuidv4()) => { + return this._invoker('getFriendApplicationListAsApplicant ', window.getFriendApplicationListAsApplicant, [operationID]); + }; + getFriendList = (operationID = uuidv4()) => { + return this._invoker('getFriendList ', window.getFriendList, [operationID]); + }; + setFriendRemark = (data, operationID = uuidv4()) => { + return this._invoker('setFriendRemark ', window.setFriendRemark, [ + operationID, + JSON.stringify(data), + ]); + }; + checkFriend = (data, operationID = uuidv4()) => { + return this._invoker('checkFriend', window.checkFriend, [ + operationID, + JSON.stringify(data), + ]); + }; + acceptFriendApplication = (data, operationID = uuidv4()) => { + return this._invoker('acceptFriendApplication', window.acceptFriendApplication, [operationID, JSON.stringify(data)]); + }; + refuseFriendApplication = (data, operationID = uuidv4()) => { + return this._invoker('refuseFriendApplication ', window.refuseFriendApplication, [operationID, JSON.stringify(data)]); + }; + deleteFriend = (data, operationID = uuidv4()) => { + return this._invoker('deleteFriend ', window.deleteFriend, [ + operationID, + data, + ]); + }; + addBlack = (data, operationID = uuidv4()) => { + return this._invoker('addBlack ', window.addBlack, [operationID, data]); + }; + removeBlack = (data, operationID = uuidv4()) => { + return this._invoker('removeBlack ', window.removeBlack, [ + operationID, + data, + ]); + }; + getBlackList = (operationID = uuidv4()) => { + return this._invoker('getBlackList ', window.getBlackList, [operationID]); + }; + inviteUserToGroup = (data, operationID = uuidv4()) => { + return this._invoker('inviteUserToGroup ', window.inviteUserToGroup, [ + operationID, + data.groupID, + data.reason, + JSON.stringify(data.userIDList), + ]); + }; + kickGroupMember = (data, operationID = uuidv4()) => { + return this._invoker('kickGroupMember ', window.kickGroupMember, [ + operationID, + data.groupID, + data.reason, + JSON.stringify(data.userIDList), + ]); + }; + isJoinGroup = (data, operationID = uuidv4()) => { + return this._invoker('isJoinGroup ', window.isJoinGroup, [ + operationID, + data, + ]); + }; + getSpecifiedGroupMembersInfo = (data, operationID = uuidv4()) => { + return this._invoker('getSpecifiedGroupMembersInfo ', window.getSpecifiedGroupMembersInfo, [operationID, data.groupID, JSON.stringify(data.userIDList)]); + }; + getGroupMemberListByJoinTimeFilter = (data, operationID = uuidv4()) => { + return this._invoker('getGroupMemberListByJoinTimeFilter ', window.getGroupMemberListByJoinTimeFilter, [ + operationID, + data.groupID, + data.offset, + data.count, + data.joinTimeBegin, + data.joinTimeEnd, + JSON.stringify(data.filterUserIDList), + ]); + }; + searchGroupMembers = (data, operationID = uuidv4()) => { + return this._invoker('searchGroupMembers ', window.searchGroupMembers, [operationID, JSON.stringify(data)]); + }; + setGroupApplyMemberFriend = (data, operationID = uuidv4()) => { + return this._invoker('setGroupApplyMemberFriend ', window.setGroupApplyMemberFriend, [operationID, data.groupID, data.rule]); + }; + setGroupLookMemberInfo = (data, operationID = uuidv4()) => { + return this._invoker('setGroupLookMemberInfo ', window.setGroupLookMemberInfo, [operationID, data.groupID, data.rule]); + }; + getJoinedGroupList = (operationID = uuidv4()) => { + return this._invoker('getJoinedGroupList ', window.getJoinedGroupList, [operationID]); + }; + createGroup = (data, operationID = uuidv4()) => { + return this._invoker('createGroup ', window.createGroup, [ + operationID, + JSON.stringify(data), + ]); + }; + setGroupInfo = (data, operationID = uuidv4()) => { + return this._invoker('setGroupInfo ', window.setGroupInfo, [ + operationID, + JSON.stringify(data), + ]); + }; + setGroupMemberNickname = (data, operationID = uuidv4()) => { + return this._invoker('setGroupMemberNickname ', window.setGroupMemberNickname, [operationID, data.groupID, data.userID, data.groupMemberNickname]); + }; + setGroupMemberInfo = (data, operationID = uuidv4()) => { + return this._invoker('setGroupMemberInfo ', window.setGroupMemberInfo, [ + operationID, + JSON.stringify(data), + ]); + }; + joinGroup = (data, operationID = uuidv4()) => { + return this._invoker('joinGroup ', window.joinGroup, [ + operationID, + data.groupID, + data.reqMsg, + data.joinSource, + ]); + }; + searchGroups = (data, operationID = uuidv4()) => { + return this._invoker('searchGroups ', window.searchGroups, [ + operationID, + JSON.stringify(data), + ]); + }; + quitGroup = (data, operationID = uuidv4()) => { + return this._invoker('quitGroup ', window.quitGroup, [ + operationID, + data, + ]); + }; + dismissGroup = (data, operationID = uuidv4()) => { + return this._invoker('dismissGroup ', window.dismissGroup, [ + operationID, + data, + ]); + }; + changeGroupMute = (data, operationID = uuidv4()) => { + return this._invoker('changeGroupMute ', window.changeGroupMute, [ + operationID, + data.groupID, + data.isMute, + ]); + }; + changeGroupMemberMute = (data, operationID = uuidv4()) => { + return this._invoker('changeGroupMemberMute ', window.changeGroupMemberMute, [operationID, data.groupID, data.userID, data.mutedSeconds]); + }; + transferGroupOwner = (data, operationID = uuidv4()) => { + return this._invoker('transferGroupOwner ', window.transferGroupOwner, [ + operationID, + data.groupID, + data.newOwnerUserID, + ]); + }; + getGroupApplicationListAsApplicant = (operationID = uuidv4()) => { + return this._invoker('getGroupApplicationListAsApplicant ', window.getGroupApplicationListAsApplicant, [operationID]); + }; + getGroupApplicationListAsRecipient = (operationID = uuidv4()) => { + return this._invoker('getGroupApplicationListAsRecipient ', window.getGroupApplicationListAsRecipient, [operationID]); + }; + acceptGroupApplication = (data, operationID = uuidv4()) => { + return this._invoker('acceptGroupApplication ', window.acceptGroupApplication, [operationID, data.groupID, data.fromUserID, data.handleMsg]); + }; + refuseGroupApplication = (data, operationID = uuidv4()) => { + return this._invoker('refuseGroupApplication ', window.refuseGroupApplication, [operationID, data.groupID, data.fromUserID, data.handleMsg]); + }; + resetConversationGroupAtType = (data, operationID = uuidv4()) => { + return this._invoker('resetConversationGroupAtType ', window.resetConversationGroupAtType, [operationID, data]); + }; + setGroupMemberRoleLevel = (data, operationID = uuidv4()) => { + return this._invoker('setGroupMemberRoleLevel ', window.setGroupMemberRoleLevel, [operationID, data.groupID, data.userID, data.roleLevel]); + }; + setGroupVerification = (data, operationID = uuidv4()) => { + return this._invoker('setGroupVerification ', window.setGroupVerification, [operationID, data.groupID, data.verification]); + }; + getGroupMemberOwnerAndAdmin = (data, operationID = uuidv4()) => { + return this._invoker('getGroupMemberOwnerAndAdmin ', window.getGroupMemberOwnerAndAdmin, [operationID, data]); + }; + setGlobalRecvMessageOpt = (opt, operationID = uuidv4()) => { + return this._invoker('setGlobalRecvMessageOpt ', window.setGlobalRecvMessageOpt, [operationID, opt]); + }; + findMessageList = (data, operationID = uuidv4()) => { + return this._invoker('findMessageList ', window.findMessageList, [operationID, JSON.stringify(data)]); + }; + uploadFile = (data, operationID = uuidv4()) => { + // 在这里执行上传文件的其他逻辑,例如设置 data.uuid 等 + // data.uuid = `${data.uuid}/${data.file.name}`; + data.uuid = data.uuid; + fileMapSet(data.uuid, data.file); + // 调用 _invoker 执行上传文件操作 + return this._invoker('uploadFile', window.uploadFile, [ + operationID, + JSON.stringify({ + ...data, + filepath: '', + cause: '', + }), + ]); + }; + cancelUploadFile = (data, operationID = uuidv4()) => { + return this._invoker('cancelUploadFile', window.cancelUploadFile, [operationID, data.uuid]); + }; + updateConfig = (data, operationID = uuidv4()) => { + // apiAdd 当前选择的线路 API + // wsAddr 当前选择的 WS + // reConn true 代表重新连接 + // operationID 随机数 + // msg code 1 更新 + // backupHttpAddrs 所有线路api 包括默认 + console.error("updateConfig 参数:", data); + return this._invoker('updateConfig ', window.updateConfig, [operationID, JSON.stringify(data), data.reConn]); + }; + subscribeUsersStatus = (data, operationID = uuidv4()) => { + return this._invoker('subscribeUsersStatus ', window.subscribeUsersStatus, [operationID, JSON.stringify(data)]); + }; + unsubscribeUsersStatus = (data, operationID = uuidv4()) => { + return this._invoker('unsubscribeUsersStatus ', window.unsubscribeUsersStatus, [operationID, JSON.stringify(data)]); + }; + getUserStatus = (operationID = uuidv4()) => { + return this._invoker('getUserStatus ', window.getUserStatus, [operationID]); + }; + getSubscribeUsersStatus = (operationID = uuidv4()) => { + return this._invoker('getSubscribeUsersStatus ', window.getSubscribeUsersStatus, [operationID]); + }; + signalingInvite = (data, operationID = uuidv4()) => { + return this._invoker('signalingInvite ', window.signalingInvite, [ + operationID, + JSON.stringify(data), + ]); + }; + signalingInviteInGroup = (data, operationID = uuidv4()) => { + return this._invoker('signalingInviteInGroup ', window.signalingInviteInGroup, [operationID, JSON.stringify(data)]); + }; + signalingAccept = (data, operationID = uuidv4()) => { + return this._invoker('signalingAccept ', window.signalingAccept, [ + operationID, + JSON.stringify(data), + ]); + }; + signalingReject = (data, operationID = uuidv4()) => { + return this._invoker('signalingReject ', window.signalingReject, [ + operationID, + JSON.stringify(data), + ]); + }; + signalingCancel = (data, operationID = uuidv4()) => { + return this._invoker('signalingCancel ', window.signalingCancel, [ + operationID, + JSON.stringify(data), + ]); + }; + signalingHungUp = (data, operationID = uuidv4()) => { + return this._invoker('signalingHungUp ', window.signalingHungUp, [ + operationID, + JSON.stringify(data), + ]); + }; + signalingGetRoomByGroupID = (groupID, operationID = uuidv4()) => { + return this._invoker('signalingGetRoomByGroupID ', window.signalingGetRoomByGroupID, [operationID, groupID]); + }; + signalingGetTokenByRoomID = (roomID, operationID = uuidv4()) => { + return this._invoker('signalingGetTokenByRoomID ', window.signalingGetTokenByRoomID, [operationID, roomID]); + }; + signalingSendCustomSignal = (data, operationID = uuidv4()) => { + return this._invoker('signalingSendCustomSignal ', window.signalingSendCustomSignal, [operationID, data.customInfo, data.roomID]); + }; + signalingCreateMeeting = (data, operationID = uuidv4()) => { + return this._invoker('signalingCreateMeeting ', window.signalingCreateMeeting, [operationID, JSON.stringify(data)]); + }; + signalingJoinMeeting = (data, operationID = uuidv4()) => { + return this._invoker('signalingJoinMeeting ', window.signalingJoinMeeting, [ + operationID, + JSON.stringify({ + roomID: data, + }), + ]); + }; + signalingUpdateMeetingInfo = (data, operationID = uuidv4()) => { + return this._invoker('signalingUpdateMeetingInfo ', window.signalingUpdateMeetingInfo, [operationID, JSON.stringify(data)]); + }; + signalingCloseRoom = (roomID, operationID = uuidv4()) => { + return this._invoker('signalingCloseRoom ', window.signalingCloseRoom, [ + operationID, + roomID, + ]); + }; + signalingGetMeetings = (operationID = uuidv4()) => { + return this._invoker('signalingGetMeetings ', window.signalingGetMeetings, [operationID]); + }; + signalingOperateStream = (data, operationID = uuidv4()) => { + return this._invoker('signalingOperateStream ', window.signalingOperateStream, [ + operationID, + data.streamType, + data.roomID, + data.userID, + data.mute, + data.muteAll, + ]); + }; + setConversationIsMsgDestruct = (data, operationID = uuidv4()) => { + return this._invoker('setConversationIsMsgDestruct ', window.setConversationIsMsgDestruct, [operationID, data.conversationID, data.isMsgDestruct]); + }; + setConversationMsgDestructTime = (data, operationID = uuidv4()) => { + return this._invoker('setConversationMsgDestructTime ', window.setConversationMsgDestructTime, [operationID, data.conversationID, data.msgDestructTime]); + }; + //生成uuid + createuuid=()=>uuidv4(); + + updateMsgStatus = (conversationID, clientMsgID, status, operationID = uuidv4()) => { + return this._invoker('updateMsgStatus', window.updateMsgStatus, + [ operationID, conversationID, clientMsgID, status ]); + }; +} +let instance; +// 客户仓用这个 +// export function getSDK(url = 'https://openim-priv-tmp.oss-cn-shenzhen.aliyuncs.com/openim/SDK/PROD/862ac9f5179f4173de3fc4952cb0ddf2.gzip') { +// if (typeof window === 'undefined') { +// return {}; +// } +// if (instance) { +// return instance; +// } +// instance = new SDK(url); +// return instance; +// } +// 本地仓用这个 +export function getSDK(url = '/openIM.wasm.gzip') { + if (typeof window === 'undefined') { + return {}; + } + if (instance) { + return instance; + } + instance = new SDK(url); + return instance; +} diff --git a/src/utils/openIM/sdk/initialize.d.ts b/src/utils/openIM/sdk/initialize.d.ts new file mode 100644 index 0000000..11806ca --- /dev/null +++ b/src/utils/openIM/sdk/initialize.d.ts @@ -0,0 +1,5 @@ +/// +export declare function initializeWasm(url: string): Promise; +export declare function reset(): void; +export declare function getGO(): Go; +export declare function getGoExitPromsie(): Promise | undefined; diff --git a/src/utils/openIM/sdk/initialize.js b/src/utils/openIM/sdk/initialize.js new file mode 100644 index 0000000..a7ebdd7 --- /dev/null +++ b/src/utils/openIM/sdk/initialize.js @@ -0,0 +1,161 @@ +// import { wait } from '../utils' +// import pako from 'pako' + +// let initiallized = false +// let go +// let goExitPromise +// export async function initializeWasm(url) { +// if (initiallized) { +// return null +// } +// if (typeof window === 'undefined') { +// return Promise.resolve(null) +// } +// // go = new Go(); +// // if ('instantiateStreaming' in WebAssembly) { +// // const wasm = await WebAssembly.instantiateStreaming(fetch(url), go.importObject); +// // go.run(wasm.instance); +// // } +// // else { +// // const bytes = await fetch(url).then(resp => resp.arrayBuffer()); +// // const wasm = await WebAssembly.instantiate(bytes, go.importObject); +// // goExitPromise = go.run(wasm.instance); +// // } +// go = new Go() +// let bytes +// console.log('>>>>>>>>url----' + url) + +// await fetch(url) +// .then((response) => { +// if (!response.ok) { +// throw new Error(`HTTP error! status: ${response.status}`) +// } + +// // 创建一个新的 pako.Inflate 实例 +// const inflater = new pako.Inflate() + +// // 返回一个 Promise,读取并解压 Gzip 数据 +// return new Promise((resolve, reject) => { +// // 创建一个空的 ArrayBuffer 用于存储解压后的数据 +// let inflatedData = new ArrayBuffer(0) + +// // 监听 pako.Inflate.prototype.onData 事件,将解压的数据追加到 ArrayBuffer 中 +// inflater.onData = function (chunk) { +// const oldSize = inflatedData.byteLength +// const newSize = oldSize + chunk.length +// const newBuffer = new Uint8Array(newSize) +// newBuffer.set(new Uint8Array(inflatedData), 0) +// newBuffer.set(chunk, oldSize) +// inflatedData = newBuffer.buffer +// } + +// // 监听 pako.Inflate.prototype.onEnd 事件,解压完成后将解压后的数据传递给 resolve 函数 +// inflater.onEnd = function () { +// resolve(inflatedData) +// } + +// // 以流的方式读取并解压 Gzip 数据 +// const reader = response.body.getReader() +// function read() { +// reader +// .read() +// .then(({ done, value }) => { +// if (done) { +// // Gzip 数据读取完成,触发 pako.Inflate.prototype.onEnd 事件 +// inflater.end() +// return +// } +// // 将读取的数据传递给 pako.Inflate 实例进行解压 +// inflater.push(value, false) +// read() +// }) +// .catch(reject) +// } +// read() +// }) +// }) +// .then((arrayBuffer) => { +// // 处理解压后的 ArrayBuffer 数据 +// bytes = arrayBuffer +// return arrayBuffer +// }) +// .catch((error) => { +// console.error('Error fetching or decompressing the file:', error) +// }) + +// const wasm = await WebAssembly.instantiate(bytes, go.importObject) +// goExitPromise = go.run(wasm.instance) + +// await wait(100) +// return go +// } +// export function reset() { +// initiallized = false +// } +// export function getGO() { +// return go +// } +// export function getGoExitPromsie() { +// return goExitPromise +// } +// fix 2024 -8-20 修复sdk 加载慢问题 + +import pako from 'pako' + +let initialized = false +let go +let goExitPromise + +export async function initializeWasm(url) { + if (initialized) { + return null + } + if (typeof window === 'undefined') { + return Promise.resolve(null) + } + + const response = await fetch(url) + if (!response.ok) { + throw new Error(`initializeWasm Error:${response.status}`) + } + + const pk = new pako.Inflate() + const chunks = [] + pk.onData = function(chunk) { + chunks.push(chunk) + } + + const reader = response.body.getReader() + while (true) { + const { done, value } = await reader.read() + if (done) break + pk.push(value, false) + } + + pk.push(new Uint8Array(0), true) + + const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0) + const bytes = new Uint8Array(totalLength) + let position = 0 + for (const chunk of chunks) { + bytes.set(chunk, position) + position += chunk.length + } + + go = new Go() + const wasm = await WebAssembly.instantiate(bytes, go.importObject) + goExitPromise = go.run(wasm.instance) + return go +} + +export function reset() { + initialized = false +} + +export function getGO() { + return go +} + +export function getGoExitPromsie() { + return goExitPromise +} \ No newline at end of file diff --git a/src/utils/openIM/sqls/index.d.ts b/src/utils/openIM/sqls/index.d.ts new file mode 100644 index 0000000..561b213 --- /dev/null +++ b/src/utils/openIM/sqls/index.d.ts @@ -0,0 +1,16 @@ +export * from './localChatLogsConversationID'; +export * from './localConversations'; +export * from './localUsers'; +export * from './localSuperGroups'; +export * from './localConversationUnreadMessages'; +export * from './localBlack'; +export * from './localFriend'; +export * from './localGroups'; +export * from './localGroupRequests'; +export * from './localAdminGroupRequests'; +export * from './localFriendRequest'; +export * from './localGroupMembers'; +export * from './tempCacheLocalChatLogs'; +export * from './localNotification'; +export * from './localUpload'; +export * from './localStranger'; diff --git a/src/utils/openIM/sqls/index.js b/src/utils/openIM/sqls/index.js new file mode 100644 index 0000000..561b213 --- /dev/null +++ b/src/utils/openIM/sqls/index.js @@ -0,0 +1,16 @@ +export * from './localChatLogsConversationID'; +export * from './localConversations'; +export * from './localUsers'; +export * from './localSuperGroups'; +export * from './localConversationUnreadMessages'; +export * from './localBlack'; +export * from './localFriend'; +export * from './localGroups'; +export * from './localGroupRequests'; +export * from './localAdminGroupRequests'; +export * from './localFriendRequest'; +export * from './localGroupMembers'; +export * from './tempCacheLocalChatLogs'; +export * from './localNotification'; +export * from './localUpload'; +export * from './localStranger'; diff --git a/src/utils/openIM/sqls/localAdminGroupRequests.d.ts b/src/utils/openIM/sqls/localAdminGroupRequests.d.ts new file mode 100644 index 0000000..33a673e --- /dev/null +++ b/src/utils/openIM/sqls/localAdminGroupRequests.d.ts @@ -0,0 +1,9 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalAdminGroupRequest = { + [key: string]: any; +}; +export declare function localAdminGroupRequests(db: Database): QueryExecResult[]; +export declare function insertAdminGroupRequest(db: Database, localGroupRequest: LocalAdminGroupRequest): QueryExecResult[]; +export declare function deleteAdminGroupRequest(db: Database, groupID: string, userID: string): QueryExecResult[]; +export declare function updateAdminGroupRequest(db: Database, localGroupRequest: LocalAdminGroupRequest): QueryExecResult[]; +export declare function getAdminGroupApplication(db: Database): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localAdminGroupRequests.js b/src/utils/openIM/sqls/localAdminGroupRequests.js new file mode 100644 index 0000000..64d954a --- /dev/null +++ b/src/utils/openIM/sqls/localAdminGroupRequests.js @@ -0,0 +1,65 @@ +import squel from 'squel'; +export function localAdminGroupRequests(db) { + return db.exec(` + create table if not exists "local_admin_group_requests" ( + "group_id" varchar(64), + "group_name" text, + "notification" varchar(255), + "introduction" varchar(255), + "face_url" varchar(255), + "create_time" integer, + "status" integer, + "creator_user_id" varchar(64), + "group_type" integer, + "owner_user_id" varchar(64), + "member_count" integer, + "user_id" varchar(64), + "nickname" varchar(255), + "user_face_url" varchar(255), + "gender" integer, + "handle_result" integer, + "req_msg" varchar(255), + "handle_msg" varchar(255), + "req_time" integer, + "handle_user_id" varchar(64), + "handle_time" integer, + "ex" varchar(1024), + "attached_info" varchar(1024), + "join_source" integer, + "inviter_user_id" text, + PRIMARY KEY ("group_id", "user_id") + ); + `); +} +export function insertAdminGroupRequest(db, localGroupRequest) { + const sql = squel + .insert() + .into('local_admin_group_requests') + .setFields(localGroupRequest) + .toString(); + return db.exec(sql); +} +export function deleteAdminGroupRequest(db, groupID, userID) { + return db.exec(` + delete + from local_admin_group_requests + where group_id = "${groupID}" + and user_id = "${userID}" + `); +} +export function updateAdminGroupRequest(db, localGroupRequest) { + const sql = squel + .update() + .table('local_admin_group_requests') + .setFields(localGroupRequest) + .where(`group_id = '${localGroupRequest.group_id}' and user_id = '${localGroupRequest.user_id}'`) + .toString(); + return db.exec(sql); +} +export function getAdminGroupApplication(db) { + return db.exec(` + select * + from local_admin_group_requests + order by create_time desc + `); +} diff --git a/src/utils/openIM/sqls/localBlack.d.ts b/src/utils/openIM/sqls/localBlack.d.ts new file mode 100644 index 0000000..a95ee83 --- /dev/null +++ b/src/utils/openIM/sqls/localBlack.d.ts @@ -0,0 +1,12 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalBlack = { + [key: string]: any; +}; +export declare function locaBlacks(db: Database): QueryExecResult[]; +export declare function getBlackList(db: Database): QueryExecResult[]; +export declare function getBlackListUserID(db: Database): QueryExecResult[]; +export declare function getBlackInfoByBlockUserID(db: Database, blockUserID: string, loginUserID: string): QueryExecResult[]; +export declare function getBlackInfoList(db: Database, blockUserIDList: string[]): QueryExecResult[]; +export declare function insertBlack(db: Database, localBlack: LocalBlack): QueryExecResult[]; +export declare function updateBlack(db: Database, localBlack: LocalBlack): QueryExecResult[]; +export declare function deleteBlack(db: Database, blockUserID: string, loginUserID: string): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localBlack.js b/src/utils/openIM/sqls/localBlack.js new file mode 100644 index 0000000..49a514d --- /dev/null +++ b/src/utils/openIM/sqls/localBlack.js @@ -0,0 +1,72 @@ +import squel from 'squel'; +export function locaBlacks(db) { + return db.exec(` + create table if not exists 'local_blacks' ( + 'owner_user_id' varchar(64), + 'block_user_id' varchar(64), + 'nickname' varchar(255), + 'face_url' varchar(255), + 'gender' INTEGER, + 'create_time' INTEGER, + 'add_source' INTEGER, + 'operator_user_id' varchar(64), + 'ex' varchar(1024), + 'attached_info' varchar(1024), + primary key ('owner_user_id', 'block_user_id') + ) + `); +} +export function getBlackList(db) { + return db.exec(` + select * + from local_blacks + `); +} +export function getBlackListUserID(db) { + return db.exec(` + SELECT block_user_id + FROM local_blacks + `); +} +export function getBlackInfoByBlockUserID(db, blockUserID, loginUserID) { + return db.exec(` + SELECT * + FROM local_blacks + WHERE owner_user_id = "${loginUserID}" + AND block_user_id = "${blockUserID}" + LIMIT 1 + `); +} +export function getBlackInfoList(db, blockUserIDList) { + const ids = blockUserIDList.map(v => `'${v}'`); + return db.exec(` + select * + from local_blacks + where block_user_id in (${ids.join(',')}) + `); +} +export function insertBlack(db, localBlack) { + const sql = squel + .insert() + .into('local_blacks') + .setFields(localBlack) + .toString(); + return db.exec(sql); +} +export function updateBlack(db, localBlack) { + const sql = squel + .update() + .table('local_blacks') + .setFields(localBlack) + .where(`owner_user_id = '${localBlack.owner_user_id}' and block_user_id = '${localBlack.block_user_id}'`) + .toString(); + return db.exec(sql); +} +export function deleteBlack(db, blockUserID, loginUserID) { + return db.exec(` + delete + from local_blacks + where owner_user_id = "${loginUserID}" + and block_user_id = "${blockUserID}" + `); +} diff --git a/src/utils/openIM/sqls/localChatLogsConversationID.d.ts b/src/utils/openIM/sqls/localChatLogsConversationID.d.ts new file mode 100644 index 0000000..16816a6 --- /dev/null +++ b/src/utils/openIM/sqls/localChatLogsConversationID.d.ts @@ -0,0 +1,232 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js' +export declare type ClientMessage = { + [key: string]: any +} +export declare function localChatLogsConversationID( + db: Database, + conversationID: string +): QueryExecResult[] +export declare function getMessage( + db: Database, + conversationID: string, + clientMsgID: string +): QueryExecResult[] +export declare function getAlreadyExistSeqList( + db: Database, + conversationID: string, + lostSeqList: number[] +): QueryExecResult[] +export declare function getMessageList( + db: Database, + conversationID: string, + count: number, + startTime: number, + isReverse: boolean +): QueryExecResult[] +export declare function getMessageBySeq( + db: Database, + conversationID: string, + seq: number +): QueryExecResult[] +export declare function getMessagesByClientMsgIDs( + db: Database, + conversationID: string, + clientMsgIDs: string[] +): QueryExecResult[] +export declare function getMessagesBySeqs( + db: Database, + conversationID: string, + seqs: number[] +): QueryExecResult[] +export declare function getMessageListNoTime( + db: Database, + conversationID: string, + count: number, + isReverse: boolean +): QueryExecResult[] +export declare function getMessageListNotDel( + db: Database, + conversationID: string, + count: number, + isReverse: boolean +): QueryExecResult[] +export declare function getLastMessageByTime( + db: Database, + conversationID: string, + count: number, + startTime: number, + isReverse: boolean +): QueryExecResult[] +export declare function getConversationNormalMsgSeq( + db: Database, + conversationID: string, + init: boolean +): QueryExecResult[] +export declare function getConversationPeerNormalMsgSeq( + db: Database, + conversationID: string, + loginUserID: string +): QueryExecResult[] +export declare function getSendingMessageList( + db: Database, + conversationID: string +): QueryExecResult[] +export declare function updateMessageTimeAndStatus( + db: Database, + conversationID: string, + clientMsgID: string, + serverMsgID: string, + sendTime: number, + status: number +): QueryExecResult[] +export declare function updateMessageStatus( + db: Database, + conversationID: string, + clientMsgID: string, + status: number +): QueryExecResult[] +export declare function updateMessage( + db: Database, + conversationID: string, + clientMsgID: string, + localChatLogs: ClientMessage +): QueryExecResult[] +export declare function updateMessageBySeq( + db: Database, + conversationID: string, + seq: number, + localChatLogs: ClientMessage +): QueryExecResult[] +export declare function batchInsertMessageList( + db: Database, + conversationID: string, + messageList: ClientMessage[] +): QueryExecResult[] +export declare function insertMessage( + db: Database, + conversationID: string, + localChatLogs: ClientMessage +): QueryExecResult[] +export declare function getMultipleMessage( + db: Database, + conversationID: string, + clientMsgIDs: string[] +): QueryExecResult[] +export declare function searchMessageByKeyword( + db: Database, + conversationID: string, + contentType: number[], + keywordList: string[], + keywordListMatchType: number, + startTime: number, + endTime: number, + offset: number, + count: number +): QueryExecResult[] +export declare function searchMessageByContentType( + db: Database, + conversationID: string, + contentType: number[], + startTime: number, + endTime: number, + offset: number, + count: number +): QueryExecResult[] +export declare function searchMessageByContentTypeAndKeyword( + db: Database, + conversationID: string, + contentType: number[], + keywordList: string[], + keywordListMatchType: number, + startTime: number, + endTime: number +): QueryExecResult[] +export declare function messageIfExists( + db: Database, + conversationID: string, + clientMsgID: string +): QueryExecResult[] +export declare function updateMsgSenderFaceURLAndSenderNickname( + db: Database, + conversationID: string, + sendID: string, + faceURL: string, + nickname: string +): QueryExecResult[] +export declare function deleteConversationAllMessages( + db: Database, + conversationID: string +): QueryExecResult[] +export declare function deleteConversationAllMessagesBySeq( + db: Database, + conversationID: string, + maxSeq: number +): QueryExecResult[] +export declare function markDeleteConversationAllMessages( + db: Database, + conversationID: string +): QueryExecResult[] +export declare function getUnreadMessage( + db: Database, + conversationID: string, + loginUserID: string +): QueryExecResult[] +export declare function markConversationMessageAsReadBySeqs( + db: Database, + conversationID: string, + seqList: number[], + loginUserID: string +): QueryExecResult[] +export declare function markConversationMessageAsRead( + db: Database, + conversationID: string, + clientMsgIDList: string[], + loginUserID: string +): QueryExecResult[] +export declare function updateColumnsMessage( + db: Database, + conversationID: string, + clientMsgID: string, + localChatLogs: ClientMessage +): QueryExecResult[] +export declare function deleteConversationMsgs( + db: Database, + conversationID: string, + clientMsgIDList: string[] +): QueryExecResult[] +export declare function markConversationAllMessageAsRead( + db: Database, + conversationID: string, + loginUserID: string +): QueryExecResult[] +export declare function searchAllMessageByContentType( + db: Database, + conversationID: string, + contentType: number +): QueryExecResult[] + +export declare function updateMessageBySeqEx( + db: Database, + conversationID: string, + clientMsgID: string, + seq: number, + localChatLogs: ClientMessage +): QueryExecResult[] +// type SpecialClientMessage = { +// [status: string]: 4 +// } +export declare function updateMessageBySeqs( + db: Database, + conversationID: string, + seqs: number[], + localChatLogs: ClientMessage, +): QueryExecResult[] + + + +export declare function updateMessageByUserID( + db: Database, + conversationID: string, + userID: string, + localChatLogs: ClientMessage, +): QueryExecResult[] diff --git a/src/utils/openIM/sqls/localChatLogsConversationID.js b/src/utils/openIM/sqls/localChatLogsConversationID.js new file mode 100644 index 0000000..44637a7 --- /dev/null +++ b/src/utils/openIM/sqls/localChatLogsConversationID.js @@ -0,0 +1,419 @@ +import squel from 'squel' +function _initLocalChatLogsTable(db, conversationID) { + localChatLogsConversationID(db, conversationID) +} +export function localChatLogsConversationID(db, conversationID) { + return db.exec(` + create table if not exists 'chat_logs_${conversationID}' ( + 'client_msg_id' char(32), + 'server_msg_id' char(32), + 'send_id' char(32), + 'recv_id' char(32), + 'sender_platform_id' smallint, + 'sender_nick_name' varchar(255), + 'sender_face_url' varchar(255), + 'session_type' smallint, + 'msg_from' smallint, + 'content_type' smallint, + 'content' varchar(1000), + 'is_read' tinyint(1), + 'status' smallint, + 'seq' int DEFAULT 0, + 'send_time' int, + 'create_time' int, + 'attached_info' varchar(1024), + 'ex' varchar(1024), + 'local_ex' varchar(1024), + 'is_react' tinyint(1), + 'is_external_extensions' tinyint(1), + 'msg_first_modify_time' int, + PRIMARY KEY ('client_msg_id') + ); + `) +} +export function getMessage(db, conversationID, clientMsgID) { + _initLocalChatLogsTable(db, conversationID) + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE client_msg_id = '${clientMsgID}' limit 1 + `) +} +export function getAlreadyExistSeqList(db, conversationID, lostSeqList) { + return db.exec(` + SELECT seq FROM 'chat_logs_${conversationID}' WHERE seq in (${lostSeqList.join(',')}) + `) +} +export function getMessageList(db, conversationID, count, startTime, isReverse) { + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE send_time ${ + !isReverse ? '<' : '>' + } ${startTime} ORDER BY send_time ${!isReverse ? 'DESC' : 'ASC'} LIMIT ${count} + `) +} +export function getLastMessageByTime(db, conversationID, count, startTime, isReverse) { + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE send_time ${ + !isReverse ? '<=' : '>=' + } ${startTime} ORDER BY send_time ${!isReverse ? 'DESC' : 'ASC'} LIMIT ${count} + `) +} +export function getMessageBySeq(db, conversationID, seq) { + _initLocalChatLogsTable(db, conversationID) + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE seq = ${seq} limit 1; + `) +} +export function getMessagesByClientMsgIDs(db, conversationID, clientMsgIDs) { + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE client_msg_id in (${clientMsgIDs + .map((item) => `'${item}'`) + .join(',')}) order by send_time desc; + `) +} +export function getMessagesBySeqs(db, conversationID, seqs) { + _initLocalChatLogsTable(db, conversationID) + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE seq in (${seqs.join( + ',' + )}) order by send_time desc; + `) +} +export function getMessageListNoTime(db, conversationID, count, isReverse) { + _initLocalChatLogsTable(db, conversationID) + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' ORDER BY send_time ${ + !isReverse ? 'DESC' : 'ASC' + } LIMIT ${count} + `) +} +export function getMessageListNotDel(db, conversationID, count, isReverse) { + _initLocalChatLogsTable(db, conversationID) + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' Where status = 2 order by send_time ${ + !isReverse ? 'DESC' : 'ASC' + } LIMIT ${count} + `) +} +export function getConversationNormalMsgSeq(db, conversationID, init) { + if (init) { + _initLocalChatLogsTable(db, conversationID) + } + return db.exec(` + SELECT seq FROM 'chat_logs_${conversationID}' order by seq desc limit 1; + `) +} +export function getConversationPeerNormalMsgSeq(db, conversationID, loginUserID) { + return db.exec(` + SELECT seq FROM 'chat_logs_${conversationID}' where send_id != '${loginUserID}' order by seq desc limit 1; + `) +} +export function getSendingMessageList(db, conversationID) { + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE status = 1; + `) +} +export function updateMessageTimeAndStatus( + db, + conversationID, + clientMsgID, + serverMsgID, + sendTime, + status +) { + return db.exec(` + UPDATE 'chat_logs_${conversationID}' SET server_msg_id = '${serverMsgID}', send_time = ${sendTime}, status = ${status} WHERE client_msg_id = '${clientMsgID}' and seq=0; + `) +} +export function updateMessageStatus( + db, + conversationID, + clientMsgID, + status +) { + return db.exec(` + UPDATE 'chat_logs_${conversationID}' SET status = ${status} WHERE client_msg_id = '${clientMsgID}' and seq=0; + `) +} +export function updateMessage(db, conversationID, clientMsgID, localChatLogs) { + const sql = squel + .update() + .table(`chat_logs_${conversationID}`) + .setFields(localChatLogs) + .where(`client_msg_id = '${clientMsgID}'`) + .toString() + return db.exec(sql) +} +export function updateMessageBySeq(db, conversationID, seq, localChatLogs) { + const sql = squel + .update() + .table(`chat_logs_${conversationID}`) + .setFields(localChatLogs) + .where(`seq = '${seq}'`) + .toString() + return db.exec(sql) +} +export function batchInsertMessageList(db, conversationID, messageList) { + const sql = squel + .insert() + .into(`chat_logs_${conversationID}`) + .setFieldsRows(messageList) + .toString() + return db.exec(sql) +} +export function insertMessage(db, conversationID, localChatLogs) { + const sql = squel.insert().into(`chat_logs_${conversationID}`).setFields(localChatLogs).toString() + return db.exec(sql) +} +export function getMultipleMessage(db, conversationID, clientMsgIDs) { + return db.exec(` + SELECT * FROM 'chat_logs_${conversationID}' WHERE client_msg_id in (${clientMsgIDs + .map((item) => `'${item}'`) + .join(',')}) order by send_time desc; + `) +} +export function searchMessageByKeyword( + db, + conversationID, + contentType, + keywordList, + keywordListMatchType, + startTime, + endTime, + offset, + count +) { + const finalEndTime = endTime ? endTime : new Date().getTime() + let subCondition = '' + const values = contentType.map((v) => `${v}`).join(',') + const connectStr = keywordListMatchType === 0 ? 'or ' : 'and ' + keywordList?.forEach((keyword, index) => { + if (index == 0) { + subCondition += 'And (' + } + if (index + 1 >= keywordList.length) { + subCondition += 'content like ' + "'%" + keywordList[index] + "%') " + } else { + subCondition += 'content like ' + "'%" + keywordList[index] + "%' " + connectStr + } + }) + return db.exec(` + SELECT * FROM chat_logs_${conversationID} + WHERE send_time between ${startTime} and ${finalEndTime} + AND status <=3 + And content_type IN (${values}) + ${subCondition} + ORDER BY send_time DESC LIMIT ${count} OFFSET ${offset}; + `) +} +export function searchMessageByContentType( + db, + conversationID, + contentType, + startTime, + endTime, + offset, + count +) { + const values = contentType.map((v) => `${v}`).join(',') + const finalEndTime = endTime ? endTime : new Date().getTime() + return db.exec(` + SELECT * FROM chat_logs_${conversationID} + WHERE send_time between ${startTime} and ${finalEndTime} + AND status <=3 + And content_type IN (${values}) + ORDER BY send_time DESC LIMIT ${count} OFFSET ${offset}; + `) +} +export function searchMessageByContentTypeAndKeyword( + db, + conversationID, + contentType, + keywordList, + keywordListMatchType, + startTime, + endTime +) { + const values = contentType.map((v) => `${v}`).join(',') + const finalEndTime = endTime ? endTime : new Date().getTime() + let subCondition = '' + const connectStr = keywordListMatchType === 0 ? 'or ' : 'and ' + keywordList?.forEach((keyword, index) => { + if (index == 0) { + subCondition += 'And (' + } + if (index + 1 >= keywordList.length) { + subCondition += 'content like ' + "'%" + keywordList[index] + "%') " + } else { + subCondition += 'content like ' + "'%" + keywordList[index] + "%' " + connectStr + } + }) + return db.exec(` + SELECT * FROM chat_logs_${conversationID} + WHERE send_time between ${startTime} and ${finalEndTime} + AND status <=3 + And content_type IN (${values}) + ${subCondition} + ORDER BY send_time DESC; + `) +} +export function messageIfExists(db, conversationID, clientMsgID) { + return db.exec(` + SELECT * FROM chat_logs_${conversationID} WHERE client_msg_id = '${clientMsgID}'; + `) +} +// export function MessageIfExistsBySeq( +// db: Database, +// conversationID: string, +// seq: number +// ): QueryExecResult[] { +// return db.exec( +// ` +// SELECT * FROM 'chat_logs_${conversationID}' WHERE seq = ${seq}; +// ` +// ); +// } +// export function UpdateGroupMessageHasRead( +// db: Database, +// ) +// export function getMultipleMessage( +// db: Database, +// ) +// export function updateMsgSenderNickname( +// db: Database, +// ) +// export function updateMsgSenderFaceURL( +// db: Database, +// ) +export function updateMsgSenderFaceURLAndSenderNickname( + db, + conversationID, + sendID, + faceURL, + nickname +) { + return db.exec(` + UPDATE chat_logs_${conversationID} SET sender_face_url = '${faceURL}', sender_nick_name = '${nickname}' WHERE send_id = '${sendID}'; + `) +} +// export function getMsgSeqByClientMsgID( +// db: Database, +// ) +// export function getMsgSeqListByGroupID( +// db: Database, +// ) +// export function getMsgSeqListByPeerUserID( +// db: Database, +// ) +// export function getMsgSeqListBySelfUserID( +// db: Database, +// ) +// export function deleteAllMessage( +// db: Database, +// ) +// export function getAllUnDeleteMessageSeqList( +// db: Database, +// ) +export function deleteConversationAllMessages(db, conversationID) { + return db.exec(` + DELETE FROM chat_logs_${conversationID} WHERE 1=1; + `) +} +export function deleteConversationAllMessagesBySeq(db, conversationID, maxSeq) { + return db.exec(` + DELETE FROM chat_logs_${conversationID} WHERE seq <= ${maxSeq}; + `) +} +export function markDeleteConversationAllMessages(db, conversationID) { + return db.exec(` + UPDATE chat_logs_${conversationID} SET status = 2 WHERE (1=1) and (conversation_id = '${conversationID}')'; + `) +} +export function getUnreadMessage(db, conversationID, loginUserID) { + return db.exec(` + SELECT * FROM chat_logs_${conversationID} WHERE send_id != '${loginUserID}' and is_read = 0; + `) +} +export function markConversationMessageAsReadBySeqs(db, conversationID, seqList, loginUserID) { + _initLocalChatLogsTable(db, conversationID) + const values = seqList.map((v) => `${v}`).join(',') + return db.exec(` + UPDATE chat_logs_${conversationID} SET is_read = 1 WHERE seq IN (${values}) and send_id != '${loginUserID}'; + `) +} +export function markConversationMessageAsRead(db, conversationID, clientMsgIDList, loginUserID) { + const values = clientMsgIDList.map((v) => `'${v}'`).join(',') + return db.exec(` + UPDATE chat_logs_${conversationID} SET is_read = 1 WHERE client_msg_id IN (${values}) and send_id != '${loginUserID}'; + `) +} +export function updateColumnsMessage(db, conversationID, clientMsgID, localChatLogs) { + const sql = squel + .update() + .table(`chat_logs_${conversationID}`) + .setFields(localChatLogs) + .where(`client_msg_id = '${clientMsgID}'`) + .toString() + return db.exec(sql) +} +export function deleteConversationMsgs(db, conversationID, clientMsgIDList) { + const values = clientMsgIDList.map((v) => `'${v}'`).join(',') + return db.exec(` + DELETE FROM chat_logs_${conversationID} WHERE client_msg_id IN (${values}); + `) +} +// export function updateSingleMessageHasRead( +// db: Database, +// ) +// export function updateGroupMessageHasRead( +// db: Database, +// ) +// export function updateMessageStatusBySourceID( +// db: Database, +// ) +export function markConversationAllMessageAsRead(db, conversationID, loginUserID) { + return db.exec(` + UPDATE chat_logs_${conversationID} SET is_read = 1 WHERE is_read = 0 and send_id != '${loginUserID}'; + `) +} +// export function deleteConversationMsgsBySeqs( +// db: Database, +// ) +export function searchAllMessageByContentType(db, conversationID, contentType) { + return db.exec(` + SELECT * FROM chat_logs_${conversationID} WHERE content_type = ${contentType}; + `) +} +export function updateMessageBySeqEx(db, conversationID, seq, localChatLogs) { + const sql = squel + .update() + .table(`chat_logs_${conversationID}`) + .setFields(localChatLogs) + .where(`seq = '${seq}'`) + .toString() + console.log('updateMessageBySeqEx---------------------------------------', seq) + return db.exec(sql) +} + +export function updateMessageBySeqs(db, conversationID, seqs, localChatLogs) { + const values = seqs.map((v) => `${v}`).join(',') + const sql = squel + .update() + .table(`chat_logs_${conversationID}`) + .setFields(localChatLogs) + .where(`seq IN (${values})`) + .toString() + console.log('updateMessageBySeqs', values) + return db.exec(sql) +} + + +export function updateMessageByUserID(db, conversationID, userID, localChatLogs) { + + const sql = squel + .update() + .table(`chat_logs_${conversationID}`) + .setFields(localChatLogs) + .where(`send_id = '${userID}'`) + .toString() + console.log('updateMessageByUserID') + return db.exec(sql) +} \ No newline at end of file diff --git a/src/utils/openIM/sqls/localConversationUnreadMessages.d.ts b/src/utils/openIM/sqls/localConversationUnreadMessages.d.ts new file mode 100644 index 0000000..3667ad9 --- /dev/null +++ b/src/utils/openIM/sqls/localConversationUnreadMessages.d.ts @@ -0,0 +1,7 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type ClientLocalConversationUnreadMessage = { + [key: string]: any; +}; +export declare function localConversationUnreadMessages(db: Database): QueryExecResult[]; +export declare function deleteConversationUnreadMessageList(db: Database, conversationID: string, sendTime: number): QueryExecResult[]; +export declare function batchInsertConversationUnreadMessageList(db: Database, messageList: ClientLocalConversationUnreadMessage[]): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localConversationUnreadMessages.js b/src/utils/openIM/sqls/localConversationUnreadMessages.js new file mode 100644 index 0000000..6aeadc9 --- /dev/null +++ b/src/utils/openIM/sqls/localConversationUnreadMessages.js @@ -0,0 +1,28 @@ +import squel from 'squel'; +export function localConversationUnreadMessages(db) { + return db.exec(` + create table if not exists 'local_conversation_unread_messages' ( + 'conversation_id' char(128), + 'client_msg_id' char(64), + 'send_time' integer, + 'ex' varchar(1024), + primary key ( + 'conversation_id', + 'client_msg_id' + ) + ); + `); +} +export function deleteConversationUnreadMessageList(db, conversationID, sendTime) { + return db.exec(` + delete from local_conversation_unread_messages where conversation_id = '${conversationID}' and send_time <= ${sendTime}; + `); +} +export function batchInsertConversationUnreadMessageList(db, messageList) { + const sql = squel + .insert() + .into('local_conversation_unread_messages') + .setFieldsRows(messageList) + .toString(); + return db.exec(sql); +} diff --git a/src/utils/openIM/sqls/localConversations.d.ts b/src/utils/openIM/sqls/localConversations.d.ts new file mode 100644 index 0000000..5d2c9aa --- /dev/null +++ b/src/utils/openIM/sqls/localConversations.d.ts @@ -0,0 +1,35 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type ClientConversation = { + [key: string]: any; +}; +export declare function localConversations(db: Database): QueryExecResult[]; +export declare function getConversationByUserID(db: Database, userID: string): QueryExecResult[]; +export declare function getAllConversationList(db: Database): QueryExecResult[]; +export declare function getAllConversationListToSync(db: Database): QueryExecResult[]; +export declare function getAllSingleConversationIDList(db: Database): QueryExecResult[]; +export declare function getAllConversationIDList(db: Database): QueryExecResult[]; +export declare function getHiddenConversationList(db: Database): QueryExecResult[]; +export declare function getConversationListSplit(db: Database, offset: number, count: number): QueryExecResult[]; +export declare function getConversation(db: Database, conversationID: string): QueryExecResult[]; +export declare function getMultipleConversation(db: Database, conversationIDList: string[]): QueryExecResult[]; +export declare function updateColumnsConversation(db: Database, conversationID: string, conversation: ClientConversation): QueryExecResult[]; +export declare function incrConversationUnreadCount(db: Database, conversationID: string): QueryExecResult[]; +export declare function decrConversationUnreadCount(db: Database, conversationID: string, count: number): QueryExecResult[]; +export declare function batchInsertConversationList(db: Database, conversationList: ClientConversation[]): QueryExecResult[]; +export declare function insertConversation(db: Database, localConversation: ClientConversation): QueryExecResult[]; +export declare function updateConversation(db: Database, localConversation: ClientConversation): QueryExecResult[]; +export declare function deleteConversation(db: Database, conversationID: string): QueryExecResult[]; +export declare function conversationIfExists(db: Database, conversationID: string): QueryExecResult[]; +export declare function resetConversation(db: Database, conversationID: string): QueryExecResult[]; +export declare function resetAllConversation(db: Database): QueryExecResult[]; +export declare function clearConversation(db: Database, conversationID: string): QueryExecResult[]; +export declare function clearConversationByTime(db: Database, conversationID: string): QueryExecResult[]; +export declare function clearAllConversation(db: Database): QueryExecResult[]; +export declare function setConversationDraft(db: Database, conversationID: string, draftText: string): QueryExecResult[]; +export declare function removeConversationDraft(db: Database, conversationID: string, draftText: string): QueryExecResult[]; +export declare function unPinConversation(db: Database, conversationID: string, isPinned: number): QueryExecResult[]; +export declare function getTotalUnreadMsgCount(db: Database): QueryExecResult[]; +export declare function setMultipleConversationRecvMsgOpt(db: Database, conversationIDList: string[], opt: number): QueryExecResult[]; +export declare function getAllConversations(db: Database): QueryExecResult[]; +export declare function batchUpdateConversationsByList(db: Database, conversations: ClientConversation[]): QueryExecResult[]; +export declare function batchDeleteConversations(db: Database, conversationIds: string[]): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localConversations.js b/src/utils/openIM/sqls/localConversations.js new file mode 100644 index 0000000..dafb68e --- /dev/null +++ b/src/utils/openIM/sqls/localConversations.js @@ -0,0 +1,271 @@ +import { update } from 'lodash' +import squel from 'squel' +export function localConversations(db) { + return db.exec(` + create table if not exists 'local_conversations' ( + 'conversation_id' char(128), + 'conversation_type' integer, + 'user_id' char(64), + 'group_id' char(128), + 'show_name' varchar(255), + 'face_url' varchar(255), + 'recv_msg_opt' integer, + 'unread_count' integer, + 'group_at_type' integer, + 'latest_msg' varchar(1000), + 'latest_msg_send_time' integer, + 'draft_text' text, + 'draft_text_time' integer, + 'is_pinned' numeric, + 'burn_duration' integer, + 'is_private_chat' numeric, + 'is_not_in_group' numeric, + 'update_unread_count_time' integer, + 'attached_info' varchar(1024), + 'ex' varchar(1024), + 'max_seq' integer, + 'min_seq' integer, + 'has_read_seq' integer, + 'msg_destruct_time' integer default 604800, + 'is_msg_destruct' numeric default false, + primary key ('conversation_id') + ) + `) +} +export function getConversationByUserID(db, userID) { + return db.exec(` + select * from local_conversations where user_id = "${userID}" limit 1; + `) +} +export function getAllConversationList(db) { + return db.exec(` + select * from local_conversations where latest_msg_send_time > 0 order by case when is_pinned=1 then 0 else 1 end,max(latest_msg_send_time,draft_text_time) desc; + `) +} +export function getAllConversationListToSync(db) { + return db.exec(` + select * from local_conversations; + `) +} +export function getAllSingleConversationIDList(db) { + return db.exec(` + select conversation_id from local_conversations where conversation_type = 1; + `) +} +export function getAllConversationIDList(db) { + return db.exec(` + select conversation_id from local_conversations; + `) +} +export function getHiddenConversationList(db) { + return db.exec(` + select * from local_conversations where latest_msg_send_time = 0; + `) +} +export function getConversationListSplit(db, offset, count) { + return db.exec(` + SELECT * + FROM local_conversations + WHERE latest_msg_send_time > 0 + ORDER BY case + when is_pinned = 1 then 0 + else 1 end, max(latest_msg_send_time, draft_text_time) DESC + LIMIT ${count} OFFSET ${offset} + `) +} +export function getConversation(db, conversationID) { + return db.exec(` + select * from local_conversations where conversation_id = '${conversationID}' limit 1; + `) +} +export function getMultipleConversation(db, conversationIDList) { + const ids = conversationIDList.map((v) => `'${v}'`) + return db.exec(` + select * from local_conversations where conversation_id in (${ids.join(',')}); + `) +} +export function updateColumnsConversation(db, conversationID, conversation) { + const sql = squel + .update() + .table('local_conversations') + .setFields(conversation) + .where(`conversation_id = '${conversationID}'`) + .toString() + return db.exec(sql) +} +export function incrConversationUnreadCount(db, conversationID) { + return db.exec(` + update local_conversations set + unread_count=unread_count+1 + where conversation_id = '${conversationID}'; + `) +} +export function decrConversationUnreadCount(db, conversationID, count) { + db.exec('begin') + db.exec(` + update local_conversations set + unread_count=unread_count-${count} + where conversation_id = '${conversationID}'; + `) + const current = db.exec( + `select unread_count from local_conversations where conversation_id = '${conversationID}'` + ) + if (Number(current[0].values[0]) < 0) { + db.exec(` + update local_conversations set + unread_count=${0} + where conversation_id = '${conversationID}'; + `) + } + return db.exec('commit') +} +export function batchInsertConversationList(db, conversationList) { + const sql = squel.insert().into('local_conversations').setFieldsRows(conversationList).toString() + return db.exec(sql) +} +export function insertConversation(db, localConversation) { + const sql = squel.insert().into('local_conversations').setFields(localConversation).toString() + return db.exec(sql) +} +export function updateConversation(db, localConversation) { + const sql = squel + .update() + .table('local_conversations') + .setFields(localConversation) + .where(`conversation_id = '${localConversation.conversation_id}'`) + .toString() + return db.exec(sql) +} +export function deleteConversation(db, conversationID) { + return db.exec(` + DELETE + FROM local_conversations + WHERE conversation_id = "${conversationID}" + `) +} +export function batchDeleteConversations(db, conversationIds) { + const values = conversationIds.map((v) => `${v}`).join(',') + return db.exec(` + DELETE + FROM local_conversations + WHERE conversation_id in "${values}" + `) +} +export function conversationIfExists(db, conversationID) { + return db.exec(` + SELECT count(*) + FROM local_conversations + WHERE conversation_id = "${conversationID}" + `) +} +export function resetConversation(db, conversationID) { + return db.exec(` + UPDATE local_conversations + SET unread_count=0, + latest_msg="", + latest_msg_send_time=0, + draft_text="", + draft_text_time=0 +WHERE conversation_id = "${conversationID}" + `) +} +export function resetAllConversation(db) { + return db.exec(` + UPDATE local_conversations + SET unread_count=0, + latest_msg="", + latest_msg_send_time=0, + draft_text="", + draft_text_time=0 + `) +} +export function clearConversation(db, conversationID) { + return db.exec(` + UPDATE local_conversations +SET unread_count=0, + latest_msg="", + draft_text="", + draft_text_time=0 +WHERE conversation_id = "${conversationID}" + `) +} +export function clearConversationByTime(db, conversationID, endTime) { + return db.exec(` + UPDATE local_conversations +SET unread_count=0, + latest_msg="", + draft_text="", + draft_text_time=0 +WHERE conversation_id = "${conversationID}" + AND latest_msg_send_time <= ${endTime} + `) +} +export function clearAllConversation(db) { + return db.exec(` + UPDATE local_conversations +SET unread_count=0, + latest_msg="", + draft_text="", + draft_text_time=0 + `) +} +export function setConversationDraft(db, conversationID, draftText) { + const nowDate = new Date().getTime() + return db.exec(` + update local_conversations + set draft_text='${draftText}', + draft_text_time=${nowDate}, + latest_msg_send_time=case when latest_msg_send_time = 0 then ${nowDate} else latest_msg_send_time end + where conversation_id = "${conversationID}" + `) +} +export function removeConversationDraft(db, conversationID, draftText) { + return db.exec(` + update local_conversations + set draft_text="${draftText}", + draft_text_time=0 + where conversation_id = "${conversationID}" + `) +} +export function unPinConversation(db, conversationID, isPinned) { + return db.exec(` + update local_conversations + set is_pinned=${isPinned}, + draft_text_time=case when draft_text = "" then 0 else draft_text_time end + where conversation_id = "${conversationID}" + `) +} +export function getTotalUnreadMsgCount(db) { + return db.exec(` + select sum(unread_count) from local_conversations where recv_msg_opt < 2 and latest_msg_send_time > 0; + `) +} +export function setMultipleConversationRecvMsgOpt(db, conversationIDList, opt) { + const values = conversationIDList.map((v) => `${v}`).join(',') + return db.exec(` + UPDATE local_conversations + SET recv_msg_opt=${opt} + WHERE conversation_id IN (${values}) + `) +} +export function getAllConversations(db) { + return db.exec(` + SELECT * FROM local_conversations + `) +} + +export function batchUpdateConversationsByList (db, conversations) { + var result = {} + conversations.forEach(item => { + result = updateConversation(db, item); + }); + return result +}; + +export function batchUpdateConversationsByMap (db, conversations) { + var result = {} + conversations.forEach(item => { + result = updateColumnsConversation(db, item.ConversationId, item.Data); + }); + return result +}; \ No newline at end of file diff --git a/src/utils/openIM/sqls/localFriend.d.ts b/src/utils/openIM/sqls/localFriend.d.ts new file mode 100644 index 0000000..0cbae79 --- /dev/null +++ b/src/utils/openIM/sqls/localFriend.d.ts @@ -0,0 +1,15 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalFriend = { + [key: string]: any; +}; +export declare function localFriends(db: Database): QueryExecResult[]; +export declare function insertFriend(db: Database, localFriend: LocalFriend): QueryExecResult[]; +export declare function deleteFriend(db: Database, friendUserID: string, loginUserID: string): QueryExecResult[]; +export declare function updateFriend(db: Database, localFriend: LocalFriend): QueryExecResult[]; +export declare function getAllFriendList(db: Database, loginUser: string): QueryExecResult[]; +export declare function getPageFriendList(db: Database, offset: number, count: number, loginUser: string): QueryExecResult[]; +export declare function searchFriendList(db: Database, keyword: string, isSearchUserID: boolean, isSearchNickname: boolean, isSearchRemark: boolean): QueryExecResult[]; +export declare function getFriendInfoByFriendUserID(db: Database, friendUserID: string, loginUser: string): QueryExecResult[]; +export declare function getFriendInfoList(db: Database, friendUserIDList: string[]): QueryExecResult[]; +export declare function getFriendInfos(db: Database, friendUserIDList: string[]): QueryExecResult[]; +export declare function batchInsertFriends(db: Database, friends: LocalFriend[]): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localFriend.js b/src/utils/openIM/sqls/localFriend.js new file mode 100644 index 0000000..84e3ac9 --- /dev/null +++ b/src/utils/openIM/sqls/localFriend.js @@ -0,0 +1,130 @@ +import squel from 'squel'; +export function localFriends(db) { + return db.exec(` + create table if not exists 'local_friends' + ( + 'owner_user_id' varchar(64), + 'friend_user_id' varchar(64), + 'remark' varchar(255), + 'create_time' INTEGER, + 'add_source' INTEGER, + 'operator_user_id' varchar(64), + 'name' varchar(255), + 'face_url' varchar(255), + 'ex' varchar(1024), + 'attached_info' varchar(1024), + primary key ('owner_user_id', 'friend_user_id') + ) + `); +} +export function insertFriend(db, localFriend) { + const sql = squel + .insert() + .into('local_friends') + .setFields(localFriend) + .toString(); + return db.exec(sql); +} +export function deleteFriend(db, friendUserID, loginUserID) { + return db.exec(` + DELETE FROM local_friends + WHERE owner_user_id="${loginUserID}" + and friend_user_id="${friendUserID}" + `); +} +export function batchDeleteFriends(db, loginUserID, friendUserIds) { + const values = friendUserIds.map(v => `'${v}'`).join(','); + return db.exec(` + DELETE FROM local_friends + WHERE owner_user_id="${loginUserID}" + and friend_user_id in "${values}" + `); +} +export function updateFriend(db, localFriend) { + if (localFriend.remark == undefined) { + localFriend.remark = "" + } + const sql = squel + .update() + .table('local_friends') + .setFields(localFriend) + .where(`owner_user_id = '${localFriend.owner_user_id}' and friend_user_id = '${localFriend.friend_user_id}'`) + .toString(); + return db.exec(sql); +} + +export function getAllFriendList(db, loginUser) { + return db.exec(` + select * + from local_friends + where owner_user_id = "${loginUser}" + `); +} +export function getPageFriendList(db, offset, count, loginUser) { + return db.exec(` + select * + from local_friends + where owner_user_id = "${loginUser}" + order by name + limit ${count} offset ${offset} + `); +} +export function searchFriendList(db, keyword, isSearchUserID, isSearchNickname, isSearchRemark) { + let totalConditionStr = ''; + const userIDCondition = `friend_user_id like "%${keyword}%"`; + const nicknameCondition = `name like "%${keyword}%"`; + const remarkCondition = `remark like "%${keyword}%"`; + if (isSearchUserID) { + totalConditionStr = userIDCondition; + } + if (isSearchNickname) { + totalConditionStr = totalConditionStr + ? totalConditionStr + ' or ' + nicknameCondition + : nicknameCondition; + } + if (isSearchRemark) { + totalConditionStr = totalConditionStr + ? totalConditionStr + ' or ' + remarkCondition + : remarkCondition; + } + return db.exec(` + select * + from local_friends + where ${totalConditionStr} + order by create_time desc + `); +} +export function getFriendInfoByFriendUserID(db, friendUserID, loginUser) { + return db.exec(` + select * + from local_friends + where owner_user_id = "${loginUser}" + and friend_user_id = "${friendUserID}" + limit 1 + `); +} +export function getFriendInfoList(db, friendUserIDList) { + const values = friendUserIDList.map(v => `'${v}'`).join(','); + return db.exec(` + select * + from local_friends + where friend_user_id in (${values}) + `); +} + +export function getFriendInfos(db, ownerUserID, friendUserIds) { + const values = friendUserIds.map(v => `'${v}'`).join(','); + return db.exec(` + select * from local_friends + where owner_user_id = "${ownerUserID}" and friend_user_id in (${values}) + `); +} + +export function batchInsertFriends(db, localFriends) { + const sql = squel + .insert() + .into('local_friends') + .setFieldsRows(localFriends) + .toString(); + return db.exec(sql); +} \ No newline at end of file diff --git a/src/utils/openIM/sqls/localFriendRequest.d.ts b/src/utils/openIM/sqls/localFriendRequest.d.ts new file mode 100644 index 0000000..38e4ff1 --- /dev/null +++ b/src/utils/openIM/sqls/localFriendRequest.d.ts @@ -0,0 +1,14 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalFriendRequest = { + [key: string]: any; +}; +export declare function localFriendRequests(db: Database): QueryExecResult[]; +export declare function insertFriendRequest(db: Database, localFriendRequest: LocalFriendRequest): QueryExecResult[]; +export declare function deleteFriendRequestBothUserID(db: Database, fromUserID: string, toUserID: string): QueryExecResult[]; +export declare function updateFriendRequest(db: Database, localFriendRequest: LocalFriendRequest): QueryExecResult[]; +export declare function getRecvFriendApplication(db: Database, loginUserID: string): QueryExecResult[]; +export declare function getSendFriendApplication(db: Database, loginUserID: string): QueryExecResult[]; +export declare function getFriendApplicationByBothID(db: Database, fromUserID: string, toUserID: boolean): QueryExecResult[]; +export declare function getBothFriendReq(db: Database, fromUserID: string, toUserID: boolean): QueryExecResult[]; +export declare function batchInsertFriendRequests(db: Database, localFriendRequests: LocalFriendRequest[]): QueryExecResult[]; +export declare function batchDeleteFriendRequests(db: Database, toUserID: string,fromUserID: string[]): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localFriendRequest.js b/src/utils/openIM/sqls/localFriendRequest.js new file mode 100644 index 0000000..227b03f --- /dev/null +++ b/src/utils/openIM/sqls/localFriendRequest.js @@ -0,0 +1,100 @@ +import squel from 'squel'; +export function localFriendRequests(db) { + return db.exec(` + create table if not exists 'local_friend_requests' + ( + 'from_user_id' varchar(64), + 'from_nickname' varchar(255), + 'from_face_url' varchar(255), + 'to_user_id' varchar(64), + 'to_nickname' varchar(255), + 'to_face_url' varchar(255), + 'handle_result' INTEGER, + 'req_msg' varchar(255), + 'create_time' INTEGER, + 'handler_user_id' varchar(64), + 'handle_msg' varchar(255), + 'handle_time' INTEGER, + 'ex' varchar(1024), + 'attached_info' varchar(1024), + primary key ('from_user_id', 'to_user_id') + ); + `); +} +export function insertFriendRequest(db, localFriendRequest) { + const sql = squel + .insert() + .into('local_friend_requests') + .setFields(localFriendRequest) + .toString(); + return db.exec(sql); +} +export function batchInsertFriendRequests(db, localFriendRequests) { + const sql = squel + .insert() + .into('local_friend_requests') + .setFieldsRows(localFriendRequests) + .toString(); + return db.exec(sql); +} +export function deleteFriendRequestBothUserID(db, fromUserID, toUserID) { + return db.exec(` + delete + from local_friend_requests + where from_user_id = "${fromUserID}" + and to_user_id = "${toUserID}" + `); +} +export function batchDeleteFriendRequests(db, toUserID, fromUserIds ) { + const values = fromUserIds.map(v => `'${v}'`).join(','); + return db.exec(` + delete + from local_friend_requests + where from_user_id in "${values}" + and to_user_id = "${toUserID}" + `); +} + +export function updateFriendRequest(db, localFriendRequest) { + const sql = squel + .update() + .table('local_friend_requests') + .setFields(localFriendRequest) + .where(`from_user_id = '${localFriendRequest.from_user_id}' and to_user_id = '${localFriendRequest.to_user_id}'`) + .toString(); + return db.exec(sql); +} +export function getRecvFriendApplication(db, loginUserID) { + return db.exec(` + select * + from local_friend_requests + where to_user_id = "${loginUserID}" + order by create_time desc + `); +} +export function getSendFriendApplication(db, loginUserID) { + return db.exec(` + select * from local_friend_requests + where from_user_id = "${loginUserID}" + order by create_time desc + `); +} +export function getFriendApplicationByBothID(db, fromUserID, toUserID) { + return db.exec(` + select * + from local_friend_requests + where from_user_id = "${fromUserID}" + and to_user_id = "${toUserID}" + limit 1 + `); +} +export function getBothFriendReq(db, fromUserID, toUserID) { + return db.exec(` + select * + from local_friend_requests + where (from_user_id = "${fromUserID}" + and to_user_id = "${toUserID}") + or (from_user_id = "${toUserID}" + and to_user_id = "${fromUserID}") + `); +} diff --git a/src/utils/openIM/sqls/localGroupMembers.d.ts b/src/utils/openIM/sqls/localGroupMembers.d.ts new file mode 100644 index 0000000..5e6ffad --- /dev/null +++ b/src/utils/openIM/sqls/localGroupMembers.d.ts @@ -0,0 +1,29 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalGroupMember = { + [key: string]: any; +}; +export declare function localGroupMembers(db: Database): QueryExecResult[]; +export declare function getGroupMemberInfoByGroupIDUserID(db: Database, groupID: string, userID: string): QueryExecResult[]; +export declare function getAllGroupMemberList(db: Database): QueryExecResult[]; +export declare function getAllGroupMemberCount(db: Database): QueryExecResult[]; +export declare function getAllGroupMemberUserIDList(db: Database): QueryExecResult[]; +export declare function getGroupMemberCount(db: Database, groupID: string): QueryExecResult[]; +export declare function getGroupSomeMemberInfo(db: Database, groupID: string, userIDList: string[]): QueryExecResult[]; +export declare function getGroupAdminID(db: Database, groupID: string): QueryExecResult[]; +export declare function getGroupMemberListByGroupID(db: Database, groupID: string): QueryExecResult[]; +export declare function getGroupMemberListSplit(db: Database, groupID: string, filter: number, offset: number, count: number): QueryExecResult[]; +export declare function getGroupMemberOwnerAndAdmin(db: Database, groupID: string): QueryExecResult[]; +export declare function getGroupMemberOwner(db: Database, groupID: string): QueryExecResult[]; +export declare function getGroupMemberListSplitByJoinTimeFilter(db: Database, groupID: string, offset: number, count: number, joinTimeBegin: number | undefined, joinTimeEnd: number | undefined, userIDList: string[]): QueryExecResult[]; +export declare function getGroupOwnerAndAdminByGroupID(db: Database, groupID: string): QueryExecResult[]; +export declare function getGroupMemberUIDListByGroupID(db: Database, groupID: string): QueryExecResult[]; +export declare function insertGroupMember(db: Database, localGroupMember: LocalGroupMember): QueryExecResult[]; +export declare function batchInsertGroupMember(db: Database, localGroupMember: LocalGroupMember[]): QueryExecResult[]; +export declare function deleteGroupMember(db: Database, groupID: string, userID: string): QueryExecResult[]; +export declare function batchDeleteGroupMembers(db: Database, groupID: string, userIds: string[]): QueryExecResult[]; +export declare function deleteGroupAllMembers(db: Database, groupID: string): QueryExecResult[]; +export declare function updateGroupMember(db: Database, localGroupMember: LocalGroupMember): QueryExecResult[]; +export declare function updateGroupMemberField(db: Database, groupID: string, userID: string, localGroupMember: LocalGroupMember): QueryExecResult[]; +export declare function searchGroupMembers(db: Database, keyword: string, groupID: string, isSearchMemberNickname: boolean, isSearchUserID: boolean, offset: number, count: number): QueryExecResult[]; +export declare function getUserJoinedGroupIDs(db: Database, userID: string): QueryExecResult[]; + diff --git a/src/utils/openIM/sqls/localGroupMembers.js b/src/utils/openIM/sqls/localGroupMembers.js new file mode 100644 index 0000000..4fd8d61 --- /dev/null +++ b/src/utils/openIM/sqls/localGroupMembers.js @@ -0,0 +1,280 @@ +import squel from 'squel'; +import {updateConversation} from "./localConversations.js"; +export function localGroupMembers(db) { + return db.exec(` + create table if not exists 'local_group_members' ( + 'group_id' varchar(64), + 'user_id' varchar(64), + 'nickname' varchar(255), + 'user_group_face_url' varchar(255), + 'role_level' integer, + 'join_time' integer, + 'join_source' integer, + 'inviter_user_id' text, + 'mute_end_time' integer DEFAULT 0, + 'operator_user_id' varchar(64), + 'ex' varchar(1024), + 'attached_info' varchar(1024), + PRIMARY KEY ('group_id', 'user_id') + ) + `); +} +export function getGroupMemberInfoByGroupIDUserID(db, groupID, userID) { + return db.exec(` + select * + from local_group_members + WHERE group_id = "${groupID}" + AND user_id = "${userID}" + LIMIT 1 + `); +} +export function getAllGroupMemberList(db) { + return db.exec(` + SELECT * + FROM local_group_members + `); +} +export function getAllGroupMemberCount(db) { + return db.exec(` + SELECT count(*) + FROM local_group_members + `); +} +export function getAllGroupMemberUserIDList(db) { + return db.exec(` + SELECT user_id + FROM local_group_members + `); +} +export function getGroupMemberCount(db, groupID) { + return db.exec(` + SELECT count(*) FROM local_group_members + WHERE group_id = "${groupID}" + `); +} +export function getGroupSomeMemberInfo(db, groupID, userIDList) { + const ids = userIDList.map(v => `'${v}'`); + return db.exec(` + select * + from local_group_members + where group_id = "${groupID}" + and user_id in (${ids.join(',')}) + `); +} +export function getGroupAdminID(db, groupID) { + return db.exec(` + SELECT user_id FROM local_group_members + WHERE group_id = "${groupID}" + And role_level = 3 + `); +} +export function getGroupMemberListByGroupID(db, groupID) { + return db.exec(` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + `); +} +export function getGroupMemberListSplit(db, groupID, filter, offset, count) { + let condition = ` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And role_level > 0 + ORDER BY role_level DESC,join_time ASC + LIMIT ${count} OFFSET ${offset} + `; + if (filter === 1) { + condition = ` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And role_level = 1 + ORDER BY join_time ASC + LIMIT ${count} OFFSET ${offset} + `; + } + if (filter === 4) { + condition = ` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And ( role_level = 1 OR role_level = 3 ) + ORDER BY role_level DESC,join_time ASC + LIMIT ${count} OFFSET ${offset} + `; + } + return db.exec(condition); +} +export function getGroupMemberOwnerAndAdmin(db, groupID) { + return db.exec(` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And role_level > 1 + ORDER BY join_time DESC + `); +} +export function getGroupMemberOwner(db, groupID) { + return db.exec(` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And role_level = 2 + `); +} +export function getGroupMemberListSplitByJoinTimeFilter(db, groupID, offset, count, joinTimeBegin = 0, joinTimeEnd = 100000000000, userIDList) { + let condition = ''; + if (userIDList.length === 0) { + condition = ` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And join_time between ${joinTimeBegin} and ${joinTimeEnd} + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + else { + const ids = userIDList.map(v => `'${v}'`); + condition = ` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And join_time between ${joinTimeBegin} and ${joinTimeEnd} + And user_id NOT IN (${ids.join(',')}) + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + return db.exec(condition); +} +export function getGroupOwnerAndAdminByGroupID(db, groupID) { + return db.exec(` + SELECT * FROM local_group_members + WHERE group_id = "${groupID}" + And role_level > 1 + `); +} +export function getGroupMemberUIDListByGroupID(db, groupID) { + return db.exec(` + SELECT user_id FROM local_group_members + WHERE group_id = "${groupID}" + `); +} +export function insertGroupMember(db, localGroupMember) { + const sql = squel + .insert() + .into('local_group_members') + .setFields(localGroupMember) + .toString(); + return db.exec(sql); +} +export function batchInsertGroupMember(db, localGroupMember) { + const sql = squel + .insert() + .into('local_group_members') + .setFieldsRows(localGroupMember) + .toString(); + return db.exec(sql); +} +export function deleteGroupMember(db, groupID, userID) { + return db.exec(` + DELETE FROM local_group_members + WHERE group_id="${groupID}" + and user_id="${userID}" + `); +} + +export function batchDeleteGroupMembers(db, groupID, userIds) { + const ids = userIds.map(v => `'${v}'`); + return db.exec(` + DELETE FROM local_group_members + WHERE group_id="${groupID}" + and user_id in (${ids.join(',')}) `); +} +export function deleteGroupAllMembers(db, groupID) { + return db.exec(` + DELETE FROM local_group_members + WHERE group_id="${groupID}" + `); +} +export function updateGroupMember(db, localGroupMember) { + const sql = squel + .update() + .table('local_group_members') + .setFields(localGroupMember) + .where(`group_id = '${localGroupMember.group_id}' and user_id = '${localGroupMember.user_id}'`) + .toString(); + return db.exec(sql); +} + +export function updateGroupMemberField(db, groupID, userID, localGroupMember) { + const sql = squel + .update() + .table('local_group_members') + .setFields(localGroupMember) + .where(`group_id = '${groupID}' and user_id = '${userID}'`) + .toString(); + return db.exec(sql); +} +export function searchGroupMembers(db, keyword, groupID, isSearchMemberNickname, isSearchUserID, offset, count) { + let condition = ''; + if (groupID) { + if (isSearchMemberNickname && isSearchUserID) { + condition = ` + SELECT * FROM local_group_members + WHERE ( user_id like "%${keyword}%" or nickname like "%${keyword}%" ) + and group_id IN ("${groupID}") + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + else if (!isSearchMemberNickname && !isSearchUserID) { + condition = ` + SELECT * FROM local_group_members + WHERE group_id IN ("${groupID}") + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + else { + const subCondition = isSearchMemberNickname + ? `nickname like "%${keyword}%"` + : `user_id like "%${keyword}%"`; + condition = ` + SELECT * FROM local_group_members + WHERE ${subCondition} + and group_id IN ("${groupID}") + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + } + else { + if (isSearchMemberNickname && isSearchMemberNickname) { + condition = ` + SELECT * FROM local_group_members + WHERE user_id like "%${keyword}%" or nickname like "%${keyword}%" + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + else if (!isSearchMemberNickname && !isSearchMemberNickname) { + condition = ` + SELECT * FROM local_group_members + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + else { + const subCondition = isSearchMemberNickname + ? `nickname like "%${keyword}%"` + : `user_id like "%${keyword}%"`; + condition = ` + SELECT * FROM local_group_members + WHERE ${subCondition} + ORDER BY join_time DESC + LIMIT ${count} OFFSET ${offset} + `; + } + } + return db.exec(condition); +} +export function getUserJoinedGroupIDs(db, userID) { + return db.exec(` + select group_id from local_group_members where user_id = ${userID}; + `); +} diff --git a/src/utils/openIM/sqls/localGroupRequests.d.ts b/src/utils/openIM/sqls/localGroupRequests.d.ts new file mode 100644 index 0000000..b2c8194 --- /dev/null +++ b/src/utils/openIM/sqls/localGroupRequests.d.ts @@ -0,0 +1,9 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalGroupRequest = { + [key: string]: any; +}; +export declare function localGroupRequests(db: Database): QueryExecResult[]; +export declare function insertGroupRequest(db: Database, localGroupRequest: LocalGroupRequest): QueryExecResult[]; +export declare function deleteGroupRequest(db: Database, groupID: string, userID: string): QueryExecResult[]; +export declare function updateGroupRequest(db: Database, localGroupRequest: LocalGroupRequest): QueryExecResult[]; +export declare function getSendGroupApplication(db: Database): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localGroupRequests.js b/src/utils/openIM/sqls/localGroupRequests.js new file mode 100644 index 0000000..8f94372 --- /dev/null +++ b/src/utils/openIM/sqls/localGroupRequests.js @@ -0,0 +1,65 @@ +import squel from 'squel'; +export function localGroupRequests(db) { + return db.exec(` + create table if not exists "local_group_requests" ( + "group_id" varchar(64), + "group_name" text, + "notification" varchar(255), + "introduction" varchar(255), + "face_url" varchar(255), + "create_time" integer, + "status" integer, + "creator_user_id" varchar(64), + "group_type" integer, + "owner_user_id" varchar(64), + "member_count" integer, + "user_id" varchar(64), + "nickname" varchar(255), + "user_face_url" varchar(255), + "gender" integer, + "handle_result" integer, + "req_msg" varchar(255), + "handle_msg" varchar(255), + "req_time" integer, + "handle_user_id" varchar(64), + "handle_time" integer, + "ex" varchar(1024), + "attached_info" varchar(1024), + "join_source" integer, + "inviter_user_id" text, + PRIMARY KEY ("group_id", "user_id") + ); + `); +} +export function insertGroupRequest(db, localGroupRequest) { + const sql = squel + .insert() + .into('local_group_requests') + .setFields(localGroupRequest) + .toString(); + return db.exec(sql); +} +export function deleteGroupRequest(db, groupID, userID) { + return db.exec(` + delete + from local_group_requests + where group_id = "${groupID}" + and user_id = "${userID}" + `); +} +export function updateGroupRequest(db, localGroupRequest) { + const sql = squel + .update() + .table('local_group_requests') + .setFields(localGroupRequest) + .where(`group_id = '${localGroupRequest.group_id}' and user_id = '${localGroupRequest.user_id}'`) + .toString(); + return db.exec(sql); +} +export function getSendGroupApplication(db) { + return db.exec(` + select * + from local_group_requests + order by create_time desc + `); +} diff --git a/src/utils/openIM/sqls/localGroups.d.ts b/src/utils/openIM/sqls/localGroups.d.ts new file mode 100644 index 0000000..42c8ba2 --- /dev/null +++ b/src/utils/openIM/sqls/localGroups.d.ts @@ -0,0 +1,17 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalGroup = { + [key: string]: any; +}; +export declare function localGroups(db: Database): QueryExecResult[]; +export declare function insertGroup(db: Database, localGroup: LocalGroup): QueryExecResult[]; +export declare function batchInsertGroups(db: Database, localGroup: LocalGroup[]): QueryExecResult[]; +export declare function deleteGroup(db: Database, groupID: string): QueryExecResult[]; +export declare function updateGroup(db: Database, groupID: string, localGroup: LocalGroup): QueryExecResult[]; +export declare function getJoinedGroupList(db: Database): QueryExecResult[]; +export declare function getGroupInfoByGroupID(db: Database, groupID: string): QueryExecResult[]; +export declare function getAllGroupInfoByGroupIDOrGroupName(db: Database, keyword: string, isSearchGroupID: boolean, isSearchGroupName: boolean): QueryExecResult[]; +export declare function subtractMemberCount(db: Database, groupID: string): QueryExecResult[]; +export declare function addMemberCount(db: Database, groupID: string): QueryExecResult[]; +export declare function getGroupMemberAllGroupIDs(db: Database): QueryExecResult[]; +export declare function getGroups(db: Database, groupIDs: string[]): QueryExecResult[]; +export declare function batchDeleteGroups(db: Database, groupIds: string[]): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localGroups.js b/src/utils/openIM/sqls/localGroups.js new file mode 100644 index 0000000..c0a1936 --- /dev/null +++ b/src/utils/openIM/sqls/localGroups.js @@ -0,0 +1,115 @@ +import squel from 'squel'; +export function localGroups(db) { + return db.exec(` + create table if not exists 'local_groups' + ( + 'group_id' varchar(64) PRIMARY KEY, + 'name' TEXT, + 'notification' varchar(255), + 'introduction' varchar(255), + 'face_url' varchar(255), + 'create_time' INTEGER, + 'status' INTEGER, + 'creator_user_id' varchar(64), + 'group_type' INTEGER, + 'owner_user_id' varchar(64), + 'member_count' INTEGER, + 'ex' varchar(1024), + 'attached_info' varchar(1024), + 'need_verification' INTEGER, + 'look_member_info' INTEGER, + 'apply_member_friend' INTEGER, + 'notification_update_time' INTEGER, + 'notification_user_id' TEXT + ) + `); +} +export function insertGroup(db, localGroup) { + const sql = squel + .insert() + .into('local_groups') + .setFields(localGroup) + .toString(); + return db.exec(sql); +} +export function batchInsertGroups(db, localGroups) { + const sql = squel + .insert() + .into('local_groups') + .setFieldsRows(localGroups) + .toString(); + return db.exec(sql); +} +export function deleteGroup(db, groupID) { + return db.exec(` + DELETE FROM local_groups + WHERE group_id="${groupID}" + `); +} +export function batchDeleteGroups(db, groupIds) { + const ids = groupIds.map(v => `'${v}'`); + return db.exec(` + DELETE FROM local_group_members + WHERE group_id in "${ids}" `); +} +export function updateGroup(db, groupID, localGroup) { + const sql = squel + .update() + .table('local_groups') + .setFields(localGroup) + .where(`group_id = '${groupID}'`) + .toString(); + return db.exec(sql); +} +export function getJoinedGroupList(db) { + return db.exec(` + SELECT * FROM local_groups where status != 2 + `); +} +export function getGroupInfoByGroupID(db, groupID) { + return db.exec(` + SELECT * + FROM local_groups + WHERE group_id = "${groupID}" + `); +} +export function getAllGroupInfoByGroupIDOrGroupName(db, keyword, isSearchGroupID, isSearchGroupName) { + let totalConditionStr = ''; + const groupIDCondition = `group_id like "%${keyword}%"`; + const groupNameCondition = `name like "%${keyword}%"`; + if (isSearchGroupID) { + totalConditionStr = groupIDCondition; + } + if (isSearchGroupName) { + totalConditionStr = groupNameCondition; + } + if (isSearchGroupName && isSearchGroupID) { + totalConditionStr = groupIDCondition + ' or ' + groupNameCondition; + } + return db.exec(` + select * + from local_groups + where ${totalConditionStr} + order by create_time desc + `); +} +export function subtractMemberCount(db, groupID) { + return db.exec(` + update local_groups set member_count = member_count-1 where group_id = '${groupID}' + `); +} +export function addMemberCount(db, groupID) { + return db.exec(` + update local_groups set member_count = member_count+1 where group_id = '${groupID}' + `); +} +export function getGroupMemberAllGroupIDs(db) { + return db.exec(` + select distinct group_id from local_group_members + `); +} +export function getGroups(db, groupIDs) { + return db.exec(` + select * from local_groups where group_id in (${groupIDs.join(',')}); + `); +} diff --git a/src/utils/openIM/sqls/localNotification.d.ts b/src/utils/openIM/sqls/localNotification.d.ts new file mode 100644 index 0000000..0444cbf --- /dev/null +++ b/src/utils/openIM/sqls/localNotification.d.ts @@ -0,0 +1,8 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalNotification = { + [key: string]: any; +}; +export declare function localNotification(db: Database): QueryExecResult[]; +export declare function insertNotificationSeq(db: Database, conversationID: string, seq: number): QueryExecResult[]; +export declare function setNotificationSeq(db: Database, conversationID: string, seq: number): QueryExecResult[]; +export declare function getNotificationAllSeqs(db: Database): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localNotification.js b/src/utils/openIM/sqls/localNotification.js new file mode 100644 index 0000000..865ad9d --- /dev/null +++ b/src/utils/openIM/sqls/localNotification.js @@ -0,0 +1,19 @@ +export function localNotification(db) { + return db.exec(` + create table if not exists 'local_notification_seqs' + ( + 'conversation_id' char(128), + 'seq' integer, + PRIMARY KEY ('conversation_id') + ) + `); +} +export function insertNotificationSeq(db, conversationID, seq) { + return db.exec(`INSERT INTO local_notification_seqs (conversation_id, seq) VALUES ("${conversationID}", ${seq});`); +} +export function setNotificationSeq(db, conversationID, seq) { + return db.exec(`UPDATE local_notification_seqs set seq = ${seq} where conversation_id = "${conversationID}"`); +} +export function getNotificationAllSeqs(db) { + return db.exec('SELECT * from local_notification_seqs where 1 = 1;'); +} diff --git a/src/utils/openIM/sqls/localStranger.d.ts b/src/utils/openIM/sqls/localStranger.d.ts new file mode 100644 index 0000000..2582658 --- /dev/null +++ b/src/utils/openIM/sqls/localStranger.d.ts @@ -0,0 +1,8 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type LocalStranger = { + [key: string]: any; +}; +export declare function localStranger(db: Database): QueryExecResult[]; +export declare function getStrangerInfo(db: Database, userIDList: string[]): QueryExecResult[]; +export declare function insertStrangerInfo(db: Database, localStranger: LocalStranger): QueryExecResult[]; +export declare function updateStrangerInfo(db: Database, localStranger: LocalStranger): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localStranger.js b/src/utils/openIM/sqls/localStranger.js new file mode 100644 index 0000000..224a321 --- /dev/null +++ b/src/utils/openIM/sqls/localStranger.js @@ -0,0 +1,42 @@ +import squel from 'squel'; +export function localStranger(db) { + return db.exec(` + create table if not exists 'local_stranger' + ( + 'user_id' varchar(64), + 'name' varchar(255), + 'face_url' varchar(255), + 'create_time' integer, + 'app_manger_level' integer, + 'ex' varchar(1024), + 'attached_info' varchar(1024), + 'global_recv_msg_opt' integer, + PRIMARY KEY ('user_id') + ) + `); +} +export function getStrangerInfo(db, userIDList) { + const ids = userIDList.map(v => `'${v}'`); + return db.exec(` + select * + from local_stranger + WHERE user_id = (${ids.join(',')}) + `); +} +export function insertStrangerInfo(db, localStranger) { + const sql = squel + .insert() + .into('local_stranger') + .setFields(localStranger) + .toString(); + return db.exec(sql); +} +export function updateStrangerInfo(db, localStranger) { + const sql = squel + .update() + .table('local_stranger') + .setFields(localStranger) + .where(`user_id = '${localStranger.user_id}'`) + .toString(); + return db.exec(sql); +} diff --git a/src/utils/openIM/sqls/localSuperGroups.d.ts b/src/utils/openIM/sqls/localSuperGroups.d.ts new file mode 100644 index 0000000..4e98df1 --- /dev/null +++ b/src/utils/openIM/sqls/localSuperGroups.d.ts @@ -0,0 +1,10 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type ClientGroup = { + [key: string]: unknown; +}; +export declare function localSuperGroups(db: Database): QueryExecResult[]; +export declare function getJoinedSuperGroupList(db: Database): QueryExecResult[]; +export declare function insertSuperGroup(db: Database, group: ClientGroup): QueryExecResult[]; +export declare function updateSuperGroup(db: Database, groupID: string, group: ClientGroup): QueryExecResult[]; +export declare function deleteSuperGroup(db: Database, groupID: string): QueryExecResult[]; +export declare function getSuperGroupInfoByGroupID(db: Database, groupID: string): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localSuperGroups.js b/src/utils/openIM/sqls/localSuperGroups.js new file mode 100644 index 0000000..1f40dd2 --- /dev/null +++ b/src/utils/openIM/sqls/localSuperGroups.js @@ -0,0 +1,58 @@ +import squel from 'squel'; +export function localSuperGroups(db) { + return db.exec(` + create table if not exists 'local_super_groups' ( + 'group_id' varchar(64), + 'name' text, + 'notification' varchar(255), + 'introduction' varchar(255), + 'face_url' varchar(255), + 'create_time' integer, + 'status' integer, + 'creator_user_id' varchar(64), + 'group_type' integer, + 'owner_user_id' varchar(64), + 'member_count' integer, + 'ex' varchar(1024), + 'attached_info' varchar(1024), + 'need_verification' integer, + 'look_member_info' integer, + 'apply_member_friend' integer, + 'notification_update_time' integer, + 'notification_user_id' text, + primary key ('group_id') + ) + `); +} +export function getJoinedSuperGroupList(db) { + return db.exec(` + select * from local_super_groups; + `); +} +export function insertSuperGroup(db, group) { + const sql = squel + .insert() + .into('local_super_groups') + .setFields(group) + .toString(); + return db.exec(sql); +} +export function updateSuperGroup(db, groupID, group) { + const sql = squel + .update() + .table('local_super_groups') + .setFields(group) + .where(`group_id = '${groupID}'`) + .toString(); + return db.exec(sql); +} +export function deleteSuperGroup(db, groupID) { + return db.exec(` + delete from local_super_groups where group_id = '${groupID}'; + `); +} +export function getSuperGroupInfoByGroupID(db, groupID) { + return db.exec(` + select * from local_super_groups where group_id = '${groupID}' LIMIT 1; + `); +} diff --git a/src/utils/openIM/sqls/localUpload.d.ts b/src/utils/openIM/sqls/localUpload.d.ts new file mode 100644 index 0000000..f7573b2 --- /dev/null +++ b/src/utils/openIM/sqls/localUpload.d.ts @@ -0,0 +1,10 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type ClientUpload = { + [key: string]: unknown; +}; +export declare function localUploads(db: Database): QueryExecResult[]; +export declare function getUpload(db: Database, partHash: string): QueryExecResult[]; +export declare function insertUpload(db: Database, upload: ClientUpload): QueryExecResult[]; +export declare function updateUpload(db: Database, upload: ClientUpload): QueryExecResult[]; +export declare function deleteUpload(db: Database, partHash: string): QueryExecResult[]; +export declare function deleteExpireUpload(db: Database): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localUpload.js b/src/utils/openIM/sqls/localUpload.js new file mode 100644 index 0000000..17325ee --- /dev/null +++ b/src/utils/openIM/sqls/localUpload.js @@ -0,0 +1,47 @@ +import squel from 'squel'; +export function localUploads(db) { + return db.exec(` + create table if not exists 'local_uploads' ( + 'part_hash' text, + 'upload_id' varchar(1000), + 'upload_info' varchar(2000), + 'expire_time' integer, + 'create_time' integer, + PRIMARY KEY ('part_hash') + ) + `); +} +export function getUpload(db, partHash) { + return db.exec(` + select * from local_uploads where part_hash = '${partHash}' limit 1; + `); +} +export function insertUpload(db, upload) { + const sql = squel.insert().into('local_uploads').setFields(upload).toString(); + return db.exec(sql); +} +export function updateUpload(db, upload) { + const sql = squel + .update() + .table('local_uploads') + .setFields(upload) + .where(`part_hash = '${upload.part_hash}'`) + .toString(); + return db.exec(sql); +} +export function deleteUpload(db, partHash) { + const sql = squel + .delete() + .from('local_uploads') + .where(`part_hash = '${partHash}'`) + .toString(); + return db.exec(sql); +} +export function deleteExpireUpload(db) { + const sql = squel + .delete() + .from('local_uploads') + .where(`expire_time <= ${Date.now()}`) + .toString(); + return db.exec(sql); +} diff --git a/src/utils/openIM/sqls/localUsers.d.ts b/src/utils/openIM/sqls/localUsers.d.ts new file mode 100644 index 0000000..b134295 --- /dev/null +++ b/src/utils/openIM/sqls/localUsers.d.ts @@ -0,0 +1,8 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type ClientUser = { + [key: string]: unknown; +}; +export declare function localUsers(db: Database): QueryExecResult[]; +export declare function getLoginUser(db: Database, userID: string): QueryExecResult[]; +export declare function insertLoginUser(db: Database, user: ClientUser): QueryExecResult[]; +export declare function updateLoginUser(db: Database, user: ClientUser): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/localUsers.js b/src/utils/openIM/sqls/localUsers.js new file mode 100644 index 0000000..1d193c0 --- /dev/null +++ b/src/utils/openIM/sqls/localUsers.js @@ -0,0 +1,34 @@ +import squel from 'squel'; +export function localUsers(db) { + return db.exec(` + create table if not exists 'local_users' ( + 'user_id' varchar(64), + 'name' varchar(255), + 'face_url' varchar(255), + 'create_time' integer, + 'app_manger_level' integer, + 'ex' varchar(1024), + 'attached_info' varchar(1024), + 'global_recv_msg_opt' integer, + primary key ('user_id') + ) + `); +} +export function getLoginUser(db, userID) { + return db.exec(` + select *, name as nickname from local_users where user_id = '${userID}' limit 1; + `); +} +export function insertLoginUser(db, user) { + const sql = squel.insert().into('local_users').setFields(user).toString(); + return db.exec(sql); +} +export function updateLoginUser(db, user) { + const sql = squel + .update() + .table('local_users') + .setFields(user) + .where(`user_id = '${user.user_id}'`) + .toString(); + return db.exec(sql); +} diff --git a/src/utils/openIM/sqls/tempCacheLocalChatLogs.d.ts b/src/utils/openIM/sqls/tempCacheLocalChatLogs.d.ts new file mode 100644 index 0000000..cc30a5c --- /dev/null +++ b/src/utils/openIM/sqls/tempCacheLocalChatLogs.d.ts @@ -0,0 +1,6 @@ +import { Database, QueryExecResult } from '@jlongster/sql.js'; +export declare type TempCacheClientMessage = { + [key: string]: any; +}; +export declare function tempCacheLocalChatLogs(db: Database): QueryExecResult[]; +export declare function batchInsertTempCacheMessageList(db: Database, messageList: TempCacheClientMessage[]): QueryExecResult[]; diff --git a/src/utils/openIM/sqls/tempCacheLocalChatLogs.js b/src/utils/openIM/sqls/tempCacheLocalChatLogs.js new file mode 100644 index 0000000..81c7782 --- /dev/null +++ b/src/utils/openIM/sqls/tempCacheLocalChatLogs.js @@ -0,0 +1,34 @@ +import squel from 'squel'; +export function tempCacheLocalChatLogs(db) { + return db.exec(` + create table if not exists 'temp_cache_local_chat_logs' ( + 'client_msg_id' char(64), + 'server_msg_id' char(64), + 'send_id' char(64), + 'recv_id' char(64), + 'sender_platform_id' integer, + 'sender_nick_name' varchar(255), + 'sender_face_url' varchar(255), + 'session_type' integer, + 'msg_from' integer, + 'content_type' integer, + 'content' varchar(1000), + 'is_read' numeric, + 'status' integer, + 'seq' integer DEFAULT 0, + 'send_time' integer, + 'create_time' integer, + 'attached_info' varchar(1024), + 'ex' varchar(1024), + PRIMARY KEY ('client_msg_id') + ); + `); +} +export function batchInsertTempCacheMessageList(db, messageList) { + const sql = squel + .insert() + .into('temp_cache_local_chat_logs') + .setFieldsRows(messageList) + .toString(); + return db.exec(sql); +} diff --git a/src/utils/openIM/types/entity.d.ts b/src/utils/openIM/types/entity.d.ts new file mode 100644 index 0000000..00fa8e1 --- /dev/null +++ b/src/utils/openIM/types/entity.d.ts @@ -0,0 +1,441 @@ +import { CbEvents } from '../constant'; +import { GroupType, SessionType, MessageType, Platform, MessageStatus, GroupStatus, GroupVerificationType, AllowType, GroupJoinSource, GroupMemberRole, MessageReceiveOptType, GroupAtType, LogLevel, ApplicationHandleResult, Relationship, OnlineState } from './enum'; +export declare type WSEvent = { + event: CbEvents; + data: T; + errCode: number; + errMsg: string; + operationID: string; +}; +export declare type WsResponse = { + event: string; + errCode: number; + errMsg: string; + data: T; + operationID: string; +}; +export declare type IMConfig = { + platformID: Platform; + apiAddr: string; + wsAddr: string; + dataDir: string; + logLevel: LogLevel; + isLogStandardOutput: boolean; + logFilePath: string; + isExternalExtensions: boolean; +}; +export declare type MessageEntity = { + type: string; + offset: number; + length: number; + url?: string; + info?: string; +}; +export declare type PicBaseInfo = { + uuid: string; + type: string; + size: number; + width: number; + height: number; + url: string; +}; +export declare type AtUsersInfoItem = { + atUserID: string; + groupNickname: string; +}; +export declare type GroupInitInfo = { + groupID?: string; + groupType: GroupType; + groupName: string; + introduction?: string; + notification?: string; + faceURL?: string; + ex?: string; +}; +export declare type GroupApplicationItem = { + createTime: number; + creatorUserID: string; + ex: string; + groupFaceURL: string; + groupID: string; + groupName: string; + groupType: GroupType; + handleResult: ApplicationHandleResult; + handleUserID: string; + handledMsg: string; + handledTime: number; + introduction: string; + memberCount: number; + nickname: string; + notification: string; + ownerUserID: string; + reqMsg: string; + reqTime: number; + joinSource: GroupJoinSource; + status: GroupStatus; + userFaceURL: string; + userID: string; +}; +export declare type FriendApplicationItem = { + createTime: number; + ex: string; + fromFaceURL: string; + fromNickname: string; + fromUserID: string; + handleMsg: string; + handleResult: ApplicationHandleResult; + handleTime: number; + handlerUserID: string; + reqMsg: string; + toFaceURL: string; + toNickname: string; + toUserID: string; +}; +export declare type FullUserItem = { + blackInfo: BlackUserItem | null; + friendInfo: FriendUserItem | null; + publicInfo: PublicUserItem | null; +}; +export declare type FullUserItemWithCache = { + blackInfo: BlackUserItem | null; + friendInfo: FriendUserItem | null; + publicInfo: PublicUserItem | null; + groupMemberInfo: GroupMemberItem | null; +}; +export declare type PublicUserItem = { + nickname: string; + userID: string; + faceURL: string; + ex: string; +}; +export declare type SelfUserInfo = { + createTime: number; + ex: string; + faceURL: string; + nickname: string; + userID: string; + globalRecvMsgOpt: MessageReceiveOptType; +}; +export declare type PartialUserInfo = { + userID: string; +} & Partial>; +export declare type FriendUserItem = { + addSource: number; + createTime: number; + ex: string; + faceURL: string; + userID: string; + nickname: string; + operatorUserID: string; + ownerUserID: string; + remark: string; + attachedInfo: string; +}; +export declare type SearchedFriendsInfo = FriendUserItem & { + relationship: Relationship; +}; +export declare type FriendshipInfo = { + result: number; + userID: string; +}; +export declare type BlackUserItem = { + addSource: number; + userID: string; + createTime: number; + ex: string; + faceURL: string; + nickname: string; + operatorUserID: string; + ownerUserID: string; +}; +export declare type GroupItem = { + groupID: string; + groupName: string; + notification: string; + notificationUserID: string; + notificationUpdateTime: number; + introduction: string; + faceURL: string; + ownerUserID: string; + createTime: number; + memberCount: number; + status: GroupStatus; + creatorUserID: string; + groupType: GroupType; + needVerification: GroupVerificationType; + ex: string; + applyMemberFriend: AllowType; + lookMemberInfo: AllowType; +}; +export declare type GroupMemberItem = { + groupID: string; + userID: string; + nickname: string; + faceURL: string; + roleLevel: GroupMemberRole; + muteEndTime: number; + joinTime: number; + joinSource: GroupJoinSource; + inviterUserID: string; + operatorUserID: string; + ex: string; +}; +export declare type ConversationItem = { + conversationID: string; + conversationType: SessionType; + userID: string; + groupID: string; + showName: string; + faceURL: string; + recvMsgOpt: MessageReceiveOptType; + unreadCount: number; + groupAtType: GroupAtType; + latestMsg: string; + latestMsgSendTime: number; + draftText: string; + draftTextTime: number; + burnDuration: number; + msgDestructTime: number; + isPinned: boolean; + isNotInGroup: boolean; + isPrivateChat: boolean; + isMsgDestruct: boolean; + attachedInfo: string; + ex: string; + localEx: localEx; +}; +export declare type MessageItem = { + clientMsgID: string; + serverMsgID: string; + createTime: number; + sendTime: number; + sessionType: SessionType; + sendID: string; + recvID: string; + msgFrom: number; + contentType: MessageType; + senderPlatformID: Platform; + senderNickname: string; + senderFaceUrl: string; + groupID: string; + content: string; + seq: number; + isRead: boolean; + status: MessageStatus; + isReact: boolean; + isExternalExtensions: boolean; + offlinePush: OfflinePush; + attachedInfo: string; + ex: string; + localEx: localEx; + textElem: TextElem; + cardElem: CardElem; + pictureElem: PictureElem; + soundElem: SoundElem; + videoElem: VideoElem; + fileElem: FileElem; + mergeElem: MergeElem; + atTextElem: AtTextElem; + faceElem: FaceElem; + locationElem: LocationElem; + customElem: CustomElem; + quoteElem: QuoteElem; + notificationElem: NotificationElem; + advancedTextElem: AdvancedTextElem; + typingElem: TypingElem; + attachedInfoElem: AttachedInfoElem; +}; +export declare type TextElem = { + content: string; +}; +export declare type CardElem = { + userID: string; + nickname: string; + faceURL: string; + ex: string; +}; +export declare type AtTextElem = { + text: string; + atUserList: string[]; + atUsersInfo?: AtUsersInfoItem[]; + quoteMessage?: MessageItem; + isAtSelf?: boolean; +}; +export declare type NotificationElem = { + detail: string; +}; +export declare type AdvancedTextElem = { + text: string; + messageEntityList: MessageEntity[]; +}; +export declare type TypingElem = { + msgTips: string; +}; +export declare type CustomElem = { + data: string; + description: string; + extension: string; +}; +export declare type localEx = { + status: number; +}; +export declare type FileElem = { + filePath: string; + uuid: string; + sourceUrl: string; + fileName: string; + fileSize: number; +}; +export declare type FaceElem = { + index: number; + data: string; +}; +export declare type LocationElem = { + description: string; + longitude: number; + latitude: number; +}; +export declare type MergeElem = { + title: string; + abstractList: string[]; + multiMessage: MessageItem[]; + messageEntityList: MessageEntity[]; +}; +export declare type OfflinePush = { + title: string; + desc: string; + ex: string; + iOSPushSound: string; + iOSBadgeCount: boolean; +}; +export declare type PictureElem = { + sourcePath: string; + sourcePicture: Picture; + bigPicture: Picture; + snapshotPicture: Picture; +}; +export declare type AttachedInfoElem = { + groupHasReadInfo: GroupHasReadInfo; + isPrivateChat: boolean; + isEncryption: boolean; + inEncryptStatus: boolean; + burnDuration: number; + hasReadTime: number; + notSenderNotificationPush: boolean; + messageEntityList: MessageEntity[]; + uploadProgress: UploadProgress; +}; +export declare type UploadProgress = { + total: number; + save: number; + current: number; +}; +export declare type GroupHasReadInfo = { + hasReadCount: number; + hasReadUserIDList: string[]; + groupMemberCount: number; +}; +export declare type Picture = { + uuid: string; + type: string; + size: number; + width: number; + height: number; + url: string; +}; +export declare type QuoteElem = { + text: string; + quoteMessage: MessageItem; +}; +export declare type SoundElem = { + uuid: string; + soundPath: string; + sourceUrl: string; + dataSize: number; + duration: number; +}; +export declare type VideoElem = { + videoPath: string; + videoUUID: string; + videoUrl: string; + videoType: string; + videoSize: number; + duration: number; + snapshotPath: string; + snapshotUUID: string; + snapshotSize: number; + snapshotUrl: string; + snapshotWidth: number; + snapshotHeight: number; +}; +export declare type AdvancedRevokeContent = { + clientMsgID: string; + revokeTime: number; + revokerID: string; + revokerNickname: string; + revokerRole: number; + seq: number; + sessionType: SessionType; + sourceMessageSendID: string; + sourceMessageSendTime: number; + sourceMessageSenderNickname: string; +}; +export declare type RevokedInfo = { + revokerID: string; + revokerRole: number; + clientMsgID: string; + revokerNickname: string; + revokeTime: number; + sourceMessageSendTime: number; + sourceMessageSendID: string; + sourceMessageSenderNickname: string; + sessionType: number; + seq: number; + ex: string; +}; +export declare type ReceiptInfo = { + userID: string; + groupID: string; + msgIDList: string[]; + readTime: number; + msgFrom: number; + contentType: MessageType; + sessionType: SessionType; +}; +export declare type SearchMessageResult = { + totalCount: number; + searchResultItems: SearchMessageResultItem[]; +}; +export declare type SearchMessageResultItem = { + conversationID: string; + messageCount: number; + conversationType: SessionType; + showName: string; + faceURL: string; + messageList: MessageItem[]; +}; +export declare type AdvancedGetMessageResult = { + isEnd: boolean; + lastMinSeq: number; + errCode: number; + errMsg: string; + messageList: MessageItem[]; +}; +export declare type RtcInvite = { + inviterUserID: string; + inviteeUserIDList: string[]; + customData?: string; + groupID: string; + roomID: string; + timeout: number; + mediaType: string; + sessionType: number; + platformID: number; + initiateTime?: number; + busyLineUserIDList?: string[]; +}; +export declare type UserOnlineState = { + platformIDs?: Platform[]; + status: OnlineState; + userID: string; +}; diff --git a/src/utils/openIM/types/entity.js b/src/utils/openIM/types/entity.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/utils/openIM/types/entity.js @@ -0,0 +1 @@ +export {}; diff --git a/src/utils/openIM/types/enum.d.ts b/src/utils/openIM/types/enum.d.ts new file mode 100644 index 0000000..ae891b0 --- /dev/null +++ b/src/utils/openIM/types/enum.d.ts @@ -0,0 +1,133 @@ +export declare enum MessageReceiveOptType { + Nomal = 0, + NotReceive = 1, + NotNotify = 2 +} +export declare enum AllowType { + Allowed = 0, + NotAllowed = 1 +} +export declare enum GroupType { + WorkingGroup = 2 +} +export declare enum GroupJoinSource { + Invitation = 2, + Search = 3, + QrCode = 4 +} +export declare enum GroupMemberRole { + Nomal = 20, + Admin = 60, + Owner = 100 +} +export declare enum GroupVerificationType { + ApplyNeedInviteNot = 0, + AllNeed = 1, + AllNot = 2 +} +export declare enum MessageStatus { + Sending = 1, + Succeed = 2, + Failed = 3 +} +export declare enum Platform { + iOS = 1, + Android = 2, + Windows = 3, + MacOSX = 4, + Web = 5, + Linux = 7, + AndroidPad = 8, + iPad = 9 +} +export declare enum LogLevel { + Debug = 5, + Info = 4, + Warn = 3, + Error = 2, + Fatal = 1, + Panic = 0 +} +export declare enum ApplicationHandleResult { + Unprocessed = 0, + Agree = 1, + Reject = -1 +} +export declare enum MessageType { + TextMessage = 101, + PictureMessage = 102, + VoiceMessage = 103, + VideoMessage = 104, + FileMessage = 105, + AtTextMessage = 106, + MergeMessage = 107, + CardMessage = 108, + LocationMessage = 109, + CustomMessage = 110, + TypingMessage = 113, + QuoteMessage = 114, + FaceMessage = 115, + FriendAdded = 1201, + OANotification = 1400, + GroupCreated = 1501, + GroupInfoUpdated = 1502, + MemberQuit = 1504, + GroupOwnerTransferred = 1507, + MemberKicked = 1508, + MemberInvited = 1509, + MemberEnter = 1510, + GroupDismissed = 1511, + GroupMemberMuted = 1512, + GroupMemberCancelMuted = 1513, + GroupMuted = 1514, + GroupCancelMuted = 1515, + GroupMemberInfoUpdated = 1516, + GroupMemberToAdmin = 1517, + GroupAdminToNomal = 1518, + GroupAnnouncementUpdated = 1519, + GroupNameUpdated = 1520, + BurnMessageChange = 1701, + RevokeMessage = 2101, + HasReadReceiptMessage = 2150, + GroupHasReadReceipt = 2155 +} +export declare enum SessionType { + Single = 1, + Group = 2, + WorkingGroup = 3, + Notification = 4 +} +export declare enum GroupStatus { + Nomal = 0, + Baned = 1, + Dismissed = 2, + Muted = 3 +} +export declare enum GroupAtType { + AtNormal = 0, + AtMe = 1, + AtAll = 2, + AtAllAtMe = 3, + AtGroupNotice = 4 +} +export declare enum GroupMemberFilter { + All = 0, + Owner = 1, + Admin = 2, + Nomal = 3, + AdminAndNomal = 4, + AdminAndOwner = 5 +} +export declare enum Relationship { + isBlack = 0, + isFriend = 1 +} +export declare enum LoginStatus { + Logout = 1, + Logging = 2, + Logged = 3 +} +export declare enum OnlineState { + Online = 1, + Offline = 0 +} diff --git a/src/utils/openIM/types/enum.js b/src/utils/openIM/types/enum.js new file mode 100644 index 0000000..8230bf6 --- /dev/null +++ b/src/utils/openIM/types/enum.js @@ -0,0 +1,152 @@ +export var MessageReceiveOptType; +(function (MessageReceiveOptType) { + MessageReceiveOptType[MessageReceiveOptType["Nomal"] = 0] = "Nomal"; + MessageReceiveOptType[MessageReceiveOptType["NotReceive"] = 1] = "NotReceive"; + MessageReceiveOptType[MessageReceiveOptType["NotNotify"] = 2] = "NotNotify"; +})(MessageReceiveOptType || (MessageReceiveOptType = {})); +export var AllowType; +(function (AllowType) { + AllowType[AllowType["Allowed"] = 0] = "Allowed"; + AllowType[AllowType["NotAllowed"] = 1] = "NotAllowed"; +})(AllowType || (AllowType = {})); +export var GroupType; +(function (GroupType) { + GroupType[GroupType["WorkingGroup"] = 2] = "WorkingGroup"; +})(GroupType || (GroupType = {})); +export var GroupJoinSource; +(function (GroupJoinSource) { + GroupJoinSource[GroupJoinSource["Invitation"] = 2] = "Invitation"; + GroupJoinSource[GroupJoinSource["Search"] = 3] = "Search"; + GroupJoinSource[GroupJoinSource["QrCode"] = 4] = "QrCode"; +})(GroupJoinSource || (GroupJoinSource = {})); +export var GroupMemberRole; +(function (GroupMemberRole) { + GroupMemberRole[GroupMemberRole["Nomal"] = 20] = "Nomal"; + GroupMemberRole[GroupMemberRole["Admin"] = 60] = "Admin"; + GroupMemberRole[GroupMemberRole["Owner"] = 100] = "Owner"; +})(GroupMemberRole || (GroupMemberRole = {})); +export var GroupVerificationType; +(function (GroupVerificationType) { + GroupVerificationType[GroupVerificationType["ApplyNeedInviteNot"] = 0] = "ApplyNeedInviteNot"; + GroupVerificationType[GroupVerificationType["AllNeed"] = 1] = "AllNeed"; + GroupVerificationType[GroupVerificationType["AllNot"] = 2] = "AllNot"; +})(GroupVerificationType || (GroupVerificationType = {})); +export var MessageStatus; +(function (MessageStatus) { + MessageStatus[MessageStatus["Sending"] = 1] = "Sending"; + MessageStatus[MessageStatus["Succeed"] = 2] = "Succeed"; + MessageStatus[MessageStatus["Failed"] = 3] = "Failed"; +})(MessageStatus || (MessageStatus = {})); +export var Platform; +(function (Platform) { + Platform[Platform["iOS"] = 1] = "iOS"; + Platform[Platform["Android"] = 2] = "Android"; + Platform[Platform["Windows"] = 3] = "Windows"; + Platform[Platform["MacOSX"] = 4] = "MacOSX"; + Platform[Platform["Web"] = 5] = "Web"; + Platform[Platform["Linux"] = 7] = "Linux"; + Platform[Platform["AndroidPad"] = 8] = "AndroidPad"; + Platform[Platform["iPad"] = 9] = "iPad"; +})(Platform || (Platform = {})); +export var LogLevel; +(function (LogLevel) { + LogLevel[LogLevel["Debug"] = 5] = "Debug"; + LogLevel[LogLevel["Info"] = 4] = "Info"; + LogLevel[LogLevel["Warn"] = 3] = "Warn"; + LogLevel[LogLevel["Error"] = 2] = "Error"; + LogLevel[LogLevel["Fatal"] = 1] = "Fatal"; + LogLevel[LogLevel["Panic"] = 0] = "Panic"; +})(LogLevel || (LogLevel = {})); +export var ApplicationHandleResult; +(function (ApplicationHandleResult) { + ApplicationHandleResult[ApplicationHandleResult["Unprocessed"] = 0] = "Unprocessed"; + ApplicationHandleResult[ApplicationHandleResult["Agree"] = 1] = "Agree"; + ApplicationHandleResult[ApplicationHandleResult["Reject"] = -1] = "Reject"; +})(ApplicationHandleResult || (ApplicationHandleResult = {})); +export var MessageType; +(function (MessageType) { + MessageType[MessageType["TextMessage"] = 101] = "TextMessage"; + MessageType[MessageType["PictureMessage"] = 102] = "PictureMessage"; + MessageType[MessageType["VoiceMessage"] = 103] = "VoiceMessage"; + MessageType[MessageType["VideoMessage"] = 104] = "VideoMessage"; + MessageType[MessageType["FileMessage"] = 105] = "FileMessage"; + MessageType[MessageType["AtTextMessage"] = 106] = "AtTextMessage"; + MessageType[MessageType["MergeMessage"] = 107] = "MergeMessage"; + MessageType[MessageType["CardMessage"] = 108] = "CardMessage"; + MessageType[MessageType["LocationMessage"] = 109] = "LocationMessage"; + MessageType[MessageType["CustomMessage"] = 110] = "CustomMessage"; + MessageType[MessageType["TypingMessage"] = 113] = "TypingMessage"; + MessageType[MessageType["QuoteMessage"] = 114] = "QuoteMessage"; + MessageType[MessageType["FaceMessage"] = 115] = "FaceMessage"; + MessageType[MessageType["FriendAdded"] = 1201] = "FriendAdded"; + MessageType[MessageType["OANotification"] = 1400] = "OANotification"; + MessageType[MessageType["GroupCreated"] = 1501] = "GroupCreated"; + MessageType[MessageType["GroupInfoUpdated"] = 1502] = "GroupInfoUpdated"; + MessageType[MessageType["MemberQuit"] = 1504] = "MemberQuit"; + MessageType[MessageType["GroupOwnerTransferred"] = 1507] = "GroupOwnerTransferred"; + MessageType[MessageType["MemberKicked"] = 1508] = "MemberKicked"; + MessageType[MessageType["MemberInvited"] = 1509] = "MemberInvited"; + MessageType[MessageType["MemberEnter"] = 1510] = "MemberEnter"; + MessageType[MessageType["GroupDismissed"] = 1511] = "GroupDismissed"; + MessageType[MessageType["GroupMemberMuted"] = 1512] = "GroupMemberMuted"; + MessageType[MessageType["GroupMemberCancelMuted"] = 1513] = "GroupMemberCancelMuted"; + MessageType[MessageType["GroupMuted"] = 1514] = "GroupMuted"; + MessageType[MessageType["GroupCancelMuted"] = 1515] = "GroupCancelMuted"; + MessageType[MessageType["GroupMemberInfoUpdated"] = 1516] = "GroupMemberInfoUpdated"; + MessageType[MessageType["GroupMemberToAdmin"] = 1517] = "GroupMemberToAdmin"; + MessageType[MessageType["GroupAdminToNomal"] = 1518] = "GroupAdminToNomal"; + MessageType[MessageType["GroupAnnouncementUpdated"] = 1519] = "GroupAnnouncementUpdated"; + MessageType[MessageType["GroupNameUpdated"] = 1520] = "GroupNameUpdated"; + MessageType[MessageType["BurnMessageChange"] = 1701] = "BurnMessageChange"; + // notification + MessageType[MessageType["RevokeMessage"] = 2101] = "RevokeMessage"; + MessageType[MessageType["HasReadReceiptMessage"] = 2150] = "HasReadReceiptMessage"; + MessageType[MessageType["GroupHasReadReceipt"] = 2155] = "GroupHasReadReceipt"; +})(MessageType || (MessageType = {})); +export var SessionType; +(function (SessionType) { + SessionType[SessionType["Single"] = 1] = "Single"; + SessionType[SessionType["Group"] = 2] = "Group"; + SessionType[SessionType["WorkingGroup"] = 3] = "WorkingGroup"; + SessionType[SessionType["Notification"] = 4] = "Notification"; +})(SessionType || (SessionType = {})); +export var GroupStatus; +(function (GroupStatus) { + GroupStatus[GroupStatus["Nomal"] = 0] = "Nomal"; + GroupStatus[GroupStatus["Baned"] = 1] = "Baned"; + GroupStatus[GroupStatus["Dismissed"] = 2] = "Dismissed"; + GroupStatus[GroupStatus["Muted"] = 3] = "Muted"; +})(GroupStatus || (GroupStatus = {})); +export var GroupAtType; +(function (GroupAtType) { + GroupAtType[GroupAtType["AtNormal"] = 0] = "AtNormal"; + GroupAtType[GroupAtType["AtMe"] = 1] = "AtMe"; + GroupAtType[GroupAtType["AtAll"] = 2] = "AtAll"; + GroupAtType[GroupAtType["AtAllAtMe"] = 3] = "AtAllAtMe"; + GroupAtType[GroupAtType["AtGroupNotice"] = 4] = "AtGroupNotice"; +})(GroupAtType || (GroupAtType = {})); +export var GroupMemberFilter; +(function (GroupMemberFilter) { + GroupMemberFilter[GroupMemberFilter["All"] = 0] = "All"; + GroupMemberFilter[GroupMemberFilter["Owner"] = 1] = "Owner"; + GroupMemberFilter[GroupMemberFilter["Admin"] = 2] = "Admin"; + GroupMemberFilter[GroupMemberFilter["Nomal"] = 3] = "Nomal"; + GroupMemberFilter[GroupMemberFilter["AdminAndNomal"] = 4] = "AdminAndNomal"; + GroupMemberFilter[GroupMemberFilter["AdminAndOwner"] = 5] = "AdminAndOwner"; +})(GroupMemberFilter || (GroupMemberFilter = {})); +export var Relationship; +(function (Relationship) { + Relationship[Relationship["isBlack"] = 0] = "isBlack"; + Relationship[Relationship["isFriend"] = 1] = "isFriend"; +})(Relationship || (Relationship = {})); +export var LoginStatus; +(function (LoginStatus) { + LoginStatus[LoginStatus["Logout"] = 1] = "Logout"; + LoginStatus[LoginStatus["Logging"] = 2] = "Logging"; + LoginStatus[LoginStatus["Logged"] = 3] = "Logged"; +})(LoginStatus || (LoginStatus = {})); +export var OnlineState; +(function (OnlineState) { + OnlineState[OnlineState["Online"] = 1] = "Online"; + OnlineState[OnlineState["Offline"] = 0] = "Offline"; +})(OnlineState || (OnlineState = {})); diff --git a/src/utils/openIM/types/params.d.ts b/src/utils/openIM/types/params.d.ts new file mode 100644 index 0000000..83a86e1 --- /dev/null +++ b/src/utils/openIM/types/params.d.ts @@ -0,0 +1,405 @@ +import { MessageEntity, OfflinePush, PicBaseInfo, AtUsersInfoItem, GroupInitInfo, MessageItem, SelfUserInfo, RtcInvite } from './entity'; +import { AllowType, GroupJoinSource, GroupVerificationType, MessageType, MessageReceiveOptType, GroupMemberRole, GroupMemberFilter, LogLevel } from './enum'; +export declare type InitAndLoginConfig = { + userID: string; + token: string; + platformID: number; + apiAddr: string; + wsAddr: string; + logLevel?: LogLevel; + isLogStandardOutput?: boolean; + isExternalExtensions?: boolean; + tryParse?: boolean; +}; +export declare type GetOneConversationParams = { + sourceID: string; + sessionType: number; +}; +export declare type GetAdvancedHistoryMsgParams = { + userID?: string; + groupID?: string; + lastMinSeq: number; + count: number; + startClientMsgID: string; + conversationID: string; +}; +export declare type GetHistoryMsgParams = { + userID: string; + groupID: string; + count: number; + startClientMsgID: string; + conversationID?: string; +}; +export declare type MarkNotiParams = { + conversationID: string; + clientMsgIDList: string[]; +}; +export declare type GetGroupMemberParams = { + groupID: string; + filter: GroupMemberFilter; + offset: number; + count: number; +}; +export declare type SendMsgParams = { + recvID: string; + groupID: string; + offlinePushInfo?: OfflinePush; + message: MessageItem; + fileArrayBuffer?: ArrayBuffer; + snpFileArrayBuffer?: ArrayBuffer; +}; +export declare type SetMessageLocalExParams = { + conversationID: string; + clientMsgID: string; + localEx: string; +}; +export declare type ImageMsgParams = { + sourcePicture: PicBaseInfo; + bigPicture: PicBaseInfo; + snapshotPicture: PicBaseInfo; +}; +export declare type VideoMsgParams = { + videoPath: string; + duration: number; + videoType: string; + snapshotPath: string; + videoUUID: string; + videoUrl: string; + videoSize: number; + snapshotUUID: string; + snapshotSize: number; + snapshotUrl: string; + snapshotWidth: number; + snapshotHeight: number; + snapShotType?: string; +}; +export declare type VideoMsgFullParams = { + videoFullPath: string; + videoType: string; + duration: number; + snapshotFullPath: string; +}; +export declare type CustomMsgParams = { + data: string; + extension: string; + description: string; +}; +export declare type QuoteMsgParams = { + text: string; + message: string; +}; +export declare type AdvancedQuoteMsgParams = { + text: string; + message: MessageItem; + messageEntityList?: MessageEntity[]; +}; +export declare type AdvancedMsgParams = { + text: string; + messageEntityList?: MessageEntity[]; +}; +export declare type SetPrvParams = { + conversationID: string; + isPrivate: boolean; +}; +export declare type SplitConversationParams = { + offset: number; + count: number; +}; +export declare type SetDraftParams = { + conversationID: string; + draftText: string; +}; +export declare type PinCveParams = { + conversationID: string; + isPinned: boolean; +}; +export declare type IsRecvParams = { + conversationIDList: string[]; + opt: MessageReceiveOptType; +}; +export declare type UpdateMemberNameParams = { + groupID: string; + userID: string; + GroupMemberNickname: string; +}; +export declare type GroupBaseInfo = Partial> & { + groupID: string; +}; +export declare type JoinGroupParams = { + groupID: string; + reqMsg: string; + joinSource: GroupJoinSource; +}; +export declare type SearchGroupParams = { + keywordList: string[]; + isSearchGroupID: boolean; + isSearchGroupName: boolean; +}; +export declare type ChangeGroupMuteParams = { + groupID: string; + isMute: boolean; +}; +export declare type ChangeGroupMemberMuteParams = { + groupID: string; + userID: string; + mutedSeconds: number; +}; +export declare type TransferGroupParams = { + groupID: string; + newOwnerUserID: string; +}; +export declare type AccessGroupParams = { + groupID: string; + fromUserID: string; + handleMsg: string; +}; +export declare type SetGroupRoleParams = { + groupID: string; + userID: string; + roleLevel: GroupMemberRole; +}; +export declare type SetGroupVerificationParams = { + verification: GroupVerificationType; + groupID: string; +}; +export declare type SetBurnDurationParams = { + conversationID: string; + burnDuration: number; +}; +export declare type GetUserInfoWithCacheParams = { + userIDList: string[]; + groupID: string; +}; +export declare type AtMsgParams = { + text: string; + atUserIDList: string[]; + atUsersInfo?: AtUsersInfoItem[]; + message?: MessageItem; +}; +export declare type SoundMsgParams = { + uuid: string; + soundPath: string; + sourceUrl: string; + dataSize: number; + duration: number; + soundType?: string; +}; +export declare type FileMsgParams = { + filePath: string; + fileName: string; + uuid: string; + sourceUrl: string; + fileSize: number; + fileType?: string; +}; +export declare type FileMsgFullParams = { + fileFullPath: string; + fileName: string; +}; +export declare type SouondMsgFullParams = { + soundPath: string; + duration: number; +}; +export declare type MergerMsgParams = { + messageList: MessageItem[]; + title: string; + summaryList: string[]; +}; +export declare type FaceMessageParams = { + index: number; + data: string; +}; +export declare type LocationMsgParams = { + description: string; + longitude: number; + latitude: number; +}; +export declare type GroupMsgReadParams = { + groupID: string; + msgIDList: string[]; +}; +export declare type InsertSingleMsgParams = { + message: MessageItem; + recvID: string; + sendID: string; +}; +export declare type InsertGroupMsgParams = { + message: MessageItem; + groupID: string; + sendID: string; +}; +export declare type AccessMessageParams = { + conversationID: string; + clientMsgID: string; +}; +export declare type AccessMessagesParams = { + conversationID: string; + seqs: string; +}; +export declare type TypingUpdateParams = { + recvID: string; + msgTip: string; +}; +export declare type SplitParams = { + offset: number; + count: number; +}; +export declare type GetOneCveParams = { + sourceID: string; + sessionType: number; +}; +export declare type isRecvParams = { + conversationID: string; + opt: MessageReceiveOptType; +}; +export declare type SearchLocalParams = { + conversationID: string; + keywordList: string[]; + keywordListMatchType?: number; + senderUserIDList?: string[]; + messageTypeList?: MessageType[]; + searchTimePosition?: number; + searchTimePeriod?: number; + pageIndex?: number; + count?: number; +}; +export declare type AddFriendParams = { + toUserID: string; + reqMsg: string; +}; +export declare type SearchFriendParams = { + keywordList: string[]; + isSearchUserID: boolean; + isSearchNickname: boolean; + isSearchRemark: boolean; +}; +export declare type RemarkFriendParams = { + toUserID: string; + remark: string; +}; +export declare type AccessFriendParams = { + toUserID: string; + handleMsg: string; +}; +export declare type InviteGroupParams = { + groupID: string; + reason: string; + userIDList: string[]; +}; +export declare type GetGroupMemberByTimeParams = { + groupID: string; + filterUserIDList: string[]; + offset: number; + count: number; + joinTimeBegin: number; + joinTimeEnd: number; +}; +export declare type SearchGroupMemberParams = { + groupID: string; + keywordList: string[]; + isSearchUserID: boolean; + isSearchMemberNickname: boolean; + offset: number; + count: number; +}; +export declare type SetMemberAuthParams = { + rule: AllowType; + groupID: string; +}; +export declare type CreateGroupParams = { + memberUserIDs: string[]; + groupInfo: GroupInitInfo; + adminUserIDs?: string[]; + ownerUserID?: string; +}; +export declare type GroupInfoParams = Partial & { + groupID: string; + needVerification: GroupVerificationType; + lookMemberInfo: AllowType; + applyMemberFriend: AllowType; +}; +export declare type MemberNameParams = { + groupID: string; + userID: string; + groupMemberNickname: string; +}; +export declare type MemberExParams = { + groupID: string; + userID: string; + ex: string; +}; +export declare type FindMessageParams = { + conversationID: string; + clientMsgIDList: string[]; +}; +export declare type UploadFileParams = { + name: string; + contentType: string; + uuid: string; + file: File; +}; +export declare type PartialUserItem = Partial; +export declare type SignalingInviteParams = { + invitation: RtcInvite; + offlinePushInfo?: OfflinePush; +}; +export declare type RtcActionParams = { + opUserID: string; + invitation: RtcInvite; +}; +export declare type CustomSignalParams = { + roomID: string; + customInfo: string; +}; +export declare type CreateMeetingParams = { + meetingName: string; + meetingHostUserID: string; + startTime: number; + meetingDuration: number; + inviteeUserIDList: string[]; +}; +export declare type UpdateMeetingParams = { + roomID: string; + meetingName: string; + startTime: number; + endTime: number; + participantCanUnmuteSelf: boolean; + participantCanEnableVideo: boolean; + onlyHostInviteUser: boolean; + onlyHostShareScreen: boolean; + joinDisableMicrophone: boolean; + joinDisableVideo: boolean; + isMuteAllVideo: boolean; + isMuteAllMicrophone: boolean; + addCanScreenUserIDList: string[]; + reduceCanScreenUserIDList: string[]; + addDisableMicrophoneUserIDList: string[]; + reduceDisableMicrophoneUserIDList: string[]; + addDisableVideoUserIDList: string[]; + reduceDisableVideoUserIDList: string[]; + addPinedUserIDList: string[]; + reducePinedUserIDList: string[]; + addBeWatchedUserIDList: string[]; + reduceBeWatchedUserIDList: string[]; +}; +export declare type MeetingOperateStreamParams = { + streamType: string; + roomID: string; + userID?: string; + mute: boolean; + muteAll: boolean; +}; +export declare type SetConversationMsgDestructParams = { + conversationID: string; + isMsgDestruct: boolean; +}; +export declare type SetConversationMsgDestructTimeParams = { + conversationID: string; + msgDestructTime: number; +}; +export declare type ConfigParams = { + apiAddr: string; + wsAddr: string; + reConn: boolean; + backupHttpAddrs: string[]; +}; \ No newline at end of file diff --git a/src/utils/openIM/types/params.js b/src/utils/openIM/types/params.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/utils/openIM/types/params.js @@ -0,0 +1 @@ +export {}; diff --git a/src/utils/openIM/utils/emitter.d.ts b/src/utils/openIM/utils/emitter.d.ts new file mode 100644 index 0000000..0d04c2a --- /dev/null +++ b/src/utils/openIM/utils/emitter.d.ts @@ -0,0 +1,11 @@ +import { WSEvent } from '../types/entity'; +import { CbEvents } from '../constant'; +declare type Cbfn = (data: WSEvent) => void; +declare class Emitter { + private events; + constructor(); + emit(event: CbEvents, data: WSEvent): this; + on(event: CbEvents, fn: Cbfn): this; + off(event: CbEvents, fn: Cbfn): this | undefined; +} +export default Emitter; diff --git a/src/utils/openIM/utils/emitter.js b/src/utils/openIM/utils/emitter.js new file mode 100644 index 0000000..890db50 --- /dev/null +++ b/src/utils/openIM/utils/emitter.js @@ -0,0 +1,39 @@ +class Emitter { + events; + constructor() { + this.events = {}; + } + emit(event, data) { + if (this.events[event]) { + this.events[event].forEach(fn => { + return fn(data); + }); + } + return this; + } + on(event, fn) { + if (this.events[event]) { + this.events[event].push(fn); + } + else { + this.events[event] = [fn]; + } + return this; + } + off(event, fn) { + if (event && typeof fn === 'function' && this.events[event]) { + const listeners = this.events[event]; + if (!listeners || listeners.length === 0) { + return; + } + const index = listeners.findIndex(_fn => { + return _fn === fn; + }); + if (index !== -1) { + listeners.splice(index, 1); + } + } + return this; + } +} +export default Emitter; diff --git a/src/utils/openIM/utils/escape.d.ts b/src/utils/openIM/utils/escape.d.ts new file mode 100644 index 0000000..6170806 --- /dev/null +++ b/src/utils/openIM/utils/escape.d.ts @@ -0,0 +1,23 @@ +/** + * Escapes the given string to protect against SQL injection attacks. + * + * By default it assumes that backslashes are not supported as they are not part of the standard SQL spec. + * Quoting from the [SQLlite web site](https://sqlite.org/lang_expr.html): + * + * > C-style escapes using the backslash character are not supported because they are not standard SQL. + * + * This means three things: + * + * - backslashes and double quotes `"` are not escaped by default + * - single quotes are escaped via `''` instead of `\'` + * - your sql engine should throw an error when encountering a backslash escape + * as part of a string, unless it is a literal backslash, i.e. `'backslash: \\'`. + * + * It is recommended to set the `backslashSupported` option `true` if your SQL + * engine supports it. In that case backslash sequences are escaped and single + * and double quotes are escaped via a backslash, i.e. `'\''`. + * + */ +export declare function escapeString(val: string, opts?: { + backslashSupported: boolean; +}): string; diff --git a/src/utils/openIM/utils/escape.js b/src/utils/openIM/utils/escape.js new file mode 100644 index 0000000..a45e431 --- /dev/null +++ b/src/utils/openIM/utils/escape.js @@ -0,0 +1,57 @@ +// eslint-disable-next-line no-control-regex +const CHARS_GLOBAL_BACKSLASH_SUPPORTED_RX = /[\0\b\t\n\r\x1a"'\\]/g; +const CHARS_ESCAPE_BACKSLASH_SUPPORTED_MAP = { + '\0': '\\0', + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\r': '\\r', + '\x1a': '\\Z', + '"': '\\"', + "'": "\\'", + '\\': '\\\\', +}; +/** + * Escapes the given string to protect against SQL injection attacks. + * + * By default it assumes that backslashes are not supported as they are not part of the standard SQL spec. + * Quoting from the [SQLlite web site](https://sqlite.org/lang_expr.html): + * + * > C-style escapes using the backslash character are not supported because they are not standard SQL. + * + * This means three things: + * + * - backslashes and double quotes `"` are not escaped by default + * - single quotes are escaped via `''` instead of `\'` + * - your sql engine should throw an error when encountering a backslash escape + * as part of a string, unless it is a literal backslash, i.e. `'backslash: \\'`. + * + * It is recommended to set the `backslashSupported` option `true` if your SQL + * engine supports it. In that case backslash sequences are escaped and single + * and double quotes are escaped via a backslash, i.e. `'\''`. + * + */ +export function escapeString(val, opts = { backslashSupported: false }) { + if (val == null) { + throw new Error('Need to pass a valid string'); + } + opts = opts || {}; + const backslashSupported = !!opts.backslashSupported; + if (!backslashSupported) + return "'" + val.replace(/'/g, "''") + "'"; + const charsRx = CHARS_GLOBAL_BACKSLASH_SUPPORTED_RX; + const charsEscapeMap = CHARS_ESCAPE_BACKSLASH_SUPPORTED_MAP; + let chunkIndex = (charsRx.lastIndex = 0); + let escapedVal = ''; + let match; + while ((match = charsRx.exec(val))) { + escapedVal += val.slice(chunkIndex, match.index) + charsEscapeMap[match[0]]; + chunkIndex = charsRx.lastIndex; + } + // Nothing was escaped + if (chunkIndex === 0) + return "'" + val + "'"; + if (chunkIndex < val.length) + return "'" + escapedVal + val.slice(chunkIndex) + "'"; + return "'" + escapedVal + "'"; +} diff --git a/src/utils/openIM/utils/index.d.ts b/src/utils/openIM/utils/index.d.ts new file mode 100644 index 0000000..eb5287b --- /dev/null +++ b/src/utils/openIM/utils/index.d.ts @@ -0,0 +1,7 @@ +export * from './response'; +export * from './timer'; +export * from './key'; +export * from './value'; +export * from './is'; +export * from './escape'; +export * from './logFormat'; diff --git a/src/utils/openIM/utils/index.js b/src/utils/openIM/utils/index.js new file mode 100644 index 0000000..eb5287b --- /dev/null +++ b/src/utils/openIM/utils/index.js @@ -0,0 +1,7 @@ +export * from './response'; +export * from './timer'; +export * from './key'; +export * from './value'; +export * from './is'; +export * from './escape'; +export * from './logFormat'; diff --git a/src/utils/openIM/utils/is.d.ts b/src/utils/openIM/utils/is.d.ts new file mode 100644 index 0000000..15cd989 --- /dev/null +++ b/src/utils/openIM/utils/is.d.ts @@ -0,0 +1,6 @@ +export declare function isString(value: unknown): boolean; +export declare function isNumber(value: unknown): boolean; +export declare function isBoolean(value: unknown): boolean; +export declare function isUndefined(value: unknown): boolean; +export declare function isObject(value: unknown): boolean; +export declare function isFunction(value: unknown): boolean; diff --git a/src/utils/openIM/utils/is.js b/src/utils/openIM/utils/is.js new file mode 100644 index 0000000..b9b2dae --- /dev/null +++ b/src/utils/openIM/utils/is.js @@ -0,0 +1,18 @@ +export function isString(value) { + return typeof value === 'string'; +} +export function isNumber(value) { + return typeof value === 'number'; +} +export function isBoolean(value) { + return typeof value === 'boolean'; +} +export function isUndefined(value) { + return typeof value === 'undefined'; +} +export function isObject(value) { + return typeof value === 'object' && value !== null; +} +export function isFunction(value) { + return typeof value === 'function'; +} diff --git a/src/utils/openIM/utils/key.d.ts b/src/utils/openIM/utils/key.d.ts new file mode 100644 index 0000000..afde508 --- /dev/null +++ b/src/utils/openIM/utils/key.d.ts @@ -0,0 +1,3 @@ +export declare type KeyType = 'CamelCase' | 'SnakeCase'; +export declare function convertSnakeCaseToCamelCase(key: string): string; +export declare function convertCamelCaseToSnakeCase(key: string): string; diff --git a/src/utils/openIM/utils/key.js b/src/utils/openIM/utils/key.js new file mode 100644 index 0000000..7b08415 --- /dev/null +++ b/src/utils/openIM/utils/key.js @@ -0,0 +1,117 @@ +const InternalConstraint = [ + ['user_id', 'userID'], + ['group_id', 'groupID'], + ['client_msg_id', 'clientMsgID'], + ['server_msg_id', 'serverMsgID'], + ['send_id', 'sendID'], + ['recv_id', 'recvID'], + ['sender_platform_id', 'senderPlatformID'], + ['sender_nick_name', 'senderNickname'], + ['sender_face_url', 'senderFaceURL'], + ['session_type', 'sessionType'], + ['msg_from', 'msgFrom'], + ['content_type', 'contentType'], + ['content', 'content'], + ['is_read', 'isRead'], + ['is_react', 'isReact'], + ['is_external_extensions', 'isExternalExtensions'], + ['msg_first_modify_time', 'msgFirstModifyTime'], + ['status', 'status'], + ['seq', 'seq'], + ['send_time', 'sendTime'], + ['create_time', 'createTime'], + ['attached_info', 'attachedInfo'], + ['ex', 'ex'], + ['face_url', 'faceURL'], + ['creator_user_id', 'creatorUserID'], + ['conversation_id', 'conversationID'], + ['owner_user_id', 'ownerUserID'], + ['notification_user_id', 'notificationUserID'], + ['operator_user_id', 'operatorUserID'], + ['from_face_url', 'fromFaceURL'], + ['from_user_id', 'fromUserID'], + ['from_gender', 'fromGender'], + ['from_nickname', 'fromNickname'], + ['to_user_id', 'toUserID'], + ['to_nickname', 'toNickname'], + ['to_face_url', 'toFaceURL'], + ['to_gender', 'toGender'], + ['req_msg', 'reqMsg'], + ['handle_msg', 'handleMsg'], + ['handle_time', 'handleTime'], + ['handle_result', 'handleResult'], + ['handler_user_id', 'handlerUserID'], + ['handle_user_id', 'handleUserID'], + ['inviter_user_id', 'inviterUserID'], + ['mute_end_time', 'muteEndTime'], + ['role_level', 'roleLevel'], + ['join_time', 'joinTime'], + ['join_source', 'joinSource'], + ['friend_user_id', 'friendUserID'], + ['recv_msg_opt', 'recvMsgOpt'], + ['group_at_type', 'groupAtType'], + ['latest_msg_send_time', 'latestMsgSendTime'], + ['draft_text_time', 'draftTextTime'], + ['is_private_chat', 'isPrivateChat'], + ['is_not_in_group', 'isNotInGroup'], + ['update_unread_count_time', 'updateUnreadCountTime'], + ['is_msg_destruct', 'isMsgDestruct'], + ['msg_destruct_time', 'msgDestructTime'], + ['part_hash', 'partHash'], + ['upload_id', 'uploadID'], + ['upload_info', 'uploadInfo'], + ['expire_time', 'expireTime'], +]; +function _getInternalCamelCaseBySnakeCase(key) { + const pair = InternalConstraint.find(p => { + return p[0] === key; + }); + if (pair) { + return pair[1]; + } +} +function _getInternalSnakeCaseByCamelCase(key) { + const pair = InternalConstraint.find(p => { + return p[1] === key; + }); + if (pair) { + return pair[0]; + } +} +export function convertSnakeCaseToCamelCase(key) { + const internalKey = _getInternalCamelCaseBySnakeCase(key); + if (internalKey) { + return internalKey; + } + const cArr = []; + let lastSign = -2; + for (let i = 0; i < key.length; i++) { + const c = key[i]; + if (c === '_' && i < key.length - 1) { + lastSign = i; + continue; + } + if (i - 1 === lastSign) { + cArr.push(c.toUpperCase()); + } + else { + cArr.push(c); + } + } + return cArr.join(''); +} +export function convertCamelCaseToSnakeCase(key) { + const internalKey = _getInternalSnakeCaseByCamelCase(key); + if (internalKey) { + return internalKey; + } + const cArr = []; + for (let i = 0; i < key.length; i++) { + const c = key[i]; + if (c.toLowerCase() !== c) { + cArr.push('_'); + } + cArr.push(c.toLowerCase()); + } + return cArr.join(''); +} diff --git a/src/utils/openIM/utils/logFormat.d.ts b/src/utils/openIM/utils/logFormat.d.ts new file mode 100644 index 0000000..edadfa9 --- /dev/null +++ b/src/utils/openIM/utils/logFormat.d.ts @@ -0,0 +1 @@ +export declare function logBoxStyleValue(backgroundColor?: string, color?: string): string; diff --git a/src/utils/openIM/utils/logFormat.js b/src/utils/openIM/utils/logFormat.js new file mode 100644 index 0000000..60a082b --- /dev/null +++ b/src/utils/openIM/utils/logFormat.js @@ -0,0 +1,3 @@ +export function logBoxStyleValue(backgroundColor, color) { + return `font-size:0.875rem; background:${backgroundColor ?? '#ffffff'}; color:${color ?? '#000000'}; border-radius:4px; padding-inline:4px;`; +} diff --git a/src/utils/openIM/utils/response.d.ts b/src/utils/openIM/utils/response.d.ts new file mode 100644 index 0000000..4dbbbb2 --- /dev/null +++ b/src/utils/openIM/utils/response.d.ts @@ -0,0 +1 @@ +export declare function formatResponse(data: unknown, errCode?: number, errMsg?: string): any; diff --git a/src/utils/openIM/utils/response.js b/src/utils/openIM/utils/response.js new file mode 100644 index 0000000..5793af0 --- /dev/null +++ b/src/utils/openIM/utils/response.js @@ -0,0 +1,11 @@ +export function formatResponse(data, errCode, errMsg) { + let serializedData = data; + if (typeof data === 'object') { + serializedData = JSON.stringify(data); + } + return { + data: data !== undefined ? serializedData : '{}', + errCode: errCode || 0, + errMsg: errMsg || '', + }; +} diff --git a/src/utils/openIM/utils/timer.d.ts b/src/utils/openIM/utils/timer.d.ts new file mode 100644 index 0000000..adb83a3 --- /dev/null +++ b/src/utils/openIM/utils/timer.d.ts @@ -0,0 +1 @@ +export declare function wait(duration: number): Promise; diff --git a/src/utils/openIM/utils/timer.js b/src/utils/openIM/utils/timer.js new file mode 100644 index 0000000..b0d2884 --- /dev/null +++ b/src/utils/openIM/utils/timer.js @@ -0,0 +1,8 @@ +export async function wait(duration) { + return new Promise(resolve => { + const timer = setTimeout(() => { + clearTimeout(timer); + resolve(null); + }, duration); + }); +} diff --git a/src/utils/openIM/utils/value.d.ts b/src/utils/openIM/utils/value.d.ts new file mode 100644 index 0000000..9a4029b --- /dev/null +++ b/src/utils/openIM/utils/value.d.ts @@ -0,0 +1,6 @@ +import { QueryExecResult } from '@jlongster/sql.js'; +import { KeyType } from './key'; +export declare function converSqlExecResult(record: QueryExecResult, keyType?: KeyType, booleanKeys?: string[], convertMap?: Record): Record[]; +export declare function convertToCamelCaseObject(obj: Record): Record; +export declare function convertToSnakeCaseObject(obj: Record, escape?: boolean): Record; +export declare function convertObjectField(obj: Record, convertMap?: Record): Record; diff --git a/src/utils/openIM/utils/value.js b/src/utils/openIM/utils/value.js new file mode 100644 index 0000000..2e711d0 --- /dev/null +++ b/src/utils/openIM/utils/value.js @@ -0,0 +1,53 @@ +import { escapeString } from './escape'; +import { isString } from './is'; +import { convertSnakeCaseToCamelCase, convertCamelCaseToSnakeCase, } from './key'; +export function converSqlExecResult(record, keyType = 'CamelCase', booleanKeys = [], convertMap = {}) { + const { columns = [], values = [] } = record || {}; + const result = []; + values.forEach(v => { + const converted = {}; + columns.forEach((k, i) => { + let ck = k; + let cv = v[i]; + if (keyType === 'CamelCase') { + ck = convertSnakeCaseToCamelCase(k); + } + if (keyType === 'SnakeCase') { + ck = convertCamelCaseToSnakeCase(k); + } + if (booleanKeys.find(bk => bk === ck)) { + cv = !!cv; + } + ck = convertMap[k] || ck; + converted[ck] = cv; + }); + result.push(converted); + }); + return result; +} +export function convertToCamelCaseObject(obj) { + const retObj = {}; + Object.keys(obj).forEach(k => { + retObj[convertSnakeCaseToCamelCase(k)] = obj[k]; + }); + return retObj; +} +export function convertToSnakeCaseObject(obj, escape = true) { + const retObj = {}; + Object.keys(obj).forEach(k => { + let value = obj[k]; + if (escape && isString(value)) { + value = escapeString(value).slice(1, -1); + } + retObj[convertCamelCaseToSnakeCase(k)] = value; + }); + return retObj; +} +export function convertObjectField(obj, convertMap = {}) { + const ret = {}; + Object.keys(obj).forEach(k => { + const nk = convertMap[k] || k; + ret[nk] = obj[k]; + }); + return ret; +} diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..8a784b6 --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,143 @@ +import axios from 'axios' +import router from '@/router' +import { showToast,showDialog } from 'vant' +import { useUserStore } from "@/stores/user"; + +const clearToken = () => { + window.localStorage.removeItem('token') +} +// 这里是用于设定请求后端时,所用的 Token KEY +// 可以根据自己的需要修改,常见的如 Access-Token,Authorization +// 需要注意的是,请尽量保证使用中横线`-` 来作为分隔符, +// 避免被 nginx 等负载均衡器丢弃了自定义的请求头 + +// 创建 axios 实例 +const request = axios.create({ + // API 请求的默认前缀 + baseURL: import.meta.env.VITE_APP_API_BASE_URL, + timeout: 6000, // 请求超时时间 + withCredentials: false, // 异步请求携带cookie + headers: { + // 设置后端需要的传参类型 + 'Content-Type': 'application/json', + // 'Origin': 'wp' + // 'X-Requested-With': 'XMLHttpRequest', + }, +}) + +// 请求拦截器 +request.interceptors.request.use( + (config) => { + //给请求头设置token + const token = window.localStorage.getItem('token') || '' + + if (token) { + config.headers.Token = token + } + + if (config.requestBaseUrl === 'MSG_API') { + config.baseURL = DEV ? VITE_APP_MSG_API : msg_api; + } + + return config + }, + (error) => { + showToast({ position: 'top', message: error.message }) + return Promise.reject(error); + } +); + +// 响应拦截器 +request.interceptors.response.use( + (response) => { + const { data, request } = response; + const { code, msg, detail } = data; + const { responseURL } = request + const userStore = useUserStore(); + const { setToken } = userStore; + if (code === 511) { + if (responseURL.includes('/v1/account/login')) { + showToast({ message: '系统正在维护中' }) + } else { + // router.push('/placeholder') + } + return data + } + if (code === 401) { + // 登录失效 + // showDialog({ + // title: '温馨提示', + // message: '您的账户已在其他设备登录,如非本人操作,请立即修改登录密码!', + // }).then(() => { + // on close + // clearToken() + setToken('') + router.push({path: '/login'}) + return data + // }); + } + + if (code === 1001) { + // 账号被锁定 + // clearToken() + setToken('') + router.push({path: '/login'}) + return data + } + if(code===1119){ + // 登录失效 + showDialog({ + title: '温馨提示', + message: '您的账户已在其他设备登录,如非本人操作,请立即修改登录密码!', + confirmButtonText:'我知道了' + }).then(() => { + // clearToken() + setToken('') + router.push({path: '/login'}) + return data + }); + return + } + if (code === 200) { + // 将组件用的数据返回 + return data; + } else { + // 处理业务错误。 + if (detail && code != 5050 && code!==500) { + showToast({ position: 'top', message: detail }) + } + + // return Promise.reject(new Error(detail)); + return data; + } + }, + (error) => { + // 处理 HTTP 网络错误 + let message = ""; + // HTTP 状态码 + const status = error.response?.status; + switch (status) { + case 401: + message = "token失效,请重新登录"; + router.push('/user/Login') + // 这里可以触发退出的 action + break; + case 403: + message = "没有权限,请获取权限后登录"; + break; + case 404: + message = "页面不存在"; + break; + case 500: + message = "服务器故障"; + break; + case 502: + message = "数据库查询错误"; + break; + default: + message = "网络连接错误"; + } + return Promise.reject(error); + } +); +export default request diff --git a/src/utils/sdkInit.js b/src/utils/sdkInit.js new file mode 100644 index 0000000..6e7a990 --- /dev/null +++ b/src/utils/sdkInit.js @@ -0,0 +1,187 @@ +import { getSDK } from '@/utils/openIM' +import { CbEvents } from '@/utils/openIM/constant' +import { useUserStore } from '@/stores/user.js' +import user from '@/api/user.js' +import { showImagePreview, showToast } from "vant"; +import { removeAllLocalstorage } from '@/utils/tools' +import { useContactStore } from "@/stores/contact"; +import FingerprintJS from '@fingerprintjs/fingerprintjs' +import { showLoadingToast } from "vant"; +import { syncAllConversation } from '@/utils/msgTimeDeleteManager' +import { useCommonStore } from "@/stores/modules/common"; +import use from "@/use"; +const { useSystem } = use; +let im_sdk = null +const ws_login_status = 1 // 1 未登录 2 登录中 3 已登录 +export const im_config = () => { + const im_token = window.localStorage.getItem('im_token') + const token = window.localStorage.getItem('token') + const user_id = window.localStorage.getItem('user_id') + const visitorId = window.localStorage.getItem('visitorId') + const { ws_url, api_3, backup_http_addrs } = window.WEB_CONFIG + return { + userID: user_id, // IM 用户 userID + token: im_token, // IM 用户令牌 + // platformID: 3, // 当前登录平台号 + platformID: useSystem(), //随便填的 + apiAddr: api_3, + wsAddr: ws_url, + backupHttpAddrs: backup_http_addrs, + deviceID: visitorId, + } +} +let close_showLoadingToast = null +let close_showLoadingToast_timer = null //初始化loading时,如果js逻辑报错,可能导致loading无法关闭所以用定时器,如果5分钟之后没有关闭强制关闭 +/** + * 初始化 + * **/ +export const sdkInit = async (needLogin = true) => { + const userStore = useUserStore(); + const { setToken, setUserInfo } = userStore; + const contactStore = useContactStore() + const { clearStore } = contactStore; + console.log(im_sdk,'11111111') + if (im_sdk === null) { + console.log('开始初始化数据') + im_sdk = getSDK() + if(sessionStorage.getItem('close_showLoadingToast')==1){ + if(close_showLoadingToast)close_showLoadingToast.close() + if(close_showLoadingToast_timer){ + close_showLoadingToast_timer = null + clearTimeout(close_showLoadingToast_timer) + } + close_showLoadingToast = showLoadingToast({ + message: '加载中...', + forbidClick: true, + loadingType: 'spinner', + duration:0, + }); + if(!close_showLoadingToast_timer)close_showLoadingToast_timer = setTimeout(() => { + if(close_showLoadingToast)close_showLoadingToast.close() + close_showLoadingToast_timer = null + clearTimeout(close_showLoadingToast_timer) + }, 5*60*1000); + } + im_sdk.on(CbEvents.OnSyncServerFailed, () => { + console.log("同步会话失败") + if(close_showLoadingToast)close_showLoadingToast.close() + sessionStorage.setItem('close_showLoadingToast',1) + }); + im_sdk.on(CbEvents.OnSyncServerFinish, () => { + console.log("同步会话成功") + if(close_showLoadingToast)close_showLoadingToast.close() + sessionStorage.setItem('close_showLoadingToast',1) + syncAllConversation() + // const commonStore = useCommonStore() + // commonStore.getNewTimestamp(new Date().getTime()) + }); + im_sdk.on(CbEvents.OnConnectFailed, ({ errCode, errMsg }) => { + console.error("建立wss失败:", errMsg) + if(close_showLoadingToast)close_showLoadingToast.close() + sessionStorage.setItem('close_showLoadingToast',1) + }); + im_sdk.on(CbEvents.OnConnectSuccess, () => { + console.log("建立wss成功") + }); + im_sdk.on(CbEvents.OnConnecting, () => { + console.log("wss连接中...") + }); + im_sdk.on(CbEvents.OnKickedOffline, async (data) => { + console.log('OnKickedOffline==========', data) + showToast("已被强制下线,请重新登录!") + setTimeout(() => { + setToken(''); + setUserInfo({}); + removeAllLocalstorage(); + im_sdk.logout(); + clearStore(); + localStorage.removeItem('im_token') + localStorage.removeItem('token') + localStorage.removeItem('user_id') + location.reload(); + }, 3000) + }); + im_sdk.on(CbEvents.OnKickedOfflineWithExData, async (data) => { + console.log('OnKickedOfflineWithExData==========', data) + const { data: innerData } = data || {} + const { code, data: kickInfo } = innerData || {} + // 20001 为移除信任设备踢掉 + if (code === 20001) { + const info = JSON.parse(kickInfo || '{}') || {} + const activeDeviceId = localStorage.getItem('visitorId') + if (info.device_id !== activeDeviceId) { + return + } + + showToast("该设备登录存在风险,请使用可信设备登录") + } + + // 20002 注销踢掉 + if (code === 20002 && kickInfo === '') { + showToast("账号已注销") + } + + if (![20001, 20002].includes(code)) { + showToast("已被强制下线,请重新登录!") + } + + setTimeout(() => { + setToken(''); + setUserInfo({}); + removeAllLocalstorage(); + im_sdk.logout(); + clearStore(); + localStorage.removeItem('im_token') + localStorage.removeItem('token') + localStorage.removeItem('user_id') + location.reload(); + }, 3000) + }); + im_sdk.on(CbEvents.OnUserTokenExpired, () => { + setTimeout(() => { + setToken(''); + setUserInfo({}); + removeAllLocalstorage(); + im_sdk.logout(); + clearStore(); + localStorage.removeItem('im_token') + localStorage.removeItem('token') + localStorage.removeItem('user_id') + // sessionStorage.setItem('againLoadingSign','')//允许群聊页面如果获取不到数据就可以进行强制重新加载 + if(close_showLoadingToast)close_showLoadingToast.close() + close_showLoadingToast_timer = null + location.reload(); + }, 3000) + }); + console.log("》》》》》》》》》》sdk登录开始") + // sdk登录前获取设备信息并填入 + const fpPromise = await FingerprintJS.load() + const res = await fpPromise.get() + localStorage.setItem('visitorId', res.visitorId) + // 其他tab不需要登录 + if (!needLogin) return + try { + await im_sdk.login(im_config()) + console.log("wss登录成功") + } catch (error) { + console.error("sdk 触发了登录失败",error) + // if (error.errCode!==10102) { + // im_sdk.logout(); + // } + } + } +} +// 获取sdk +export const get_sdk = () => { + return im_sdk +} +// 重置sdk +export const reset_sdk = () => { + if (im_sdk) { + im_sdk.logout(); + } + im_sdk = null + return im_sdk +} + + diff --git a/src/utils/sendMessage.js b/src/utils/sendMessage.js new file mode 100644 index 0000000..004f1df --- /dev/null +++ b/src/utils/sendMessage.js @@ -0,0 +1,99 @@ +import { getPicInfo, getMediaDuration } from './common.js' +import { getSDK } from './openIM/index.js' +const OpenIM = getSDK(); +export const getImageMessage = async (file) => { + const { width, height } = await getPicInfo(file); + console.log(width, height, '9999') + const baseInfo = { + uuid: OpenIM.createuuid(), + type: file.type, + size: file.size, + width, + height, + url: URL.createObjectURL(file), + }; + const options = { + sourcePicture: baseInfo, + bigPicture: baseInfo, + snapshotPicture: baseInfo, + file, + sourcePath: `/${file.name}`, + }; + return (await OpenIM.createImageMessageByFile(options)).data; +}; + +export const getVideoMessage = async (file, snapShotFile) => { + const { width, height } = await getPicInfo(snapShotFile); + const options = { + videoFile: file, + snapshotFile: snapShotFile, + videoPath: "", + duration: await getMediaDuration(URL.createObjectURL(file)), + videoType: file.type, + snapshotPath: "", + videoUUID: OpenIM.createuuid(), + videoUrl: "", + videoSize: file.size, + snapshotUUID: OpenIM.createuuid(), + snapshotSize: snapShotFile.size, + snapshotUrl: URL.createObjectURL(snapShotFile), + snapshotWidth: width, + snapshotHeight: height, + snapShotType: snapShotFile.type, + }; + return (await OpenIM.createVideoMessageByFile(options)).data; +}; + +export const getVideoSnshotFile = (file) => { + const url = URL.createObjectURL(file); + return new Promise((reslove, reject) => { + const video = document.createElement("VIDEO"); + video.setAttribute("autoplay", "autoplay"); + video.setAttribute("muted", "muted"); + video.innerHTML = ``; + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + video.addEventListener("canplay", () => { + const anw = document.createAttribute("width"); + //@ts-ignore + anw.nodeValue = video.videoWidth; + const anh = document.createAttribute("height"); + //@ts-ignore + anh.nodeValue = video.videoHeight; + canvas.setAttributeNode(anw); + canvas.setAttributeNode(anh); + //@ts-ignore + ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); + const base64 = canvas.toDataURL("image/png"); + //@ts-ignore + video.pause(); + const file = base64toFile(base64); + reslove(file); + }); + }); +}; + +export const base64toFile = (base64Str) => { + var arr = base64Str.split(","), + fileType = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), + n = bstr.length, + u8arr = new Uint8Array(n); + + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + + return new File([u8arr], `screenshot${Date.now()}.png`, { + type: fileType, + }); +}; + +export const createFileMessage = async (file) => { + const isImage = file.type.includes("image"); + if (isImage) { + return await getImageMessage(file); + } + const snapShotFile = await getVideoSnshotFile(file); + return await getVideoMessage(file, snapShotFile); +}; \ No newline at end of file diff --git a/src/utils/tools.js b/src/utils/tools.js new file mode 100644 index 0000000..d49ab23 --- /dev/null +++ b/src/utils/tools.js @@ -0,0 +1,1643 @@ +import clipboard3 from "vue-clipboard3"; +import { showToast } from "vant"; +import pinyin from "js-pinyin"; +import dayjs from "dayjs"; +import axios from 'axios'; +import { i18n } from "@/lang/index"; +import { computed } from "vue"; +import { storeToRefs } from "pinia"; +import FingerprintJS from '@fingerprintjs/fingerprintjs' +import { useContactStore } from "@/stores/contact"; +import defaultAvatar from "@/assets/mine-avatar.png"; +import contactApi from '@/api/contact' +import use from "@/use"; + +const { useSystem } = use||{}; +const { t } = i18n.global; + +const { toClipboard } = clipboard3(); + +export const copyUserId = async id => { + try { + await toClipboard(String(id)); + showToast("已复制到剪切板"); + } catch (err) { + showToast("复制失败,请手动复制"); + } +}; +export const useFormatTime = (time) => { + return time && dayjs(time, 'YYYY-MM-DDTHH:mm:ssZ').isValid() + ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') + : time +} +export const getDates = date => { + if (!date) return ""; + const d = new Date(date); + const year = d.getFullYear(); + const month = (d.getMonth() + 1).toString().padStart(2, "0"); + const day = d + .getDate() + .toString() + .padStart(2, "0"); + const time = d.toLocaleTimeString(); + return `${year}-${month}-${day} ${time}`; +}; +export const getDate = date => { + if (!date) return ""; + const d = new Date(date); + return d.toLocaleDateString() + " " + d.toLocaleTimeString(); +}; +export const formatTimestamp = (timestamp, format = "-") => { + const date = new Date(timestamp * 1000); + + // 获取年、月、日 + const year = date.getFullYear(); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const day = ("0" + date.getDate()).slice(-2); + + // 获取时、分、秒 + const hours = ("0" + date.getHours()).slice(-2); + const minutes = ("0" + date.getMinutes()).slice(-2); + const seconds = ("0" + date.getSeconds()).slice(-2); + + // 返回格式化后的时间字符串 + return year + format + month + format + day + " " + hours + ":" + minutes; +}; +export const formatsTimestamp = (timestamp, format = "-") => { + if (!timestamp) return '' + const date = new Date(timestamp); + + // 获取年、月、日 + const year = date.getFullYear(); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const day = ("0" + date.getDate()).slice(-2); + + // 获取时、分、秒 + const hours = ("0" + date.getHours()).slice(-2); + const minutes = ("0" + date.getMinutes()).slice(-2); + const seconds = ("0" + date.getSeconds()).slice(-2); + + // 返回格式化后的时间字符串 + return `${year}/${month}/${day} ${hours}:${minutes}`; +}; +export const formatTtamp = dateTimeString => { + const dateTime = new Date(dateTimeString); + const year = dateTime.getFullYear(); + const month = ("0" + (dateTime.getMonth() + 1)).slice(-2); + const day = ("0" + dateTime.getDate()).slice(-2); + const hours = ("0" + dateTime.getHours()).slice(-2); + const minutes = ("0" + dateTime.getMinutes()).slice(-2); + const seconds = ("0" + dateTime.getSeconds()).slice(-2); + + const formattedDateTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + return formattedDateTime; +}; + +export const formatMinutestamp = timestamp => { + const date = new Date(timestamp); + + // 获取年、月、日 + const year = date.getFullYear(); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const day = ("0" + date.getDate()).slice(-2); + + // 获取时、分、秒 + const hours = ("0" + date.getHours()).slice(-2); + const minutes = ("0" + date.getMinutes()).slice(-2); + const seconds = ("0" + date.getSeconds()).slice(-2); + + // 返回格式化后的时间字符串 + return hours + ":" + minutes; +}; +export const showPhone = computed(() => { + const userAgent = navigator.userAgent; + const mobileKeywords = [ + "Android", + "webOS", + "iPhone", + "iPad", + "iPod", + "BlackBerry", + "Windows Phone" + ]; + for (let i = 0; i < mobileKeywords.length; i++) { + if (userAgent.indexOf(mobileKeywords[i]) > -1) { + return true; + } + } + return false; +}); +export const formatTimestamps = timestamp => { + const date = new Date(timestamp * 1000); + + // 获取年、月、日 + const year = date.getFullYear(); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const day = ("0" + date.getDate()).slice(-2); + + // 获取时、分、秒 + const hours = ("0" + date.getHours()).slice(-2); + const minutes = ("0" + date.getMinutes()).slice(-2); + const seconds = ("0" + date.getSeconds()).slice(-2); + + // 返回格式化后的时间字符串 + return ( + year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds + ); +}; +// 时间戳转化为年月日 +export const consttimestampToTime = (timestamp, formatTime = 'YYYY-MM-DD HH:mm') => { + const date = new Date(timestamp); // 将时间戳转化为Date对象 + // 获取年、月、日 + const year = date.getFullYear(); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const day = ("0" + date.getDate()).slice(-2); + + // 获取时、分、秒 + const hours = ("0" + date.getHours()).slice(-2); + const minutes = ("0" + date.getMinutes()).slice(-2); + const seconds = ("0" + date.getSeconds()).slice(-2); + if (formatTime === 'YYYY-MM-DD HH:mm:ss') { + // 返回格式化后的时间字符串 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + } else { + // 返回格式化后的时间字符串 + return `${year}/${month}/${day} ${hours}:${minutes}`; + } + +}; + +export const formatNumber = number => { + // 将数字转换为字符串 + var strNumber = String(number); + + // 检查是否有小数部分 + var hasDecimal = strNumber.indexOf(".") !== -1; + + // 将整数部分每隔三位加逗号 + var parts = strNumber.split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + + // 补充小数部分的两位小数 + if (hasDecimal) { + parts[1] = parts[1].padEnd(2, "0"); + } else { + parts.push("00"); + } + + // 返回格式化后的数字 + return parts.join("."); +}; + +// 分组函数 +export const filterCity = list => { + let ortherArr = []; + list.forEach(el => { + el.pinyin = pinyin.getFullChars(el.showName) || el.showName; + }); + let letterArray = []; + for (let i = 65; i < 91; i++) { + letterArray.push(String.fromCharCode(i)); + } + let newCities = []; + letterArray.forEach(item => { + newCities.push({ + type: `${item}`, + list: list.filter( + item1 => item1.pinyin.substring(0, 1).toUpperCase() === `${item}` + ) + }); + }); + list.forEach(el => { + if (!letterArray.includes(el.pinyin.substring(0, 1).toUpperCase())) { + ortherArr.push(el); + } + }); + newCities.push({ + type: "#", + list: ortherArr + }); + newCities = newCities.filter(item => item.list.length > 0); + return newCities; +}; + +export const getGender = gender => { + if (gender === 1) { + return "男"; + } else if (gender === 2) { + return "女"; + } else if (gender === 3) { + return "保密"; + } else { + return ""; + } +}; +// 交集 +export const getIntersection = (array1, array2, key) => { + return array1.filter(item1 => + array2.some(item2 => item2[key] !== item1[key]) + ); +}; +// 差集 +export const getDifference = (array1, array2, key) => { + return array1.filter( + item1 => + !array2.some(item2 => (item2[key] ? item2[key] : item2) === item1[key]) + ); +}; + +export const getAvatarColor = name => { + const colorList = [ + "#6898E6", + "#0095F6", + "#4DB9FF", + "#61CDB3", + "#5CC4D0", + "#6898E6", + "#F1A55E" + ]; + + try { + if (name != null && name.length > 0) { + // const pinyin = pinyin.getFullChars(name); + const pinyin = name; + let code; + if (pinyin.length > 1) { + code = + pinyin.charCodeAt(0) + + pinyin.length + + pinyin.charCodeAt(pinyin.length - 1); + } else if (pinyin.length === 1) { + code = pinyin.charCodeAt(0); + } else { + code = 0; + } + const index = code % colorList.length; + return colorList[index]; + } + } catch (e) { + console.log("getAvatarColor", "err:" + e); + } + + return colorList[0]; +}; + +export const hidePhoneNumber = phone => { + const hidden = phone.substring(0, 3) + "****" + phone.substring(7); + return hidden; +}; + +export const isValidEmail = value => { + return /^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$/.test(value); +}; + +export const isValidPassword = value => { + return /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d]{6,15}$/.test(value); +}; + +export const hideEmailNumber = email => { + if (!email || email.length === 0) return ""; + let showEmail = email; + let end = email.indexOf("@"); + let start = 0; + if (end > 3) { + start = 3; + } else if (end > 2) { + start = 2; + } else if (end > 1) { + start = 1; + } + + if (start > 0 && end >= start) { + let r = ""; + for (let i = 0; i < end - start; i++) { + r += "*"; + } + showEmail = showEmail.substring(0, start) + r + showEmail.substring(end); + } + return showEmail; +}; + +export const replaceStringWithValues = (str, ...values) => { + return str.replace(/%s/g, () => values.shift()); +}; + +export const removeAllLocalstorage = () => { + const localList = [ + "user_id", + "token", + "im_token", + "userInfo", + "detailSearch", + "applyUserList", + "contactList", + "chatList", + "isFriendList", + "friendGroupId", + "friendDetailId", + "groupSessionInfo", + "payConfig", + "incomeConfig", + "groupInfo", + "groupChatConfig", + "friendCreateCount", + "myGroupInfo", + "groupId", + "newFriendApplyCount", + "hasNewFriendApply", + "unreadMsgCount", + "toppingList", + "groupMemberInfo", + "privateChatConfig", + "groupChatList", + "friendCreateCount", + "newFriendApplyCount", + "privateChatConfigs", + "groupChatConfigs", + "friendApplication", + "allGroupOwnerOrAdmin", + "visitorId" + ]; + localList.forEach(el => { + sessionStorage.removeItem(el); + }); + localStorage.removeItem('token') + localStorage.removeItem('user_id') + localStorage.removeItem('im_token') +}; + +export const isSelf = userId => localStorage.getItem("user_id") === userId; + +export const getNameAndCheckSelf = (userInfo, arr) => { + let remark = ""; + if (userInfo) { + const arrs = arr.filter(item => item.userID === userInfo.userID); + if (arrs.length > 0) { + remark = arrs[0].remark + ? arrs[0].remark + : arrs[0].showName + ? arrs[0].showName + : arrs[0].nickname; + } else { + remark = transformTexts(userInfo.userID, userInfo.nickname); + } + if (isSelf(userInfo.userID)) { + return t("you"); + } else { + return remark; + } + } else { + return "-"; + } + // return userInfo ? (isSelf(userInfo.userID) ? t('you') : userInfo.nickname) : '-' +}; + +export const getAtNickname = (atUserID, atNickname, hasPromission = true) => atUserID === "atAllTag" ? t("everyone") : transformTexts(atUserID, atNickname, hasPromission); + +const transformTexts = (atUserID, atNickname, hasPromission = true) => { + const groupId = window.sessionStorage.getItem('groupId') || '' + const contactStore = useContactStore(); + const { + groupMemberInfoObj, + } = storeToRefs(contactStore); + // const groupMemberInfo = JSON.parse(window.sessionStorage.getItem('groupMemberInfo')) || {} + const groupMemberInfo = groupMemberInfoObj || {} + const contactList = JSON.parse(window.sessionStorage.getItem('contactList') || '[]') + // senderNickname + const index = contactList.findIndex( + items => items.userID === atUserID + ); + // 在群聊中找到群聊备注 + const arr = groupMemberInfo[groupId] || []; + const groupItem = arr.find(item => item.userID === atUserID) + let groupRemark = groupItem ? (JSON.parse(groupItem.ex || '{}')?.remark?.value || '') : '' + // 如果查看权限,就清掉 + if (!hasPromission) { + groupRemark = '' + } + // 在好友中找到好友备注 + if (index != -1) { + // 群聊中展示权限 + // 好友备注 --> 群聊备注 --> 用户个人昵称 --> 群聊昵称 + const friendItem = contactList[index] + return friendItem.remark || groupRemark || friendItem.nickname + } else { + return groupRemark || atNickname; + } +}; + +export const banTimeMap = { + "600": t("tenMinutes"), + "3600": t("oneHour"), + "43200": t("twelveHours"), + "86400": t("oneDay"), + "2592000": t("smmvMuteTime_30day") +}; + +const mutedTime = mss => { + const days = Math.floor(mss / (60 * 60 * 24)); + const hours = Math.floor((mss % (60 * 60 * 24)) / (60 * 60)); + const minutes = Math.floor((mss % (60 * 60)) / 60); + const seconds = mss % 60; + return `${combTime(days, t("day"))}${combTime(hours, t("hours"))}${combTime( + minutes, + t("minute") + )}${combTime(seconds, t("seconds"))}`; +}; + +const combTime = (value, unit) => { + return value > 0 ? `${value}${unit}` : ""; +}; + +export const parseNtf = (message, isConversation = false, arr) => { + let resText = ""; + if (!message.contentType || message.contentType < 1000) return resText; + const { contentType, notificationElem } = message; + const detail = JSON.parse(notificationElem.detail); + const { + group, + opUser, + quitUser, + newGroupOwner, + entrantUser, + mutedUser, + mutedSeconds, + invitedUserList, + kickedUserList, + isPrivate + } = detail; + const name = getNameAndCheckSelf(opUser, arr); + const quitName = getNameAndCheckSelf(quitUser, arr); + const enterName = getNameAndCheckSelf(entrantUser, arr); + const newOwnerName = getNameAndCheckSelf(newGroupOwner, arr); + const mutedName = getNameAndCheckSelf(mutedUser, arr); + try { + switch (contentType) { + // a 创建了群聊 + case 1501: + resText = replaceStringWithValues(t("createGroupNtf"), name); + break; + // a 修改了群资料 + case 1502: + const noti = group.notification; + resText = + noti && isConversation + ? noti + : replaceStringWithValues(t("editGroupInfoNtf"), name); + break; + // a 退出了群聊 + case 1504: + resText = replaceStringWithValues(t("quitGroupNtf"), quitName); + break; + // a 将群转让给了 b + case 1507: + resText = replaceStringWithValues( + t("transferredGroupNtf"), + name, + newOwnerName + ); + break; + // b 被 a 踢出群聊 + case 1508: + const kickedNames = kickedUserList + .map(item => getNameAndCheckSelf(item, arr)) + .join("、"); + resText = replaceStringWithValues( + t("kickedGroupNtf"), + kickedNames, + name + ); + break; + // a 邀请 b 加入群聊 + case 1509: + const invitNames = invitedUserList + .map(item => getNameAndCheckSelf(item, arr)) + .join("、"); + resText = replaceStringWithValues( + t("invitedJoinGroupNtf"), + name, + invitNames + ); + break; + // a 加入了群聊 + case 1510: + resText = replaceStringWithValues(t("joinGroupNtf"), enterName); + break; + // a 解散了群聊 + case 1511: + resText = replaceStringWithValues(t("dismissGroupNtf"), name); + break; + // b 被 a 禁言 + case 1512: + resText = replaceStringWithValues( + t("muteMemberNtf"), + mutedName, + name, + // banTimeMap[mutedSeconds] + mutedTime(mutedSeconds) + ); + break; + // b 被 a 取消了禁言 + case 1513: + resText = replaceStringWithValues( + t("muteCancelMemberNtf"), + mutedName, + name + ); + break; + // a 开起了群禁言 + case 1514: + resText = replaceStringWithValues(t("muteGroupNtf"), name); + break; + // a 关闭了群禁言 + case 1515: + resText = replaceStringWithValues(t("muteCancelGroupNtf"), name); + break; + // 群成员信息改变 + case 1516: + resText = replaceStringWithValues(t("memberInfoChangedNtf"), name); + break; + // 群公告修改 + case 1519: + const notification = group.notification; + if (isConversation && notification) { + // 群公告 xxx + resText = `[${t("groupAc")}]${group?.notification ?? ""}`; + } else { + // xxx清除了 群公告 + resText = `${name}${t("clearGroupNotification")}`; + } + break; + // 群名字修改 + case 1520: + resText = + replaceStringWithValues(t("whoModifyGroupName"), name) + + (group.groupName || ""); + resText = name + resText + break; + // 你们已成为好友 + case 1201: + resText = t("friendAddedNtf"); + break; + // 阅后即焚 + case 1701: + resText = isPrivate + ? t("openPrivateChatNtf") + : t("closePrivateChatNtf"); + break; + default: + break; + } + } catch (e) { + console.log("err", e); + } + + return resText; +}; + +/* 内容转换 */ +export const parseMsg = ( + message, + isConversation = false, + replaceIdToNickname = false, + showSendName = false, + arr +) => { + let sendName = ""; + let content = ""; + const arrs = arr.filter(item => item.userID === message.sendID); + if (arrs.length > 0) { + sendName = message.senderNickname || (arrs[0].remark + ? arrs[0].remark + : arrs[0].showName + ? arrs[0].showName + : arrs[0].nickname); + } else { + sendName = + showSendName && message.senderNickname ? message.senderNickname : ""; + } + // sendName = showSendName && message.senderNickname ? message.senderNickname : '' + //判断是否是群聊进入 + if(showSendName){ + //判断是否设置了好友备注 + const contactList = JSON.parse(sessionStorage.getItem('contactList')||'[]') + const fileterItem = contactList.filter(item=>item.userID==message.sendID) + if(fileterItem&&fileterItem[0]&&fileterItem[0].remark){ + sendName = fileterItem[0].remark + }else{ + const contactStore = useContactStore(); + const { + groupMemberInfoObj, + } = storeToRefs(contactStore); + //如果没有好友备注,判断是否设置了群聊备注 + // const groupMemberInfo = JSON.parse(sessionStorage.getItem('groupMemberInfo')||'{}') + const groupMemberInfo = groupMemberInfoObj||{} + const groupMemberList = groupMemberInfo&&groupMemberInfo[message.groupID]?groupMemberInfo[message.groupID]:[] + if(groupMemberList.length>0){ + const groupItem = groupMemberList.filter(item=>item.userID==message.sendID) + if(groupItem&&groupItem[0]&&groupItem[0].ex){ + const ex = JSON.parse(groupItem[0].ex) || {} + if(ex&&ex.remark&& ex.remark.value)sendName = ex.remark.value + }else{ + const allGroupOwnerOrAdmin = JSON.parse(sessionStorage.getItem('allGroupOwnerOrAdmin')||'{}') + if(allGroupOwnerOrAdmin&&allGroupOwnerOrAdmin[message.sendID]&&allGroupOwnerOrAdmin[message.sendID].ex){ + const ex = JSON.parse(allGroupOwnerOrAdmin[message.sendID].ex) || {} + if(ex&&ex.remark&& ex.remark.value)sendName = ex.remark.value + } + } + + } + } + } + + try { + const { + contentType, + textElem, + atTextElem, + quoteElem, + sendID, + notificationElem, + customElem, + ex, + isRead, + status + } = message; + + switch (contentType) { + // 普通文本 + case 101: + content = textElem?.content?.trim(); + // if (ex && ex.includes('burn_msg')) { + // // content = `[${t('burnAfterReading')}${t('message')}]` + // content = '...' + // } + break; + // 图片 + case 102: + content = `[${t("picture")}]`; + break; + // 语音 + case 103: + content = `[${t("voice")}]`; + break; + // 视频 + case 104: + content = `[${t("video")}]`; + break; + // 文件 + case 105: + content = `[${t("file")}]`; + break; + // @消息 + case 106: + content = atTextElem?.text?.trim() ?? ""; + if (replaceIdToNickname && content) { + let list = atTextElem?.atUsersInfo; + list.forEach(item => { + content = content?.replaceAll( + `@${item.atUserID}`, + `@${getAtNickname(item.atUserID, item.groupNickname)}` + ); + }); + } + break; + // 合并 + case 107: + content = `[${t("chatRecord")}]`; + break; + // 名片 + case 108: + content = `[${t("carte")}]`; + break; + // 位置 + case 109: + content = `[${t("location")}]`; + break; + // 自定义 + case 110: + sendName = ""; + const cusData = JSON.parse(customElem?.data); + if (cusData) { + const { customType, data: customData } = cusData; + // 自定义消息类型判断 + switch (customType) { + // 通話 + case 901: + content = `[${ + customData.type === "video" ? t("callVideo") : t("callVoice") + }]`; + break; + // emoji + case 902: + content = `[${t("emoji")}]`; + break; + // 标签 + case 903: + const textCon = customData?.textElem.content; + const soundCon = customData?.soundElem && `[${t("voice")}]`; + content = textCon || soundCon || `[${t("unsupportedMessage")}]`; + break; + // 会议 + case 905: + content = `[${t("meetingMessage")}]`; + break; + // blockedByFriend 被拉黑 + case 910: + content = t("blockedByFriendHint"); + break; + // deletedByFriend 被删除 + case 911: + content = t("deletedByFriendHint"); + break; + // removedFromGroup 被移除群聊 + case 912: + content = t("removedFromGroupHint"); + break; + // groupDisbanded 群聊解散 + case 913: + content = t("groupDisbanded"); + break; + // 清除会话信息 + case 1001: + content = `[${t("clearMsg")}]`; + break; + // 红包消息 + case 1003: + const remark = message.redPackageInfo?.remark || ""; + content = remark + ? `[${t("envelope")}${remark}]` + : `[${t("envelope")}]${t("dajidali")}`; + break; + // 接收红包的消息 + case 1004: + const info = JSON.parse(message.customElem.data).data; + if (info) { + const receiverName = isSelf(info.receiverUserId) + ? t("me") + : info.receiverNickName; + const sendName = isSelf(info.userId) + ? t("me") + : info.nickName ?? "-"; + // xxx 領取了xxx的紅包 + content = `${receiverName}${t("envelope_get")}${sendName}${t( + "de" + )}${t("envelope")}`; + + if (info.lastAmountOver) { + content += `,${sendName}${t("envelope_get_over")}`; + } + } else { + content = t("envelope_get_message"); + } + break; + // 群公告 + case 1005: + content = `[${t("groupAc")}]${customData.text}`; + break; + // 群聊面具 + case 1006: + // content = `${isSelf(customData.opUser.userID) ? t('you') : customData.opUser.nickname} ${t('revokeMsg')}` + content = `${ + isSelf(customData.opUser.userID) + ? t("you") + : customData.opUser.nickname + } ${t('open')}${t('groupMaskOpenTipNew')}`; + // + break; + // 转发消息 + case 1100: + content = `[${t("chatRecord")}]`; + break; + case 1010: + content = "..."; + break; + case 1011: + content = `[${t("burnMsgNotify")}]`; + break; + // 语音通话 + case 1013: + content = `[${t("callVoice")}]`; + break; + default: + content = `[${t("unsupportedMessage")}]`; + break; + } + } else { + content = `[${t("unsupportedMessage")}]`; + } + break; + // 引用回复 + case 114: + content = quoteElem?.text?.trim() ?? ""; + break; + // 自定义表情 + case 115: + content = `[${t("emoji")}]`; + break; + // OA通知 + case 1400: + sendName = ""; + const ndt = JSON.parse(notificationElem.detail); + content = ndt?.oa?.text ?? ""; + break; + // 撤回消息 + case 2101: + sendName = ""; + // 单聊 + if (message.sessionType === 1) { + content = `${isSelf(sendID) ? t("you") : message.senderNickname} ${t( + "revokeMsg" + )}`; + } else { + const detail = JSON.parse(notificationElem.detail); + if (detail) { + // 群聊撤回包含:撤回自己消息,群组或管理员撤回其他人消息 + if (detail?.revokerID === detail?.sourceMessageSendID) { + content = `${ + isSelf(sendID) ? t("you") : message.senderNickname + } ${t("revokeMsg")}`; + } else { + const revoker = isSelf(detail.revokerID) + ? t("you") + : detail.revokerNickname; + const sender = isSelf(detail.sourceMessageSendID) + ? t("you") + : detail.sourceMessageSenderNickname; + content = replaceStringWithValues( + t("aRevokeBMsg"), + revoker, + sender + ); + } + } else { + content = t("revokeMsgTip"); + } + } + break; + default: + content = `[${t("unsupportedMessage")}]`; + break; + } + } catch (e) { + console.log("err", e); + } + + if (sendName && content) { + if (message.sessionType === 3) { + content = `${sendName}:${content}`; + } else if (message.sessionType === 1) { + content = `${content}`; + } + } + + // 如果是阅后即焚消息全部显示阅后即焚消息 + const isNeedBurnMsg = message.ex && message.ex.includes('burn_msg') + if (isNeedBurnMsg) { + content = `[${t('burnAfterReading')}${t('message')}]` + } + + // 目前只有阅后即焚的消息有4的状态,4表示已焚毁消息 + if (message.status === 4 || message.contentType === 2300) { + content = '...' + } + + return content ?? `[${t("unsupportedMessage")}]`; +}; + +export const isCurrentWeek = time => { + const pastTime = new Date(time).getTime(); + const today = new Date(new Date().toLocaleDateString()); + let day = today.getDay(); + day = day == 0 ? 7 : day; + const oneDayTime = 60 * 60 * 24 * 1000; + const monday = new Date(today.getTime() - oneDayTime * (day - 1)); + const nextMonday = new Date(today.getTime() + oneDayTime * (8 - day)); + if (monday.getTime() <= pastTime && nextMonday.getTime() > pastTime) { + return true; + } else { + return false; + } +}; + +export const isZhFn = () => { + const lang = localStorage.getItem("language"); + return lang === "zh-cn"; +}; + +export const getLatestInfo = (info, list) => { + // console.log(info,list,'kds') + // 有草稿就用草稿 + if (info.draftText) { + return `[${t("draftText")}]${ + info.draftText + }`; + } + + let prefix = ""; + + // 0 正常 2勿扰 + const { latestMsg, conversationType, recvMsgOpt, unreadCount } = info; + if (!latestMsg) return ""; + const lastMsgParse = JSON.parse(latestMsg); + if (!lastMsgParse.clientMsgID) { + return ""; + } + let text = ""; + text = parseNtf(lastMsgParse, true, list); + // console.log(lastMsgParse,'latestMsg0000000000000000000000') + // console.log(text,'0000000000000000000000000000000000000') + if (!text) { + text = parseMsg(lastMsgParse, true, true, conversationType === 3, list); + // console.log(lastMsgParse,'latestMsg11111111111111111111111111',text) + // console.log(text,'111111111111111111111111111111111111') + } + // 勿扰模式 & 有未读的情况下要把未读数量写上 + if (recvMsgOpt !== 0 && unreadCount > 0) { + text = `[${replaceStringWithValues( + t("nPieces"), + unreadCount > 99 ? "99+" : unreadCount + )}] ${text}`; + } + // 会话强提示内容 + // 目前app h5都只有1的场景,现在不翻译2,3 + // console.log('info-----',info); + switch (info.groupAtType) { + // @了我提示 + case 1: + // prefix = `${t("Someone")}`; + prefix = `[${t("atTip")}]`; + + break; + // @了所有人提示 + case 2: + prefix = `[@所有人]`; + break; + // @了所有人@了我 + case 3: + prefix = `[@所有且@你]`; + break; + // 群公告提示 + case 4: + prefix = `[${t('groupAc')}]`; + break; + } + // console.log(text,'999999999999999999999999999') + return prefix + text ?? `[${t("unsupportedMessage")}]`; +}; + +export const getChatTimeline = (ms, formatter = "HH:mm") => { + const lang = localStorage.getItem("language"); + const isZh = lang || "us"; + + if (dayjs(ms).isToday()) { + return dayjs(ms).format(formatter); + } + + if (dayjs(ms).isYesterday()) { + let pre = ""; + switch (isZh) { + case "zh-cn": + pre = "昨天"; + break; + case "us": + pre = "Yesterday"; + break; + case "zh-hant-cn": + pre = "昨天"; + break; + case "vi-VN": + pre = "Hôm qua"; + break; + } + return `${pre} ${dayjs(ms).format(formatter)}`; + } + + if (isCurrentWeek(ms)) { + const weekDayzh = [ + "星期天", + "星期一", + "星期二", + "星期三", + "星期四", + "星期五", + "星期六" + ]; + const weekDayen = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ]; + const weekDayVi = [ + "Chủ nhật", + "Thứ hai", + "Thứ ba", + "Thứ tư", + "Thứ năm", + "Thứ sáu", + "Thứ bảy" + ]; + let weekDayArr = []; + switch (isZh) { + case "zh-cn": + weekDayArr = weekDayzh; + break; + case "us": + weekDayArr = weekDayen; + break; + case "zh-hant-cn": + weekDayArr = weekDayzh; + break; + case "vi-VN": + weekDayArr = weekDayVi; + break; + } + return `${weekDayArr[new Date(ms).getDay()]} ${dayjs(ms).format( + formatter + )}`; + } + + const msYear = dayjs(ms).format("YYYY"); + const activeYear = dayjs().format("YYYY"); + if (msYear === activeYear) { + return dayjs(ms).format( + isZh === "zh-cn" ? "MM月DD日 HH:mm" : "MM/DD HH:mm" + ); + } + + return dayjs(ms).format( + isZh === "zh-cn" ? "YYYY年MM月DD日 HH:mm" : "YYYY/MM/DD HH:mm" + ); +}; + +export const conversationSort = list => + list.sort((a, b) => { + // 都置顶或都不置顶的时候,按照时间排序 + if ( + (a.isPinned == true && b.isPinned == true) || + (a.isPinned != true && b.isPinned != true) + ) { + // 草稿时间优先 + const aCompare = + a.draftTextTime > a.latestMsgSendTime + ? a.draftTextTime + : a.latestMsgSendTime; + const bCompare = + b.draftTextTime > b.latestMsgSendTime + ? b.draftTextTime + : b.latestMsgSendTime; + + if (aCompare > bCompare) { + return -1; + } else if (aCompare < bCompare) { + return 1; + } else { + // return (a.showName ?? "").compareTo((b.showName ?? "")); + return 0; + } + } else if (a.isPinned == true && b.isPinned != true) { + return -1; + } else { + return 1; + } + }); + +export const replaceSearchText = (name, matchText) => { + if (!name) return ""; + const regex = new RegExp(matchText, "g"); // 创建正则表达式对象,设置全局标志'g' + return name.replace(regex, `${matchText}`); +}; + +export const calChatTimeInterval = (list, calculate = true) => { + if (!calculate) return list; + let milliseconds = list[0]?.sendTime; + if (milliseconds == null) return list; + // list[0].exMap['showTime'] = true; + let lastShowTimeStamp = milliseconds; + for (let i = 0; i < list.length; i++) { + const index = i + 1; + if (index <= list.length - 1) { + const cur = getDateTimeByMs(lastShowTimeStamp); + const milliseconds = list[index].sendTime; + const next = getDateTimeByMs(milliseconds); + if (next.getTime() - cur.getTime() > 5 * 60 * 1000) { + lastShowTimeStamp = milliseconds; + // list[index].exMap['showTime'] = true; + } + } + } + return list; +}; + +export const getDateTimeByMs = (ms, isUtc = false) => { + return new Date(ms); +}; + +export const toJsonNoOrigin = val => { + const data = {}; + data["clientMsgID"] = val.clientMsgID; + data["serverMsgID"] = val.serverMsgID; + data["createTime"] = val.createTime; + data["sendTime"] = val.sendTime; + + //不显示发送id + //data['sendID'] = val.sendID; + //不显示接收者id + // data['recvID'] = val.recvID; + + data["msgFrom"] = val.msgFrom; + data["contentType"] = val.contentType; + data["senderPlatformID"] = val.senderPlatformID; + + //不显示昵称 + //data['senderNickname'] = val.senderNickname; + //data['senderFaceUrl'] = val.senderFaceUrl; + //data['groupID'] = val.groupID; + + // data['content'] = val.content; + data["seq"] = val.seq; + //data['isRead'] = val.isRead; + // data['hasReadTime'] = val.hasReadTime; + data["status"] = val.status; + // data['offlinePush'] = val.offlinePush?.toJson(); + data["attachedInfo"] = val.attachedInfo; + data["ex"] = val.ex; + data["exMap"] = val.exMap; + data["sessionType"] = val.sessionType; + data["pictureElem"] = val.pictureElem; + data["soundElem"] = val.soundElem; + data["videoElem"] = val.videoElem; + data["fileElem"] = val.fileElem; + data["atTextElem"] = val.atTextElem; + data["locationElem"] = val.locationElem; + data["customElem"] = val.customElem; + //去除引用消息的消息 + if (val.quoteElem) { + const qe = { + text: "", + quoteMessage: null + }; + qe.text = val.quoteElem?.text; + data["quoteElem"] = qe; //val.quoteElem?.toJson(); + } + + data["mergeElem"] = val.mergeElem; + data["notificationElem"] = val.notificationElem; + data["faceElem"] = val.faceElem; + data["attachedInfoElem"] = val.attachedInfoElem; + data["isExternalExtensions"] = val.isExternalExtensions; + data["isReact"] = val.isReact; + data["textElem"] = val.textElem; + data["cardElem"] = val.cardElem; + data["advancedTextElem"] = val.advancedTextElem; + //data['typingElem'] = (val.typingElem);; + data["localEx"] = val.localEx; + return data; +}; + +export const toJsonNoQuoteMsg = val => { + const data = {}; + data["clientMsgID"] = val.clientMsgID; + data["serverMsgID"] = val.serverMsgID; + data["createTime"] = val.createTime; + data["sendTime"] = val.sendTime; + data["sendID"] = val.sendID; + data["recvID"] = val.recvID; + data["msgFrom"] = val.msgFrom; + data["contentType"] = val.contentType; + data["senderPlatformID"] = val.senderPlatformID; + data["senderNickname"] = val.senderNickname; + data["senderFaceUrl"] = val.senderFaceUrl; + data["groupID"] = val.groupID; + // data['content'] = val.content; + data["seq"] = val.seq; + data["isRead"] = val.isRead; + data["hasReadTime"] = val.hasReadTime; + data["status"] = val.status; + data["offlinePush"] = val.offlinePush; + data["attachedInfo"] = val.attachedInfo; + data["ex"] = val.ex; + data["exMap"] = val.exMap; + data["sessionType"] = val.sessionType; + data["pictureElem"] = val.pictureElem; + data["soundElem"] = val.soundElem; + data["videoElem"] = val.videoElem; + data["fileElem"] = val.fileElem; + data["atTextElem"] = val.atTextElem; + data["locationElem"] = val.locationElem; + data["customElem"] = val.customElem; + + //去除引用消息的消息 + if (val.quoteElem) { + const qe = { + text: "", + quoteMessage: null + }; + qe.text = val.quoteElem?.text; + data["quoteElem"] = qe; //val.quoteElem?.toJson(); + } + + data["mergeElem"] = val.mergeElem; + data["notificationElem"] = val.notificationElem; + data["faceElem"] = val.faceElem; + data["attachedInfoElem"] = val.attachedInfoElem; + data["isExternalExtensions"] = val.isExternalExtensions; + data["isReact"] = val.isReact; + data["textElem"] = val.textElem; + data["cardElem"] = val.cardElem; + data["advancedTextElem"] = val.advancedTextElem; + data["typingElem"] = val.typingElem; + data["localEx"] = val.localEx; + return data; +}; + +// type txt为文本,不传则为写死的 dhms +export const formatTime = (mss, type) => { + let result = ''; + const days = Math.floor(mss / (60 * 60 * 24)); + const hours = Math.floor((mss % (60 * 60 * 24)) / (60 * 60)); + const minutes = Math.floor((mss % (60 * 60)) / 60); + const seconds = mss % 60; + if (days > 0) { + result += combTime(days, type === 'txt' ? t("day") : 'd'); + } + if (hours > 0) { + result += combTime(hours, type === 'txt' ? t("hours") : 'h'); + } + if (minutes > 0) { + result += combTime(minutes, type === 'txt' ? t("minute") : 'm'); + } + if (seconds > 0) { + result += combTime(seconds, type === 'txt' ? t("seconds") : 's'); + } + return result; +} + +// 把被转义过的字符串转回原本的字符串 +export const escape2Html = (str) => { + const arrEntities = {'lt':'<','gt':'>','nbsp':' ','amp':'&','quot':'"'}; + return str.replace(/&(lt|gt|nbsp|amp|quot);/ig,function(all,t){ + return arrEntities[t]; + }) +} + +export const fileTypeMap = { + 'image': ['jpeg', 'jpg', 'png', 'gif', 'webp', 'bmp', 'tiff', 'svg', 'psd', 'sketch'], + 'video': ['mp4', 'api', 'mov', 'mkv', 'wmv', 'flv', 'mpeg', 'webm'], + 'audio': ['mp3', 'wav', 'flac', 'aac', 'wma', 'm4a', 'ogg', 'aiff', 'alac', 'midi'], + 'doc': ['doc', 'docx'], + 'xls': ['xls', 'xlsx'], + 'ppt': ['ppt', 'pptx'], + 'numbers': ['numbers'], + 'pages': ['pages'], + 'key': ['key'], + 'txt': ['txt'], + 'pdf': ['pdf'], + 'epub': ['epub'], + 'exe': ['exe', 'msi'], + 'apk': ['apk'], + 'ipa': ['app', 'ipa'], + 'rar': ['zip', 'rar', '7z'], + 'dmg': ['dmg', 'iso'], + 'code': ['py', 'java', 'cpp', 'html', 'css', 'js', 'php', 'pyc', 'json', 'xml', 'sql', 'sqlite', 'csv'] +} + +// type 真实文件类型 获取对应的icon +export const getFileTypeIconName = (type) => { + let resIcon = 'unknown' + if (!type) return resIcon + for (const [key, list] of Object.entries(fileTypeMap)) { + if (list.includes(type.toLowerCase())) { + resIcon = key + } + } + + return resIcon +} + +export const formatFileSize = sizeInBytes => { + if (!sizeInBytes) return '' + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let i = 0; + while (sizeInBytes >= 1024) { + sizeInBytes /= 1024; + i++; + } + return sizeInBytes.toFixed(2) + units[i]; +} + +export const isMobileBrowser = () => { + const isMobileNavigator = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i); + // 是手机 或者屏幕小于750 + return isMobileNavigator || window.innerWidth <= 750 +} + +export const splitArray = (arr, size) => { + return arr.reduce((acc, cur, index) => { + const chunkIndex = Math.floor(index / size); + if (!acc[chunkIndex]) { + acc[chunkIndex] = []; // 初始化子数组 + } + acc[chunkIndex].push(cur); + return acc; + }, []); +} + +export const setImgStyle = (width, height) => { + let obj = { + width: width, + height: width, + }; + // 宽度大于180的图片 + if (width > 180) { + obj.width = 180; + obj.height = height / (width / 180); + } + // 横向图片 + if (width > height) { + obj.width = 220; + obj.height = height / (width / 220); + } + // 纵向小图 + if (width <= 180 && width < height) { + obj.width = width + obj.height = height + } + return obj; +}; + +export const getUrlPrefix = (url) => { + // 使用URL对象来解析URL + const parsedUrl = new URL(url); + // 获取协议和域名部分 + const prefixDomain = `${parsedUrl.protocol}//${parsedUrl.host}`; + return prefixDomain || parsedUrl.origin +} + +export const checkImgUrlLoadSuccess = (url) => new Promise((res) => { + const image = new Image() + image.onload = () => { + res(image) + } + image.onerror = () => { + res(null) + } + image.src = url +}) + +export const checkVideoUrlLoadSuccess = (url) => new Promise((res) => { + const video = document.createElement('video') + video.src = url + video.addEventListener('canplaythrough', () => { + res(true) + video.remove() + }); + video.addEventListener('error', () => { + res(false) + video.remove() + }) + document.body.appendChild(video) +}) + +export const switchCdnLoadSource = async (defaultPath, type = 'image', defaultImg = defaultAvatar) => { + const appConfig = sessionStorage.getItem("appConfig") ? JSON.parse(sessionStorage.getItem("appConfig")) : {} + // 找出当前oss地址对应的cdn地址列表 + const cdnList = appConfig.oss_cdn_list.filter(item => defaultPath.startsWith(item.oss_prefix)) + if (!cdnList.length) return + const preUrlPrefix = getUrlPrefix(defaultPath) + async function tryLoadImg(list, path) { + if (!list.length) { + return defaultImg + } + // 第一个开始取 + const cdnPrefix = list.shift() + const realPath = path.replace(preUrlPrefix, cdnPrefix.cdn_prefix) + let res = null + if (type === 'image') { + res = await checkImgUrlLoadSuccess(realPath) + } + if (type === 'video') { + res = await checkVideoUrlLoadSuccess(realPath) + } + if (res) { + return realPath + } else { + return tryLoadImg(list, path) + } + } + + const resUrl = await tryLoadImg(cdnList, defaultPath) + return resUrl +} + +// 这里只有直接使用van-image的图片并传入的url是对象中取的时候才可以这么使用 +/** + * + * @param {Event} e 浏览器事件对象 + * @param {Object} source 需要更新的对象 + * @param {String} updateKey 需要更新对象的key值 + */ +export const handleImgCdnError = async (e, source, updateKey, defaultImg = defaultAvatar) => { + const errorUrl = e.target.src + console.log('errorUrlerrorUrlerrorUrlerrorUrl', errorUrl) + const validUrl = await switchCdnLoadSource(errorUrl, 'image', defaultImg) + if (source) { + source[updateKey] = validUrl || defaultAvatar + } +} + +export const uploadFileByOss = async (file, isNext = false) => { + const ossData = { + file_name: file.name, + content_type: file.type, + uuid: file.uid, + file_from: 1 + } + const upLoadingFn = isNext ? contactApi.awsPutUrls : contactApi.ossPutUrls + + try { + const res = await upLoadingFn(ossData) + if (res.code != 0) { + // 如果上传失败,尝试使用另一个api上传 + if (!isNext) { + uploadFileByOss(file, true) + } else { + // 如果是aws上传仍然失败,就直接失败了 + console.log('上传失败~') + resolve(false) + } + return + } + const { access_url, put_url } = res.data; + const fileRes = await axios({ + method: 'put', + url: put_url, + data: file, + headers: { + 'Content-Type': file.type + } + }); + return { access_url, fileRes } + } catch(e) { + console.log('eeeee', e) + if (!isNext) { + return await uploadFileByOss(file, true) + } + } +} + +export const getFingerIdAndSave = async () => { + // sdk登录前获取设备信息并填入 + const fpPromise = await FingerprintJS.load() + const res = await fpPromise.get() + console.log('res.visitorId:::', res.visitorId) + localStorage.setItem('visitorId', res.visitorId) +} + +export const im_config = () => { + const im_token = window.localStorage.getItem('im_token') + const token = window.localStorage.getItem('token') + const user_id = window.localStorage.getItem('user_id') + const visitorId = window.localStorage.getItem('visitorId') + const usePlatformID = JSON.parse(sessionStorage.getItem('usePlatformID')); + const { ws_url, api_3, backup_http_addrs } = window.WEB_CONFIG + var config = { + userID: user_id, // IM 用户 userID + token: im_token, // IM 用户令牌 + // platformID: 3, // 当前登录平台号0 + // platformID: useSystem(), //随便填的 + platformID: usePlatformID, //随便填的 + apiAddr: api_3, + wsAddr: ws_url, + backupHttpAddrs: backup_http_addrs, + deviceID: visitorId, + } + return config +} + +/** + * 获取通话显示的文本 + * + * @param callMsg 通话消息对象 +// 1 发起通话超时--由于发送通知给对方时,请求失败导致的超时 +static const END_SEND_TIMEOUT = 1; +// 2 等待对方接通超时 +static const END_WAIT_RECEIVER_TIMEOUT = 2; +// 3 主动点击了挂断 +static const END_USER_CLICK = 3; +// 4 TXSDK发生错误 +static const END_SDK_ERROR = 4; +// 5 进入房间失败了 +static const END_SDK_ENTER_ROOM_ERROR = 5; +// 6 我退出房间 +static const END_SDK_LEAVE_ROOM = 6; +// 7 对方退出房间 +static const END_SDK_OTHER_LEAVE_ROOM = 7; +// 8 发起通话错误--由于发送通知给对方时,请求失败导致的错误 +static const END_SEND_ERROR = 8; +// 9 当前正在通话中,不能接听其他通话 +static const END_CURRENT_CALLING = 9; + * @returns 通话显示的文本 + */ +export const getCallShowText = (callMsg) => { + if (!callMsg) { + return '[通话]' + } + const duration = callMsg.call_duration || 0 + // 成功通话的 + if (duration > 0) { + return replaceStringWithValues(t('callDuration'), formatTimeForCallTime(duration)) + } + + // 没有结束人id + if(!callMsg.end_from_uid) { + return `已结束通话` + } + const myUserId = localStorage.getItem("user_id") + // 当前角色发起 + const isSelfCall = callMsg.senderId === myUserId + // 自己点结束 + const isSelfEnd = callMsg.end_from_uid === myUserId + // 对方点结束 + const isOtherEnd = !isSelfCall && callMsg.sender_id === callMsg.end_from_uid + let prefix = '' + prefix = isSelfEnd ? '' : t('target') + // 是本人结束的 或者 是对方发起对方就取消的 + if (isSelfEnd || isOtherEnd) { + return `${prefix}${t('cancelled')}` + } + + let content = '' + switch (callMsg.end_code) { + // 当前正在通话中,不能接听其他通话 + case 9: + content = isSelfCall ? t('callDoing') : t('callRemoteDoing') + break; + // 主动点击了挂断 + case 3: + content = isSelfCall ? t('cancelledByCaller') : t('cancelled') + break; + default: + content = isSelfCall ? t('cancelled') : t('notAccept') + break; + } + + return `${prefix}${content}` +} + +const formatTimeForCallTime = mss => { + const days = Math.floor(mss / (60 * 60 * 24)); + const hours = Math.floor((mss % (60 * 60 * 24)) / (60 * 60)); + const minutes = Math.floor((mss % (60 * 60)) / 60); + const seconds = mss % 60; + return `${hours > 0 ? `${`0${minutes}`.slice(-2)}:` : '' }${`0${minutes}`.slice(-2)}:${`0${seconds}`.slice(-2)}`; +}; +///多少分钟前在线--单位是:秒 +///一小时以内,显示多少分钟前在线 +///小于24小时,显示xx小时前在线 +///离线大于15天,显示很久没上线 +///检测是否是昨天在线-显示昨天在线 +///其他,显示近期在线 +export const calculateOnlineStatus = (dTime, lastLogoutTime, serverTime, row) => { + var minute = Math.floor(dTime / 60) + if (row.userStatus.status === 1) { + return '在线' + } + if (row.userStatus.lastLogoutTime == 0 && row.userStatus.lastLoginTime == 0) { + return '从未登录' + } + if (minute === 0) { + minute = 1 + } + if (minute < 60) { + return minute + '分钟前在线' + } + + var hour = Math.floor(minute / 60) + if (hour < 24) { + return hour + '小时前在线' + } + + if (hour > 24 * 15) { + return '很久没上线' + } + + // 超过24小时,判断是否是昨天 + var currentTime = new Date(serverTime * 1000) + var todayStart = new Date( + currentTime.getFullYear(), + currentTime.getMonth(), + currentTime.getDate() + ) + // 昨天开始的时间 + var ms = todayStart.getTime() - 1000 * 60 * 60 * 24 + if (lastLogoutTime * 1000 > ms) { + return '昨天在线' + } else { + return '近期在线' + } +} \ No newline at end of file diff --git a/src/utils/useConversationState.js b/src/utils/useConversationState.js new file mode 100644 index 0000000..0193b26 --- /dev/null +++ b/src/utils/useConversationState.js @@ -0,0 +1,60 @@ +import useConversationStore from "@/store/modules/conversation"; +import { getSDK } from '@/utils/openIM' +import { GroupAtType, GroupSessionTypes } from '@/utils/constant' +import { onBeforeRouteLeave } from "vue-router"; + +export default function useConversationState() { + const conversationStore = useConversationStore(); + + // group info + const getCurrentGroupInfo = () => { + if ( + GroupSessionTypes.includes( + conversationStore.storeCurrentConversation.conversationType + ) + ) { + conversationStore.getCurrentGroupInfoFromReq(); + conversationStore.getCurrentMemberInGroupFromReq(); + } + }; + + // conversation state + const checkConversationState = () => { + if (conversationStore.storeCurrentConversation.unreadCount > 0) { + getSDK.markConversationMessageAsRead( + conversationStore.storeCurrentConversation.conversationID + ); + } + if ( + conversationStore.storeCurrentConversation.groupAtType !== + GroupAtType.AtNormal + ) { + getSDK.resetConversationGroupAtType( + conversationStore.storeCurrentConversation.conversationID + ); + } + }; + + watch( + () => conversationStore.storeCurrentConversation.conversationID, + async (newVal) => { + if (newVal) { + getCurrentGroupInfo(); + checkConversationState(); + } + }, + { + immediate: true, + } + ); + + onBeforeRouteLeave((to, from, next) => { + if (to.name === "Conversation") { + checkConversationState(); + conversationStore.updateCurrentConversation({}); + conversationStore.updateQuoteMessage(); + } + next(); + }); + +} diff --git a/src/utils/useGlobalEvent.js b/src/utils/useGlobalEvent.js new file mode 100644 index 0000000..7537882 --- /dev/null +++ b/src/utils/useGlobalEvent.js @@ -0,0 +1,959 @@ +import { onMounted, onUnmounted } from 'vue' +import { useContactStore } from "@/stores/contact"; +import { useUserStore } from "@/stores/user"; +import { conversationSort, IMSDK } from "@/utils/imCommon"; +import { CbEvents } from '@/utils/openIM/constant' +import { useI18n } from "vue-i18n" +import { + MessageType, + MessageReceiveOptType, + SessionType, +} from "@/utils/constant"; +import { useMessageStore } from "@/stores/modules/message"; +import useConversationStore from "@/stores/modules/conversation"; +import emitter from "@/utils/events"; +import { useThrottleFn } from "@vueuse/core"; +import { storeToRefs } from 'pinia' +// import { +// getAccessedFriendApplication, +// getAccessedGroupApplication, +// } from "@/utils/storage"; +import { showLoadingToast } from "vant"; +import { feedbackToast } from "@/utils/common"; +import { reset_sdk, im_config } from "@/utils/sdkInit.js"; +import { showImagePreview, showToast } from "vant"; +import { removeAllLocalstorage } from '@/utils/tools' +// import messageRing from "@assets/audio/newMsg.mp3"; + +const BusinessAllowType = { + Allow: 1, + NotAllow: 2, +} +const GroupSessionTypes = [SessionType.Group, SessionType.WorkingGroup]; +export function useGlobalEvent({ + getNewfriendApplyCount +}) { + const userStore = useUserStore(); + const { setToken, setUserInfo, setGroupChatConfigs, setPrivateChatConfigs, setFriendApplication, setPrivateChatConfig, setAppConfig,setGroupChatConfig } = userStore; + const { userInfo, privateChatConfig, appConfig,groupChatConfig } = storeToRefs(userStore); + const contactStore = useContactStore(); + const { clearStore } = contactStore; + const { groupMemberInfo } = storeToRefs(contactStore) + const { + updateContact, + pushNewContact, + pushNewBlack, + updateBlackList, + pushNewRecvFriendApplication, + pushNewSendFriendApplication, + updateRecvFriendApplicationList, + updateSendFriendApplicationList, + setGroupMemberInfo, + updateGroupMemberInfo, + updateGroupMemberInfos, + updateGroupMemberInfos1, + updateNewFriendApply, + setNewFriendApplyCount, + updateGroupList, + pushNewGroup, + setmyGroupInfo, + setGroupInfo, + getGroupMemberInfo, + setIsGroupSend + } = contactStore + const { groupInfo, chatList, contactList } = storeToRefs(contactStore); + + // conversation + const conversationStore = useConversationStore() + const { updateConversationOffset } = conversationStore + const { conversationOffset } = storeToRefs(conversationStore); + const messageStore = useMessageStore(); + const { pushNewMessage, updateOneMessage, updateMessageNicknameAndFaceUrl, storeHistoryMessageList } = messageStore + const { t } = useI18n(); + const router = useRouter(); + + let cacheConversationList = []; + let syncToast = null; + let audioEl = null; + +const $api = inject("$api"); + + const setIMListener = () => { + // account + IMSDK.on(CbEvents.OnSelfInfoUpdated, selfUpdateHandler); + IMSDK.on(CbEvents.OnConnecting, connectingHandler); + IMSDK.on(CbEvents.OnConnectFailed, connectFailedHandler); + IMSDK.on(CbEvents.OnConnectSuccess, connectSuccessHandler); + IMSDK.on(CbEvents.OnKickedOffline, kickHandler); + IMSDK.on(CbEvents.OnUserTokenExpired, expiredHandler); + IMSDK.on(CbEvents.OnPingTimeout, pingTimeout); + // sync + IMSDK.on(CbEvents.OnSyncServerStart, syncStartHandler); + IMSDK.on(CbEvents.OnSyncServerFinish, syncFinishHandler); + IMSDK.on(CbEvents.OnSyncServerFailed, syncFailedHandler); + // message + IMSDK.on(CbEvents.OnRecvNewMessage, newMessageHandler); + IMSDK.on(CbEvents.OnRecvNewMessages, newMessagesHandler); + IMSDK.on(CbEvents.OnNewRecvMessageRevoked, revokedMessageHandler); + // conversation + IMSDK.on(CbEvents.OnConversationChanged, conversationChnageHandler); + IMSDK.on(CbEvents.OnNewConversation, newConversationHandler); + IMSDK.on( + CbEvents.OnTotalUnreadMessageCountChanged, + totalUnreadChangeHandler + ); + // friend + IMSDK.on(CbEvents.OnFriendInfoChanged, friendInfoChangeHandler); + IMSDK.on(CbEvents.OnFriendAdded, friendAddedHandler); + IMSDK.on(CbEvents.OnFriendDeleted, friendDeletedHandler); + // blacklist + IMSDK.on(CbEvents.OnBlackAdded, blackAddedHandler); + IMSDK.on(CbEvents.OnBlackDeleted, blackDeletedHandler); + // group + IMSDK.on(CbEvents.OnJoinedGroupAdded, joinedGroupAddedHandler); + IMSDK.on(CbEvents.OnJoinedGroupDeleted, joinedGroupDeletedHandler); + IMSDK.on(CbEvents.OnGroupDismissed, groupDismissedHandler); + IMSDK.on(CbEvents.OnGroupInfoChanged, groupInfoChangedHandler); + IMSDK.on(CbEvents.OnGroupMemberAdded, groupMemberAddedHandler); + IMSDK.on(CbEvents.OnGroupMemberDeleted, groupMemberDeletedHandler); + IMSDK.on(CbEvents.OnGroupMemberInfoChanged, groupMemberInfoChangedHandler); + //--OnGroupMemberStatusChanged新增一个监听事件,监听后端返回邀请好友进群时好友的状态是已封号或已注销 + //是一个json格式的string,如:[{userId:'123456',status:'1'},{userId:'654321',status:'2'}] + //status:1--已封号;status--2已注销 + IMSDK.on(CbEvents.OnGroupMemberStatusChanged,()=>{ + //这里写监听到数据后的处理方法 + }); + // application + IMSDK.on(CbEvents.OnFriendApplicationAdded, friendApplicationAddedHandler); + IMSDK.on( + CbEvents.OnFriendApplicationAccepted, + friendApplicationProcessedHandler + ); + IMSDK.on( + CbEvents.OnFriendApplicationRejected, + friendApplicationProcessedHandler + ); + IMSDK.on(CbEvents.OnGroupApplicationAdded, groupApplicationAddedHandler); + IMSDK.on( + CbEvents.OnGroupApplicationAccepted, + groupApplicationProcessedHandler + ); + IMSDK.on( + CbEvents.OnGroupApplicationRejected, + groupApplicationProcessedHandler + ); + IMSDK.on( + CbEvents.OnCustomNotifications, + customNotificationsHandler + ); + IMSDK.on( + CbEvents.OnUserStatusChanged, + userStatusChangedHandler + ); + IMSDK.on(CbEvents.OnKickedOfflineWithExData, kickedOfflineWidthExData); + IMSDK.on(CbEvents.OnUserTokenExpired, kickedOfflineWidthExpired); + // 删除该用户在本群发送的群聊消息 + // IMSDK.on( + // CbEvents.OnDeleteUserMsg, + // deleteUserMsgHandler + // ); + }; + + const kickedOfflineWidthExpired = (data) => { + setTimeout(() => { + setToken(''); + setUserInfo({}); + removeAllLocalstorage(); + IMSDK.logout(); + clearStore(); + localStorage.removeItem('im_token') + localStorage.removeItem('token') + localStorage.removeItem('user_id') + location.reload(); + }, 3000) + } + + const kickedOfflineWidthExData = async (data) => { + console.log('OnKickedOfflineWithExData==========', data) + const { data: innerData } = data || {} + const { code, data: kickInfo, device_id, user_id } = innerData || {} + // 测试专用 + const activeDeviceId = localStorage.getItem('visitorId') + const activeUserId = localStorage.getItem('user_id') + // 同一个用户同一个设备,可以多个tab,就不踢出 + // if (data.errCode === 0 && device_id === activeDeviceId && user_id === activeUserId) { + // return + // } + if (code === 20004) { + const info = JSON.parse(kickInfo || '{}') || {} + if (info.device_id === activeDeviceId) { + return + } + showToast("该设备登录存在风险,请使用可信设备登录") + } + // 20001 为移除信任设备踢掉 + if (code === 20001) { + const info = JSON.parse(kickInfo || '{}') || {} + if (info.device_id !== activeDeviceId) { + return + } + + showToast("该设备登录存在风险,请使用可信设备登录") + } + + // 20002 注销踢掉 + if (code === 20002 && kickInfo === '') { + showToast("账号已注销") + } + + if (![20001, 20002].includes(code)) { + showToast("已被强制下线,请重新登录!") + } + + // debugger + setTimeout(() => { + setToken(''); + setUserInfo({}); + removeAllLocalstorage(); + // debugger + IMSDK.logout(); + clearStore(); + localStorage.removeItem('im_token') + localStorage.removeItem('token') + localStorage.removeItem('user_id') + location.reload(); + }, 3000) + } + // 用户状态变化 0 正常 1 封号 2 注销 + // data: [{userId: '', status: 1|2|3 }] + // 这个事件监听了,但是暂时没有地方使用(2.7.0) + const userStatusChangedHandler = (data) => { + emitter.emit('USER_STATUS_CHANGED', data) + } + + const customNotificationsHandler = async({data}) => { + // ConversationRuleNotification= 10000 //发言频率规则通知 + // GroupConfNotification= 10001 //群聊配置通知 + // AddFriendConfNotification= 10002 //添加好友配置通知 + // RedPackConfNotification= 10003 //红包配置通知 + // BurnAfterReadingConfNotification= 10004 //阅后即焚配置通知 + // UserInfoConfNotification= 10005 //用户信息配置通知 + // ForwardOrGroupSendingConfNotification = 10006 //转发&群发功能配置通知 + const messageTypeArr = [10000, 10001, 10002, 10003, 10004, 10005, 10006] + console.log('data----',data,JSON.parse(data.content),messageTypeArr); + if(messageTypeArr.includes(data.messageType)) { + const configRes = await $api.login.getConfigInfo(); + if(configRes.code !== 0) return; + setAppConfig({ + ...appConfig.value, + ...configRes.data + }); + if(data.messageType === 10006) { + setIsGroupSend(appConfig.value,userInfo.value) + } + } + } + + const selfUpdateHandler = ({ data }) => { + const imUserInfo = data; + userStore.updateSelfInfo({ + ...imUserInfo, + ...userStore.storeSelfInfo, + globalRecvMsgOpt: imUserInfo.globalRecvMsgOpt, + }); + }; + const connectingHandler = () => { }; + const connectFailedHandler = ({ errCode }) => { + console.error("建立wss失败:", errMsg) + if (errCode == 705) { + tryOut(t("messageTip.loginExpiration")); + } + }; + const connectSuccessHandler = () => { }; + const kickHandler = () => { + console.log('OnKickedOffline==========', data) + showToast("已被强制下线,请重新登录!") + // tryOut(t("messageTip.loginKicked")) + }; + const expiredHandler = () => { + console.log('token过期') + // tryOut(t("messageTip.loginExpiration")) + }; + + const pingTimeout =(data)=>{ + console.log('ping timeout', data) + } + const tryOut = (message) => + feedbackToast({ + message, + error: message, + onClose: () => { + userStore.userLogout(true); + router.push("/login"); + }, + }); + + // sync + const syncStartHandler = () => { + userStore.isSyncing = true; + syncToast = showLoadingToast({ + message: t("loading_ing"), + forbidClick: true, + }); + }; + const syncFinishHandler = () => { + userStore.isSyncing = false; + syncToast?.close(); + syncToast = null; + }; + const syncFailedHandler = () => { + userStore.isSyncing = false; + if (!syncToast) return; + syncToast.message = t("syncFailed"); + syncToast.close(); + syncToast = null; + }; + // 单个处理消息的回调 + const newMessageHandler = ({ + data, + }) => { + handleNewMessage(data); + }; + + // 批量处理消息的回调 + const newMessagesHandler = ({ + data, + }) => { + handleNewMessages(data); + }; + + // 批量处理消息的【方法】 + const handleNewMessages =async (msgs) => { + let groupArr = [] + let singleArr = [] + let msgMap = {} + msgs.forEach(function(newServerMsg){ + if (inCurrentConversation(newServerMsg)) { + handleCurrentConversation(newServerMsg) + return + } + if (newServerMsg.contentType !== MessageType.TypingMessage && + newServerMsg.sendID !== userInfo.value.userID) { + handleOtherConversation(newServerMsg, function (sessionType, msg){ + msgMap[msg.sendID] = msg + if(sessionType === 1){ + singleArr.push({ sourceID: newServerMsg.sendID, sessionType: sessionType }) + } + else if(sessionType === 3){ + groupArr.push({ sourceID: newServerMsg.sendID, sessionType: sessionType }) + } + }) + } + }); + // 分开两个 try catch 执行 防止一个执行失败后 另外一个无法执行 + try{ + const { data } = await IMSDK.batchGetConversationsBySessionTypeAndSourceId(groupArr) + if(data !== null) { + setPrivateChatConfigs({}) + data.forEach(function (item) { + setGroupChatConfigs({ + group: item, + msg: msgMap[item.userID] + }) + }) + } + }catch(err){ + console.error(err) + } + try{ + const { data } = await IMSDK.batchGetConversationsBySessionTypeAndSourceId(singleArr) + setGroupChatConfigs({}) + if(data === null || data === undefined){ + return + } + data.forEach(function(item){ + setPrivateChatConfigs({ + group: item, + msg: msgMap[item.userID] + }) + }) + }catch(err){ + console.error(err) + } + }; + + const handleCurrentConversation = (newServerMsg)=>{ + const isSingleMessage = newServerMsg.sessionType === SessionType.Single; + + if (isSingleMessage) { + useThrottleFn(() => emitter.emit("ONLINE_STATE_CHECK"), 2000)(); + } + + if (newServerMsg.contentType === MessageType.TypingMessage) { + if (isSingleMessage) { + useThrottleFn(() => emitter.emit("TYPING_UPDATE"), 2000)(); + } + return + } + if (newServerMsg.contentType !== MessageType.RevokeMessage) { + newServerMsg.isAppend = true; + pushNewMessage(newServerMsg); + emitter.emit("CHECK_SCROLL_BOTTOM"); + } + } + const handleOtherConversation = (newServerMsg, callback)=>{ + // useThrottleFn(() => newMessageNotify(newServerMsg), 1000)(); + // 推送群消息 + if (newServerMsg.sessionType === 3) { + // 开启勿扰模式 + if (userInfo.value.globalRecvMsgOpt === 2) { + return + } + if(callback !== null){ + callback(3, newServerMsg) + } + } else if (newServerMsg.sessionType === 1) { + // 推送私聊消息 + // 开启勿扰模式 + if (newServerMsg.sendID === privateChatConfig.value.userID) { + return + } + if (userInfo.value.globalRecvMsgOpt === 2) { + return + } + if(callback !== null){ + callback(1, newServerMsg) + } + } + } + + const setGroupConversion = async (newServerMsg) => { + const { data } = await IMSDK.getOneConversation({ + sourceID: newServerMsg.groupID, + sessionType: 3 + }) + setPrivateChatConfigs({}) + setGroupChatConfigs({ + group: data, + msg: newServerMsg + }) + } + const setSingleConversion = async (newServerMsg) => { + const { data } = await IMSDK.getOneConversation({ + sourceID: newServerMsg.sendID, + sessionType: 1 + }) + setGroupChatConfigs({}) + setPrivateChatConfigs({ + group: data, + msg: newServerMsg + }) + } + // 单个方式处理【方法】 + const handleNewMessage = (newServerMsg) => { + if (inCurrentConversation(newServerMsg)) { + handleCurrentConversation(newServerMsg) + return + } + if (newServerMsg.contentType !== MessageType.TypingMessage && + newServerMsg.sendID !== userInfo.value.userID) { + handleOtherConversation(newServerMsg, function (sessionType, msg) { + if (sessionType === 3) { + setGroupConversion(newServerMsg) + } else if (sessionType === 1) { + setSingleConversion(newServerMsg) + } + }) + } + } + const inCurrentConversation = (newServerMsg) => { + switch (newServerMsg.sessionType) { + // case SessionType.Single: + // return ( + // newServerMsg.sendID === + // privateChatConfig.value.userID || + // (newServerMsg.sendID === userInfo.value.userID && + // newServerMsg.recvID === + // privateChatConfig.value.userID) + // ); + case SessionType.Group: + case SessionType.WorkingGroup: + return ( + newServerMsg.groupID === + groupInfo.value.groupID + ); + // case SessionType.Notification: + // return ( + // newServerMsg.sendID === + // conversationStore.storeCurrentConversation.userID + // ); + default: + return false; + } + }; + // const newMessageNotify = async (newServerMsg) => { + // if (userStore.storeIsSyncing) { + // return; + // } + + // if ( + // userStore.storeSelfInfo.allowBeep === BusinessAllowType.NotAllow || + // userStore.storeSelfInfo.globalRecvMsgOpt !== MessageReceiveOptType.Nomal + // ) { + // return; + // } + + // let cveItem = [ + // ...conversationStore.storeConversationList, + // ...cacheConversationList, + // ].find((conversation) => { + // if (GroupSessionTypes.includes(newServerMsg.sessionType)) { + // return newServerMsg.groupID === conversation.groupID; + // } + // return newServerMsg.sendID === conversation.userID; + // }); + + // if (!cveItem) { + // try { + // const { data } = await IMSDK.getOneConversation({ + // sessionType: newServerMsg.sessionType, + // sourceID: newServerMsg.groupID || newServerMsg.sendID, + // }); + // cveItem = data; + // cacheConversationList = [...cacheConversationList, { ...cveItem }]; + // } catch (e) { + // return; + // } + // } + + // if (cveItem.recvMsgOpt !== MessageReceiveOptType.Nomal) { + // return; + // } + + // if (!audioEl) { + // audioEl = document.createElement("audio"); + // } + // audioEl.src = messageRing; + // audioEl.play(); + // }; + const revokedMessageHandler = ({ data }) => { + updateOneMessage({ + clientMsgID: data.clientMsgID, + contentType: MessageType.RevokeMessage, + notificationElem: { + detail: JSON.stringify(data), + } + }, true); + }; + // conversation + const conversationChnageHandler = ({ data }) => { + // console.log('conversationChnageHandler123----------',data); + if (data[0].userID && data[0].userID === privateChatConfig.value.userID) { + setPrivateChatConfig({ + ...privateChatConfig.value, + ex: data[0].ex + }); + } + let filterArr = []; + const changes = data; + const changeIds = changes.map((ch) => ch.conversationID); + // 变更的会话增加offset判断 + changes.forEach(changeItem => { + const beforeChangeItem = conversationStore.storeConversationList.find(item => item.conversationID === changeItem.conversationID) + if (beforeChangeItem) { + handleConversationOffsetChange(changeItem, beforeChangeItem) + } + }) + filterArr = conversationStore.storeConversationList.filter( + (tc) => !changeIds.includes(tc.conversationID) + ); + const idx = changes.findIndex( + (c) => + c.conversationID === + conversationStore.storeCurrentConversation.conversationID + ); + if (idx !== -1) conversationStore.updateCurrentConversation(changes[idx]); + // 变动的消息放在最上面 + const result = [...changes, ...filterArr]; + // console.log('updateConversationList',result); + + conversationStore.updateConversationList(conversationSort(result)); + }; + + const newConversationHandler = ({ data }) => { + const news = data; + // 变更的会话增加offset判断 + news.forEach(changeItem => { + const beforeChangeItem = conversationStore.storeConversationList.find(item => item.conversationID === changeItem.conversationID) + if (beforeChangeItem) { + handleConversationOffsetChange(changeItem, beforeChangeItem) + } + }) + + const result = [...news, ...conversationStore.storeConversationList]; + conversationStore.updateConversationList(conversationSort(result)); + }; + + const handleConversationOffsetChange = (newValue, oldValue) => { + // 取消置顶 + const cancelPinned = oldValue.isPinned === true && newValue.isPinned === false + // 删除会话消息 会话最后一条消息的时间变小了,可能是删除了会话消息 + const isDel = Math.max(oldValue.draftTextTime ?? 0, oldValue.latestMsgSendTime ?? 0) > Math.max(newValue.draftTextTime ?? 0, newValue.latestMsgSendTime ?? 0) + if (cancelPinned || isDel) { + const newOffset = conversationOffset.value - 1 + updateConversationOffset(newOffset) + } + } + const totalUnreadChangeHandler = ({ data }) => { + conversationStore.updateUnReadCount(data); + }; + + // friend + const friendInfoChangeHandler = ({ data }) => { + updateContact(data); + if (groupInfo.value.groupID && groupMemberInfo.value) { + const idx = groupMemberInfo.value[groupInfo.value.groupID].findIndex( + (msg) => msg.userID === data.userID + ); + if (idx !== -1) { + updateMessageNicknameAndFaceUrl({ + sendID: data.userID, + senderNickname: data.remark || data.nickname, + senderFaceUrl: data.faceURL, + }); + // 更新群成员备注信息 + updateGroupMemberInfos(groupInfo.value.groupID, data) + } + } + }; + + /** + * 新增好友 + * @param {*} param0 + */ + const friendAddedHandler = ({ data }) => { + const time = Number(`${data.createTime}`.slice(0, 10)) + getNewfriendApplyCount(time) + // updateNewFriendApply(true) + pushNewContact(data); + }; + /** + * 删除好友-删除好友不需要通知,将好友删除即可 + * @param {*} param0 + */ + const friendDeletedHandler = ({ data }) => { + // updateNewFriendApply(true) + updateContact(data, true); + }; + + // blacklist + const blackAddedHandler = ({ data }) => { + pushNewBlack(data); + }; + const blackDeletedHandler = ({ data }) => { + updateBlackList(data, true); + }; + + // group + const joinedGroupAddedHandler = ({ data }) => { + // if (data.groupID === groupInfo.value.groupID) { + // conversationStore.updateCurrentGroupInfo(data); + // } + pushNewGroup(data); + /** 注释是因为进入群聊会一直触发getMemberList()方法, + * 导致groupMemberLoading.value状态切换, + * 解决chartFooter输入框在已退出群聊与输入之间切换问题 + */ + // setTimeout(() => { + // console.log('joinedGroupAddedHandler',data); + + // getGroupMemberInfo(data.groupID) + // }, 500) + }; + const joinedGroupDeletedHandler = ({ data }) => { + updateGroupList(data, true); + updateGroupMemberInfo(data.groupID, data.ownerUserID) + }; + + const groupDismissedHandler = ({ data }) => { + updateGroupList(data, true); + setGroupMemberInfo({ + ...groupMemberInfo.value, + [data.groupID]: [] + }) + }; + /** + * 监听群信息发生改变-群设置发生改变时触发 + * @param {*} param0 + */ + const groupInfoChangedHandler = ({ data }) => { + const idx = chatList.value.findIndex( + (group) => group.groupID === data.groupID + ); + if (idx !== -1) { + updateGroupList(data, true); + } + // 更新当前群的信息 + if (data.groupID === groupInfo.value.groupID) { + setGroupInfo({ + ...groupInfo.value, + ...data + }) + setGroupChatConfig({ + ...groupChatConfig.value, + ...data + }) + } + // if (data.groupID === groupInfo.value.groupID) { + // conversationStore.updateCurrentGroupInfo(data); + // } + }; + const groupMemberAddedHandler = ({ data }) => { + const arr = groupMemberInfo.value[data.groupID] + const index = contactList.value.findIndex((item) => item.userID === data.userID) + if (index != -1) { + arr.push({ + ...data, + isFriend: true, + remark: contactList.value[index].remark ? contactList.value[index].remark : '', + groupNickName: data.nickname + }) + setGroupMemberInfo({ + ...groupMemberInfo.value, + [data.groupID]: arr + }) + } else { + setGroupMemberInfo({ + ...groupMemberInfo.value, + [data.groupID]: [ + ...arr, + data + ] + }) + } + + }; + const groupMemberDeletedHandler = ({ data }) => { + console.log(data, '退出群聊11222') + updateGroupMemberInfo(data.groupID, data.userID) + }; + const groupMemberInfoChangedHandler = async ({ + data, + }) => { + console.log(data, 'groupMemberInfoChangedHandler') + // 邀请人进群 更新备注等信息 + updateMessageNicknameAndFaceUrl({ + sendID: data.userID, + senderNickname: data.nickname, // 不需要改变群昵称 + senderFaceUrl: data.faceURL, + }); + // 如果当前用户在群中,更新当前群的信息 + if (data.groupID === groupInfo.value.groupID) { + if (data.userID === userInfo.value.userID) { + // 我在群里的信息只有id是自己的时候才会改 + setmyGroupInfo(data) // 更改群成员权限会设置 + // setGroupInfo({ + // ...groupInfo.value, + // ...data + // }) + } + updateGroupMemberInfos1(groupInfo.value.groupID, data); + } + // 如果当前用户在其他页面,也要更新用户在其他群的信息 + Object.keys(groupMemberInfo.value).forEach(groupID => { + updateGroupMemberInfos1(groupID, data); + }) + }; + + // rtc + const newInvitationHandler = () => { }; + const otherHandler = () => { }; + + let friendApplicationAddedHandler_timer = null + //application 添加好友,需要对方同意才能添加上好友 + const friendApplicationAddedHandler = ({ data }) => { + clearTimeout(friendApplicationAddedHandler_timer) + friendApplicationAddedHandler_timer = null + + const time = Number(`${data.createTime}`.slice(0, 10)) + // 勿扰模式 + if (userInfo.value.globalRecvMsgOpt === 2 && userInfo.value.userID !== data.toUserID) { + setFriendApplication(data) + } + getNewfriendApplyCount(time) + // updateNewFriendApply(true) + + const application = data; + const isRecv = application.toUserID === userInfo.value.userID; + if (isRecv) { + pushNewRecvFriendApplication(application); + } else { + pushNewSendFriendApplication(application); + } + }; + //当前添加还有是无需同意即可添加的好友,不需要通知用户 + const friendApplicationProcessedHandler = ({ data }) => { + const time = Number(`${data.createTime}`.slice(0, 10)) + getNewfriendApplyCount(time) + // updateNewFriendApply(true) + + const application = data; + const isRecv = application.toUserID === userInfo.value.userID; + if (isRecv) { + updateRecvFriendApplicationList(application); + } else { + updateSendFriendApplicationList(application); + } + }; + const groupApplicationAddedHandler = ({ data }) => { + const application = data; + const isRecv = application.userID !== userInfo.value.userID; + if (isRecv) { + contactStore.pushNewRecvGroupApplication(application); + } else { + contactStore.pushNewSendGroupApplication(application); + } + }; + const groupApplicationProcessedHandler = ({ data }) => { + const application = data; + const isRecv = application.userID !== userInfo.value.userID; + if (isRecv) { + contactStore.updateRecvGroupApplicationList(application); + } else { + contactStore.updateSendGroupApplicationList(application); + } + }; + + // const deleteUserMsgHandler = ({ data }) => { + // const { conversationID, userID } = data + // console.log('?????????', conversationID, userID) + + // storeHistoryMessageList.value = storeHistoryMessageList.value.filter(item => item.sendID !== userID) + // } + + const disposeIMListener = () => { + IMSDK.off(CbEvents.OnSelfInfoUpdated, selfUpdateHandler); + IMSDK.off(CbEvents.OnConnecting, connectingHandler); + IMSDK.off(CbEvents.OnConnectFailed, connectFailedHandler); + IMSDK.off(CbEvents.OnConnectSuccess, connectSuccessHandler); + IMSDK.off(CbEvents.OnKickedOffline, kickHandler); + IMSDK.off(CbEvents.OnUserTokenExpired, expiredHandler); + IMSDK.off(CbEvents.OnPingTimeout, pingTimeout); + // sync + IMSDK.off(CbEvents.OnSyncServerStart, syncStartHandler); + IMSDK.off(CbEvents.OnSyncServerFinish, syncFinishHandler); + IMSDK.off(CbEvents.OnSyncServerFailed, syncFailedHandler); + // 单个方式处理消息 + IMSDK.off(CbEvents.OnRecvNewMessage, newMessageHandler); + // 批量方式处理消息 + IMSDK.off(CbEvents.OnRecvNewMessages, newMessagesHandler); + IMSDK.on(CbEvents.OnNewRecvMessageRevoked, revokedMessageHandler); + // conversation + IMSDK.off(CbEvents.OnConversationChanged, conversationChnageHandler); + IMSDK.off(CbEvents.OnNewConversation, newConversationHandler); + IMSDK.off( + CbEvents.OnTotalUnreadMessageCountChanged, + totalUnreadChangeHandler + ); + // friend + IMSDK.off(CbEvents.OnFriendInfoChanged, friendInfoChangeHandler); + IMSDK.off(CbEvents.OnFriendAdded, friendAddedHandler); + IMSDK.off(CbEvents.OnFriendDeleted, friendDeletedHandler); + // blacklist + IMSDK.off(CbEvents.OnBlackAdded, blackAddedHandler); + IMSDK.off(CbEvents.OnBlackDeleted, blackDeletedHandler); + // group + IMSDK.off(CbEvents.OnJoinedGroupAdded, joinedGroupAddedHandler); + IMSDK.off(CbEvents.OnJoinedGroupDeleted, joinedGroupDeletedHandler); + IMSDK.off(CbEvents.OnGroupDismissed, groupDismissedHandler); + IMSDK.off(CbEvents.OnGroupInfoChanged, groupInfoChangedHandler); + IMSDK.off(CbEvents.OnGroupMemberAdded, groupMemberAddedHandler); + IMSDK.off(CbEvents.OnGroupMemberDeleted, groupMemberDeletedHandler); + IMSDK.off(CbEvents.OnGroupMemberInfoChanged, groupMemberInfoChangedHandler); + // application + IMSDK.off(CbEvents.OnFriendApplicationAdded, friendApplicationAddedHandler); + IMSDK.off( + CbEvents.OnFriendApplicationAccepted, + friendApplicationProcessedHandler + ); + IMSDK.off( + CbEvents.OnFriendApplicationRejected, + friendApplicationProcessedHandler + ); + IMSDK.off(CbEvents.OnGroupApplicationAdded, groupApplicationAddedHandler); + IMSDK.off( + CbEvents.OnGroupApplicationAccepted, + groupApplicationProcessedHandler + ); + IMSDK.off( + CbEvents.OnGroupApplicationRejected, + groupApplicationProcessedHandler + ); + + IMSDK.off( + CbEvents.OnCustomNotifications, + customNotificationsHandler + ); + IMSDK.off( + CbEvents.OnUserStatusChanged, + userStatusChangedHandler + ); + // IMSDK.off( + // CbEvents.OnDeleteUserMsg, + // deleteUserMsgHandler + // ); + }; + + watch( + () => userInfo.value.userID, + () => { + cacheConversationList = []; + } + ); + + // watch( + // [ + // () => contactStore.storeRecvFriendApplicationList, + // () => contactStore.storeRecvGroupApplicationList, + // () => userInfo.value.userID, + // ], + // (newValue) => { + // const userID = newValue[2]; + // if (!userID) return; + // const accessedFriendApplications = getAccessedFriendApplication(); + // let unHandleFriendApplicationNum = newValue[0].filter( + // (application) => + // application.handleResult === 0 && + // !accessedFriendApplications.includes( + // `${application.fromUserID}_${application.createTime}` + // ) + // ).length; + + // const accessedGroupApplications = getAccessedGroupApplication(); + // let unHandleGroupApplicationNum = newValue[1].filter( + // (application) => + // application.handleResult === 0 && + // !accessedGroupApplications.includes( + // `${application.userID}_${application.createTime}` + // ) + // ).length; + // contactStore.updateUnHandleFriendApplicationNum( + // unHandleFriendApplicationNum + // ); + // contactStore.updateUnHandleGroupApplicationNum( + // unHandleGroupApplicationNum + // ); + // } + // ); + + onMounted(() => { + setIMListener(); + if (localStorage.getItem("token")) { + console.log("[onMounted]getNewfriendApplyCount") + getNewfriendApplyCount(Date.now()) + } + }); + onUnmounted(() => { + disposeIMListener(); + }); +} diff --git a/src/utils/voice.js b/src/utils/voice.js new file mode 100644 index 0000000..e69de29 diff --git a/src/views/banner/components/demoPage.vue b/src/views/banner/components/demoPage.vue new file mode 100644 index 0000000..86037c5 --- /dev/null +++ b/src/views/banner/components/demoPage.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/views/banner/components/transferPage.vue b/src/views/banner/components/transferPage.vue new file mode 100644 index 0000000..967b13d --- /dev/null +++ b/src/views/banner/components/transferPage.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/views/banner/index.vue b/src/views/banner/index.vue new file mode 100644 index 0000000..40ea84e --- /dev/null +++ b/src/views/banner/index.vue @@ -0,0 +1,276 @@ + + + diff --git a/src/views/charts/index.vue b/src/views/charts/index.vue new file mode 100644 index 0000000..597d849 --- /dev/null +++ b/src/views/charts/index.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/src/views/community/components/eventPreview.vue b/src/views/community/components/eventPreview.vue new file mode 100644 index 0000000..b9be099 --- /dev/null +++ b/src/views/community/components/eventPreview.vue @@ -0,0 +1,263 @@ + + + + + \ No newline at end of file diff --git a/src/views/community/components/eventsItem.vue b/src/views/community/components/eventsItem.vue new file mode 100644 index 0000000..e969f79 --- /dev/null +++ b/src/views/community/components/eventsItem.vue @@ -0,0 +1,307 @@ + + + + + diff --git a/src/views/community/components/eventsList.vue b/src/views/community/components/eventsList.vue new file mode 100644 index 0000000..06dd47e --- /dev/null +++ b/src/views/community/components/eventsList.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/views/community/eventDetails.vue b/src/views/community/eventDetails.vue new file mode 100644 index 0000000..0b09303 --- /dev/null +++ b/src/views/community/eventDetails.vue @@ -0,0 +1,456 @@ + + + + + diff --git a/src/views/community/eventVideoDetails.vue b/src/views/community/eventVideoDetails.vue new file mode 100644 index 0000000..a1d1985 --- /dev/null +++ b/src/views/community/eventVideoDetails.vue @@ -0,0 +1,422 @@ + + + + + diff --git a/src/views/community/index.vue b/src/views/community/index.vue new file mode 100644 index 0000000..437c614 --- /dev/null +++ b/src/views/community/index.vue @@ -0,0 +1,240 @@ + + + diff --git a/src/views/contact/add/apply.vue b/src/views/contact/add/apply.vue new file mode 100644 index 0000000..46b47c6 --- /dev/null +++ b/src/views/contact/add/apply.vue @@ -0,0 +1,429 @@ + + + + + diff --git a/src/views/contact/add/applyGroup.vue b/src/views/contact/add/applyGroup.vue new file mode 100644 index 0000000..8e5156f --- /dev/null +++ b/src/views/contact/add/applyGroup.vue @@ -0,0 +1,432 @@ + + + + + diff --git a/src/views/contact/add/index.vue b/src/views/contact/add/index.vue new file mode 100644 index 0000000..25f6b01 --- /dev/null +++ b/src/views/contact/add/index.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/src/views/contact/add/remark.vue b/src/views/contact/add/remark.vue new file mode 100644 index 0000000..4449446 --- /dev/null +++ b/src/views/contact/add/remark.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/src/views/contact/add/result.vue b/src/views/contact/add/result.vue new file mode 100644 index 0000000..7f2de5b --- /dev/null +++ b/src/views/contact/add/result.vue @@ -0,0 +1,361 @@ + + + + + diff --git a/src/views/contact/add/search.vue b/src/views/contact/add/search.vue new file mode 100644 index 0000000..a8fc463 --- /dev/null +++ b/src/views/contact/add/search.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/src/views/contact/addFriend/confirm.vue b/src/views/contact/addFriend/confirm.vue new file mode 100644 index 0000000..f267722 --- /dev/null +++ b/src/views/contact/addFriend/confirm.vue @@ -0,0 +1,410 @@ + + + + + diff --git a/src/views/contact/addFriend/index.vue b/src/views/contact/addFriend/index.vue new file mode 100644 index 0000000..8f35cf0 --- /dev/null +++ b/src/views/contact/addFriend/index.vue @@ -0,0 +1,461 @@ + + + + + diff --git a/src/views/contact/chat/components/InviteFriends.vue b/src/views/contact/chat/components/InviteFriends.vue new file mode 100644 index 0000000..cec092d --- /dev/null +++ b/src/views/contact/chat/components/InviteFriends.vue @@ -0,0 +1,591 @@ + + + + + diff --git a/src/views/contact/chat/components/addAdministrator.vue b/src/views/contact/chat/components/addAdministrator.vue new file mode 100644 index 0000000..12ddcd7 --- /dev/null +++ b/src/views/contact/chat/components/addAdministrator.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/src/views/contact/chat/components/addChatFriendList.vue b/src/views/contact/chat/components/addChatFriendList.vue new file mode 100644 index 0000000..5aa9f26 --- /dev/null +++ b/src/views/contact/chat/components/addChatFriendList.vue @@ -0,0 +1,138 @@ + + + + + \ No newline at end of file diff --git a/src/views/contact/chat/components/addDetails.vue b/src/views/contact/chat/components/addDetails.vue new file mode 100644 index 0000000..dffdc60 --- /dev/null +++ b/src/views/contact/chat/components/addDetails.vue @@ -0,0 +1,405 @@ + + + + + diff --git a/src/views/contact/chat/components/editGroupChatAvatarUrl.vue b/src/views/contact/chat/components/editGroupChatAvatarUrl.vue new file mode 100644 index 0000000..569d50f --- /dev/null +++ b/src/views/contact/chat/components/editGroupChatAvatarUrl.vue @@ -0,0 +1,321 @@ + + + + + diff --git a/src/views/contact/chat/components/editNameAvatar.vue b/src/views/contact/chat/components/editNameAvatar.vue new file mode 100644 index 0000000..216752a --- /dev/null +++ b/src/views/contact/chat/components/editNameAvatar.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/src/views/contact/chat/components/editNameAvatarContent.vue b/src/views/contact/chat/components/editNameAvatarContent.vue new file mode 100644 index 0000000..f495277 --- /dev/null +++ b/src/views/contact/chat/components/editNameAvatarContent.vue @@ -0,0 +1,497 @@ + + + + + diff --git a/src/views/contact/chat/components/editNameAvatarUrl.vue b/src/views/contact/chat/components/editNameAvatarUrl.vue new file mode 100644 index 0000000..3458b5f --- /dev/null +++ b/src/views/contact/chat/components/editNameAvatarUrl.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/src/views/contact/chat/components/enterGroupApply.vue b/src/views/contact/chat/components/enterGroupApply.vue new file mode 100644 index 0000000..061b2b0 --- /dev/null +++ b/src/views/contact/chat/components/enterGroupApply.vue @@ -0,0 +1,282 @@ + + + + + diff --git a/src/views/contact/chat/components/groupChatLogs.vue b/src/views/contact/chat/components/groupChatLogs.vue new file mode 100644 index 0000000..014274c --- /dev/null +++ b/src/views/contact/chat/components/groupChatLogs.vue @@ -0,0 +1,318 @@ + + + + + diff --git a/src/views/contact/chat/components/groupFileRecord.vue b/src/views/contact/chat/components/groupFileRecord.vue new file mode 100644 index 0000000..b9a2273 --- /dev/null +++ b/src/views/contact/chat/components/groupFileRecord.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/src/views/contact/chat/components/groupMediaRecord.vue b/src/views/contact/chat/components/groupMediaRecord.vue new file mode 100644 index 0000000..e131716 --- /dev/null +++ b/src/views/contact/chat/components/groupMediaRecord.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/src/views/contact/chat/components/groupMemberSearch.vue b/src/views/contact/chat/components/groupMemberSearch.vue new file mode 100644 index 0000000..91f8080 --- /dev/null +++ b/src/views/contact/chat/components/groupMemberSearch.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/src/views/contact/chat/components/groupNotice.vue b/src/views/contact/chat/components/groupNotice.vue new file mode 100644 index 0000000..3ac38f9 --- /dev/null +++ b/src/views/contact/chat/components/groupNotice.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/views/contact/chat/components/groupPrompt.vue b/src/views/contact/chat/components/groupPrompt.vue new file mode 100644 index 0000000..865dd63 --- /dev/null +++ b/src/views/contact/chat/components/groupPrompt.vue @@ -0,0 +1,115 @@ + + + + diff --git a/src/views/contact/chat/components/groupUserDetail.vue b/src/views/contact/chat/components/groupUserDetail.vue new file mode 100644 index 0000000..2ff466f --- /dev/null +++ b/src/views/contact/chat/components/groupUserDetail.vue @@ -0,0 +1,843 @@ + + + + + diff --git a/src/views/contact/chat/components/groupchatMember.vue b/src/views/contact/chat/components/groupchatMember.vue new file mode 100644 index 0000000..d7580e7 --- /dev/null +++ b/src/views/contact/chat/components/groupchatMember.vue @@ -0,0 +1,832 @@ + + + + + diff --git a/src/views/contact/chat/components/groupchatMemberItem.vue b/src/views/contact/chat/components/groupchatMemberItem.vue new file mode 100644 index 0000000..c46703c --- /dev/null +++ b/src/views/contact/chat/components/groupchatMemberItem.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/views/contact/chat/components/inviteLink.vue b/src/views/contact/chat/components/inviteLink.vue new file mode 100644 index 0000000..2ba3740 --- /dev/null +++ b/src/views/contact/chat/components/inviteLink.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/views/contact/chat/components/manageGrounp.vue b/src/views/contact/chat/components/manageGrounp.vue new file mode 100644 index 0000000..35bc146 --- /dev/null +++ b/src/views/contact/chat/components/manageGrounp.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/src/views/contact/chat/components/manageLinks.vue b/src/views/contact/chat/components/manageLinks.vue new file mode 100644 index 0000000..fe82102 --- /dev/null +++ b/src/views/contact/chat/components/manageLinks.vue @@ -0,0 +1,262 @@ + + + + + diff --git a/src/views/contact/chat/components/setAdministrator.vue b/src/views/contact/chat/components/setAdministrator.vue new file mode 100644 index 0000000..ac27822 --- /dev/null +++ b/src/views/contact/chat/components/setAdministrator.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/src/views/contact/chat/components/setGroupBans.vue b/src/views/contact/chat/components/setGroupBans.vue new file mode 100644 index 0000000..5f0fb51 --- /dev/null +++ b/src/views/contact/chat/components/setGroupBans.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/src/views/contact/chat/groupDetails.vue b/src/views/contact/chat/groupDetails.vue new file mode 100644 index 0000000..07cad3a --- /dev/null +++ b/src/views/contact/chat/groupDetails.vue @@ -0,0 +1,1089 @@ + + + + + diff --git a/src/views/contact/chat/index.vue b/src/views/contact/chat/index.vue new file mode 100644 index 0000000..c63d2e9 --- /dev/null +++ b/src/views/contact/chat/index.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/src/views/contact/chatFriendsList.vue b/src/views/contact/chatFriendsList.vue new file mode 100644 index 0000000..4e13c13 --- /dev/null +++ b/src/views/contact/chatFriendsList.vue @@ -0,0 +1,344 @@ + + + diff --git a/src/views/contact/detail/index.vue b/src/views/contact/detail/index.vue new file mode 100644 index 0000000..f310b54 --- /dev/null +++ b/src/views/contact/detail/index.vue @@ -0,0 +1,368 @@ + + + + + diff --git a/src/views/contact/detail/set.vue b/src/views/contact/detail/set.vue new file mode 100644 index 0000000..ee3e68c --- /dev/null +++ b/src/views/contact/detail/set.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/views/contact/face/index.vue b/src/views/contact/face/index.vue new file mode 100644 index 0000000..8082110 --- /dev/null +++ b/src/views/contact/face/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/views/contact/face/nextStep.vue b/src/views/contact/face/nextStep.vue new file mode 100644 index 0000000..4fa6bdd --- /dev/null +++ b/src/views/contact/face/nextStep.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/views/contact/group/details.vue b/src/views/contact/group/details.vue new file mode 100644 index 0000000..75f3082 --- /dev/null +++ b/src/views/contact/group/details.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/views/contact/group/groupAdd.vue b/src/views/contact/group/groupAdd.vue new file mode 100644 index 0000000..01ec856 --- /dev/null +++ b/src/views/contact/group/groupAdd.vue @@ -0,0 +1,374 @@ + + + + + diff --git a/src/views/contact/group/groupRemove.vue b/src/views/contact/group/groupRemove.vue new file mode 100644 index 0000000..356ddc6 --- /dev/null +++ b/src/views/contact/group/groupRemove.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/src/views/contact/group/index.vue b/src/views/contact/group/index.vue new file mode 100644 index 0000000..4c556ad --- /dev/null +++ b/src/views/contact/group/index.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/src/views/contact/index.vue b/src/views/contact/index.vue new file mode 100644 index 0000000..eb42042 --- /dev/null +++ b/src/views/contact/index.vue @@ -0,0 +1,718 @@ + + + diff --git a/src/views/contact/massTexting/friendDetails.vue b/src/views/contact/massTexting/friendDetails.vue new file mode 100644 index 0000000..7e79ce1 --- /dev/null +++ b/src/views/contact/massTexting/friendDetails.vue @@ -0,0 +1,338 @@ + + + + + \ No newline at end of file diff --git a/src/views/contact/massTexting/friendGroup.vue b/src/views/contact/massTexting/friendGroup.vue new file mode 100644 index 0000000..fed6a27 --- /dev/null +++ b/src/views/contact/massTexting/friendGroup.vue @@ -0,0 +1,232 @@ + + + + + \ No newline at end of file diff --git a/src/views/contact/massTexting/index.vue b/src/views/contact/massTexting/index.vue new file mode 100644 index 0000000..5b06405 --- /dev/null +++ b/src/views/contact/massTexting/index.vue @@ -0,0 +1,430 @@ + + + + + diff --git a/src/views/contact/massTexting/msgSend.vue b/src/views/contact/massTexting/msgSend.vue new file mode 100644 index 0000000..67ca82e --- /dev/null +++ b/src/views/contact/massTexting/msgSend.vue @@ -0,0 +1,1970 @@ + + + + + diff --git a/src/views/contact/massTexting/selectGroup.vue b/src/views/contact/massTexting/selectGroup.vue new file mode 100644 index 0000000..404bb16 --- /dev/null +++ b/src/views/contact/massTexting/selectGroup.vue @@ -0,0 +1,336 @@ + + + + + \ No newline at end of file diff --git a/src/views/contact/massTexting/selectPage.vue b/src/views/contact/massTexting/selectPage.vue new file mode 100644 index 0000000..cda3f91 --- /dev/null +++ b/src/views/contact/massTexting/selectPage.vue @@ -0,0 +1,803 @@ + + + + + \ No newline at end of file diff --git a/src/views/contact/massTexting/selectPerson.vue b/src/views/contact/massTexting/selectPerson.vue new file mode 100644 index 0000000..5267ada --- /dev/null +++ b/src/views/contact/massTexting/selectPerson.vue @@ -0,0 +1,555 @@ + + + + + diff --git a/src/views/contact/myChat/index.vue b/src/views/contact/myChat/index.vue new file mode 100644 index 0000000..e64d57f --- /dev/null +++ b/src/views/contact/myChat/index.vue @@ -0,0 +1,316 @@ + + + + + diff --git a/src/views/contact/myChat/mySearchs.vue b/src/views/contact/myChat/mySearchs.vue new file mode 100644 index 0000000..4d57b3f --- /dev/null +++ b/src/views/contact/myChat/mySearchs.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/src/views/contact/mySearch/index.vue b/src/views/contact/mySearch/index.vue new file mode 100644 index 0000000..36fba97 --- /dev/null +++ b/src/views/contact/mySearch/index.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/views/contact/qrcode/index.vue b/src/views/contact/qrcode/index.vue new file mode 100644 index 0000000..430223f --- /dev/null +++ b/src/views/contact/qrcode/index.vue @@ -0,0 +1,427 @@ + + + + + diff --git a/src/views/contact/scan/index.vue b/src/views/contact/scan/index.vue new file mode 100644 index 0000000..f94cfd3 --- /dev/null +++ b/src/views/contact/scan/index.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/src/views/dialogue/charts/index.vue b/src/views/dialogue/charts/index.vue new file mode 100644 index 0000000..4e21027 --- /dev/null +++ b/src/views/dialogue/charts/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/views/dialogue/components/addfriendbindDialog.vue b/src/views/dialogue/components/addfriendbindDialog.vue new file mode 100644 index 0000000..257b77b --- /dev/null +++ b/src/views/dialogue/components/addfriendbindDialog.vue @@ -0,0 +1,88 @@ + + + + \ No newline at end of file diff --git a/src/views/dialogue/components/conversationList.vue b/src/views/dialogue/components/conversationList.vue new file mode 100644 index 0000000..b6f468e --- /dev/null +++ b/src/views/dialogue/components/conversationList.vue @@ -0,0 +1,74 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/components/conversationListItem.vue b/src/views/dialogue/components/conversationListItem.vue new file mode 100644 index 0000000..98b96a2 --- /dev/null +++ b/src/views/dialogue/components/conversationListItem.vue @@ -0,0 +1,657 @@ + + + + + diff --git a/src/views/dialogue/components/expandBox.vue b/src/views/dialogue/components/expandBox.vue new file mode 100644 index 0000000..64b6699 --- /dev/null +++ b/src/views/dialogue/components/expandBox.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/components/fileDownload.vue b/src/views/dialogue/components/fileDownload.vue new file mode 100644 index 0000000..045b9ad --- /dev/null +++ b/src/views/dialogue/components/fileDownload.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/views/dialogue/components/infoItem.vue b/src/views/dialogue/components/infoItem.vue new file mode 100644 index 0000000..3fcc11c --- /dev/null +++ b/src/views/dialogue/components/infoItem.vue @@ -0,0 +1,72 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/components/navBar.vue b/src/views/dialogue/components/navBar.vue new file mode 100644 index 0000000..0c2dd57 --- /dev/null +++ b/src/views/dialogue/components/navBar.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/src/views/dialogue/components/searchLogs.vue b/src/views/dialogue/components/searchLogs.vue new file mode 100644 index 0000000..330feac --- /dev/null +++ b/src/views/dialogue/components/searchLogs.vue @@ -0,0 +1,872 @@ + + + + + diff --git a/src/views/dialogue/components/unread.vue b/src/views/dialogue/components/unread.vue new file mode 100644 index 0000000..46fadff --- /dev/null +++ b/src/views/dialogue/components/unread.vue @@ -0,0 +1,61 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/groupChat/MessageItem/CatchMsgRenderer.vue b/src/views/dialogue/groupChat/MessageItem/CatchMsgRenderer.vue new file mode 100644 index 0000000..bd6310e --- /dev/null +++ b/src/views/dialogue/groupChat/MessageItem/CatchMsgRenderer.vue @@ -0,0 +1,10 @@ + + + + + diff --git a/src/views/dialogue/groupChat/MessageItem/GroupInfoRenderer.vue b/src/views/dialogue/groupChat/MessageItem/GroupInfoRenderer.vue new file mode 100644 index 0000000..0ea9344 --- /dev/null +++ b/src/views/dialogue/groupChat/MessageItem/GroupInfoRenderer.vue @@ -0,0 +1,500 @@ + + + + + diff --git a/src/views/dialogue/groupChat/MessageItem/MediaMessageRenderer.vue b/src/views/dialogue/groupChat/MessageItem/MediaMessageRenderer.vue new file mode 100644 index 0000000..0105adb --- /dev/null +++ b/src/views/dialogue/groupChat/MessageItem/MediaMessageRenderer.vue @@ -0,0 +1,75 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/groupChat/MessageItem/RedCardRenderer.vue b/src/views/dialogue/groupChat/MessageItem/RedCardRenderer.vue new file mode 100644 index 0000000..d278675 --- /dev/null +++ b/src/views/dialogue/groupChat/MessageItem/RedCardRenderer.vue @@ -0,0 +1,635 @@ + + + + + diff --git a/src/views/dialogue/groupChat/MessageItem/TextMessageRenderer.vue b/src/views/dialogue/groupChat/MessageItem/TextMessageRenderer.vue new file mode 100644 index 0000000..007903e --- /dev/null +++ b/src/views/dialogue/groupChat/MessageItem/TextMessageRenderer.vue @@ -0,0 +1,2058 @@ + + + + + diff --git a/src/views/dialogue/groupChat/MessageItem/index.vue b/src/views/dialogue/groupChat/MessageItem/index.vue new file mode 100644 index 0000000..4031a49 --- /dev/null +++ b/src/views/dialogue/groupChat/MessageItem/index.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/src/views/dialogue/groupChat/MessageItem/useMessageIsRead.js b/src/views/dialogue/groupChat/MessageItem/useMessageIsRead.js new file mode 100644 index 0000000..6ef9ae8 --- /dev/null +++ b/src/views/dialogue/groupChat/MessageItem/useMessageIsRead.js @@ -0,0 +1,52 @@ +import { useMessageStore } from "@/stores/modules/message"; +import { getSDK } from '@/utils/openIM' +import { useIntersectionObserver } from "@vueuse/core"; +import { onMounted } from 'vue' +import { storeToRefs } from 'pinia' +import { useUserStore } from "@/stores/user.js"; + +export function useMessageIsRead({ + messageContainerRef, + isSelfMsg, + isRead, + isPreView, + isGroupAnnounce, + clientMsgID, +}) { + const IMSDK = getSDK() + const userStore = useUserStore(); + const { groupChatConfig } = storeToRefs(userStore); + const messageStore = useMessageStore(); + + const markC2CAsRead = () => { + IMSDK.markMessagesAsReadByMsgID({ + conversationID: groupChatConfig.value.conversationID, + clientMsgIDList: [clientMsgID], + }); + messageStore.updateOneMessage({ + clientMsgID, + isRead: true, + isAppend: false, + }); + }; + + const getMessageVisible = () => { + if (isRead || isPreView || isGroupAnnounce) { + return; + } + + const { stop } = useIntersectionObserver( + messageContainerRef, + ([{ isIntersecting }], observerElement) => { + if (isIntersecting) { + markC2CAsRead(); + stop(); + } + } + ); + }; + + onMounted(() => { + getMessageVisible(); + }); +} diff --git a/src/views/dialogue/groupChat/chartFooter.vue b/src/views/dialogue/groupChat/chartFooter.vue new file mode 100644 index 0000000..c8c751c --- /dev/null +++ b/src/views/dialogue/groupChat/chartFooter.vue @@ -0,0 +1,2228 @@ + + + + + diff --git a/src/views/dialogue/groupChat/components/audio.vue b/src/views/dialogue/groupChat/components/audio.vue new file mode 100644 index 0000000..8ee6d68 --- /dev/null +++ b/src/views/dialogue/groupChat/components/audio.vue @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/groupChat/components/chartArtuser.vue b/src/views/dialogue/groupChat/components/chartArtuser.vue new file mode 100644 index 0000000..34edb6a --- /dev/null +++ b/src/views/dialogue/groupChat/components/chartArtuser.vue @@ -0,0 +1,470 @@ + + + diff --git a/src/views/dialogue/groupChat/components/checkTopMessage.vue b/src/views/dialogue/groupChat/components/checkTopMessage.vue new file mode 100644 index 0000000..c275542 --- /dev/null +++ b/src/views/dialogue/groupChat/components/checkTopMessage.vue @@ -0,0 +1,184 @@ + + + + diff --git a/src/views/dialogue/groupChat/components/emoji.vue b/src/views/dialogue/groupChat/components/emoji.vue new file mode 100644 index 0000000..1a69c8f --- /dev/null +++ b/src/views/dialogue/groupChat/components/emoji.vue @@ -0,0 +1,44 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/groupChat/components/media.vue b/src/views/dialogue/groupChat/components/media.vue new file mode 100644 index 0000000..3cb91d8 --- /dev/null +++ b/src/views/dialogue/groupChat/components/media.vue @@ -0,0 +1,433 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/groupChat/components/popover.vue b/src/views/dialogue/groupChat/components/popover.vue new file mode 100644 index 0000000..dde0e46 --- /dev/null +++ b/src/views/dialogue/groupChat/components/popover.vue @@ -0,0 +1,476 @@ + + + + + diff --git a/src/views/dialogue/groupChat/components/readList.vue b/src/views/dialogue/groupChat/components/readList.vue new file mode 100644 index 0000000..5080e72 --- /dev/null +++ b/src/views/dialogue/groupChat/components/readList.vue @@ -0,0 +1,168 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/groupChat/components/recorder.js b/src/views/dialogue/groupChat/components/recorder.js new file mode 100644 index 0000000..dbd3aac --- /dev/null +++ b/src/views/dialogue/groupChat/components/recorder.js @@ -0,0 +1,26 @@ + +import Recorder from 'recorder-core/recorder.mp3.min' //已包含recorder-core和mp3格式支持 +import { useWindowSize } from '@vant/use'; + +//可选的插件支持项 +import 'recorder-core/src/extensions/waveview' + + +export default function init() { + const { width, height } = useWindowSize(); + console.log(width.value) + let wave = Recorder.WaveView({ width: width.value, height: 10 }); + const rec = Recorder({ //本配置参数请参考下面的文档,有详细介绍 + type: "mp3", sampleRate: 16000, bitRate: 16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎 + , + onProcess: function (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) { + //录音实时回调,大约1秒调用12次本回调,buffers为开始到现在的所有录音pcm数据块(16位小端LE) + //可利用extensions/sonic.js插件实时变速变调,此插件计算量巨大,onProcess需要返回true开启异步模式 + //可实时上传(发送)数据,配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm上传,或使用mock方法将新数据连续的转码成其他格式上传,可以参考文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等 + //可实时绘制波形(extensions目录内的waveview.js、wavesurfer.view.js、frequency.histogram.view.js插件功能) + wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate); + } + }) + rec.wave = wave + return rec +} \ No newline at end of file diff --git a/src/views/dialogue/groupChat/components/redpktDetail.vue b/src/views/dialogue/groupChat/components/redpktDetail.vue new file mode 100644 index 0000000..a713803 --- /dev/null +++ b/src/views/dialogue/groupChat/components/redpktDetail.vue @@ -0,0 +1,535 @@ + + + + + diff --git a/src/views/dialogue/groupChat/components/selectUser.vue b/src/views/dialogue/groupChat/components/selectUser.vue new file mode 100644 index 0000000..ec304ba --- /dev/null +++ b/src/views/dialogue/groupChat/components/selectUser.vue @@ -0,0 +1,271 @@ + + + + + diff --git a/src/views/dialogue/groupChat/components/sendRedpkt.vue b/src/views/dialogue/groupChat/components/sendRedpkt.vue new file mode 100644 index 0000000..768aed0 --- /dev/null +++ b/src/views/dialogue/groupChat/components/sendRedpkt.vue @@ -0,0 +1,512 @@ + + + + + diff --git a/src/views/dialogue/groupChat/groupChat.js b/src/views/dialogue/groupChat/groupChat.js new file mode 100644 index 0000000..b295957 --- /dev/null +++ b/src/views/dialogue/groupChat/groupChat.js @@ -0,0 +1,164 @@ +import { ref, reactive } from 'vue'; +import { getSDK } from "@/utils/openIM"; +const OpenIM = getSDK(); +class BurnMsg { + constructor(readDeleteTime = 0) { + this.readDeleteTime = readDeleteTime; + } + + static fromJson(json) { + const readDeleteTime = json['read_delete_time'] ?? 0; + return new BurnMsg(readDeleteTime); + } + + toJson() { + const data = {}; + data['read_delete_time'] = this.readDeleteTime; + return data; + } +} +class ExElem { + constructor() { + this.burnMsg = null; + } + + static fromJson(json) { + const exElem = new ExElem(); + exElem.burnMsg = json['burn_msg'] !== null ? BurnMsg.fromJson(json['burn_msg']) : null; + return exElem; + } + + toJson() { + const data = {}; + data['burn_msg'] = this.burnMsg !== null ? this.burnMsg.toJson() : null; + return data; + } +} + +class LocalExElem { + constructor() { + this.status = null; + this.playState = null; + this.surplusRDTime = null; + } + + static fromJson(json) { + const localExElem = new LocalExElem(); + localExElem.status = json['status']; + localExElem.playState = json['playState']; + localExElem.surplusRDTime = json['surplusRDTime']; + return localExElem; + } + + toJson() { + const data = {}; + data['status'] = this.status; + data['playState'] = this.playState; + data['surplusRDTime'] = this.surplusRDTime; + return data; + } +} + + +export class MessageExData { + constructor(message) { + this.message = message; + this.exElem = null; + this.localExElem = null; + this.delete = false; + this.remainTime = 0; + + console.log("new==>" + message.ex + "," + message.localEx); + + if (message.localEx === "delete") { + this.delete = true; + return; + } + + if (message.ex !== null && message.ex !== "") { + try { + this.exElem = ExElem.fromJson(JSON.parse(message.ex)); + } catch (e) {} + } + + if (message.localEx !== null && message.localEx !== "") { + try { + if (message.localEx === "delete") { + this.delete = true; + } else { + this.localExElem = LocalExElem.fromJson(JSON.parse(message.localEx)); + } + } catch (e) {} + } + + this.exElem = this.exElem || new ExElem(); + this.localExElem = this.localExElem || new LocalExElem(); + + if (this.delete) { + this.remainTime = 0; + } else { + this.remainTime = this.localExElem.surplusRDTime || this.exElem.burnMsg?.readDeleteTime || 0; + } + + console.log("new==>remainTime=" + this.remainTime); + } + //标记这个消息删除 + setDelete() { + this.delete = true; + } + //语音消息-视频消息-文本消息-设置加载、播放完毕 + setPlayCompleted() { + this.localExElem.playState = "1"; + } + //语音消息-视频消息-文本消息返回 + isPlayCompleted() { + return this.localExElem.playState === "1"; + } + //设置阅读时间 + setReadDeleteTime(seconds) { + if (!this.exElem.burnMsg) { + this.exElem.burnMsg = new BurnMsg(seconds); + } else { + this.exElem.burnMsg.readDeleteTime = seconds; + } + } + //设置剩余阅读时间 + saveSurplusReadDeleteTime(seconds) { + if (this.delete) return; + this.localExElem.surplusRDTime = seconds; + } + + getLocalExStr() { + return JSON.stringify(this.localExElem.toJson()); + } + + downTime() { + this.remainTime--; + console.log("downTime:" + this.remainTime); + return this.remainTime; + } + + pause(conversationID) { + if (this.remainTime > 0) { + this.saveSurplusReadDeleteTime(this.remainTime); + } else { + this.setDelete(); + } + this.saveLocalExToDB(conversationID); + } + + saveLocalExToDB(conversationID) { + if (this.delete) { + this.message.localEx = "delete"; + } else { + this.message.localEx = JSON.stringify(this.localExElem.toJson()); + } + try { + OpenIM.iMManager.messageManager.setMessageLocalEx( + conversationID, + this.message.clientMsgID, + this.message.localEx || "" + ); + } catch (e) {} + } +} diff --git a/src/views/dialogue/groupChat/index.vue b/src/views/dialogue/groupChat/index.vue new file mode 100644 index 0000000..9de81c1 --- /dev/null +++ b/src/views/dialogue/groupChat/index.vue @@ -0,0 +1,1410 @@ + + + + + diff --git a/src/views/dialogue/index.vue b/src/views/dialogue/index.vue new file mode 100644 index 0000000..e858c2a --- /dev/null +++ b/src/views/dialogue/index.vue @@ -0,0 +1,795 @@ + + + + + diff --git a/src/views/dialogue/privateChat/components/audio.vue b/src/views/dialogue/privateChat/components/audio.vue new file mode 100644 index 0000000..4f2783d --- /dev/null +++ b/src/views/dialogue/privateChat/components/audio.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/views/dialogue/privateChat/components/chatSeting.vue b/src/views/dialogue/privateChat/components/chatSeting.vue new file mode 100644 index 0000000..b608c75 --- /dev/null +++ b/src/views/dialogue/privateChat/components/chatSeting.vue @@ -0,0 +1,802 @@ + + + + + diff --git a/src/views/dialogue/privateChat/components/chatSetingDetail.vue b/src/views/dialogue/privateChat/components/chatSetingDetail.vue new file mode 100644 index 0000000..5526bfb --- /dev/null +++ b/src/views/dialogue/privateChat/components/chatSetingDetail.vue @@ -0,0 +1,392 @@ + + + + + diff --git a/src/views/dialogue/privateChat/components/emoji.vue b/src/views/dialogue/privateChat/components/emoji.vue new file mode 100644 index 0000000..897a7b5 --- /dev/null +++ b/src/views/dialogue/privateChat/components/emoji.vue @@ -0,0 +1,48 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/privateChat/components/media.vue b/src/views/dialogue/privateChat/components/media.vue new file mode 100644 index 0000000..f15659a --- /dev/null +++ b/src/views/dialogue/privateChat/components/media.vue @@ -0,0 +1,335 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/privateChat/components/popover.vue b/src/views/dialogue/privateChat/components/popover.vue new file mode 100644 index 0000000..2b5e8fa --- /dev/null +++ b/src/views/dialogue/privateChat/components/popover.vue @@ -0,0 +1,309 @@ + + + + + \ No newline at end of file diff --git a/src/views/dialogue/privateChat/components/privateMessage.vue b/src/views/dialogue/privateChat/components/privateMessage.vue new file mode 100644 index 0000000..6066c4a --- /dev/null +++ b/src/views/dialogue/privateChat/components/privateMessage.vue @@ -0,0 +1,1743 @@ + + + + + diff --git a/src/views/dialogue/privateChat/components/recorder.js b/src/views/dialogue/privateChat/components/recorder.js new file mode 100644 index 0000000..2751688 --- /dev/null +++ b/src/views/dialogue/privateChat/components/recorder.js @@ -0,0 +1,26 @@ + +import Recorder from 'recorder-core/recorder.mp3.min' //已包含recorder-core和mp3格式支持 +import { useWindowSize } from '@vant/use'; + +//可选的插件支持项 +import 'recorder-core/src/extensions/waveview' + + +export default function init() { + const { width, height } = useWindowSize(); + let wave = Recorder.WaveView({ width: width.value, height: 10 }); + const rec = Recorder({ //本配置参数请参考下面的文档,有详细介绍 + type: "mp3", + sampleRate: 16000, + bitRate: 16, //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎 + onProcess: function (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) { + //录音实时回调,大约1秒调用12次本回调,buffers为开始到现在的所有录音pcm数据块(16位小端LE) + //可利用extensions/sonic.js插件实时变速变调,此插件计算量巨大,onProcess需要返回true开启异步模式 + //可实时上传(发送)数据,配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm上传,或使用mock方法将新数据连续的转码成其他格式上传,可以参考文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等 + //可实时绘制波形(extensions目录内的waveview.js、wavesurfer.view.js、frequency.histogram.view.js插件功能) + wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate); + } + }) + rec.wave = wave + return rec +} \ No newline at end of file diff --git a/src/views/dialogue/privateChat/index.vue b/src/views/dialogue/privateChat/index.vue new file mode 100644 index 0000000..e143fc7 --- /dev/null +++ b/src/views/dialogue/privateChat/index.vue @@ -0,0 +1,3938 @@ + + + + + diff --git a/src/views/dialogue/privateChat/privateChat.js b/src/views/dialogue/privateChat/privateChat.js new file mode 100644 index 0000000..b295957 --- /dev/null +++ b/src/views/dialogue/privateChat/privateChat.js @@ -0,0 +1,164 @@ +import { ref, reactive } from 'vue'; +import { getSDK } from "@/utils/openIM"; +const OpenIM = getSDK(); +class BurnMsg { + constructor(readDeleteTime = 0) { + this.readDeleteTime = readDeleteTime; + } + + static fromJson(json) { + const readDeleteTime = json['read_delete_time'] ?? 0; + return new BurnMsg(readDeleteTime); + } + + toJson() { + const data = {}; + data['read_delete_time'] = this.readDeleteTime; + return data; + } +} +class ExElem { + constructor() { + this.burnMsg = null; + } + + static fromJson(json) { + const exElem = new ExElem(); + exElem.burnMsg = json['burn_msg'] !== null ? BurnMsg.fromJson(json['burn_msg']) : null; + return exElem; + } + + toJson() { + const data = {}; + data['burn_msg'] = this.burnMsg !== null ? this.burnMsg.toJson() : null; + return data; + } +} + +class LocalExElem { + constructor() { + this.status = null; + this.playState = null; + this.surplusRDTime = null; + } + + static fromJson(json) { + const localExElem = new LocalExElem(); + localExElem.status = json['status']; + localExElem.playState = json['playState']; + localExElem.surplusRDTime = json['surplusRDTime']; + return localExElem; + } + + toJson() { + const data = {}; + data['status'] = this.status; + data['playState'] = this.playState; + data['surplusRDTime'] = this.surplusRDTime; + return data; + } +} + + +export class MessageExData { + constructor(message) { + this.message = message; + this.exElem = null; + this.localExElem = null; + this.delete = false; + this.remainTime = 0; + + console.log("new==>" + message.ex + "," + message.localEx); + + if (message.localEx === "delete") { + this.delete = true; + return; + } + + if (message.ex !== null && message.ex !== "") { + try { + this.exElem = ExElem.fromJson(JSON.parse(message.ex)); + } catch (e) {} + } + + if (message.localEx !== null && message.localEx !== "") { + try { + if (message.localEx === "delete") { + this.delete = true; + } else { + this.localExElem = LocalExElem.fromJson(JSON.parse(message.localEx)); + } + } catch (e) {} + } + + this.exElem = this.exElem || new ExElem(); + this.localExElem = this.localExElem || new LocalExElem(); + + if (this.delete) { + this.remainTime = 0; + } else { + this.remainTime = this.localExElem.surplusRDTime || this.exElem.burnMsg?.readDeleteTime || 0; + } + + console.log("new==>remainTime=" + this.remainTime); + } + //标记这个消息删除 + setDelete() { + this.delete = true; + } + //语音消息-视频消息-文本消息-设置加载、播放完毕 + setPlayCompleted() { + this.localExElem.playState = "1"; + } + //语音消息-视频消息-文本消息返回 + isPlayCompleted() { + return this.localExElem.playState === "1"; + } + //设置阅读时间 + setReadDeleteTime(seconds) { + if (!this.exElem.burnMsg) { + this.exElem.burnMsg = new BurnMsg(seconds); + } else { + this.exElem.burnMsg.readDeleteTime = seconds; + } + } + //设置剩余阅读时间 + saveSurplusReadDeleteTime(seconds) { + if (this.delete) return; + this.localExElem.surplusRDTime = seconds; + } + + getLocalExStr() { + return JSON.stringify(this.localExElem.toJson()); + } + + downTime() { + this.remainTime--; + console.log("downTime:" + this.remainTime); + return this.remainTime; + } + + pause(conversationID) { + if (this.remainTime > 0) { + this.saveSurplusReadDeleteTime(this.remainTime); + } else { + this.setDelete(); + } + this.saveLocalExToDB(conversationID); + } + + saveLocalExToDB(conversationID) { + if (this.delete) { + this.message.localEx = "delete"; + } else { + this.message.localEx = JSON.stringify(this.localExElem.toJson()); + } + try { + OpenIM.iMManager.messageManager.setMessageLocalEx( + conversationID, + this.message.clientMsgID, + this.message.localEx || "" + ); + } catch (e) {} + } +} diff --git a/src/views/dialogue/useHistoryMessageList.js b/src/views/dialogue/useHistoryMessageList.js new file mode 100644 index 0000000..bbf2522 --- /dev/null +++ b/src/views/dialogue/useHistoryMessageList.js @@ -0,0 +1,245 @@ +import { useMessageStore } from "@/stores/modules/message"; +import { storeToRefs } from 'pinia' +import emitter from "@/utils/events"; +import { useThrottleFn } from "@vueuse/core"; +import { useUserStore } from "@/stores/user.js"; +import { useContactStore } from "@/stores/contact"; +import { computed, ref, reactive, watch, onMounted, onUnmounted } from "vue"; + +export default function useHistoryMessageList({ + cancelMultiple = () => {} +}) { + const userStore = useUserStore(); + const contactStore = useContactStore(); + const { groupId} = storeToRefs(contactStore); + const { userInfo, groupChatList, groupChatConfig } = storeToRefs(userStore); + const messageStore = useMessageStore(); + const { getHistoryMessageListFromReq, resetHistoryMessageList } = messageStore; + const { storeHistoryMessageList, storeHistoryMessageHasMore } = storeToRefs(messageStore); + const vsl = ref(); + const overflow = ref(false); + const isFirstPage = ref(true); + const loadState = reactive({ + loading: false, + lastMinSeq: 0, + }); + const initLoading = ref(false); + const notScroll = ref(false); + + const unReadCount = computed( + () => storeHistoryMessageList.value.filter( + (message) => message.isAppend === true + ).length + ); + + const onScoll = useThrottleFn(() => { + if(!vsl.value)return + const scrollVal = vsl.value.getScrollSize() - vsl.value.getOffset() > vsl.value.getClientSize() * 1.3; + notScroll.value = scrollVal + }, 500); + + const setAllReadedAndScrollToBottom = () => { + const msgIds = [] + storeHistoryMessageList.value.forEach(message => { + if (message.isAppend) { + msgIds.push(message.clientMsgID) + message.isAppend = false + } + }) + + setVirtualListToBottom() + return msgIds + } + + const getScrollPosition = () => { + if(!vsl.value)return false + const scrollVal = vsl.value.getScrollSize() - vsl.value.getOffset() > vsl.value.getClientSize() * 1.3; + return scrollVal + } + + const onTotop = async () => { + if (storeHistoryMessageHasMore && !loadState.loading) { + const { messageIDList } = await getMessageData(); + await nextTick(); + getOffset(messageIDList); + } + }; + + const getMessageData = async () => { + const hrefUrl = window.location.href.split('/') + loadState.loading = true; + //存在一个问题groupChatConfig或者groupId.value取不到值,或者取的值是上一个群聊里面的id值 + const data = await getHistoryMessageListFromReq({ + // conversationID:groupChatConfig.value.conversationID, + conversationID:'sg_'+hrefUrl[hrefUrl.length-1],//groupChatConfig.value.conversationID, + userID: "", + // groupID:groupId.value, + groupID:hrefUrl[hrefUrl.length-1], + count: 30, + startClientMsgID: + storeHistoryMessageList.value[0]?.clientMsgID || "", + lastMinSeq: isFirstPage.value ? 0 : loadState.lastMinSeq, + }); + if (data.lastMinSeq) { + loadState.lastMinSeq = data.lastMinSeq; + } + return data; + }; + + const onItemRendered = () => { + if (!vsl.value || !isFirstPage.value) { + return; + } + const els = Array.from(document.querySelectorAll(".need_preload_message")); + const idx = els.findIndex((el) => el.clientHeight < 2); + + if (idx === -1) { + // first page items are all mounted, scroll to bottom + setTimeout(() => { + setVirtualListToBottom(); + initLoading.value = false; + loadState.loading = false; + }); + isFirstPage.value = false; + checkOverFlow(); + } + }; + + const getOffset = (clientMsgIDList) => { + const offset = clientMsgIDList.reduce((previousValue, currentSid) => { + const previousSize = + typeof previousValue === "string" && previousValue !== 0 + ? vsl.value.getSize(previousValue) + : previousValue; + return previousSize + vsl.value.getSize(currentSid); + }, 0); + setVirtualListToOffset(Number(offset)); + }; + + const setVirtualListToOffset = (offset) => { + if (vsl.value) { + vsl.value.scrollToOffset(offset); + nextTick(() => (loadState.loading = false)); + } + }; + + const checkOverFlow = () => { + if (vsl.value) { + overflow.value = vsl.value.getScrollSize() > vsl.value.getClientSize(); + } + }; + + const setVirtualListToBottom = (isAppend) => { + if (isAppend && notScroll.value) { + return; + } + if (vsl.value) { + nextTick(() => vsl.value.scrollToBottom()); + } + }; + + const setVirtualListToIndex = (idx) => { + if (vsl.value) { + vsl.value.scrollToIndex(idx); + } + }; + + const scroll2ClientMsgID = (clientMsgID) => { + const idx = storeHistoryMessageList.value.findIndex( + (message) => message.clientMsgID === clientMsgID + ); + if (idx > -1) { + storeHistoryMessageList.value[idx].jump = true; + setTimeout(() => { + storeHistoryMessageList.value[idx].jump = false; + }, 3000); + setVirtualListToIndex(idx); + } + }; + + const scrollToUnread = () => { + const idx = storeHistoryMessageList.value.findIndex( + (message) => message.isAppend === true && !message.isRead + ); + console.log(idx); + + if (idx > -1) { + setVirtualListToIndex(idx); + } + }; + + /** + * 获取群聊记录数据 + * @returns + */ + const getGroupChatList = () =>{ + return storeHistoryMessageList.value || [] + }; + // events + const setEventListener = () => { + emitter.on("CHAT_MAIN_SCROLL_TO_BOTTOM", setVirtualListToBottom); + emitter.on("CHAT_MAIN_SCROLL_TO_CLIENTMSGID", scroll2ClientMsgID); + }; + + const disposeEvemtListener = () => { + emitter.off("CHAT_MAIN_SCROLL_TO_BOTTOM", setVirtualListToBottom); + emitter.off("CHAT_MAIN_SCROLL_TO_CLIENTMSGID", scroll2ClientMsgID); + }; + onMounted(() => { + setEventListener(); + }); + onUnmounted(() => { + disposeEvemtListener(); + }); + + watch( + () => groupChatConfig.value.conversationID, + async (newVal) => { + if (newVal) { + cancelMultiple(); + isFirstPage.value = true; + resetHistoryMessageList(); + initLoading.value = true; + // const { messageIDList } = await getMessageData(); + // if (messageIDList.length === 0) { + // isFirstPage.value = false; + // initLoading.value = false; + // loadState.loading = false; + // } + } + }, + { + immediate: true, + } + ); + const getChatList_kds = async () =>{ + cancelMultiple(); + isFirstPage.value = true; + resetHistoryMessageList(); + initLoading.value = true; + const { messageIDList } = await getMessageData(); + if (messageIDList.length === 0) { + isFirstPage.value = false; + initLoading.value = false; + loadState.loading = false; + } + } + return { + vsl, + overflow, + initLoading, + loadState, + notScroll, + unReadCount, + setAllReadedAndScrollToBottom, + onTotop, + onItemRendered, + onScoll, + getScrollPosition, + scrollToUnread, + scroll2ClientMsgID, + getMessageData, + getGroupChatList, + getChatList_kds + }; +} diff --git a/src/views/index.vue b/src/views/index.vue new file mode 100644 index 0000000..c5d638f --- /dev/null +++ b/src/views/index.vue @@ -0,0 +1,784 @@ + + + + + diff --git a/src/views/log/logDisplay.vue b/src/views/log/logDisplay.vue new file mode 100644 index 0000000..0c69025 --- /dev/null +++ b/src/views/log/logDisplay.vue @@ -0,0 +1,392 @@ + + + + + \ No newline at end of file diff --git a/src/views/login.vue b/src/views/login.vue new file mode 100644 index 0000000..8d160c2 --- /dev/null +++ b/src/views/login.vue @@ -0,0 +1,1084 @@ + + + + + diff --git a/src/views/login/components/firstScreen.vue b/src/views/login/components/firstScreen.vue new file mode 100644 index 0000000..802fc45 --- /dev/null +++ b/src/views/login/components/firstScreen.vue @@ -0,0 +1,578 @@ + + + diff --git a/src/views/login/components/forgotPassword.vue b/src/views/login/components/forgotPassword.vue new file mode 100644 index 0000000..c2e8480 --- /dev/null +++ b/src/views/login/components/forgotPassword.vue @@ -0,0 +1,461 @@ + + + diff --git a/src/views/login/components/otherLogin.vue b/src/views/login/components/otherLogin.vue new file mode 100644 index 0000000..3b355ba --- /dev/null +++ b/src/views/login/components/otherLogin.vue @@ -0,0 +1,1196 @@ + + + diff --git a/src/views/login/components/otherSignin.vue b/src/views/login/components/otherSignin.vue new file mode 100644 index 0000000..01f048b --- /dev/null +++ b/src/views/login/components/otherSignin.vue @@ -0,0 +1,538 @@ + + + \ No newline at end of file diff --git a/src/views/login/components/policy-dialog.vue b/src/views/login/components/policy-dialog.vue new file mode 100644 index 0000000..f1499c3 --- /dev/null +++ b/src/views/login/components/policy-dialog.vue @@ -0,0 +1,89 @@ + + + + + \ No newline at end of file diff --git a/src/views/login/components/resetPassword.vue b/src/views/login/components/resetPassword.vue new file mode 100644 index 0000000..ffad254 --- /dev/null +++ b/src/views/login/components/resetPassword.vue @@ -0,0 +1,214 @@ + + + \ No newline at end of file diff --git a/src/views/login/components/setAvatar.vue b/src/views/login/components/setAvatar.vue new file mode 100644 index 0000000..75c64d1 --- /dev/null +++ b/src/views/login/components/setAvatar.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/src/views/login/components/setPassword.vue b/src/views/login/components/setPassword.vue new file mode 100644 index 0000000..6d64c83 --- /dev/null +++ b/src/views/login/components/setPassword.vue @@ -0,0 +1,248 @@ + + + \ No newline at end of file diff --git a/src/views/login/components/signinAccount.vue b/src/views/login/components/signinAccount.vue new file mode 100644 index 0000000..c25fa16 --- /dev/null +++ b/src/views/login/components/signinAccount.vue @@ -0,0 +1,297 @@ + + + \ No newline at end of file diff --git a/src/views/login/components/signinAccount备份.vue b/src/views/login/components/signinAccount备份.vue new file mode 100644 index 0000000..bc39ca8 --- /dev/null +++ b/src/views/login/components/signinAccount备份.vue @@ -0,0 +1,347 @@ + + + \ No newline at end of file diff --git a/src/views/login/components/userService.vue b/src/views/login/components/userService.vue new file mode 100644 index 0000000..2d2e6d7 --- /dev/null +++ b/src/views/login/components/userService.vue @@ -0,0 +1,222 @@ + + + + \ No newline at end of file diff --git a/src/views/login/components/verify.vue b/src/views/login/components/verify.vue new file mode 100644 index 0000000..e659c32 --- /dev/null +++ b/src/views/login/components/verify.vue @@ -0,0 +1,239 @@ + + + \ No newline at end of file diff --git a/src/views/login/components/welcomePage.vue b/src/views/login/components/welcomePage.vue new file mode 100644 index 0000000..edfd651 --- /dev/null +++ b/src/views/login/components/welcomePage.vue @@ -0,0 +1,217 @@ + + + + diff --git a/src/views/login/login.js b/src/views/login/login.js new file mode 100644 index 0000000..e50ad96 --- /dev/null +++ b/src/views/login/login.js @@ -0,0 +1,33 @@ + + + +const loginClick = async () => { + if (!formData.value.username || !formData.value.password) return showToast('请先输入账号或密码') + if (!data.form.checked) return agreementDialog.value = true + const formDataValue = formData.value; + const formDataClone = {}; + formDataClone.device_id = useUid(); + formDataClone.device_model = navigator.userAgent; + formDataClone.platform = 5; // 1ios 2安卓 3pc 4h5 + formDataClone.login_type = 1; // 登录类型 1用户名 2手机号码 3邮箱 + formDataClone.username = formDataValue.username; + formDataClone.password = encrypt(formDataValue.password); + console.log(formDataClone); + login(formDataClone) +} +const login = async (formDataClone) => { + const loginRes = await $api.login.login(formDataClone); + if (loginRes.code !== 0) return; + const { token, im_token, user_id } = loginRes.data; + localStorage.setItem('token', token) + localStorage.setItem('im_token', im_token) + localStorage.setItem('user_id', user_id) + showToast({ position: "top", message: "登录成功" }); + const userRes = await $api.login.userInfo(); + setUserInfo(userRes.data); + //二次登录 + if(IMSDK){ + await IMSDK.login(im_config()) + } + router.push({ path: '/home' }); +} \ No newline at end of file diff --git a/src/views/my/about/index.vue b/src/views/my/about/index.vue new file mode 100644 index 0000000..44c57e3 --- /dev/null +++ b/src/views/my/about/index.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/views/my/account/index.vue b/src/views/my/account/index.vue new file mode 100644 index 0000000..214044b --- /dev/null +++ b/src/views/my/account/index.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/views/my/account/info/index.vue b/src/views/my/account/info/index.vue new file mode 100644 index 0000000..117f199 --- /dev/null +++ b/src/views/my/account/info/index.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/views/my/account/info/modify_name/details/index.vue b/src/views/my/account/info/modify_name/details/index.vue new file mode 100644 index 0000000..68967a4 --- /dev/null +++ b/src/views/my/account/info/modify_name/details/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/views/my/account/info/modify_name/index.vue b/src/views/my/account/info/modify_name/index.vue new file mode 100644 index 0000000..f6e84d5 --- /dev/null +++ b/src/views/my/account/info/modify_name/index.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/src/views/my/account/info/phone/index.vue b/src/views/my/account/info/phone/index.vue new file mode 100644 index 0000000..5b8c78b --- /dev/null +++ b/src/views/my/account/info/phone/index.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/views/my/appearance/appBgConf.vue b/src/views/my/appearance/appBgConf.vue new file mode 100644 index 0000000..0ca92ad --- /dev/null +++ b/src/views/my/appearance/appBgConf.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/src/views/my/appearance/index.vue b/src/views/my/appearance/index.vue new file mode 100644 index 0000000..4399d19 --- /dev/null +++ b/src/views/my/appearance/index.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/src/views/my/appearance/previewContent.vue b/src/views/my/appearance/previewContent.vue new file mode 100644 index 0000000..2ba8741 --- /dev/null +++ b/src/views/my/appearance/previewContent.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/views/my/balance/applyLog.vue b/src/views/my/balance/applyLog.vue new file mode 100644 index 0000000..3529e8b --- /dev/null +++ b/src/views/my/balance/applyLog.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/src/views/my/balance/balanceDetail.vue b/src/views/my/balance/balanceDetail.vue new file mode 100644 index 0000000..bf84a2e --- /dev/null +++ b/src/views/my/balance/balanceDetail.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/src/views/my/balance/component/balanceRecord.vue b/src/views/my/balance/component/balanceRecord.vue new file mode 100644 index 0000000..c781a02 --- /dev/null +++ b/src/views/my/balance/component/balanceRecord.vue @@ -0,0 +1,99 @@ + + + \ No newline at end of file diff --git a/src/views/my/balance/component/recordItem.vue b/src/views/my/balance/component/recordItem.vue new file mode 100644 index 0000000..1cd3db0 --- /dev/null +++ b/src/views/my/balance/component/recordItem.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/src/views/my/balance/index.vue b/src/views/my/balance/index.vue new file mode 100644 index 0000000..a808749 --- /dev/null +++ b/src/views/my/balance/index.vue @@ -0,0 +1,235 @@ + + + + + diff --git a/src/views/my/balance/redPacket.vue b/src/views/my/balance/redPacket.vue new file mode 100644 index 0000000..c1b6908 --- /dev/null +++ b/src/views/my/balance/redPacket.vue @@ -0,0 +1,426 @@ + + + + diff --git a/src/views/my/balance/withdrawal.vue b/src/views/my/balance/withdrawal.vue new file mode 100644 index 0000000..d6dd91a --- /dev/null +++ b/src/views/my/balance/withdrawal.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/src/views/my/download/index.vue b/src/views/my/download/index.vue new file mode 100644 index 0000000..fe53c01 --- /dev/null +++ b/src/views/my/download/index.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/src/views/my/index.vue b/src/views/my/index.vue new file mode 100644 index 0000000..61eb0ab --- /dev/null +++ b/src/views/my/index.vue @@ -0,0 +1,511 @@ + + + + + diff --git a/src/views/my/language/index.vue b/src/views/my/language/index.vue new file mode 100644 index 0000000..18c3b63 --- /dev/null +++ b/src/views/my/language/index.vue @@ -0,0 +1,320 @@ + + + + + diff --git a/src/views/my/network/circuit.vue b/src/views/my/network/circuit.vue new file mode 100644 index 0000000..9dbb08a --- /dev/null +++ b/src/views/my/network/circuit.vue @@ -0,0 +1,472 @@ + + + + diff --git a/src/views/my/network/diagnosis.vue b/src/views/my/network/diagnosis.vue new file mode 100644 index 0000000..a6d9be5 --- /dev/null +++ b/src/views/my/network/diagnosis.vue @@ -0,0 +1,727 @@ + + + + diff --git a/src/views/my/notDisturb/index.vue b/src/views/my/notDisturb/index.vue new file mode 100644 index 0000000..732b3b2 --- /dev/null +++ b/src/views/my/notDisturb/index.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/src/views/my/profile/applicationRecord.vue b/src/views/my/profile/applicationRecord.vue new file mode 100644 index 0000000..4b66fe5 --- /dev/null +++ b/src/views/my/profile/applicationRecord.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/src/views/my/profile/components/recordItem.vue b/src/views/my/profile/components/recordItem.vue new file mode 100644 index 0000000..54a4b03 --- /dev/null +++ b/src/views/my/profile/components/recordItem.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/views/my/profile/index.vue b/src/views/my/profile/index.vue new file mode 100644 index 0000000..f048301 --- /dev/null +++ b/src/views/my/profile/index.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/src/views/my/profile/selectAvatar.vue b/src/views/my/profile/selectAvatar.vue new file mode 100644 index 0000000..b12fed2 --- /dev/null +++ b/src/views/my/profile/selectAvatar.vue @@ -0,0 +1,373 @@ + + + + + diff --git a/src/views/my/profile/sign.vue b/src/views/my/profile/sign.vue new file mode 100644 index 0000000..9826aac --- /dev/null +++ b/src/views/my/profile/sign.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/views/my/profile/user.vue b/src/views/my/profile/user.vue new file mode 100644 index 0000000..6848364 --- /dev/null +++ b/src/views/my/profile/user.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/src/views/my/qrcode/index.vue b/src/views/my/qrcode/index.vue new file mode 100644 index 0000000..c567deb --- /dev/null +++ b/src/views/my/qrcode/index.vue @@ -0,0 +1,443 @@ + + + + + diff --git a/src/views/my/security/bindEmail.vue b/src/views/my/security/bindEmail.vue new file mode 100644 index 0000000..37c65c6 --- /dev/null +++ b/src/views/my/security/bindEmail.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/src/views/my/security/bindPhone.vue b/src/views/my/security/bindPhone.vue new file mode 100644 index 0000000..906a0d3 --- /dev/null +++ b/src/views/my/security/bindPhone.vue @@ -0,0 +1,358 @@ + + + + + diff --git a/src/views/my/security/component/payDialog.vue b/src/views/my/security/component/payDialog.vue new file mode 100644 index 0000000..3b94b30 --- /dev/null +++ b/src/views/my/security/component/payDialog.vue @@ -0,0 +1,30 @@ + + + \ No newline at end of file diff --git a/src/views/my/security/component/stepThree.vue b/src/views/my/security/component/stepThree.vue new file mode 100644 index 0000000..4fa4560 --- /dev/null +++ b/src/views/my/security/component/stepThree.vue @@ -0,0 +1,63 @@ + + + diff --git a/src/views/my/security/component/stepTwo.vue b/src/views/my/security/component/stepTwo.vue new file mode 100644 index 0000000..329961e --- /dev/null +++ b/src/views/my/security/component/stepTwo.vue @@ -0,0 +1,62 @@ + + + diff --git a/src/views/my/security/component/unBindDialog.vue b/src/views/my/security/component/unBindDialog.vue new file mode 100644 index 0000000..91cf190 --- /dev/null +++ b/src/views/my/security/component/unBindDialog.vue @@ -0,0 +1,150 @@ + + + + \ No newline at end of file diff --git a/src/views/my/security/device.vue b/src/views/my/security/device.vue new file mode 100644 index 0000000..a2fe9c2 --- /dev/null +++ b/src/views/my/security/device.vue @@ -0,0 +1,235 @@ + + + + + diff --git a/src/views/my/security/deviceDetail.vue b/src/views/my/security/deviceDetail.vue new file mode 100644 index 0000000..fc6d05f --- /dev/null +++ b/src/views/my/security/deviceDetail.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/src/views/my/security/email.vue b/src/views/my/security/email.vue new file mode 100644 index 0000000..49dcb9c --- /dev/null +++ b/src/views/my/security/email.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/views/my/security/emailNext.vue b/src/views/my/security/emailNext.vue new file mode 100644 index 0000000..7e8c027 --- /dev/null +++ b/src/views/my/security/emailNext.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/src/views/my/security/index.vue b/src/views/my/security/index.vue new file mode 100644 index 0000000..557a32b --- /dev/null +++ b/src/views/my/security/index.vue @@ -0,0 +1,698 @@ + + + + + diff --git a/src/views/my/security/logout/logoutAccount.vue b/src/views/my/security/logout/logoutAccount.vue new file mode 100644 index 0000000..9a12f56 --- /dev/null +++ b/src/views/my/security/logout/logoutAccount.vue @@ -0,0 +1,213 @@ + + + + + diff --git a/src/views/my/security/logout/restoreAccount.vue b/src/views/my/security/logout/restoreAccount.vue new file mode 100644 index 0000000..c40cc61 --- /dev/null +++ b/src/views/my/security/logout/restoreAccount.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/views/my/security/logout/secureVerify.vue b/src/views/my/security/logout/secureVerify.vue new file mode 100644 index 0000000..91e6207 --- /dev/null +++ b/src/views/my/security/logout/secureVerify.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/src/views/my/security/logout/submitSuccess.vue b/src/views/my/security/logout/submitSuccess.vue new file mode 100644 index 0000000..68c076b --- /dev/null +++ b/src/views/my/security/logout/submitSuccess.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/src/views/my/security/password.vue b/src/views/my/security/password.vue new file mode 100644 index 0000000..ea37a07 --- /dev/null +++ b/src/views/my/security/password.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/src/views/my/security/pay/emailVerify.vue b/src/views/my/security/pay/emailVerify.vue new file mode 100644 index 0000000..b7b83ff --- /dev/null +++ b/src/views/my/security/pay/emailVerify.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/src/views/my/security/pay/modifyPassword.vue b/src/views/my/security/pay/modifyPassword.vue new file mode 100644 index 0000000..1a2f1a7 --- /dev/null +++ b/src/views/my/security/pay/modifyPassword.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/src/views/my/security/pay/phoneVerify.vue b/src/views/my/security/pay/phoneVerify.vue new file mode 100644 index 0000000..204f652 --- /dev/null +++ b/src/views/my/security/pay/phoneVerify.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/src/views/my/security/phone.vue b/src/views/my/security/phone.vue new file mode 100644 index 0000000..cd73a5a --- /dev/null +++ b/src/views/my/security/phone.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/views/my/security/phoneNext.vue b/src/views/my/security/phoneNext.vue new file mode 100644 index 0000000..c5bd569 --- /dev/null +++ b/src/views/my/security/phoneNext.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/src/views/my/security/setPassword.vue b/src/views/my/security/setPassword.vue new file mode 100644 index 0000000..3b5418f --- /dev/null +++ b/src/views/my/security/setPassword.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/src/views/my/typeface/index.vue b/src/views/my/typeface/index.vue new file mode 100644 index 0000000..f12ef5a --- /dev/null +++ b/src/views/my/typeface/index.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/src/views/sw.produce.min.2.1.6.js b/src/views/sw.produce.min.2.1.6.js new file mode 100644 index 0000000..67a18b5 --- /dev/null +++ b/src/views/sw.produce.min.2.1.6.js @@ -0,0 +1 @@ +(()=>{"use strict";const t={cache:"no-cache",headers:{},redirect:"follow",mode:"cors"};function e(e,s="POST",a,n,i="json"){var o;const c=n||{};const r=Object.assign(t,{method:s});Object.keys(t.headers||{}).length>0&&(r.headers=t.headers);const l=Object.assign(r,c);if(l.method=null===(o=l.method)||void 0===o?void 0:o.toUpperCase(),"GET"===l.method){if(a){let t="";const s=/([?&])_=[^&]*/,n=/\?/;"object"==typeof a&&(t=function(t){let e="";for(const s in t)t.hasOwnProperty(s)&&(e+=s+"="+encodeURIComponent(t[s])+"&");return e.slice(0,-1)}(a)),e=s.test(e)?e.replace(s,"$1"+t):e+(n.test(e)?"&":"?")+t}}else[FormData,ArrayBuffer,Blob].filter((t=>a instanceof t)).length>0?l.body=a:(l.headers=Object.assign({"Content-Type":"application/json"},l.headers),a&&(l.body="string"==typeof a?a:JSON.stringify(a)));return fetch(e,l).then((t=>{const{status:e}=t;if(e>=200&&e<400){let e;switch(i){case"json":e=new Promise((e=>{t.text().then((t=>{try{e(JSON.parse(t))}catch(s){e(t)}}))}));break;case"text":e=t.text();break;case"blob":e=t.blob();break;case"arraybuffer":e=t.arrayBuffer();break;default:e=t.json()}return e}return Promise.reject(t)})).catch((t=>Promise.reject(t)))}const s=(t,s,a,n)=>e(t,"POST",s,a,n);function a(t,e,a="W3Push"){t.forEach((t=>{const e=(new Date).getTime()/1e3|0;t.itime=e,t.msg_id&&(t.msg_id=t.msg_id.toString())}));const n={platform:"b",uid:e.engagelab_uid,app_key:e.engagelab_appkey,channel:a,content:t};s("https://webpushstat.api.engagelab.cc/v4/web/report",n,{headers:{Authorization:"Basic "+btoa(n.uid+":"+e.engagelab_passwd)}})}self.addEventListener("install",(function(){self.skipWaiting()})),self.addEventListener("message",(function(t){const e=t.data||{code:0,msg:{}};var n;9999===e.code&&(n=e.msg,s("https://webpushstat.api.engagelab.cc/v4/web/report",n.data,{headers:{Authorization:n.Authorization}})),6666===t.data.code&&a([{type:"msg_status",msg_id:e.msg.msg_id,result:3018}],e.msg)})),self.addEventListener("activate",(function(){return self.clients.claim()})),self.addEventListener("notificationclick",(function(t){const e=t.notification.data;if(e){const s="MTPush"===e.engagelab_mesg_type?"MTPush":"W3Push";a([{type:"msg_status",msg_id:t.notification.tag,result:3002}],e,s);let n=null==e?void 0:e.engagelab_url;t.action&&e.engagelab_action_urls&&e.engagelab_action_urls[t.action]&&(n=e.engagelab_action_urls[t.action]),n&&(t.notification.close(),t.waitUntil(self.clients.openWindow(n)))}})),self.addEventListener("notificationclose",(function(t){})),self.addEventListener("push",(function(t){if(!t.data)return;const e=t.data.json();var s;if(e.locationTestDempWebPush52755665)self.registration.showNotification(e.title,e);else if(a([{type:"msg_status",msg_id:e.tag,result:3001}],e.data),self.clients.matchAll().then((t=>{t&&t.length&&t.forEach((t=>{t.postMessage(e)}))})),(s=e.data.ntf_or_msg)&&(1===s||3===s)){a([{type:"msg_status",msg_id:e.tag,result:3018}],e.data);const s=self.registration.showNotification(e.title,e);t.waitUntil(s)}}))})(); \ No newline at end of file diff --git a/src/views/transmit/contactPerson.vue b/src/views/transmit/contactPerson.vue new file mode 100644 index 0000000..dc00e7f --- /dev/null +++ b/src/views/transmit/contactPerson.vue @@ -0,0 +1,698 @@ + + + + + diff --git a/src/views/transmit/selectGroupChat.vue b/src/views/transmit/selectGroupChat.vue new file mode 100644 index 0000000..f96ae1b --- /dev/null +++ b/src/views/transmit/selectGroupChat.vue @@ -0,0 +1,464 @@ + + + + + \ No newline at end of file diff --git a/src/views/transmit/transmitFriendGroup.vue b/src/views/transmit/transmitFriendGroup.vue new file mode 100644 index 0000000..dd623d8 --- /dev/null +++ b/src/views/transmit/transmitFriendGroup.vue @@ -0,0 +1,230 @@ + + + + + \ No newline at end of file diff --git a/src/views/transmit/transmitGroupDetails.vue b/src/views/transmit/transmitGroupDetails.vue new file mode 100644 index 0000000..9a32366 --- /dev/null +++ b/src/views/transmit/transmitGroupDetails.vue @@ -0,0 +1,650 @@ + + + + + diff --git a/src/views/transmit/transmitPage.vue b/src/views/transmit/transmitPage.vue new file mode 100644 index 0000000..c889c78 --- /dev/null +++ b/src/views/transmit/transmitPage.vue @@ -0,0 +1,982 @@ + + + + + \ No newline at end of file diff --git a/src/views/unocss/index.vue b/src/views/unocss/index.vue new file mode 100644 index 0000000..5fa25f0 --- /dev/null +++ b/src/views/unocss/index.vue @@ -0,0 +1,12 @@ + + + diff --git a/tests/index.spec.ts b/tests/index.spec.ts new file mode 100644 index 0000000..279ce3c --- /dev/null +++ b/tests/index.spec.ts @@ -0,0 +1,3 @@ +it('first test', () => { + expect(1 + 1).toBe(2) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9431577 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "esnext", + "jsx": "preserve", + "lib": ["esnext", "dom", "dom.iterable", "scripthost"], + "experimentalDecorators": true, + "baseUrl": ".", + "module": "esnext", + "moduleResolution": "node", + "paths": { + "@/*": ["src/*"] + }, + "types": ["node"], + "allowJs": true, + "strictNullChecks": false, + "noImplicitAny": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "importHelpers": true, + "sourceMap": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": [ + "src/App.vue", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "tests/**/*.ts", + "tests/**/*.tsx", + "components.d.ts", + "auto-imports.d.ts", + "tests/*.ts" +, "src/views/my/language/index.js", "src/views/dialogue/groupChat/MessageItem/useMessageIsRead.js" ] +} diff --git a/typed-router.d.ts b/typed-router.d.ts new file mode 100644 index 0000000..ef0ff02 --- /dev/null +++ b/typed-router.d.ts @@ -0,0 +1,154 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️ +// It's recommended to commit this file. +// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry. + +/// + +import type { + // type safe route locations + RouteLocationTypedList, + RouteLocationResolvedTypedList, + RouteLocationNormalizedTypedList, + RouteLocationNormalizedLoadedTypedList, + RouteLocationAsString, + RouteLocationAsRelativeTypedList, + RouteLocationAsPathTypedList, + + // helper types + // route definitions + RouteRecordInfo, + ParamValue, + ParamValueOneOrMore, + ParamValueZeroOrMore, + ParamValueZeroOrOne, + + // vue-router extensions + _RouterTyped, + RouterLinkTyped, + RouterLinkPropsTyped, + NavigationGuard, + UseLinkFnTyped, + + // data fetching + _DataLoader, + _DefineLoaderOptions, +} from 'unplugin-vue-router/types' + +declare module 'vue-router/auto/routes' { + export interface RouteNamedMap { + '/': RouteRecordInfo<'/', '/', Record, Record>, + '/charts/': RouteRecordInfo<'/charts/', '/charts', Record, Record>, + '/contact/': RouteRecordInfo<'/contact/', '/contact', Record, Record>, + '/dialogue/': RouteRecordInfo<'/dialogue/', '/dialogue', Record, Record>, + '/dialogue/charts/': RouteRecordInfo<'/dialogue/charts/', '/dialogue/charts', Record, Record>, + '/dialogue/mock/': RouteRecordInfo<'/dialogue/mock/', '/dialogue/mock', Record, Record>, + '/mock/': RouteRecordInfo<'/mock/', '/mock', Record, Record>, + '/my/': RouteRecordInfo<'/my/', '/my', Record, Record>, + '/my/account/': RouteRecordInfo<'/my/account/', '/my/account', Record, Record>, + '/my/account/info/': RouteRecordInfo<'/my/account/info/', '/my/account/info', Record, Record>, + '/my/account/info/modify_name/': RouteRecordInfo<'/my/account/info/modify_name/', '/my/account/info/modify_name', Record, Record>, + '/my/account/info/modify_name/details/': RouteRecordInfo<'/my/account/info/modify_name/details/', '/my/account/info/modify_name/details', Record, Record>, + '/my/account/info/phone/': RouteRecordInfo<'/my/account/info/phone/', '/my/account/info/phone', Record, Record>, + '/unocss/': RouteRecordInfo<'/unocss/', '/unocss', Record, Record>, + } +} + +declare module 'vue-router/auto' { + import type { RouteNamedMap } from 'vue-router/auto/routes' + + export type RouterTyped = _RouterTyped + + /** + * Type safe version of `RouteLocationNormalized` (the type of `to` and `from` in navigation guards). + * Allows passing the name of the route to be passed as a generic. + */ + export type RouteLocationNormalized = RouteLocationNormalizedTypedList[Name] + + /** + * Type safe version of `RouteLocationNormalizedLoaded` (the return type of `useRoute()`). + * Allows passing the name of the route to be passed as a generic. + */ + export type RouteLocationNormalizedLoaded = RouteLocationNormalizedLoadedTypedList[Name] + + /** + * Type safe version of `RouteLocationResolved` (the returned route of `router.resolve()`). + * Allows passing the name of the route to be passed as a generic. + */ + export type RouteLocationResolved = RouteLocationResolvedTypedList[Name] + + /** + * Type safe version of `RouteLocation` . Allows passing the name of the route to be passed as a generic. + */ + export type RouteLocation = RouteLocationTypedList[Name] + + /** + * Type safe version of `RouteLocationRaw` . Allows passing the name of the route to be passed as a generic. + */ + export type RouteLocationRaw = + | RouteLocationAsString + | RouteLocationAsRelativeTypedList[Name] + | RouteLocationAsPathTypedList[Name] + + /** + * Generate a type safe params for a route location. Requires the name of the route to be passed as a generic. + */ + export type RouteParams = RouteNamedMap[Name]['params'] + /** + * Generate a type safe raw params for a route location. Requires the name of the route to be passed as a generic. + */ + export type RouteParamsRaw = RouteNamedMap[Name]['paramsRaw'] + + export function useRouter(): RouterTyped + export function useRoute(name?: Name): RouteLocationNormalizedLoadedTypedList[Name] + + export const useLink: UseLinkFnTyped + + export function onBeforeRouteLeave(guard: NavigationGuard): void + export function onBeforeRouteUpdate(guard: NavigationGuard): void + + export const RouterLink: RouterLinkTyped + export const RouterLinkProps: RouterLinkPropsTyped + + // Experimental Data Fetching + + export function defineLoader< + P extends Promise, + Name extends keyof RouteNamedMap = keyof RouteNamedMap, + isLazy extends boolean = false, + >( + name: Name, + loader: (route: RouteLocationNormalizedLoaded) => P, + options?: _DefineLoaderOptions, + ): _DataLoader, isLazy> + export function defineLoader< + P extends Promise, + isLazy extends boolean = false, + >( + loader: (route: RouteLocationNormalizedLoaded) => P, + options?: _DefineLoaderOptions, + ): _DataLoader, isLazy> + + export { + _definePage as definePage, + _HasDataLoaderMeta as HasDataLoaderMeta, + _setupDataFetchingGuard as setupDataFetchingGuard, + _stopDataFetchingScope as stopDataFetchingScope, + } from 'unplugin-vue-router/runtime' +} + +declare module 'vue-router' { + import type { RouteNamedMap } from 'vue-router/auto/routes' + + export interface TypesConfig { + beforeRouteUpdate: NavigationGuard + beforeRouteLeave: NavigationGuard + + $route: RouteLocationNormalizedLoadedTypedList[keyof RouteNamedMap] + $router: _RouterTyped + + RouterLink: RouterLinkTyped + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..73019b3 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,177 @@ +/* eslint-disable node/prefer-global/process */ +import path from 'node:path' +import { loadEnv } from 'vite' +import {writeFileSync} from "fs" +import { visualizer } from 'rollup-plugin-visualizer' +import Components from 'unplugin-vue-components/vite' +import AutoImport from 'unplugin-auto-import/vite' +import { VantResolver } from 'unplugin-vue-components/resolvers' +import vue from '@vitejs/plugin-vue' +import legacy from '@vitejs/plugin-legacy' +import vueJsx from '@vitejs/plugin-vue-jsx' +import autoprefixer from 'autoprefixer' +import { viteVConsole } from 'vite-plugin-vconsole' +import mockDevServerPlugin from 'vite-plugin-mock-dev-server' +import { ViteImageOptimizer } from "vite-plugin-image-optimizer"; +import viteCompression from 'vite-plugin-compression' + +function writeWebConfig(env){ + const config = {} + const keys = ["api_1", "api_2", "api_3", "ws_url", "proxy_target", "backup_http_addrs"] + const prefix = "VITE_APP_" + for(const prop in env){ + for(const i in keys){ + const k = keys[i] + if (prop.toLowerCase()===(prefix+k).toLowerCase()){ + if(prop.toLowerCase().includes("backup_http_addrs")){ + config[k] = JSON.parse(env[prop]) + break + } + config[k] = env[prop] + break + } + } + } + for(const i in keys) { + const k = keys[i] + if(config[k]===""|| !config[k]){ + console.error('env config not empty: '+ k, config); + return null + } + } + try { + const filePath = './public/web_config.js'; + writeFileSync(filePath, `window.WEB_CONFIG = ${JSON.stringify(config)}`); + } catch (err) { + console.error('write web_config.js occur error', err); + return null + } + return config +} + +export default ({ command, mode }) => { + const root = process.cwd() + const env = loadEnv(mode, root) + console.log("current environment: "+ mode); + + // 动态生成 public/web_config.js + const config = writeWebConfig(env) + const target = config["proxy_target"] + let withPort = false + if(target === 'http://192.168.0.111' || target === 'http://localhost'){ + withPort = true + } + const apiv1 = `${target}${withPort?':8081':''}/manage-api` + const apiv2 = `${target}${withPort?':10008':''}/chat-api` + const apiv3 = `${target}${withPort?':10002':''}/chat-server-api` + + console.log("apiv1",apiv1) + console.log("apiv2",apiv2) + console.log("apiv3",apiv3) + return { + base: env.VITE_APP_PUBLIC_PATH, + plugins: [ + vue(), + vueJsx(), + visualizer(), + // UnoCSS(), + mockDevServerPlugin(), + ViteImageOptimizer(), + legacy({ + targets: ['defaults', 'not IE 11'], + }), + Components({ + dts: true, + resolvers: [VantResolver()], + types: [], + }), + AutoImport({ + include: [ + /\.[tj]sx?$/, + /\.vue$/, + /\.vue\?vue/, + ], + imports: [ + 'vue', + 'vue-router', + 'vitest', + ], + dts: true, + }), + viteVConsole({ + entry: [path.resolve('src/main.js')], + localEnabled: command === 'serve', + enabled: false, + config: { + maxLogNumber: 1000, + theme: 'light', + }, + }), + //在plugins配置数组里添加gzip插件 + viteCompression({ + verbose: true, // 默认即可 + disable: false, // 开启压缩(不禁用),默认即可 + deleteOriginFile: true, // 删除源文件 + threshold: 5120, // 压缩前最小文件大小 + algorithm: 'gzip', // 压缩算法 + ext: '.gz' // 文件类型 + // filter: /\.(js|css|json|txt|ico|svg|png)(\?.*)?$/i, // 需要压缩的文件 + }) + ], + css: { + postcss: { + plugins: [ + autoprefixer(), + ], + }, + }, + build: { + cssCodeSplit: false, + chunkSizeWarningLimit: 2048, + rollupOptions: { + output: { + // 静态资源打包做处理 + chunkFileNames: 'static/js/[name]-[hash].js', + entryFileNames: 'static/js/[name]-[hash].js', + assetFileNames: 'static/[ext]/[name]-[hash].[ext]', + manualChunks(id) { + if (id.includes('node_modules')) { + return id.toString().split('node_modules/')[1].split('/')[0].toString(); + } + }, + } + } + }, + resolve: { + alias: { + '~@': path.join(__dirname, './src'), + '@': path.join(__dirname, './src'), + '~': path.join(__dirname, './src/assets'), + }, + }, + + server: { + host: '0.0.0.0', + strictPort: false, + port: 5178, + + proxy: { + '/api_2': { + target: apiv2, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api_2/, '') + }, + '/api_3': { + target: apiv3, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api_3/, '') + }, + '/api_1': { + target: apiv1, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api_1/, '') + }, + }, + }, + } +}