This commit is contained in:
zdz
2026-03-19 15:02:23 +08:00
commit 13b28ceec9
121 changed files with 16531 additions and 0 deletions

3
.env.production Normal file
View File

@@ -0,0 +1,3 @@
# 生产环境
VITE_MODE = 'production'
VITE_BASE_URL = '//ser.szzztec.com/stock'

3
.env.test Normal file
View File

@@ -0,0 +1,3 @@
# 测试环境
VITE_MODE = 'test'
VITE_BASE_URL = '//testser.szzztec.com/stock'

18
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,18 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
rules: {
'vue/multi-word-component-names': 'off'
},
parserOptions: {
ecmaVersion: 'latest'
}
}

32
.gitignore vendored Normal file
View File

@@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
.env.development
auto-imports.d.ts
components.d.ts
package-lock.json
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

8
.prettierrc.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

45
README.md Normal file
View File

@@ -0,0 +1,45 @@
# stock-h5
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Run Unit Tests with [Vitest](https://vitest.dev/)
```sh
npm run test:unit
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

1
env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

217
index.html Normal file
View File

@@ -0,0 +1,217 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
<script src="//at.alicdn.com/t/c/font_4780705_wlsc4g1fnne.js"></script>
<style>
.sk-cube-grid {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
max-width: 678px;
margin: 0 auto;
}
.sk-cube-grid .sk-cube-grid1 {
height: 120px;
width: 120px;
margin: 0 auto;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube {
width: 33%;
height: 33%;
float: left;
-webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
background-size: cover;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube1 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube2 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube3 {
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube4 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube5 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube6 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube7 {
-webkit-animation-delay: 0;
animation-delay: 0;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube8 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube9 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube img {
width: 100%;
}
.sk-cube-grid .text1 {
font-size: 18px;
margin-top: 20px;
font-weight: bold;
}
.sk-cube-grid .text2 {
font-size: 14px;
}
.sk-cube-grid .text3 {
font-size: 12px;
}
@media screen and (min-width: 768px) {
.sk-cube-grid {
width: 750px;
margin: auto;
}
.sk-cube-grid .sk-cube-grid1 {
height: 180px;
width: 180px;
}
.sk-cube-grid .text1 {
font-size: 28px;
}
.sk-cube-grid .text2 {
font-size: 24px;
}
.sk-cube-grid .text3 {
font-size: 20px;
}
}
@-webkit-keyframes sk-cubeGridScaleDelay {
0%,
70%,
100% {
-webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1);
}
35% {
-webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1);
}
}
@keyframes sk-cubeGridScaleDelay {
0%,
70%,
100% {
-webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1);
}
35% {
-webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1);
}
}
</style>
<title>数据管理系统</title>
<% for (const i of cdnCss) { %>
<link href="<%= i %>" rel="stylesheet" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (const i of cdnJs) { %>
<script src="<%= i %>" defer></script>
<% } %>
</head>
<body>
<div id="app">
<div id="grid" class="sk-cube-grid" style="height: calc(100vh);">
<div>
<div class="sk-cube-grid1">
<div class="sk-cube sk-cube1">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="sk-cube sk-cube2">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="sk-cube sk-cube3">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="sk-cube sk-cube4">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube5">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube6">
<img
src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCABPAE8DASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+c8kAZPA5yT0wB+nsTwT9DW7Y+HNfv4FubLQ9aureQny5oNB1O6jcDHImt7eWInnorDjDbQDk7fw60CHxF4t0mxukWWyWc3eoRHdl7Wyje5aD5CrkXRXyjhgy4ypUnJ+7bdEt4ILdIfs6wQQRC2UlY4CkKbo416BC+6TAAAZ2AA6V/Svjv9IN+F2OwmXYTCUsTXqL2tTmklKzS0a6aa8vZI/qX6Hn0JofSEyTNM/zbMamEwdCShhY04y5pSurq/s3dq193b5nwGfB3is8/wDCL+Ij2+Xw9rKceuDZ5z6np0A70n/CG+Kv+hW8Sf8Agh1j/wCQ/wDP51+gWU6lAx9WJb8BnoKMx/8APJPx5/nX81y+mnnEpNxyyhyt3V5wXbvP+telz+5KX7KPhCMIxeeY9uy+xe17dXSu+u/y7n5/jwb4rGSvhfxIhK4Df2Bq7YPPb7IB6dsjPWlHg3xQE/5FjxIH3EmUaDq6uSccD/QycccAYAOeMZz9/wCEY/6qM8dCqnP4sCfwH1p21f8AnjHj02pj8tuP0q4/TZzSCUZZXSukrrmi03p09rZ3dvvexo/2SXClZc64ixcYyWkZQjdRutH+68nb/g6fn2PBnicFW/4RrxHuBK7jo+rRs6EfMrE6c7ENkhxu2uPlKkDlq+FfFImMSeHfEm5izhLTQ9Smuo3OT5kQbTbfy8sCxlW7RjIXfaHLO/6DYPZNo9FbYv12rgZ7ZxnGBnAFNKZIITDDo4b5hznhuoPPXOa5sd9M/FZnhcRhMTlFOpTq0HSSm4yjdpW92VS1lfTte53ZZ+yiyPJ8bRx+XcV4ujjKM6NSM4yqU/dpzi2m4U0lKy0trZnl/wAK5vEUfhxrfxDaXVtcWlz9ngOomd76W38mJle68+WaVZd5dCjSEqqrgDPP6m/8EuHb/hur4GqccD4l4IGOvwd+IOTgjucj0/WvgXLykCV3k2cgyOzMCAQBuJyQMthSSMkkDPNfev8AwS9JX9uf4IFeSh+JSqf9n/hT3xB9cjueuTz9K/lbAZzSzvjV5tRowwzx+NqV1RpKMVS9pBXhFQ0SvHpu33R/X/jhwhW4O+i7nvCcsXLGrJMqw1CGKrzlOpUp06tNc7lP3tW3vbz6H4dfA92i8fWciYDJp2rTKSqsN8doWQsrAq4UnO1wVPoea+1HdpJZy3JFxOO3eVm7e7H2AwBhQAPiv4IgHx3bZH/MJ1v17WsY/kx/yBX2iOXmPczyk/Utk/rX739NGjP/AF6hUu7OFO3azUW113v/AEz8F/ZXQgvBJVFGKnLHV+aaS5naSSvLd2TdvUdhT95cn1yRx+FG2P8Auf8AjzUUV/GkKcXGLa1a7L/I/wBSpfE9F/4Cm++7VxCqY4XB+pP9f8/yTYvp+p/xp1FP2NP+VfdH/IhxTbdtXva6/L0X3Ddi+n6n/GjYvp+p/wAadRR7GHZabaL/ACCytbW172u9++/kAC4wVyPYkfyr9Af+CWESy/t2fBRGA2g/ErA/7o98QPxOMdyeK/P6v0G/4JVDP7eHwSyARu+Jg57/APFnPiEccYPXB/D619fwTSlU4ly6nBxS5trL+S92urufzZ9LhcngJx7OOjWAw9pL4kvbU21fTTyvqfhN8Ec/8J3bYPTSdYOPXMEIPvyP896+0TxK4HAOXx/tMxyfyA46V8W/BLP/AAnAYceXomqPnAP3mtIz145UsPxyOnH2kvLOxHO91/4CGOBxx/X3r+oPpp1ubjalTT1jTpJ/dHT5et+/l/J/7K5JeCL7LG4l+lpx2HUUUV/GVP4I+h/qNL4n8vyQUUUVZIUUUUAFff3/AASzkMX7d3wOIzy3xMyQev8AxZz4hj/PT8a+Aa/QL/glsin9un4HMRzu+JnOT/0R34he/vX2PAP/ACVWXaLWU29N2krfcfzd9LdRfgJx5d/8wWFut/8Al/T002ufhj8DUVvGrkj/AJg98vU8gmFsfmqnPXjHQkV9lSjasxHXcDn3YRM3tyXbt3+lfG/wL/5HR/8AsE33/tKvsmb/AJaDsSmf++bb/Gv6N+mh/wAlzB9fZrXro4WP5Q/ZcpR8D1ypRvjcXe2n2o9hkf3T/vH9DUlRQ8pn3NS1/H1P4IaW91afI/0/WqV+y/IKKKKsYUUUUAD/AC7Md9ue/XOetfoL/wAEsWLft2/A1W5w/wATAOABj/hTfxF44x3Ar8+pP4P+A/1r9BP+CVxI/bw+BxH9/wCJn/qm/iNX13ATf+tWA129pby91H8ufTCnOPgRxeoyklPCU+eza5uWrScb97X0/wCAj8M/gT/yOsh9NHviPr+6r7Hl5Zx6tEPzNsDXxx8Cf+R0k/7A9/8A+0q+x5P9Y3+/D/O2r+kPpof8lzD/AK9/rA/mb9lwk/BBJrT6/WXybu/v6jYgBGf99h+FPpI/9W3/AF1alr+PoX5IXVnyrTfof6drr5PT7kFFFFWMKKKKACT+D/gP9a/QP/glf/yff8Dv9/4mf+qb+I1fn5J/B/wH+tfoH/wSv/5Pv+B3+/8AEz/1TfxGr63gJv8A1ry9dGql/wDwGP8Awx/LX0w/+TEcW/8AYJH/ANO0T//Z">
</div>
<div class="sk-cube sk-cube7">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube8">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube9">
<img
src="data:image/jpeg;base64,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">
</div>
</div>
<div class="text1">第一上海证券有限公司</div>
<div class="text2">FIRST SHANGHAI SECURITIES LIMITED</div>
<div class="text3">香港中環德輔道中 71 號永安集團大廈 19 樓D</div>
<div class="text3">19/F, Wing On House, 71 Des Voeux Road Central, Hong Kong</div>
<div class="text3">电话 Tel:8522532 1580 传真 Fax:8522537 6911</div>
</div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

68
package.json Normal file
View File

@@ -0,0 +1,68 @@
{
"name": "stock-h5",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --mode development",
"build": "run-p type-check \"build-only {@}\" --",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode production",
"preview": "vite preview",
"test:unit": "vitest",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@vant/auto-import-resolver": "^1.2.1",
"@vant/touch-emulator": "^1.4.0",
"axios": "^1.7.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.1",
"markdown-it": "^14.1.0",
"mitt": "^3.0.1",
"moment": "^2.30.1",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"rollup-plugin-external-globals": "^0.10.0",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.8",
"sass-loader": "^16.0.0",
"scss": "^0.2.4",
"secure-ls": "^2.0.0",
"unplugin-auto-import": "^0.17.6",
"unplugin-vue-components": "^0.27.0",
"vant": "^4.9.1",
"vite-plugin-html": "^3.2.2",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"vue-virtual-scroller": "^2.0.0-beta.8",
"vue-wechat-title": "^2.0.7",
"weixin-js-sdk": "^1.6.5"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4",
"@types/jsdom": "^21.1.6",
"@types/node": "^20.12.5",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.5",
"@vue/tsconfig": "^0.5.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"jsdom": "^24.0.0",
"npm-run-all2": "^6.1.2",
"prettier": "^3.2.5",
"terser": "^5.31.1",
"typescript": "~5.4.0",
"vite": "^5.2.8",
"vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-compression": "^0.5.1",
"vitest": "^1.4.0",
"vue-tsc": "^2.0.11"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
public/img/CLBG.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

BIN
public/img/GXBG.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
public/img/GXBG.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
public/img/SFBG.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

BIN
public/img/XLL.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
public/img/XLL.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
public/img/commentBg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
public/img/completeLogo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/img/headPic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
public/img/home-banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/img/indicate.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
public/img/indicate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
public/img/loginLogo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
public/img/night.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
public/img/smallLogo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
public/img/user-bg.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/img/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

276
src/App.vue Normal file
View File

@@ -0,0 +1,276 @@
<template>
<van-overlay :show="show" z-index="999">
<div class="sk-cube-grid">
<div class="sk-cube-grid1">
<div class="sk-cube sk-cube1">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="sk-cube sk-cube2">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="sk-cube sk-cube3">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="sk-cube sk-cube4">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube5">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube6">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube7">
<img
src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCABPAE4DASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8hv8AgoD/AMn4ftt/9nc/tIf+rl8eV8ix/cX6V9df8FAf+T8P22/+zuf2kP8A1cvjyvkWP7i/Sv8AQjgX/kl8l/7F9D8j8dzf+PU/wr9B9FFFfbx2XovyPHh8K+f5sKKKKZQUUUUbky1i7eT+X9aiFtvPoePrX398Mhjwf4cA6Lo8YH/f5j/nivgLAOAehODX3x8LJVl8H+HCeD/ZkqEDp+4uEj46nJkMwbJPCrjBDZ/iH6aNKUuDsClFy5MVTU1Za2lQUb3ttfS/of60/sqa0KXiLnVGckpf2ZL/ANIq6v8AK9kjz7/goD/yfh+23/2dz+0h/wCrl8eV8ix/cX6V9df8FAf+T8P22/8As7n9pD/1cvjyvkWP7i/Sv6x8PacJcKZS5RTay+jZvpoj/K/ONMRV8lt06D6KKK+t2PGh8K+f5sKKKKCgooooDYVeo+o/nX3X8HmH/CCeHZj1kh1dcY42rrl4w98/Nt9NqjjPJ+FF6j6j+dfdHwhO34d+GTgE7NYHOeh1i5bsR3P5V/F/0ydODaL6/W8O7+blQv8Aef6n/svtPE/NmtG8pi211bpVm382cb/wUB/5Pw/bb/7O5/aQ/wDVy+PK+RY/uL9K+uv+CgP/ACfh+23/ANnc/tIf+rl8eV8ix/cX6V/U3h3/AMknlX/YvoflE/zHzj/eKvp/kPooor6o8aHwr5/mwooooKCiiik9E/RgKvUfUfzr7o+EI3fDzw0D2TVjx/2F7mvhdeo+o/nX3T8H/wDknvhr/rnq3/p3ua/i/wCmX/yReE/vYnD389Yf5L7j/U79l7/yc7Nv+xSv/TdY4z/goD/yfh+23/2dz+0h/wCrl8eV8ix/cX6V9df8FAf+T8P22/8As7n9pD/1cvjyvkWP7i/Sv6n8O/8Akk8q/wCxfQ/KJ/mRnH+8VfT/ACH0UUV9UeND4V8/zYUUUUFBRRRSls/R/kAq9R9R/Ovun4P/APJPfDX/AFz1b/073NfCy9R9R/Ovur4P/wDJPPDR/wBjV/01e4/xr+MfplK/BmXO3uvF07ro0pRsnbTTof6nfsvLy8T850dllMbWW16dbt/Wv3cX/wAFAf8Ak/D9tv8A7O5/aQ/9XL48r5Fj+4v0r66/4KA/8n4ftt/9nc/tIf8Aq5fHlfIsf3F+lf1L4d/8knlX/YvoflE/zIzj/eKvp/kPooor6o8aHwr5/mwooooKCiiimrdVdPT8QTSab1V/z0FUZZQOuRX3N8IXK/D7w6GVkKLqq4kGwsralJKkig5+VlkyOvGOfX458K6DP4l16w0e3dYjczbJJnYARIOCcEEs3pgEexr7907TbbSrCx022UrDZWkdvGBj7qDljnALucM5GAWGQor/AD/+mjxdg45TluSUHVdsTGUqkqTbfK4u2/8ATsf7OfssPD7MHxFm/FLhTqYOWBqQ5nVXtOWbcKS9lvaKdr3sk/mf/9k=">
</div>
<div class="sk-cube sk-cube8">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="sk-cube sk-cube9">
<img
src="data:image/jpeg;base64,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">
</div>
</div>
</div>
</van-overlay>
<div class="page-bg">
<RouterView v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-wechat-title="$route.meta.title" v-if="$route.meta.keepAlive" :key="$route.name" />
</keep-alive>
<component :is="Component" v-wechat-title="$route.meta.title" v-if="!$route.meta.keepAlive" :key="$route.name" />
</RouterView>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import emitter from './utils/mitt'
import wx from 'weixin-js-sdk'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(() => route.fullPath, () => {
const title = route.meta.title
if (title === '点评详情' || title === '会议详情' || title === '会议' || title === '沟通详情' || title === '组合详情' ||
title === '重点公司追踪详情') return
console.log(1111111111);
setTitle({ title })
})
onMounted(async () => {
})
let show = ref(false)
let loadingText = ref('加载中...')
const showLoading = (e: string) => {
loadingText.value = e ? e : loadingText.value
show.value = true
}
const hiddenLoading = () => {
show.value = false
}
emitter.on('showLoading', showLoading)
emitter.on('hiddenLoading', hiddenLoading)
import reportInfoStore from '@/stores/reportInfo'
const reportInfo = reportInfoStore()
import fileInfoStore from '@/stores/fileInfo'
const fileInfo = fileInfoStore()
import stockInfoStore from '@/stores/stockInfo'
const stockInfo = stockInfoStore()
import speakInfoStore from '@/stores/speakInfo'
const speakInfo = speakInfoStore()
import listRefreshInfoStore from '@/stores/listRefreshInfo'
const listRefreshInfo = listRefreshInfoStore()
const setTitle = (e: any) => {
wx.ready(() => {
wx.updateAppMessageShareData({
title: '第一上海证券',
desc: e.title || '第一上海证券内部数据管理平台',
link: 'http://h5.szzztec.com' + route.fullPath,
imgUrl: 'http://h5.szzztec.com/img/smallLogo.jpg',
success: function () {
console.log('分享成功')
}
})
})
}
emitter.on('setTitle', setTitle)
onBeforeUnmount(() => {
reportInfo.reportId = ''
reportInfo.index = 0
speakInfo.speakId = ''
speakInfo.index = 0
fileInfo.fileId = ''
stockInfo.id = ''
listRefreshInfo.addId = ''
listRefreshInfo.deleteId = ''
listRefreshInfo.refreshId = ''
emitter.off('showLoading')
emitter.off('hiddenLoading')
emitter.off('setTitle')
})
</script>
<style scoped>
.wrapper {
height: 100%;
}
.block {
width: 120px;
height: 120px;
background-color: #fff;
border-radius: 4px;
}
.sk-cube-grid {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 100vh;
}
.sk-cube-grid .sk-cube-grid1 {
height: 120px;
width: 120px;
margin: 0 auto;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube {
width: 33%;
height: 33%;
float: left;
-webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
background-size: cover;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube1 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube2 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube3 {
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube4 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube5 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube6 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube7 {
-webkit-animation-delay: 0;
animation-delay: 0;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube8 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube9 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.sk-cube-grid .sk-cube-grid1 .sk-cube img {
width: 100%;
}
.sk-cube-grid .text1 {
font-size: 18px;
margin-top: 20px;
font-weight: bold;
}
.sk-cube-grid .text2 {
font-size: 14px;
}
.sk-cube-grid .text3 {
font-size: 12px;
}
@media screen and (min-width: 768px) {
.sk-cube-grid {
width: 750px;
margin: auto;
}
.sk-cube-grid .sk-cube-grid1 {
height: 180px;
width: 180px;
}
.sk-cube-grid .text1 {
font-size: 28px;
}
.sk-cube-grid .text2 {
font-size: 24px;
}
.sk-cube-grid .text3 {
font-size: 20px;
}
}
@-webkit-keyframes sk-cubeGridScaleDelay {
0%,
70%,
100% {
-webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1);
}
35% {
-webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1);
}
}
@keyframes sk-cubeGridScaleDelay {
0%,
70%,
100% {
-webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1);
}
35% {
-webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1);
}
}
</style>

542
src/assets/base.css Normal file
View File

@@ -0,0 +1,542 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
--van-gray-3: #dcdee0;
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--safe-area-inset-bottom: env(safe-area-inset-bottom);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* vant */
.van-popup--bottom {
@media screen and (min-width: 992px) {
left: calc(50vw - 496px) !important;
right: calc(50vw - 496px) !important;
width: 992px !important;
}
}
.box-popup {
min-height: 372px;
max-height: 500px;
overflow-y: auto;
}
.page-bg {
.van-tag {
border-radius: 4px;
}
}
.tabbar-list {
padding-bottom: 70px;
overflow-y: auto;
height: 100vh;
}
.page-bg {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #ededed;
bottom: env(safe-area-inset-bottom);
}
.page-content {
background-color: #F2F3F5;
max-width: 992px;
margin: 0 auto;
height: 100vh;
padding-bottom: env(safe-area-inset-bottom);
}
.page-div {
background-color: #F2F3F5;
max-width: 992px;
margin: 0 auto;
height: 100vh;
padding-bottom: env(safe-area-inset-bottom);
display: flex;
flex-direction: column;
}
/* .van-cell {
margin: 0 16px 12px;
width: calc(100% - 32px);
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .1);
@media screen and (min-width: 678px) {
width: 642px;
}
&:last-child {
margin-bottom: 0;
}
} */
.card {
padding: 10px;
border-radius: 6px;
/* width: calc(100% - 32px); */
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .1);
}
.col-6 {
width: 50%;
}
.col-12 {
width: 100%;
}
.col-4 {
width: 33.3%;
}
.van-tabbar {
height: 60px !important;
.van-icon {
font-size: 26px !important;
}
.van-tabbar-item__text {
font-size: 16px !important;
}
}
.van-cell {
border-radius: 8px;
}
.top-select-list {
padding-top: 40px;
}
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
}
.top-select {
position: fixed;
top: 0;
right: 0;
left: 0;
height: 40px;
z-index: 4;
background-color: #fff;
@media screen and (min-width: 678px) {
left: calc(50vw - 339px);
right: calc(50vw - 339px);
}
.van-grid-item__content {
padding: 0;
height: 38px;
line-height: 38px;
.grid-item-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
max-width: calc(33vw - 28px);
font-size: 12px;
@media screen and (min-width: 678px) {
max-width: 200px;
}
}
}
}
.verticalSwiper {
background-color: #fff;
width: 100vh !important;
height: 100vh;
transform-origin: 50vw 50vw;
transform: rotate(90deg) !important;
overflow: hidden;
.top-select {
display: none;
}
.chart {
width: 100vh;
height: 100vw;
}
.chartDiv {
width: 100%;
height: 100%;
}
}
.horizontalSwiper {
background-color: #fff;
.top-select {
width: 100px;
}
.chart {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.chartDiv {
width: 100%;
height: 40vh;
}
}
.chart {
position: relative;
.icon {
position: absolute;
left: 50%;
transform: translateX(-50%);
background-color: #f1f1f1;
padding: 10px;
border-radius: 100%;
z-index: 999;
}
.icon-up {
top: 20px;
}
.icon-down {
bottom: 20px;
}
.loading {
position: absolute;
z-index: 2;
}
}
.custom-style {
padding: 10px;
}
ol {
padding-left: 10px !important;
li {
list-style-type: decimal;
list-style-position: inside;
}
}
/* iconfont */
.iconfont {
font-size: 14px;
}
/* font-size */
.content {
font-size: 14px;
}
.font-12 {
font-size: 12px;
}
strong {
font-weight: bold;
}
/* color */
.primary-text-color {
color: #1989fa !important;
}
.disabled-primary-background-color {
background-color: #90d7ec !important;
}
.primary-background-color {
background-color: #1989fa !important;
}
.warning-text-color {
color: #ff976a !important;
}
.warning-background-color {
background-color: #ff976a !important;
}
.success-text-color {
color: #07c160 !important;
}
.success-background-color {
background-color: #07c160 !important;
}
.danger-text-color {
color: #f56c6c !important;
}
.danger-background-color {
background-color: #f56c6c !important;
}
.balck-text-color {
color: #130c0e !important;
}
.yellow-background-color {
background-color: #e6a23c !important;
}
.pink-background-color {
background-color: #f173ac !important;
}
.purple-background-color {
background-color: #8552a1 !important;
}
.other-background-color {
background-color: #8390e5 !important;
}
.active {
background-color: #1989fa;
color: #fff;
}
.activeColor {
color: #1989fa;
}
.white-text-color {
color: #fff;
}
.disbaled-text-color {
color: #c8c9cc;
}
.disbaled-background-color {
background-color: #c8c9cc;
}
/* flex */
.flex-wrap {
flex-wrap: wrap;
}
.justify-content-between {
justify-content: space-between;
}
.justify-content-center {
justify-content: center;
}
.justify-content-end {
justify-content: end;
}
.align-items-start {
align-items: flex-start;
}
.align-items-center {
align-items: center;
}
.align-items-end {
align-items: flex-end;
}
/* padding */
.p-10 {
padding: 10px;
}
.p-8 {
padding: 8px;
}
.ps-16 {
padding-left: 16px;
}
.pe-16 {
padding-right: 16px;
}
.pt-5 {
padding-top: 5;
}
.pb-5 {
padding-bottom: 5px;
}
/* margin */
.m-5 {
margin: 5px;
}
.m-16 {
margin: 16px;
}
.mt-5 {
margin-top: 5px;
}
.ms-10 {
margin-left: 10px;
}
.mt-10 {
margin-top: 10px;
}
.mb-10 {
margin-bottom: 10px;
}
.mx-10 {
margin: 0 10px;
}
/* border */
.border-1 {
border: 1px solid #dcdee0;
}
.border-0 {
border: 0 !important;
}
.border-bottom-1 {
border-bottom: 1px solid #dcdee0;
}
.border-right-1 {
border-right: 1px solid #dcdee0;
}
.border-radius-4 {
border-radius: 4px;
}
.border-radius-8 {
border-radius: 8px;
}
/* 省略号 */
.ellipsis-one {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
}
.addDiv {
position: fixed;
bottom: calc(env(safe-area-inset-bottom) + 70px);
right: 20px;
height: 60px;
width: 60px;
border-radius: 100%;
color: #fff;
z-index: 2;
@media screen and (min-width: 678px) {
right: calc(50vw - 319px);
}
.text-center {
height: 30px;
width: 60px;
line-height: 16px;
}
.addDiv1 {
padding-top: 14px;
}
.addDiv2 {
padding-bottom: 14px;
}
}

View File

@@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -0,0 +1,487 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=4540741" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe600;</span>
<div class="name">链接</div>
<div class="code-name">&amp;#xe600;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61f;</span>
<div class="name">良品铺子</div>
<div class="code-name">&amp;#xe61f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe62d;</span>
<div class="name">向下双箭头</div>
<div class="code-name">&amp;#xe62d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6c4;</span>
<div class="name">月历</div>
<div class="code-name">&amp;#xe6c4;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6ed;</span>
<div class="name">KHCFDC_音频文件</div>
<div class="code-name">&amp;#xe6ed;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8a6;</span>
<div class="name">excel</div>
<div class="code-name">&amp;#xe8a6;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8aa;</span>
<div class="name">image</div>
<div class="code-name">&amp;#xe8aa;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8af;</span>
<div class="name">pdf</div>
<div class="code-name">&amp;#xe8af;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8b0;</span>
<div class="name">ppt</div>
<div class="code-name">&amp;#xe8b0;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8b4;</span>
<div class="name">txt</div>
<div class="code-name">&amp;#xe8b4;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8b5;</span>
<div class="name">unknown</div>
<div class="code-name">&amp;#xe8b5;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8b6;</span>
<div class="name">word</div>
<div class="code-name">&amp;#xe8b6;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6ea;</span>
<div class="name">默认文件夹</div>
<div class="code-name">&amp;#xe6ea;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1755072825017') format('woff2'),
url('iconfont.woff?t=1755072825017') format('woff'),
url('iconfont.ttf?t=1755072825017') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-lianjie"></span>
<div class="name">
链接
</div>
<div class="code-name">.icon-lianjie
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-liangpinpuzi"></span>
<div class="name">
良品铺子
</div>
<div class="code-name">.icon-liangpinpuzi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiangxiashuangjiantou"></span>
<div class="name">
向下双箭头
</div>
<div class="code-name">.icon-xiangxiashuangjiantou
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-yueli"></span>
<div class="name">
月历
</div>
<div class="code-name">.icon-yueli
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-yinpinwenjian"></span>
<div class="name">
KHCFDC_音频文件
</div>
<div class="code-name">.icon-yinpinwenjian
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-excel"></span>
<div class="name">
excel
</div>
<div class="code-name">.icon-excel
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-image"></span>
<div class="name">
image
</div>
<div class="code-name">.icon-image
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-pdf"></span>
<div class="name">
pdf
</div>
<div class="code-name">.icon-pdf
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-ppt"></span>
<div class="name">
ppt
</div>
<div class="code-name">.icon-ppt
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-txt"></span>
<div class="name">
txt
</div>
<div class="code-name">.icon-txt
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-unknown"></span>
<div class="name">
unknown
</div>
<div class="code-name">.icon-unknown
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-word"></span>
<div class="name">
word
</div>
<div class="code-name">.icon-word
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-morenwenjianjia"></span>
<div class="name">
默认文件夹
</div>
<div class="code-name">.icon-morenwenjianjia
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-lianjie"></use>
</svg>
<div class="name">链接</div>
<div class="code-name">#icon-lianjie</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-liangpinpuzi"></use>
</svg>
<div class="name">良品铺子</div>
<div class="code-name">#icon-liangpinpuzi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiangxiashuangjiantou"></use>
</svg>
<div class="name">向下双箭头</div>
<div class="code-name">#icon-xiangxiashuangjiantou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yueli"></use>
</svg>
<div class="name">月历</div>
<div class="code-name">#icon-yueli</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yinpinwenjian"></use>
</svg>
<div class="name">KHCFDC_音频文件</div>
<div class="code-name">#icon-yinpinwenjian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-excel"></use>
</svg>
<div class="name">excel</div>
<div class="code-name">#icon-excel</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-image"></use>
</svg>
<div class="name">image</div>
<div class="code-name">#icon-image</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-pdf"></use>
</svg>
<div class="name">pdf</div>
<div class="code-name">#icon-pdf</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-ppt"></use>
</svg>
<div class="name">ppt</div>
<div class="code-name">#icon-ppt</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-txt"></use>
</svg>
<div class="name">txt</div>
<div class="code-name">#icon-txt</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-unknown"></use>
</svg>
<div class="name">unknown</div>
<div class="code-name">#icon-unknown</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-word"></use>
</svg>
<div class="name">word</div>
<div class="code-name">#icon-word</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-morenwenjianjia"></use>
</svg>
<div class="name">默认文件夹</div>
<div class="code-name">#icon-morenwenjianjia</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@@ -0,0 +1,67 @@
@font-face {
font-family: "iconfont"; /* Project id 4540741 */
src: url('iconfont.woff2?t=1755072825017') format('woff2'),
url('iconfont.woff?t=1755072825017') format('woff'),
url('iconfont.ttf?t=1755072825017') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-lianjie:before {
content: "\e600";
}
.icon-liangpinpuzi:before {
content: "\e61f";
}
.icon-xiangxiashuangjiantou:before {
content: "\e62d";
}
.icon-yueli:before {
content: "\e6c4";
}
.icon-yinpinwenjian:before {
content: "\e6ed";
}
.icon-excel:before {
content: "\e8a6";
}
.icon-image:before {
content: "\e8aa";
}
.icon-pdf:before {
content: "\e8af";
}
.icon-ppt:before {
content: "\e8b0";
}
.icon-txt:before {
content: "\e8b4";
}
.icon-unknown:before {
content: "\e8b5";
}
.icon-word:before {
content: "\e8b6";
}
.icon-morenwenjianjia:before {
content: "\e6ea";
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,100 @@
{
"id": "4540741",
"name": "template-h5-stock",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "77403",
"name": "链接",
"font_class": "lianjie",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "10033597",
"name": "良品铺子",
"font_class": "liangpinpuzi",
"unicode": "e61f",
"unicode_decimal": 58911
},
{
"icon_id": "14209038",
"name": "向下双箭头",
"font_class": "xiangxiashuangjiantou",
"unicode": "e62d",
"unicode_decimal": 58925
},
{
"icon_id": "18977410",
"name": "月历",
"font_class": "yueli",
"unicode": "e6c4",
"unicode_decimal": 59076
},
{
"icon_id": "9881679",
"name": "KHCFDC_音频文件",
"font_class": "yinpinwenjian",
"unicode": "e6ed",
"unicode_decimal": 59117
},
{
"icon_id": "1344630",
"name": "excel",
"font_class": "excel",
"unicode": "e8a6",
"unicode_decimal": 59558
},
{
"icon_id": "1344639",
"name": "image",
"font_class": "image",
"unicode": "e8aa",
"unicode_decimal": 59562
},
{
"icon_id": "1344646",
"name": "pdf",
"font_class": "pdf",
"unicode": "e8af",
"unicode_decimal": 59567
},
{
"icon_id": "1344647",
"name": "ppt",
"font_class": "ppt",
"unicode": "e8b0",
"unicode_decimal": 59568
},
{
"icon_id": "1344654",
"name": "txt",
"font_class": "txt",
"unicode": "e8b4",
"unicode_decimal": 59572
},
{
"icon_id": "1344655",
"name": "unknown",
"font_class": "unknown",
"unicode": "e8b5",
"unicode_decimal": 59573
},
{
"icon_id": "1344657",
"name": "word",
"font_class": "word",
"unicode": "e8b6",
"unicode_decimal": 59574
},
{
"icon_id": "22014791",
"name": "默认文件夹",
"font_class": "morenwenjianjia",
"unicode": "e6ea",
"unicode_decimal": 59114
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

502
src/assets/index.scss Normal file
View File

@@ -0,0 +1,502 @@
.page-div {
.back-icon {
position: fixed;
top: 5px;
left: 5px;
z-index: 200;
@media screen and (min-width: 992px) {
left: calc(50vw - 491px)
}
&.tapped {
transform: scale(0.95);
opacity: 0.8;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
i {
color: #fff;
font-size: 20px;
@media screen and (min-width: 768px) {
font-size: 30px;
}
}
}
.header {
padding: 24px 16px 16px;
text-align: center;
background: linear-gradient(135deg, #165DFF 0%, #0FC6C2 100%);
color: white;
border-radius: 0 0 24px 24px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
.title {
font-size: 22px;
font-weight: bold;
margin-bottom: 4px;
}
.subtitle {
font-size: 14px;
opacity: 0.9;
}
@media screen and (min-width: 768px) {
padding: 24px 16px 16px;
.title {
font-size: 24px;
line-height: 1.2;
}
.subtitle {
font-size: 16px;
line-height: 1.4;
}
}
}
.info-card {
background-color: #fff;
padding: 16px;
border-radius: 16px;
margin: 0 16px 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
.card-title {
font-size: clamp(14px, 3.5vw, 16px);
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
@media screen and (min-width: 768px) {
padding: 20px;
margin: 0 16px 16px;
.card-title {
font-size: 18px;
margin-bottom: 10px;
padding-bottom: 6px;
line-height: 1.4;
}
.info-text {
font-size: 14px;
line-height: 1.6;
}
}
.info-text {
font-size: 14px;
color: #666;
line-height: 1.6;
}
.chart-container {
display: flex;
align-items: center;
justify-content: center;
.loading {
position: absolute;
z-index: 2;
}
.chartDiv {
width: 100%;
height: 100%;
}
}
.tab-container {
width: 100%;
display: flex;
background-color: #ffffff;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
.tab-button {
flex: 1;
font-size: clamp(12px, 3vw, 14px);
padding: 12px 0;
transition: all 0.2s ease;
background: none;
border: none;
outline: none;
cursor: pointer;
color: #666;
}
.tab-button.active {
color: #165DFF;
font-weight: 500;
border-bottom: 2px solid #165DFF;
}
}
}
.grid {
height: 100%;
overflow-y: auto;
flex: 1;
.van-grid-item__content {
border-radius: 16px;
.item {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #ffffff;
&.tapped {
transform: scale(0.9);
opacity: 0.7;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
background-color: rgba(240, 240, 240, 0.5);
}
.card-content {
height: 100%;
width: 100%;
padding: 16px;
border-radius: 16px;
border-left: 4px solid;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
.card-code {
font-size: 16px;
font-weight: bold;
margin-bottom: 6px;
}
.card-name {
font-size: 14px;
color: #333;
font-weight: 500;
margin-bottom: 4px;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-note {
font-size: 12px;
color: #666;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
flex-grow: 1;
}
}
@media screen and (min-width: 768px) {
padding: 18px;
.card-code {
font-size: 18px;
line-height: 1.3;
}
.card-name {
font-size: 15px;
margin-bottom: 7px;
}
.card-note {
font-size: 13px;
line-height: 1.5;
}
.card-name {
font-size: 15px;
}
.card-note {
font-size: 14px;
}
}
}
}
}
}
.icon-morenwenjianjia,
.icon-folder6wenjianjia {
color: #72cffb;
}
.icon-ppt {
color: #e34221;
}
.icon-pdf {
color: #8c181a;
}
.icon-excel {
color: #45b058;
}
.icon-txt {
color: #f9ca06;
}
.icon-word {
color: #14a9da;
}
.icon-unknown {
color: #7d99af;
}
.icon-image {
color: #49c9a7;
}
.red-color {
color: red !important;
}
.blue-color {
color: blue !important;
}
.green-color {
color: green !important;
}
.mt-16 {
margin-top: 16px;
}
.financial-data {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px 8px;
}
.data-item {
display: flex;
flex-direction: column;
padding: 8px;
background-color: #f9fafb;
border-radius: 8px;
}
.data-label {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.data-value {
font-size: 14px;
font-weight: 500;
color: #333;
display: flex;
align-items: center;
}
@media screen and (min-width: 768px) {
.financial-data {
grid-template-columns: repeat(5, 1fr);
}
.data-item {
padding: 12px;
}
.data-label {
font-size: 13px;
}
.data-value {
font-size: 15px;
}
}
table {
width: 100%;
border-spacing: 0;
text-align: center;
}
.tableDiv {
overflow: auto;
table {
border: 1px solid #808080;
border-collapse: collapse;
width: 100%;
thead {
text-align: center;
tr {
th {
border: 1px solid #757575;
font-weight: 400;
padding: 0;
box-sizing: border-box;
position: sticky;
top: -1px;
background-color: #fff;
z-index: 2;
width: 100px;
@media screen and (min-width: 768px) {
font-size: 13px;
line-height: 1.5;
}
&:first-child {
position: sticky;
left: -1px;
z-index: 3;
}
}
}
}
tbody {
text-align: center;
tr {
td {
border: 1px solid #757575;
box-sizing: border-box;
background-color: #fff;
z-index: 2;
@media screen and (min-width: 768px) {
font-size: 13px;
line-height: 1.5;
}
&.td--column--1 {
position: sticky;
left: -1px;
background-color: #fff;
z-index: 3;
}
}
}
}
tbody {
text-align: center;
tr {
td {
border: 1px solid #757575;
box-sizing: border-box;
background-color: #fff;
z-index: 2;
&.td--column--1 {
position: sticky;
left: -1px;
background-color: #fff;
z-index: 3;
}
}
}
}
}
}
.d-flex {
display: flex;
}
.flex-1 {
flex: 1;
}
.align-center {
align-items: center;
}
.align-end {
align-items: flex-end;
}
.align-start {
align-items: flex-start;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.justify-end {
justify-content: flex-end;
}
.text-start {
text-align: left;
}
.text-center {
text-align: center;
}
.text-end {
text-align: right;
}
.f-b {
font-weight: bold;
}
.big-title {
font-size: 20px;
@media screen and (min-width: 768px) {
font-size: 24px;
line-height: 1.3;
}
}
.normal-title {
font-size: 18px;
}
.sub-title {
font-size: 16px;
}
.tip {
font-size: 12px;
}
.p-16 {
padding: 16px;
}
.pt-16 {
padding-top: 16px;
}
.pb-16 {
padding-bottom: 16px;
}
.mt-16 {
margin-top: 16px;
}
.mb-16 {
margin-bottom: 16px;
}
.mb-8 {
margin-bottom: 8px;
}
.mb-5 {
margin-bottom: 5px;
}

1
src/assets/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1,39 @@
<template>
<div class="back-icon" @touchstart="handleTouchStart" @touchend="handleTouchEnd"
@touchcancel="handleTouchCancel" :class="{ 'tapped': isTouched }">
<van-icon name="arrow-left" class="icon" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const isTouched = ref(false)
const handleTouchStart = () => {
isTouched.value = true
}
const handleTouchEnd = () => {
if (isTouched.value) {
setTimeout(() => {
if (window.history.length <= 1) {
router.push({ name: 'home' })
} else {
router.back()
}
isTouched.value = false
}, 150)
}
}
const handleTouchCancel = () => {
isTouched.value = false
}
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,58 @@
<template>
<div v-if="list.length > 0">
<van-cell v-for="item in list" :key="item.linkId"
@click="$router.push({ name: 'communicate-detail', query: { linkId: item.linkId } })">
<div class="d-flex justify-content-between">
<div class="name1 text-start">
<van-tag :class="item.tagColor" size="medium">{{ item.linkTypeName }}</van-tag>
</div>
<div class="name1 stockNames balck-text-color">{{ item.stockNames ? item.stockNames : '' }}</div>
</div>
<div class="d-flex justify-content-between my-2">
<div class="van-ellipsis name2 flex-1 text-start balck-text-color">{{ item.cusName }}</div>
<div class="van-ellipsis name2 flex-1 text-end balck-text-color">{{ item.cusUserName }}</div>
</div>
<div class="van-multi-ellipsis--l2 note my-2 text-start">{{ item.linkNote }}</div>
</van-cell>
</div>
<van-empty v-else description="暂无沟通" />
</template>
<script setup lang="ts">
defineProps({
list: {
type: Array<any>,
default: []
}
})
</script>
<style lang="scss" scoped>
.van-cell {
.name1 {
font-size: 16px;
line-height: 22px;
font-weight: 500;
flex: 1;
}
.stockNames {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.note {
font-size: 12px;
line-height: 14px;
}
}
.empty {
color: #808080;
font-size: 14px;
}
.my-2 {
margin: 2px 0;
}
:deep(.van-empty__image) {
width: 80px;
height: 80px;
}
</style>

View File

@@ -0,0 +1,140 @@
<template>
<div>
<van-grid v-if="dataList.length > 0">
<van-grid-item v-for="item in dataList" :key="item.fileId" @click="toShowFile(item)" :id="item.fileId">
<i class="iconfont" :class="`icon-${item.type}`" />
<div class="grid-item-text mt-10">{{ item.name }}</div>
</van-grid-item>
</van-grid>
<van-empty v-else image-size="80" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import fileInfoStore from '@/stores/fileInfo'
const fileInfo = fileInfoStore()
import { workFileListByMenuId } from '@/utils/api'
let dataList = ref<{ fileId: '', type: '', name: '' }[]>([])
const init = async (menuId: any) => {
const { data } = await workFileListByMenuId({ menuId: menuId })
data.menuList.map((ele: any) => {
ele.type = 'morenwenjianjia'
ele.name = ele.menuName
})
const reg1 = /(\.xls|\.xlsx)$/
const reg2 = /(\.txt)$/
const reg3 = /(\.pdf)$/
const reg4 = /(\.doc|\.docx)$/
const reg5 = /(\.ppt|\.pptx)$/
const reg6 = /(\.jpg|\.png)$/
const reg7 = /(\.mp3|\.m4a)$/
data.fileList.map((ele: any) => {
ele.type = 'unknown'
ele.name = ele.fileName
if (reg1.test(ele.fileName)) {
ele.type = 'excel'
ele.name = ele.fileName.replace(reg1, '')
}
if (reg2.test(ele.fileName)) {
ele.type = 'txt'
ele.name = ele.fileName.replace(reg2, '')
}
if (reg3.test(ele.fileName)) {
ele.type = 'pdf'
ele.name = ele.fileName.replace(reg3, '')
}
if (reg4.test(ele.fileName)) {
ele.type = 'word'
ele.name = ele.fileName.replace(reg4, '')
}
if (reg5.test(ele.fileName)) {
ele.type = 'ppt'
ele.name = ele.fileName.replace(reg5, '')
}
if (reg6.test(ele.fileName)) {
ele.type = 'image'
ele.name = ele.fileName.replace(reg6, '')
}
if (reg7.test(ele.fileName)) {
ele.type = 'yinpinwenjian'
ele.name = ele.fileName.replace(reg7, '')
}
})
dataList.value = [...data.menuList, ...data.fileList]
}
import { showFile } from '@/mixins/show-file'
const { loadingFile } = showFile()
const toShowFile = (item: any) => {
if (item.type === 'morenwenjianjia') {
showToast('暂时不能打开文件夹')
} else if (item.type === 'unknown') {
showToast('未知文件无法打开')
} else {
fileInfo.fileId = item.fileId
loadingFile(`${import.meta.env.VITE_BASE_URL}/api/file/${item.fileId}`)
}
}
defineExpose({ init })
</script>
<style lang='scss' scoped>
:deep(.van-grid-item__content) {
padding: 8px 4px;
font-size: 12px;
font-weight: 400;
}
.grid-item-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
max-width: calc(25vw - 10px);
@media screen and (min-width: 678px) {
max-width: 140px;
}
}
.iconfont {
font-size: 30px !important;
}
.icon-morenwenjianjia,
.icon-folder6wenjianjia {
color: #72cffb;
}
.icon-ppt {
color: #e34221;
}
.icon-pdf {
color: #8c181a;
}
.icon-excel {
color: #45b058;
}
.icon-txt {
color: #f9ca06;
}
.icon-word {
color: #14a9da;
}
.icon-image {
color: #49c9a7;
}
.icon-unknown {
color: #7d99af;
}
.van-empty {
padding: 10px 0
}
</style>

21
src/components/tabbar.vue Normal file
View File

@@ -0,0 +1,21 @@
<template>
<van-tabbar route>
<van-tabbar-item replace to="/" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item replace to="/target-table" icon="guide-o">指标</van-tabbar-item>
<van-tabbar-item replace to="/user" icon="friends-o">我的</van-tabbar-item>
</van-tabbar>
</template>
<script setup lang="ts">
</script>
<style lang='scss' scoped>
.van-tabbar {
height: 60px;
max-width: 992px;
padding-bottom: env(safe-area-inset-bottom);
@media screen and (min-width: 992px) {
left: calc(50vw - 496px) !important;
}
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div>
<div class="d-flex align-items-center">
<div class="grid-item-text" @click="showDate">{{ name }}</div>
<van-icon name="close" v-if="name !== initialName" @click="clearName" />
<van-icon name="arrow-down" v-else @click="showDate" />
</div>
<van-calendar v-model:show="showCalendar" @select="onDateConfirm" :show-confirm="false" switch-mode="year-month"
:default-date="defaultDate" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
import moment from 'moment'
let showCalendar = ref(false)
let name: any = ref('')
let defaultDate: any = ref()
let prop = defineProps({
initialName: String,
selectDate: String
})
onMounted(() => {
nextTick(() => {
name.value = prop.selectDate
})
})
const showDate = () => {
defaultDate.value = name.value === prop.initialName ? null : new Date(name.value)
showCalendar.value = true
}
const clearName = () => {
name.value = prop.initialName
emit('refresh', name.value)
}
const onDateConfirm = (e: any) => {
name.value = moment(e).format('YYYY-MM-DD')
showCalendar.value = false
emit('refresh', name.value)
}
const emit = defineEmits(['refresh'])
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,118 @@
<template>
<div>
<div class="d-flex align-items-center">
<div class="grid-item-text" @click="showPicker">{{ name }}</div>
<van-icon name="close" v-if="name !== initialName && type !== 'echart_date' && type !== 'stock'"
@click="clearName" />
<van-icon name="arrow-down" v-else @click="showPicker" />
</div>
<van-popup v-model:show="showPickerFlag" position="bottom">
<van-picker show-toolbar :title="initialName" :columns="list" @confirm="onConfirm"
@cancel="showPickerFlag = false" v-model="value" />
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
let showPickerFlag = ref(false)
let name: any = ref('')
let value = ref<string[]>([])
let popupValue: any = ref('')
let list: any = ref([])
let prop = defineProps({
type: String,
initialName: String,
selectName: String,
selectValue: String
})
onMounted(() => {
nextTick(() => {
name.value = prop.selectName
popupValue.value = prop.selectValue
setList()
})
})
import { sysDicListByType, sysUserGetSaleAndManagerList, workStaffLookup } from '@/utils/api'
const setList = async () => {
if (prop.type === 'sale') {
const { data } = await sysUserGetSaleAndManagerList()
list.value = data.map((ele: any) => {
ele.text = ele.userName
ele.value = ele.userId
return ele
})
} else if (prop.type === 'echart_date') {
list.value = [{
text: '最近10年',
value: '1'
}, {
text: '最近1年',
value: '2'
}, {
text: '最近半年',
value: '3'
}, {
text: '最近3个月',
value: '4'
}, {
text: '最近1个月',
value: '5'
}, {
text: '最近1周',
value: '6'
}]
} else if (prop.type === 'link_type') {
list.value = [{
text: '上市公司按需会议',
value: '31'
}, {
text: '机构路演',
value: '32'
}, {
text: '机构沟通',
value: '34'
}]
} else if (prop.type === 'create_user') {
const { data } = await workStaffLookup()
list.value = data.map((ele: any) => {
ele.text = ele.staffName
ele.value = ele.staffId
return ele
})
} else {
const { data } = await sysDicListByType({ dicType: prop.type })
list.value = data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
if (prop.type === 'meeting_type') {
list.value.push({
text: '全选',
value: 'all'
})
}
}
}
const showPicker = () => {
value.value = [`${popupValue.value}`]
showPickerFlag.value = true
}
const clearName = () => {
name.value = prop.initialName
popupValue.value = ''
emit('refresh', { value: popupValue.value, name: name.value })
}
const onConfirm = (e: any) => {
const { text, value, cid } = e.selectedOptions[0]
name.value = text
popupValue.value = value
showPickerFlag.value = false
emit('refresh', { value: popupValue.value, name: text, cid })
}
const emit = defineEmits(['refresh'])
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,90 @@
<template>
<div>
<div class="d-flex align-items-center">
<div class="grid-item-text" @click="showPicker">{{ name }}</div>
<van-icon name="close" v-if="name !== initialName" @click="clearName" />
<van-icon name="arrow-down" v-else @click="showPicker" />
</div>
<van-popup v-model:show="showPickerFlag" position="bottom">
<van-field v-model="searchValue" :placeholder="'请输入' + initialName" @input="onSearchInput" />
<van-picker show-toolbar :title="initialName + '列表'" :columns="list" @confirm="onConfirm"
@cancel="showPickerFlag = false" />
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
let showPickerFlag = ref(false)
let name: any = ref('')
let popupValue: any = ref('')
let searchValue = ref('')
let list: any = ref([])
let prop = defineProps({
type: String,
initialName: String,
selectName: String,
selectValue: String,
other: String
})
onMounted(() => {
nextTick(() => {
name.value = prop.selectName
popupValue.value = prop.selectValue
})
})
const showPicker = () => {
searchValue.value = name.value === prop.initialName ? '' : prop.type === 'cus' ? name.value : popupValue.value
list.value = name.value === prop.initialName ? [] : [{
text: name.value,
value: popupValue.value
}]
showPickerFlag.value = true
}
import { stockStockInfoLookUp, cusInfoLookUp } from '@/utils/api'
import { debounceThrottle } from '@/mixins/debounce-throttle'
const { throttle } = debounceThrottle()
const onSearchInput = throttle(
async () => {
if (searchValue.value) {
if (prop.type === 'stock') {
const { data } = await stockStockInfoLookUp({ stockCode: searchValue.value })
list.value = data.map((ele: any) => {
ele.text = `${ele.stockCode}${ele.stockName}`
ele.value = ele.stockCode
return ele
})
} else if (prop.type === 'cus') {
const { data } = await cusInfoLookUp({ cusName: searchValue.value })
list.value = data.map((ele: any) => {
ele.text = ele.cusName
ele.value = ele.cusId
return ele
})
}
} else {
list.value = []
}
},
500
)
const clearName = () => {
name.value = prop.initialName
popupValue.value = ''
emit('refresh', { value: popupValue.value, name: name.value })
}
const onConfirm = (e: any) => {
console.log(e)
popupValue.value = e.selectedIndexes > -1 ? e.selectedOptions[0].value : ''
name.value = e.selectedIndexes > -1 ? e.selectedOptions[0].text : prop.initialName
showPickerFlag.value = false
emit('refresh', { value: popupValue.value, name: name.value })
}
const emit = defineEmits(['refresh'])
</script>
<style lang="scss"></style>

31
src/main.ts Normal file
View File

@@ -0,0 +1,31 @@
import './assets/iconfont/iconfont.css'
import './assets/base.css'
import './assets/index.scss'
import 'vant/es/toast/style'
import 'vant/es/dialog/style'
import '@vant/touch-emulator'
import { createApp } from 'vue'
import pinia from './stores/index'
import wx from 'weixin-js-sdk'
import { wxMpJsapiGetJsapiTicket } from '@/utils/api'
import VueWechatTitle from 'vue-wechat-title'
wxMpJsapiGetJsapiTicket({ url: window.location.href }).then(({ data }) => {
console.log(data)
wx.config({
appId: 'wx1d52f91f259a691f',
timestamp: data.timestamp,
nonceStr: data.nonceStr,
signature: data.signature,
jsApiList: ['updateAppMessageShareData']
})
})
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(VueWechatTitle)
app.use(router)
app.use(pinia)
//Vue3挂载全局API
app.mount('#app')

View File

@@ -0,0 +1,41 @@
import { ref, onMounted, onBeforeUnmount, nextTick, type Ref } from 'vue'
import type { EChartsType } from 'echarts'
export function chartMixins() {
const charts: Ref<(EChartsType | null)[]> = ref([])
const destroyedFlag: Ref<boolean> = ref(false)
const disposeAll = () => {
charts.value.map((chart) => {
if (chart) {
chart.dispose()
chart = null
}
})
}
const changeChartSize = () => {
nextTick(() => {
charts.value.forEach((chart) => {
if (chart) {
chart.resize()
}
})
})
}
onMounted(() => {
window.addEventListener('resize', changeChartSize)
})
onBeforeUnmount(() => {
destroyedFlag.value = true
window.removeEventListener('resize', changeChartSize)
disposeAll()
})
return {
charts,
disposeAll,
destroyedFlag,
}
}

View File

@@ -0,0 +1,40 @@
export function debounceThrottle() {
// 防抖
function debounce(fn: any, delay: number) {
let timer: any = null
return function () {
// 保留调用时的this上下文
const context = this
// 保留调用时传入的参数
const args: any = arguments
// 每次事件被触发时,都去清除之前的旧定时器
clearTimeout(timer)
// 设立新定时器
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
// 节流
function throttle(fn: any, delay: number) {
let timer: any = null
return function () {
// 保留调用时的this上下文
const context = this
// 保留调用时传入的参数
const args: any = arguments
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(context, args)
timer = null
}, delay)
}
}
return {
debounce,
throttle
}
}

View File

@@ -0,0 +1,65 @@
import { ref } from 'vue'
import emitter from '@/utils/mitt'
export function listLoadAndRefresh(getData: any, type: string, scrollPage: any = {}) {
const refreshing = ref(false)
const finished = ref(false)
const curPage = ref(1)
const loading = ref(false)
const list: any = ref([])
const totalCount = ref(0)
const firstFlag = ref(true)
async function onLoad() {
if (firstFlag.value) {
emitter.emit('showLoading', '')
}
loading.value = true
try {
const data = await getData(curPage.value)
totalCount.value = data.totalCount
if (refreshing.value) {
list.value = data.list
refreshing.value = false
} else {
list.value = list.value.concat(data.list)
}
if (list.value.length >= data.totalCount) {
finished.value = true
} else {
curPage.value++
}
} catch (error) {
console.log(error)
}
if (type === 'internal' || type === 'report') {
loading.value = false
scrollPage()
} else {
loading.value = false
if (firstFlag.value) {
emitter.emit('hiddenLoading', '')
firstFlag.value = false
}
}
}
function onRefresh() {
// list.value = []
refreshing.value = true
finished.value = false
curPage.value = 1
onLoad()
}
return {
refreshing,
finished,
loading,
list,
onLoad,
onRefresh,
curPage,
totalCount
}
}

View File

@@ -0,0 +1,104 @@
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
export const resizeListenerAndSwipeIndicator = (
charts: any,
getData: any
) => {
const horizontalFlag = ref(true)
const active = ref(0)
const swipe = ref()
const horizontalDiv = ref()
onMounted(() => {
console.log(swipe.value);
if (horizontalDiv.value) {
horizontalDiv.value.addEventListener('touchstart', handleTouchStart)
horizontalDiv.value.addEventListener('touchmove', handleTouchMove)
horizontalDiv.value.addEventListener('touchend', handleTouchEnd)
}
})
onUnmounted(() => {
disposeAll()
if (horizontalDiv.value) {
horizontalDiv.value.removeEventListener('touchstart', handleTouchStart)
horizontalDiv.value.removeEventListener('touchmove', handleTouchMove)
horizontalDiv.value.removeEventListener('touchend', handleTouchEnd)
}
})
let startX = 0
let startY = 0
let isSwiping = false
const handleTouchStart = (event: TouchEvent) => {
if (horizontalFlag.value) return
const touch = event.touches[0]
startX = touch.pageX
startY = touch.pageY
isSwiping = true
}
const handleTouchMove = (event: TouchEvent) => {
if (!isSwiping || horizontalFlag.value) return
const touch = event.touches[0]
const deltaX = touch.pageX - startX
const deltaY = touch.pageY - startY
if (Math.abs(deltaX) > Math.abs(deltaY)) {
if (deltaX > 0) {
next()
} else if (deltaX < 0) {
pre()
}
isSwiping = false
}
}
const handleTouchEnd = () => {
if (horizontalFlag.value) {
isSwiping = false
}
}
const disposeAll = () => {
charts.value.forEach((chart: any) => {
chart && chart.dispose()
chart = null
})
}
const onChange = (e: number) => {
active.value = e
const chart = charts.value[e]
chart && chart.dispose()
getData(active.value)
}
const showHorizontalChart = () => {
horizontalFlag.value = !horizontalFlag.value
if (!swipe.value) {
nextTick(() => {
charts.value[active.value].resize()
})
} else {
disposeAll()
swipe.value && swipe.value.resize()
getData(active.value)
}
}
const next = () => {
swipe.value.next()
}
const pre = () => {
swipe.value.prev()
}
return {
onChange,
active,
next,
pre,
swipe,
disposeAll,
showHorizontalChart,
horizontalFlag,
horizontalDiv
}
}

59
src/mixins/scroll-list.ts Normal file
View File

@@ -0,0 +1,59 @@
import { ref, onActivated, nextTick } from 'vue'
import listRefreshInfoStore from '@/stores/listRefreshInfo'
const listRefreshInfo = listRefreshInfoStore()
export function scrollList(pageContent: any, scrollPosition: any, refresh: any) {
const refreshFlag = ref(true)
function setScrollTop() {
console.log(1)
onActivated(() => {
nextTick(() => {
if (listRefreshInfo.refreshId) {
refresh('refresh', listRefreshInfo.refreshId)
listRefreshInfo.refreshId = ''
} else if (listRefreshInfo.deleteId) {
refresh('delete', listRefreshInfo.deleteId)
listRefreshInfo.deleteId = ''
} else if (listRefreshInfo.addId) {
listRefreshInfo.addId = ''
refresh()
} else {
if (scrollPosition.value && pageContent.value) {
pageContent.value.scrollTop = scrollPosition.value
}
if (refreshFlag.value === true) {
refresh()
}
}
})
})
}
function setScrollPositionAndRefreshFlag(to: any, page: string) {
if (pageContent.value) {
scrollPosition.value = pageContent.value.scrollTop
}
if (page.includes(',')) {
const pages = page.split(',')
const index = pages.findIndex((ele: any) => {
return to.name === ele
})
if (index > -1) {
refreshFlag.value = false
} else {
refreshFlag.value = true
}
} else {
if (to.name === page) {
refreshFlag.value = false
} else {
refreshFlag.value = true
}
}
}
return {
setScrollTop,
setScrollPositionAndRefreshFlag
}
}

14
src/mixins/show-file.ts Normal file
View File

@@ -0,0 +1,14 @@
export function showFile() {
function loadingFile(url: any) {
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
return {
loadingFile
}
}

48
src/mixins/tap-mixins.ts Normal file
View File

@@ -0,0 +1,48 @@
import { ref } from 'vue'
import { useRouter } from 'vue-router'
export function tapMixins() {
const tappedItem = ref<string | null>(null)
const isProcessing = ref(false)
const router = useRouter()
const onTouchStart = (name: string) => {
if (isProcessing.value) return
tappedItem.value = name
}
const onTouchEnd = (name: string, url: string, id?: number, delay: number = 150) => {
if (isProcessing.value || tappedItem.value !== name) return
isProcessing.value = true
setTimeout(() => {
tappedItem.value = null
setTimeout(() => {
if (url) {
if (id) {
router.push({ name: url, params: { id } })
} else {
router.push({ name: url })
}
} else {
showToast('正在开发中')
}
isProcessing.value = false
}, 50)
}, delay)
}
const onTouchCancel = (name: string) => {
if (tappedItem.value === name) {
tappedItem.value = null
}
}
return {
tappedItem,
isProcessing,
onTouchStart,
onTouchEnd,
onTouchCancel
}
}

246
src/router/index.ts Normal file
View File

@@ -0,0 +1,246 @@
import { createRouter, createWebHistory } from 'vue-router'
import userInfoStore from '@/stores/userInfo'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/404',
name: '404',
component: () => import('../views/common/error.vue'),
meta: { title: '404未找到', isPass: true }
},
{
path: '/login',
name: 'login',
component: () => import('../views/common/login.vue'),
meta: { title: '登录', isPass: true }
},
{
path: '/loading-page',
name: 'loading-page',
component: () => import('../views/common/loading-page.vue'),
meta: { title: '加载', isPass: true }
},
{
path: '/organization-list',
name: 'organization-list',
component: () => import('../views/organization/list.vue'),
meta: { title: '机构列表', keepAlive: true }
},
{
path: '/organization-detail',
name: 'organization-detail',
component: () => import('../views/organization/detail.vue'),
meta: { title: '机构详情' }
},
{
path: '/comment-list',
name: 'comment-list',
component: () => import('../views/comment/list.vue'),
meta: { title: '点评列表', keepAlive: true }
},
{
path: '/comment-detail',
name: 'comment-detail',
component: () => import('../views/comment/detail.vue'),
meta: { title: '点评详情' }
},
{
path: '/meeting-list',
name: 'meeting-list',
component: () => import('../views/meeting/list.vue'),
meta: { title: '会议列表', keepAlive: true }
},
{
path: '/meeting-detail',
name: 'meeting-detail',
component: () => import('../views/meeting/detail.vue'),
meta: { title: '会议详情' }
},
{
path: '/add-or-update-meeting',
name: 'add-or-update-meeting',
component: () => import('../views/meeting/add-or-update-meeting.vue'),
meta: { title: '会议' }
},
{
path: '/communicate-list',
name: 'communicate-list',
component: () => import('../views/communicate/list.vue'),
meta: { title: '沟通列表', keepAlive: true }
},
{
path: '/communicate-detail',
name: 'communicate-detail',
component: () => import('../views/communicate/detail.vue'),
meta: { title: '沟通详情' }
},
{
path: '/add-cus',
name: 'add-cus',
component: () => import('../views/communicate/add-cus.vue'),
meta: { title: '添加机构' }
},
{
path: '/add-cusUser',
name: 'add-cusUser',
component: () => import('../views/communicate/add-cusUser.vue'),
meta: { title: '添加联系人' }
},
{
path: '/report-list',
name: 'report-list',
component: () => import('../views/report/list.vue'),
meta: { title: '报告列表' }
},
{
path: '/stock-list',
name: 'stock-list',
component: () => import('../views/stock/list.vue'),
meta: { title: '股票动态', keepAlive: true }
},
{
path: '/stock-detail',
name: 'stock-detail',
component: () => import('../views/stock/detail.vue'),
meta: { title: '股票详情', keepAlive: true }
},
{
path: '/target-table',
name: 'target-table',
component: () => import('../views/target/table.vue'),
meta: { title: '重要指标', keepAlive: true, isPass: true }
},
{
path: '/economy-list',
name: 'economy-list',
component: () => import('../views/target/economy/economy-list.vue'),
meta: { title: '经济专题', keepAlive: true }
},
{
path: '/economy-detail/:id?',
name: 'economy-detail',
component: () => import('../views/target/economy/economy-detail.vue'),
meta: { title: '专题详情' }
},
{
path: '/fred-list',
name: 'fred-list',
component: () => import('../views/target/fred/fred-list.vue'),
meta: { title: 'FRED专题', keepAlive: true }
},
{
path: '/fred-detail/:id?',
name: 'fred-detail',
component: () => import('../views/target/fred/fred-detail.vue'),
meta: { title: 'FRED详情' }
},
{
path: '/series-list',
name: 'series-list',
component: () => import('../views/target/series/series-list.vue'),
meta: { title: '指数系列', keepAlive: true }
},
{
path: '/index-list/:id?',
name: 'index-list',
component: () => import('../views/target/index/index-list.vue'),
meta: { title: '指数专题', keepAlive: true }
},
{
path: '/index-detail/:id?',
name: 'index-detail',
component: () => import('../views/target/index/index-detail.vue'),
meta: { title: '指数详情', keepAlive: true }
},
{
path: '/etf-detail/:id?',
name: 'etf-detail',
component: () => import('../views/target/etf/etf-detail.vue'),
meta: { title: 'ETF详情' }
},
{
path: '/fund-list',
name: 'fund-list',
component: () => import('../views/target/fund/fund-list.vue'),
meta: { title: '基金列表', keepAlive: true }
},
{
path: '/fund-detail/:id?',
name: 'fund-detail',
component: () => import('../views/target/fund/fund-detail.vue'),
meta: { title: '基金详情' }
},
{
path: '/group-list',
name: 'group-list',
component: () => import('../views/target/group/group-list.vue'),
meta: { title: '精选组合', keepAlive: true }
},
{
path: '/group-detail/:id?',
name: 'group-detail',
component: () => import('../views/target/group/group-detail.vue'),
meta: { title: '组合详情' }
},
{
path: '/internal-meeting',
name: 'internal-meeting',
component: () => import('../views/internal/meeting.vue'),
meta: { title: '内部会议' }
},
{
path: '/subscription',
name: 'subscription',
component: () => import('../views/common/subscription.vue'),
meta: { title: '订阅' }
},
{
path: '/flow-company',
name: 'flow-company',
component: () => import('../views/flow/company.vue'),
meta: { title: '重点公司', keepAlive: true }
},
{
path: '/flow',
name: 'flow',
component: () => import('../views/flow/flow.vue'),
meta: { title: '重点公司追踪', keepAlive: true }
},
{
path: '/flow-detail',
name: 'flow-detail',
component: () => import('../views/flow/flow-detail.vue'),
meta: { title: '重点公司追踪详情' }
},
{
path: '/user',
name: 'user',
component: () => import('../views/user/index.vue'),
meta: { title: '个人中心', isPass: true }
},
{
path: '/log',
name: 'log',
component: () => import('../views/common/log.vue'),
meta: { title: '日志详情', isPass: true }
},
{
path: '/',
name: 'home',
component: () => import('../views/home/index.vue'),
meta: { title: '首页', isPass: true }
}
]
})
router.beforeEach((to, from, next) => {
const userInfo = userInfoStore()
if (!to.meta.isPass && !userInfo.token ) {
return next({ name: 'login' })
}
next()
})
export default router

View File

@@ -0,0 +1,17 @@
import { defineStore } from 'pinia'
export default defineStore('etfStockInfo', {
state: () => ({
stockCode: '',
companyList: <any>[],
xAxisData: []
}),
persist: {
enabled: true,
strategies: [
{
key: 'etfStockInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

15
src/stores/fileInfo.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineStore } from 'pinia'
export default defineStore('fileInfo', {
state: () => ({
fileId: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'fileInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

38
src/stores/index.ts Normal file
View File

@@ -0,0 +1,38 @@
import { createPinia } from 'pinia'
import persistedState from 'pinia-plugin-persistedstate'
import SecureLS from 'secure-ls'
export const ls = new SecureLS({
encodingType: 'aes',
isCompression: false
})
// 创建
const pinia = createPinia()
pinia.use(persistedState)
pinia.use(({ store }) => {
// 加密状态并存储到 SecureLS 中
const encryptAndStoreState = () => {
ls.set(store.$id, store.$state)
}
// 解密状态并恢复到 Pinia 中
const decryptAndRestoreState = () => {
try {
const data = ls.get(store.$id)
if (data) {
store.$state = data
}
} catch (error) {
console.error('Error decrypting and restoring state:', error)
// 处理异常情况,比如重新初始化状态或显示错误信息
store.$reset()
}
}
// 在每次状态变更时调用加密函数
store.$subscribe(encryptAndStoreState)
// 在初始化时调用解密函数
decryptAndRestoreState()
})
// 导出
export default pinia

View File

@@ -0,0 +1,17 @@
import { defineStore } from 'pinia'
export default defineStore('fileInfo', {
state: () => ({
refreshId: '',
deleteId: '',
addId: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'fileInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

21
src/stores/reportInfo.ts Normal file
View File

@@ -0,0 +1,21 @@
import { defineStore } from 'pinia'
export default defineStore('speakInfo', {
state: () => ({
reportId: '',
index: 0,
stockCode: '',
stockName: '股票',
date: '日期',
reportTypeName: '类型',
reportType: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'speakInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

View File

@@ -0,0 +1,45 @@
import { defineStore } from 'pinia'
export default defineStore('saveCommunicateInfo', {
state: () => ({
linkTypeName: '',
selectlinkType: '',
dicNote2: '',
dicNote3: '',
sysDay: '',
selectsysDay: [] as string[],
sysTime: '',
selectsysTime: [] as string[],
cusName: '',
selectcus: '',
cusLevelName: '',
selectcusLevel: '',
linkTitle: '',
cusUserNames: '',
selectcusUserIds: [],
staffNames: '',
selectstaffIds: [],
saleNames: '',
selectsaleIds: [],
linkFromName: '',
linkFrom: 1,
linkWayName: '',
selectlinkWay: '',
linkNote: '',
meetingName: '',
selectmeeting: '',
staffNote: '',
cusLinkStockList: <any>[],
cusLinkStockListForLinkId: <any>[],
saleScore: 0,
saleNote: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'saveCommunicateInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

45
src/stores/saveCusInfo.ts Normal file
View File

@@ -0,0 +1,45 @@
import { defineStore } from 'pinia'
export default defineStore('saveCusInfo', {
state: () => ({
cusName: '',
cusLevelName: '',
selectcusLevel: '',
cusUserName: '',
selectcusUser: '',
companyTypeName: '',
selectcompanyType: '',
cityName: '',
selectcity: '',
address: '',
accountStatus: '1',
accountTime: '',
selectaccountTime: [] as string[],
accountTypeNames: '',
selectaccountTypeIds: [],
isMoney: '0',
tradeMoney: '',
capitalScaleName: '',
selectcapitalScale: '',
capitalScaleValue: '',
fundNum: '',
investOverseasPer: '',
investResearchNum: '',
investChannelName: '',
selectinvestChannel: '',
website: '',
companyCreateTime: '',
selectcompanyCreateTime: [] as string[],
introduction: '',
saleUserNames: '',
selectsaleUserIds: []
}),
persist: {
enabled: true,
strategies: [
{
key: 'saveCusInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

19
src/stores/saveInfo.ts Normal file
View File

@@ -0,0 +1,19 @@
import { defineStore } from 'pinia'
export default defineStore('speakInfo', {
state: () => ({
id: '',
name: 0,
type: '',
positionName: '',
day: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'speakInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

18
src/stores/speakInfo.ts Normal file
View File

@@ -0,0 +1,18 @@
import { defineStore } from 'pinia'
export default defineStore('speakInfo', {
state: () => ({
speakId: '',
index: 0,
speakType: '',
speakTypeName: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'speakInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

15
src/stores/stockInfo.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineStore } from 'pinia'
export default defineStore('stockInfo', {
state: () => ({
id: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'stockInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

View File

@@ -0,0 +1,45 @@
import { defineStore } from 'pinia'
export default defineStore('updateCommunicateInfo', {
state: () => ({
linkTypeName: '',
selectlinkType: '',
dicNote2: '',
dicNote3: '',
sysDay: '',
selectsysDay: [] as string[],
sysTime: '',
selectsysTime: [] as string[],
cusName: '',
selectcus: '',
cusLevelName: '',
selectcusLevel: '',
linkTitle: '',
cusUserNames: '',
selectcusUserIds: [],
staffNames: '',
selectstaffIds: [],
saleNames: '',
selectsaleIds: [],
linkFromName: '',
linkFrom: 1,
linkWayName: '',
selectlinkWay: '',
linkNote: '',
meetingName: '',
selectmeeting: '',
staffNote: '',
cusLinkStockList: <any>[],
cusLinkStockListForLinkId: <any>[],
saleScore: 0,
saleNote: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'updateCommunicateInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

22
src/stores/userInfo.ts Normal file
View File

@@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
export default defineStore('userInfo', {
state: () => ({
permissions: [] as string[],
token: '',
id: '',
username: '',
password: '',
roleId: '',
realname: '',
headPic: ''
}),
persist: {
enabled: true,
strategies: [
{
key: 'userInfo', // 持久化状态的键
storage: localStorage // 使用localStorage来持久化状态
}
]
}
})

1
src/types/vue-virtual-scroller.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'vue-virtual-scroller';

818
src/utils/api.ts Normal file
View File

@@ -0,0 +1,818 @@
import http from './request'
/** 系统 **/
// 登录微信公众号
export function wxMpJsapiGetJsapiTicket(params: object) {
return http.request({
url: '/wx/mp/jsapi/getJsapiTicket',
method: 'get',
params: params
})
}
export function sysLog(id: number) {
return http.request({
url: `/sys/user/log/app/info/${id}`,
method: 'get',
})
}
// 登录
export function sysLogin(data: object) {
return http.request({
url: '/sys/login',
method: 'post',
data: data
})
}
export function sysMenuNav() {
return http.request({
url: '/sys/menu/nav',
method: 'get'
})
}
// 获取用户信息
export function sysUserInfo() {
return http.request({
url: '/sys/user/info',
method: 'get'
})
}
// 获取研究员和管理层
export function sysUserGetStaffAndManagerList() {
return http.request({
url: '/sys/user/getStaffAndManagerList',
method: 'get'
})
}
// 获取销售和管理层
export function sysUserGetSaleAndManagerList() {
return http.request({
url: '/sys/user/getSaleAndManagerList',
method: 'get'
})
}
// 员工列表
export function workStaffLookup() {
return http.request({
url: '/work/staff/lookup',
method: 'get'
})
}
// 获取全公司
export function sysUserGetCompanyUserList() {
return http.request({
url: '/sys/user/getCompanyUserList',
method: 'get'
})
}
// 根据类型获取字典
export function sysDicListByType(params: object) {
return http.request({
url: '/sys/dic/listByType',
method: 'get',
params: params
})
}
// 根据类型和note获取字典
export function sysDicListByNote(params: object) {
return http.request({
url: '/sys/dic/listByNote',
method: 'get',
params: params
})
}
/** 点评 **/
// 点评列表
export function workInboxList(params: object) {
return http.request({
url: '/work/inbox/list',
method: 'get',
params: params
})
}
// 点评详情
export function workInboxInfo(inboxId: string) {
return http.request({
url: `/work/inbox/info/${inboxId}`,
method: 'get'
})
}
/** 会议 **/
// 会议列表
export function workMeetingList(query: string) {
return http.request({
url: `/work/meeting/list?${query}`,
method: 'get'
})
}
export function workMeetingUpdateOrSave(type: string, data: object) {
return http.request({
url: `/work/meeting/${type ? 'update' : 'save'}`,
method: type ? 'put' : 'post',
data: data
})
}
// 会议详情
export function workMeetingInfo(meetingId: string) {
return http.request({
url: `/work/meeting/info/${meetingId}`,
method: 'get'
})
}
// 删除会议
export function workMeetingDelete(meetingId: string) {
return http.request(
{
url: '/work/meeting/delete',
method: 'delete',
data: [meetingId]
},
true
)
}
// 会议可见人员
export function workMeetingUserList(params: object) {
return http.request({
url: '/work/meeting/user/list',
method: 'get',
params: params
})
}
// 通过会议id获取会议资料
export function workMeetingGetFileId(params: object) {
return http.request({
url: '/work/meeting/getFileId',
method: 'get',
params: params
})
}
// 通过会议id获取参会资料
export function workMeetinGetUserFileMenuId(params: object) {
return http.request({
url: '/work/meeting/getUserFileMenuId',
method: 'get',
params: params
})
}
// 反查会议
export function workMeetingLookUp(params: object) {
return http.request({
url: '/work/meeting/lookup',
method: 'get',
params: params
})
}
// 参会人员
export function cusUserMeetingLogList(params: object) {
return http.request({
url: '/cus/usermeetinglog/list',
method: 'get',
params: params
})
}
// 删除参会人员
export function cusUserMeetingLogDelete(data: object) {
return http.request(
{
url: '/cus/usermeetinglog/delete',
method: 'delete',
data: data
},
true
)
}
// 修改或新增参会人员
export function cusUserMeetingLogUpdateOrSave(type: string, data: object) {
return http.request(
{
url: `/cus/usermeetinglog/${type ? 'update' : 'save'}`,
method: type ? 'put' : 'post',
data: type ? data : [data]
},
!!type
)
}
// 获取文件列表
export function workFileListByMenuId(params: object) {
return http.request({
url: '/work/file/listByMenuId',
method: 'get',
params: params
})
}
/** 报告 **/
// 报告列表
export function stockReportList(query: string, params: object) {
return http.request({
url: `/stock/report/list${query}`,
method: 'get',
params: params
})
}
/** 沟通 **/
// 沟通列表
export async function cusLinkList(query: string) {
const { data } = await http.request({
url: `/cus/link/list?${query}`,
method: 'get'
})
return data.list.map((ele: any) => {
ele.sysDay = ele.sysDate.slice(0, 10)
switch (ele.linkType) {
case '32':
ele.tagColor = 'danger-background-color'
break
case '34':
ele.tagColor = 'primary-background-color'
break
case '31':
ele.tagColor = 'success-background-color'
break
}
return ele
})
}
// 沟通详情
export function cusLinkInfo(linkId: string) {
return http.request({
url: `/cus/link/info/${linkId}`,
method: 'get'
})
}
// 删除沟通
export function cusLinkDeleteByOne(params: object) {
return http.request({
url: '/cus/link/deleteByOne',
method: 'delete',
params: params
})
}
// 修改新增沟通
export function cusLinkUpdateOrSave(type: string, data: object) {
return http.request({
url: `/cus/link/${type ? 'update' : 'save'}`,
method: type ? 'put' : 'post',
data: type ? data : { cusLinkEntityList: [data] }
})
}
// 反查机构
export function cusInfoLookUp(params: object) {
return http.request({
url: '/cus/info/lookup',
method: 'get',
params: params
})
}
// 新增机构
export function cusInfoSave(data: object) {
return http.request({
url: '/cus/info/save',
method: 'post',
data: data
})
}
// 机构列表
export function cusInfoList(params: object) {
return http.request({
url: 'cus/info/list',
method: 'get',
params
})
}
export function cusInfoInfo(id: number) {
return http.request({
url: `cus/info/info/${id}`,
method: 'get'
})
}
export function cusServiceGetByCusId (params: object) {
return http.request({
url: '/cus/service/getByCusId',
method: 'get',
params
})
}
export function cusYearList (params: object) {
return http.request({
url: '/cus/year/list',
method: 'get',
params
})
}
export function cusAccountList (params: object) {
return http.request({
url: '/cus/account/list',
method: 'get',
params
})
}
export function cusMeetingList (params: object) {
return http.request({
url: '/cus/usermeetinglog/listByCusId',
method: 'get',
params
})
}
export function cusInfoCountByMonth (params: object) {
return http.request({
url: '/cus/info/countByMonth',
method: 'get',
params
})
}
// 交易详情
export function stockCusTradeMyTrade () {
return http.request({
url: '/stock/cus/trade/myTrade',
method: 'get'
})
}
// 反查联系人
export function cusUserLookUp(params: object) {
return http.request({
url: '/cus/user/lookup',
method: 'get',
params: params
})
}
export function cusUserCusPositionList() {
return http.request({
url: '/cus/user/cusPositionList',
method: 'get'
})
}
export function cusUserSave(data: object) {
return http.request({
url: '/cus/user/save',
method: 'post',
data: data
})
}
// 通过机构获取联系人
export function cusUserListByCusId(params: object) {
return http.request({
url: '/cus/user/listByCusId',
method: 'get',
params: params
})
}
/* 股票 */
// 股票列表
export function stockStockinfoEventNews(params: object) {
return http.request({
url: '/stock/stockinfo/event/news',
method: 'get',
params: params
})
}
export function stockStockinfoEventList(params: object) {
return http.request({
url: '/stock/stockinfo/event/list',
method: 'get',
params: params
})
}
// 反查股票
export function stockStockInfoLookUp(params: object) {
return http.request({
url: '/stock/stockinfo/lookup',
method: 'get',
params: params
})
}
export function reportEconomyMonthDepositVsM2() {
return http.request({
url: '/report/economy/month/depositVsM2',
method: 'get'
})
}
export function reportEconomyMonthMarketHSVsDeposit() {
return http.request({
url: '/report/economy/month/marketHSVsDeposit',
method: 'get'
})
}
export function reportEconomyMonthMarketHSJVsDeposit() {
return http.request({
url: '/report/economy/month/marketHSJVsDeposit',
method: 'get'
})
}
export function reportEconomyMonthListByItem(params: object) {
return http.request({
url: '/report/economy/month/listByItem',
method: 'get',
params: params
})
}
export function stockStockrzrqQueryRzrq() {
return http.request({
url: '/stock/stockrzrq/queryRzrq',
method: 'get'
})
}
export function stockStockrzrqQueryRzToTotalMarket() {
return http.request({
url: '/stock/stockrzrq/queryRzToTotalMarket',
method: 'get'
})
}
export function stockStockrzrqQueryRzjme(params: object) {
return http.request({
url: 'stock/stockrzrq/queryRzjme',
method: 'get',
params: params
})
}
export function reportEconomyMonthStockShares() {
return http.request({
url: '/report/economy/month/stockShares',
method: 'get'
})
}
export function reportEconomyMonthStockMarket() {
return http.request({
url: '/report/economy/month/stockMarket',
method: 'get'
})
}
export function reportEconomyMonthStockAmount() {
return http.request({
url: '/report/economy/month/stockAmount',
method: 'get'
})
}
export function reportEconomyMonthStockTurnoverRate() {
return http.request({
url: '/report/economy/month/stockTurnoverRate',
method: 'get'
})
}
export function reportEconomyMonthStockVolume() {
return http.request({
url: '/report/economy/month/stockVolume',
method: 'get'
})
}
export function reportEconomyMonthNewAccountNum() {
return http.request({
url: '/report/economy/month/newAccountNum',
method: 'get'
})
}
export function stockStockHsList() {
return http.request({
url: '/stock/stockhs/list',
method: 'get'
})
}
export function fredInfoList() {
return http.request({
url: '/fred/info/list',
method: 'get'
})
}
export function fredInfoInfo(id: string) {
return http.request({
url: `/fred/info/info/${id}`,
method: 'get'
})
}
export function fredDetailQueryAaaAndBbb(params: object) {
return http.request({
url: '/fred/detail/query/AaaAndBbb',
method: 'get',
params: params
})
}
export function fredDetailQueryFred(params: object) {
return http.request({
url: '/fred/detail/queryFred',
method: 'get',
params: params
})
}
export const indexInfoSeries = (params: object) => {
return http.request({
url: '/index/info/series',
method: 'get',
params: params
})
}
export const indexInfo = (id: string) => {
return http.request({
url: `/index/info/${id}`,
method: 'get'
})
}
export const indexInfoIndexHistChart = (id: string) => {
return http.request({
url: `/index/info/indexHistChart/${id}`,
method: 'get'
})
}
export function indexFundHisQueryFundHis(params: object) {
return http.request({
url: '/index/fund/his/queryFundHis',
method: 'get',
params: params
})
}
export const indexInfoIndustry1Chart = (id: string) => {
return http.request({
url: `/index/info/industry1Chart/${id}`,
method: 'get'
})
}
export const indexInfoIndustry2Chart = (id: string) => {
return http.request({
url: `/index/info/industry2Chart/${id}`,
method: 'get'
})
}
export const indexInfoStockGetChart = (params: object) => {
return http.request({
url: '/index/info/stock/getChart',
method: 'get',
params: params
})
}
export function indexFundList(params: object) {
return http.request({
url: '/index/fund/list',
method: 'get',
params: params
})
}
export function indexFundGetByEtfCode(params: object) {
return http.request({
url: '/index/fund/getByEtfCode',
method: 'get',
params: params
})
}
export function indexFundHisQueryEtfShareHis(params: object) {
return http.request({
url: '/index/fund/his/queryEtfShareHis',
method: 'get',
params: params
})
}
export function indexFundDetailDayCoinList(params: object) {
return http.request({
url: '/index/fund/detail/day/coinList',
method: 'get',
params: params
})
}
export function indexFundDetailDayList(params: object) {
return http.request({
url: '/index/fund/detail/day/list',
method: 'get',
params: params
})
}
export function indexFundDetailQuarterCircleByExchange(params: object) {
return http.request({
url: '/index/fund/detail/quarter/circleByExchange',
method: 'get',
params: params
})
}
export function indexFundDetailQuarterHisByExchange(params: object) {
return http.request({
url: '/index/fund/detail/quarter/hisByExchange',
method: 'get',
params: params
})
}
export function indexFundDetailQuarterGetChart(params: object) {
return http.request({
url: '/index/fund/detail/quarter/getChart',
method: 'get',
params: params
})
}
export function stockAFundList(params: object) {
return http.request({
url: '/stock/a/fund/list',
method: 'get',
params: params
})
}
export const stockAFundInfo = (id: string) => {
return http.request({
url: `/stock/a/fund/info/${id}`,
method: 'get'
})
}
export function stockAFundHisFundHisChart(params: object) {
return http.request({
url: '/stock/a/fund/his/fundHis/chart',
method: 'get',
params: params
})
}
export function stockAFundFileList(params: object) {
return http.request({
url: '/stock/a/fund/file/list',
method: 'get',
params: params
})
}
export function stockAFundHisFundHisList(params: object) {
return http.request({
url: '/stock/a/fund/his/fundHis/list',
method: 'get',
params: params
})
}
export function stockAFundListFund() {
return http.request({
url: 'stock/a/fund/listFund',
method: 'get'
})
}
export const groupList = (params: string) => {
return http.request({
url: `/group/list?${params}`,
method: 'get'
})
}
export function groupInfo(id: string) {
return http.request({
url: `/group/info/${id}`,
method: 'get'
})
}
export function groupHisChartIncomeRate(params: object) {
return http.request({
url: '/group/his/chart/incomeRate',
method: 'get',
params: params
})
}
export function groupHoldList (params: object) {
return http.request({
url: '/group/holdList',
method: 'get',
params
})
}
export function groupTradeList(params: object) {
return http.request({
url: '/group/trade/list',
method: 'get',
params: params
})
}
export function stockEtfDetailLookup(params: object) {
return http.request({
url: '/stock/etf/detail/lookup',
method: 'get',
params: params
})
}
// 内部会议
export function workSpeakList(params: object) {
return http.request({
url: '/work/speak/list',
method: 'get',
params: params
})
}
// 获取订阅信息
export function getSubscriptionList() {
return http.request({
url: '/stock/subscription/config/list',
method: 'get'
})
}
export function delSubscription(id: number) {
return http.request({
url: '/stock/subscription/config/delete',
method: 'delete',
params: {
id
}
})
}
export function saveSubscription(subscriptionItem: string) {
return http.request({
url: '/stock/subscription/config/save',
method: 'post',
data: {
subscriptionItem
}
})
}
// 重点公司追踪
export function stockCompanyFlowInfoListCompany() {
return http.request({
url: '/stock/company/follow/info/listCompany',
method: 'get'
})
}
export function stockCompanyFlowInfoList(params: object) {
return http.request({
url: '/stock/company/follow/info/list',
method: 'get',
params: params
})
}
export function stockCompanyFlowInfoInfo(id: number) {
return http.request({
url: `/stock/company/follow/info/info/${id}`,
method: 'get'
})
}

180
src/utils/chart.ts Normal file
View File

@@ -0,0 +1,180 @@
export function getXAxis(data: any) {
const xAxis: any = {
type: data.type || 'category',
name: data.name || '',
data: data.data || [],
show: data.showFlag !== false,
axisLabel: {
show: data.showFlag !== false,
showMaxLabel: true,
interval: 'auto',
rotate: data.rotate || 0,
formatter: data.axisLabel || '{value}',
},
gridIndex: data.gridIndex || 0,
}
if (data.max !== null && data.max !== undefined) xAxis.max = data.max
if (data.min !== null && data.min !== undefined) xAxis.min = data.min
if (data.maxInterval !== undefined) xAxis.maxInterval = data.maxInterval
if (data.minInterval !== undefined) xAxis.minInterval = data.minInterval
return xAxis
}
export function getYAxis(data: any) {
const yAxis: any = {
type: data.type || 'value',
name: data.name || '',
nameLocation: data.nameLocation || 'end',
scale: true,
show: data.showFlag !== false,
axisLabel: {
show: data.showFlag !== false,
formatter: data.axisLabel || '{value}',
},
axisLine: {
show: true,
lineStyle: {
color: data.color || '#000',
},
},
splitLine: {
show: false
},
position: data.position || 'left',
gridIndex: data.gridIndex || 0,
offset: data.offset || 0,
}
if (data.max !== null && data.max !== undefined) yAxis.max = data.max
if (data.min !== null && data.min !== undefined) yAxis.min = data.min
if (data.maxInterval !== undefined) yAxis.maxInterval = data.maxInterval
if (data.minInterval !== undefined) yAxis.minInterval = data.minInterval
if (data.splitNumber !== undefined) yAxis.splitNumber = data.splitNumber
return yAxis
}
export function getLineSeries(data: any) {
const series = {
type: 'line',
name: data.name || '',
data: data.data || [],
yAxisIndex: data.yAxisIndex || 0,
xAxisIndex: data.xAxisIndex || 0,
connectNulls: data.connectNulls || false,
symbol: data.symbol || 'none',
symbolSize: data.symbolSize || 0,
lineStyle:
data.color || data.lineType
? {
color: data.color,
type: data.lineType,
}
: undefined,
markPoint: data.markPoint,
areaStyle: data.areaStyle,
}
return series
}
export function getBarSeries (data: any) {
const serise = {
type: 'bar',
name: data.name || '',
data: data.data || [],
emphasis: {
focus: 'series'
},
label: {
show: data.labelFlag !== false,
position: data.position || 'top'
},
yAxisIndex: data.yAxisIndex || 0,
xAxisIndex: data.xAxisIndex || 0,
stack: data.stack || '',
showEmptyData: false
}
return serise
}
export function getBaseOption(data: any) {
data.series.map((ele: any) => {
if (ele.type !== 'line' || !ele?.data) return
if (ele.data.length === 1) {
ele.symbolSize = 4
ele.symbol = 'circle'
} else if (data.xAxis?.[0]?.type === 'category') {
ele.data.forEach((res: any, index: number) => {
if (typeof res !== 'object' || res === null || !('value' in res) || res.value !== null)
return
const prevPoint = ele.data?.[index - 1]
if (
index > 0 &&
prevPoint &&
typeof prevPoint === 'object' &&
'value' in prevPoint &&
prevPoint.value !== null
) {
prevPoint.symbolSize = 4
prevPoint.symbol = 'circle'
}
const nextPoint = ele.data?.[index + 1]
if (
ele.data &&
index < ele.data.length - 1 &&
nextPoint &&
typeof nextPoint === 'object' &&
'value' in nextPoint &&
nextPoint.value !== null
) {
nextPoint.symbolSize = 4
nextPoint.symbol = 'circle'
}
})
}
})
const option: any = {
series: data.series,
yAxis: Array.isArray(data.yAxis) ? data.yAxis : data.yAxis ? [data.yAxis] : undefined,
xAxis: Array.isArray(data.xAxis) ? data.xAxis : data.xAxis ? [data.xAxis] : undefined,
}
if (data.tooltip) {
option.tooltip = data.tooltip
}
if (data.tooltipFormatter) {
option.tooltip = {
...option.tooltip,
trigger: 'axis',
formatter: data.tooltipFormatter,
}
}
if (data.visualMap) option.visualMap = data.visualMap
if (data.toolbox) option.toolbox = data.toolbox
if (data.grid) option.grid = data.grid
if (data.legend) option.legend = data.legend
if (data.color) option.color = data.color
if (data.dataZoom) option.dataZoom = data.dataZoom
if (data.title) {
option.title = Array.isArray(data.title)
? data.title.map(convertTitle)
: [convertTitle(data.title)]
}
return option
}
function convertTitle(title: any) {
return {
text: title.text,
subtext: title.subtext,
show: title.show ?? true,
backgroundColor: title.backgroundColor || '#5FCFCB',
textStyle: {
color: title.textStyle?.color || '#fff',
fontSize: title.textStyle?.fontSize || '18',
...title.textStyle,
},
left: title.left || 'center',
top: title.top || 0,
}
}

15
src/utils/colorList.ts Normal file
View File

@@ -0,0 +1,15 @@
// 公共颜色列表,用于统一各页面的颜色方案
export const colorList = [
{ color: '#165DFF', bgColor: '#E8F3FF' },
{ color: '#FF7D00', bgColor: '#FFF7E8' },
{ color: '#00B42A', bgColor: '#E8FFEF' },
{ color: '#F53F3F', bgColor: '#FEE8E8' },
{ color: '#A855F7', bgColor: '#F3E8FF' },
{ color: '#EC4899', bgColor: '#FFE8EA' },
{ color: '#0FC6C2', bgColor: '#EBFFFA' },
{ color: '#722ED1', bgColor: '#F9F0FF' },
{ color: '#FFC53D', bgColor: '#FFF9E8' },
{ color: '#36CFC9', bgColor: '#E6FFFB' },
{ color: '#72BCFF', bgColor: '#EDF5FF' },
{ color: '#73D13D', bgColor: '#F0FFF4' }
]

241
src/utils/index.ts Normal file
View File

@@ -0,0 +1,241 @@
export function getWeek(week: any) {
const weeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
return weeks[week]
}
export function deepCopy(obj: any) {
const a = JSON.stringify(obj)
const newobj = JSON.parse(a)
return newobj
}
export function getStockTwoArray(data: any, type: string) {
const list: any = []
data[0].forEach((ele: any, index: number) => {
let res = {
stockCode1: ele.stockCode,
stockName1: ele.stockName,
value1: `${ele[type]}${type === 'companyNum' ? '家' : ''}`,
stockCode2: '',
stockName2: '',
value2: '',
stockCode3: '',
stockName3: '',
value3: '',
stockCode4: '',
stockName4: '',
value4: ''
}
res = getStockOneArray(res, data, 1, index, type)
res = getStockOneArray(res, data, 2, index, type)
res = getStockOneArray(res, data, 3, index, type)
list.push(res)
})
return list
}
export function getStockOneArray(res: any, data: any, len: number, index: number, type: string) {
if (data.length > len && data[len].length > index) {
res[`stockCode${len + 1}`] = data[len][index].stockCode
res[`stockName${len + 1}`] = data[len][index].stockName
res[`value${len + 1}`] = `${data[len][index][type]}${type === 'companyNum' ? '家' : ''}`
}
return res
}
export function formatMoney(value: any) {
const arr = (value + '').split('-')
const negativeFlag = ''
value = arr.length === 1 ? arr[0] : arr[1]
if (value > 100000000) {
return negativeFlag + Math.round(value / 1000000) / 100 + '亿'
} else if (value > 10000) {
return negativeFlag + Math.round(value / 100) / 100 + '万'
} else {
return negativeFlag + value
}
}
export function setDecimalPlaces(value: any, fixedNum: number, multiply = 100, divide = 100) {
if (value) {
return (Math.round(value * multiply) / divide).toFixed(fixedNum)
} else {
return ''
}
}
import moment from 'moment'
export function getEchartTime(dateValue: any) {
let time = ''
switch (dateValue) {
case '1':
time = moment().subtract(10, 'years').format('YYYY-MM-DD')
break
case '2':
time = moment().subtract(1, 'years').format('YYYY-MM-DD')
break
case '3':
time = moment().subtract(6, 'months').format('YYYY-MM-DD')
break
case '4':
time = moment().subtract(3, 'months').format('YYYY-MM-DD')
break
case '5':
time = moment().subtract(1, 'months').format('YYYY-MM-DD')
break
case '6':
time = moment().subtract(1, 'weeks').format('YYYY-MM-DD')
break
}
return time
}
export function sortArr(arr: any) {
return arr.sort((value1: any, value2: any) => {
return Date.parse(value1) - Date.parse(value2)
})
}
export function convertToUrl(obj: any) {
let url = ''
for (const key in obj) {
if (typeof obj[key] === 'number' || obj[key]) {
url += `&${key}=${obj[key]}`
}
}
return url ? url.slice(1) : ''
}
import userInfoStore from '@/stores/userInfo'
const userInfo = userInfoStore()
export function isAuth(key: string) {
return userInfo.permissions.indexOf(key) !== -1 || false
}
import * as CryptoJS from 'crypto-js'
export function encrypt(word: any) {
const keyStr = 'firshshanghai!@#' // 判断是否存在ksy不存在就用定义好的key
const key = CryptoJS.enc.Utf8.parse(keyStr)
const srcs = CryptoJS.enc.Utf8.parse(word)
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return encrypted.toString()
}
export function addOrSubtractTime(date: string | Date, value: number, unit: 'year' | 'month') {
const currentDate = typeof date === 'string' ? new Date(date) : date;
const newDate = new Date(currentDate.getTime())
if (unit === 'year') {
newDate.setFullYear(newDate.getFullYear() + value)
} else if (unit === 'month') {
newDate.setMonth(newDate.getMonth() + value)
}
const year = newDate.getFullYear();
const month = String(newDate.getMonth() + 1).padStart(2, '0')
const day = String(newDate.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`;
}
export function treeDataTranslate(data: any, id = 'id', pid = 'parentId') {
const res: any = []
const temp: any = {}
for (let i = 0; i < data.length; i++) {
temp[data[i][id]] = data[i]
}
for (let k = 0; k < data.length; k++) {
if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) {
if (!temp[data[k][pid]]['children']) {
temp[data[k][pid]]['children'] = []
}
if (!temp[data[k][pid]]['_level']) {
temp[data[k][pid]]['_level'] = 1
}
data[k]['_level'] = temp[data[k][pid]]._level + 1
temp[data[k][pid]]['children'].push(data[k])
} else {
res.push(data[k])
}
}
return res
}
export function formatNumberByUnit (value: any) {
if (value) {
value = value.toString()
const negativeFlag = value.indexOf('-') > -1
if (value.indexOf('-') > -1) {
value = value.replaceAll('-', '')
}
if (Number(value) >= 100000000) {
return `${negativeFlag ? '-' : ''}${Math.round(Number(value) / 1000000) / 100 + '亿'}`
} else if (Number(value) >= 10000) {
return `${negativeFlag ? '-' : ''}${Math.round(Number(value) / 100) / 100 + '万'}`
} else {
return `${negativeFlag ? '-' : ''}${value}`
}
} else {
return ''
}
}
export function formatNumber (n: any) {
if (n) {
n = n.toString()
const negativeFlag = n.indexOf('-') > -1
if (n.indexOf('-') > -1) {
n = n.replaceAll('-', '')
}
const numArr = n.toString().split('.')
const decmial = numArr[1] ? '.' + numArr[1] : ''
const a = numArr[0]
const b = parseInt(a).toString()
const len = a.length
if (len <= 3) {
return negativeFlag ? `-${b + decmial}` : b + decmial
}
const r = len % 3
const returnNumber = r > 0
? b.slice(0, r) + ',' + (b.slice(r, len).match(/\d{3}/g) || []).join(',') + decmial : (b.slice(r, len).match(/\d{3}/g) || []).join(',') + decmial
return negativeFlag ? `-${returnNumber}` : returnNumber
} else {
return '0'
}
}
export function padZeroAfterDecimal (num: number) {
let numStr = num.toString()
if (numStr.includes('.')) {
const parts = numStr.split('.')
let decimalPart = parts[1]
if (decimalPart.length < 2) {
decimalPart = decimalPart.padEnd(2, '0')
} else {
decimalPart = decimalPart.slice(0, 2)
}
numStr = parts[0] + '.' + decimalPart
} else {
numStr = numStr + '.00'
}
return numStr
}
export function getHtmlByValue(
val: any,
unit: any,
nullStr = '',
compareVal = 0,
raiseFlag = '+',
data?: any,
color?: any
) {
if (val === null || val === undefined) return nullStr
if (parseFloat(val) === compareVal) {
return `<b style="color: ${color || '#1b9c35'}">${data || val}${unit}</b>`
} else if (parseFloat(val) > compareVal) {
return `<b style="color: ${color || 'red'}">${raiseFlag}${data || val}${unit}</b>`
} else {
return `<b style="color: ${color || '#07c160'}">${data || val}${unit}</b>`
}
}

11
src/utils/mitt.ts Normal file
View File

@@ -0,0 +1,11 @@
import mitt from 'mitt'
type Events = {
showLoading: string
hiddenLoading: string
noteType: string
setTitle: object
}
const emitter = mitt<Events>()
export default emitter

92
src/utils/request.ts Normal file
View File

@@ -0,0 +1,92 @@
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
import router from '@/router'
import emitter from '@/utils/mitt'
import userInfoStore from '@/stores/userInfo'
class HttpRequest {
getInsideConfig() {
const config = {
baseURL: import.meta.env.VITE_BASE_URL,
timeout: 600000,
withCredentials: false,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
}
return config
}
// 请求拦截
interceptors(instance: AxiosInstance, url: string | number | undefined) {
instance.interceptors.request.use(
(config: any) => {
const userInfo = userInfoStore()
config.headers.token = userInfo.token
return config
},
(error: any) => {
return Promise.reject(error)
}
)
// 响应拦截
instance.interceptors.response.use(
(res: any) => {
const { data } = res
console.log('返回数据处理', res)
if (data.code === 0) {
return data
} else if (data.code === 401 || data.code === 404) {
const userInfo = userInfoStore()
userInfo.token = ''
router.push({
name: 'login',
query: {
// currentRoute存储了当前路由信息的对象
redirect: router.currentRoute.value.fullPath
}
})
emitter.emit('hiddenLoading', '')
} else {
return data
}
},
(error: any) => {
console.log('error==>', error)
if (error.response && (error.response.status === 401)) {
const userInfo = userInfoStore()
userInfo.token = ''
router.push({
name: 'login',
query: {
// currentRoute存储了当前路由信息的对象
redirect: router.currentRoute.value.fullPath
}
})
}
emitter.emit('hiddenLoading', '')
}
)
}
request(options: AxiosRequestConfig, openDefaultData = false) {
const instance = axios.create()
options = Object.assign(this.getInsideConfig(), options)
if (!options.url) {
throw new Error('URL is not defined in request options')
}
this.interceptors(instance, options.url)
if (options.data && openDefaultData) {
options.data = JSON.stringify(options.data)
}
if (options.url.includes('?')) {
options.url = options.url + '&t=' + new Date().getTime()
} else {
options.url = options.url + '?t=' + new Date().getTime()
}
return instance(options)
}
}
const http = new HttpRequest()
export default http

326
src/utils/types.ts Normal file
View File

@@ -0,0 +1,326 @@
export interface CommunicateObj {
showPop1: boolean
showPop2: boolean
showPop3: boolean
showPop4: boolean
showPop5: boolean
showPop6: boolean
popTitle: string
type: string
linkId: string
value1: string[]
value3: string[]
value4: string[]
value5: string[]
linkTypeName: string
selectlinkType: string
linkTypeList: any[]
dicNote2: string
dicNote3: string
sysDay: string
selectsysDay: any[]
sysTime: string
selectsysTime: any[]
cusName: string
searchValue: string
selectcus: string
showPop2List: any[]
cusLevelName: string
selectcusLevel: string
linkTitle: string
cusLevelList: any[]
cusUserNames: string
cusUserList: any[]
selectcusUserIds: any[]
staffNames: string
staffList: any[]
selectstaffIds: any[]
saleNames: string
saleList: any[]
selectsaleIds: any[]
linkFromName: string
linkFrom: number
linkFromRadio: string
linkFromField: string
linkFromList: any[]
linkWayName: string
selectlinkWay: string
linkWayList: any[]
linkNote: string
meetingName: string
selectmeeting: string
staffNote: string
cusLinkStockList: any[]
cusLinkStockListForLinkId: any[]
saleScore: number
saleNote: string
[key: string]: any
handleShowPop1: (val: string, title: string) => void
handleShowPop2: (val: string, title: string) => void
handleShowPop3: (val: string, title: string) => void
handleShowPop4: (val: string, title: string, index: number) => void
handleShowPop5: (val: string, title: string) => void
handleShowPop6: (index: number) => void
onConfirm1: ({ selectedOptions }: { selectedOptions?: any[] }) => void
onConfirm2: ({ selectedOptions }: { selectedOptions?: any[] }) => void
onSearchInput: () => any
deleteStock: (index: number) => void
onConfirm3: () => void
onConfirm4: ({ selectedValues }: { selectedValues?: any[] }) => void
changeLinkFrom: () => void
onConfirm5: () => void
}
export interface CusObj {
showPop1: boolean
showPop2: boolean
showPop3: boolean
showPop4: boolean
popTitle: string
type: string
value1: string[]
value3: string[]
value4: string[]
cusName: string
cusLevelName: string
selectcusLevel: string
cusLevelList: any[]
cusUserName: string
selectcusUser: string
searchValue: string
showPop2List: any[]
companyTypeName: string
selectcompanyType: string
companyTypeList: any[]
cityName: string
selectcity: string
cityList: any[]
address: string
accountStatus: string
accountTime: string
selectaccountTime: any[]
accountTypeNames: string
selectaccountTypeIds: any[]
accountTypeList: any[]
isMoney: string
tradeMoney: string
capitalScaleName: string
selectcapitalScale: string
capitalScaleList: any[]
capitalScaleValue: string
fundNum: string
investOverseasPer: string
investResearchNum: string
investChannelName: string
selectinvestChannel: string
investChannelList: any[]
website: string
companyCreateTime: string
selectcompanyCreateTime: any[]
introduction: string
saleUserNames: string
selectsaleUserIds: any[]
saleUserList: any[]
[key: string]: any
handleShowPop1: (val: string, title: string) => void
handleShowPop2: (val: string, title: string) => void
handleShowPop3: (val: string, title: string) => void
handleShowPop4: (val: string, title: string) => void
onConfirm1: ({ selectedOptions }: { selectedOptions?: any[] }) => void
onConfirm2: ({ selectedOptions }: { selectedOptions?: any[] }) => void
onSearchInput: () => any
onConfirm3: () => void
onConfirm4: ({ selectedValues }: { selectedValues?: any[] }) => void
setAccountStatus: () => any
}
export interface CusUserObj {
showPop1: boolean
showPop3: boolean
popTitle: string
type: string
value1: string[]
value3: string[]
cusUserName: string
positionName: string
selectposition: string
positionList: any[]
saleUserNames: string
selectsaleUserIds: any[]
saleUserList: any[]
selectcusUser: string
phone: string
mobile: string
email: string
wxName: string
address: string
preference: string
[key: string]: any
handleShowPop1: (val: string, title: string) => void
handleShowPop3: (val: string, title: string) => void
onConfirm1: ({ selectedOptions }: { selectedOptions?: any[] }) => void
onConfirm3: () => void
}
export interface MeetingObj {
showPop1: boolean
showPop2: boolean
showPop3: boolean
showPop4: boolean
showPop5: boolean
showPop6: boolean
popTitle: string
type: string
value1: string[]
value3: string[]
meetingTitle: string
sysDay: any
selectsysDay: any[]
sysTime: string
selectsysTime: any[]
meetingTypeName: string
selectmeetingType: string
meetingTypeList: any[]
meetingByName: string
selectmeetingBy: string
meetingByList: any[]
stockCodeList: any[]
searchValue: string
showPop2List: any[]
meetingGuestList: any[]
linkFromName: string
linkFrom: number
linkFromRadio: string
linkFromField: string
linkFromList: any[]
phoneBy: string
netBy: string
meetingPassword: string
organizerNames: string
selectorganizerIds: any[]
organizerList: any[]
content: string
visibleName: string
selectvisible: string
visibleList: any[]
staffNames: string
staffList: any[]
selectstaffIds: any[]
[key: string]: any
handleShowPop1: (val: string, title: string) => void
handleShowPop3: (val: string, title: string) => void
handleShowPop4: (val: string, title: string, index: number) => void
handleShowPop5: (val: string, title: string) => void
handleShowPop6: (val: string, title: string, index: number) => void
onSearchInput: () => any
deleteItemByType: (val: string, index: number) => void
onConfirm1: ({ selectedOptions }: { selectedOptions?: any[] }) => void
onConfirm2: ({ selectedOptions }: { selectedOptions?: any[] }) => void
onConfirm3: () => void
onConfirm4: ({ selectedValues }: { selectedValues?: any[] }) => void
changeLinkFrom: () => void
onConfirm5: () => void
}
export interface GroupObj {
stockGroupName: string
isPublic: string
}
export interface HisObj {
sysDate: any
selectsysDate: any[]
groupCodeId: any
id: any
dividend24Percent: string
dividend25Percent: string
showPop: boolean
value: string[]
type: string
popTitle: string
handleShowPop: (val: string, title: string) => void
onConfirm: ({ selectedValues }: { selectedValues?: any[] }) => void
[key: string]: any
}
export interface BuyOrSellObj {
groupId: any
tradeTime: any
selecttradeTime: any[]
isSell: number
num: number
stockName: string
stockCode: string
tradePrice: string
note: string
showPop: boolean
showPop2: boolean
searchValue: string
value: string[]
type: string
popTitle: string
handleShowPop: (val: string, title: string) => void
handleShowPop2: (val: string, title: string) => void
onConfirm: ({ selectedValues }: { selectedValues?: any[] }) => void
onSearchInput: () => any
onConfirm2: ({ selectedOptions }: { selectedOptions?: any[] }) => void
[key: string]: any
}
export interface bonnusObj {
groupId: any
stockName: string
selectstock: string
stockList: any[]
dividendDate: any
selectdividendDate: any[]
exDividendDate: any
selectexDividendDate: any[]
dps: number
sg1: any
sg2: any
note: string
showPop: boolean
showPop2: boolean
value: string[]
value1: string[]
type: string
popTitle: string
handleShowPop: (val: string, title: string) => void
handleShowPop2: (val: string, title: string) => void
onConfirm: ({ selectedValues }: { selectedValues?: any[] }) => void
onConfirm2: ({ selectedOptions }: { selectedOptions?: any[] }) => void
[key: string]: any
}
export interface IHoldList {
grouId?: number
stockCode?: string | number
stockName?: string
stockPricePercent?: number | string
stockPrice?: number | string
costPrice?: number | string
hkStockMarket?: number | string
holdPercent?: string | number
todayIncome?: number | string
totalIncome?: number | string
holdCost?: number | string
shares?: number | string
dividendMoney?: number | string
priceIncome?: number | string
dividendTaxMoney?: number | string
}
export interface IDicList {
id: number
dicType: string
dicKey: string
dicValue: string
dicNote: string
dicNote2: string
dicNote3: string
dicColor: string
dicSort: string
}
export interface IUserLog {
note: string
userName: string
}

View File

@@ -0,0 +1,90 @@
<template>
<div class="page-content">
<div class="normal-title f-b text-center inboxTitle p-10">{{ workInbox.inboxTitle }}</div>
<div class="text-center">
<van-tag type="primary" size="medium" v-if="workInbox.stockCode">
{{ workInbox.stockCode }}{{ workInbox.stockName }}
</van-tag>
<van-tag type="success" size="medium" class="ms-10">{{ workInbox.inboxTypeSecondName }}</van-tag>
</div>
<div class="content text-end mb-5 pe-16">{{ workInbox.firstChargeUserName }}</div>
<div class="content text-end pe-16">{{ workInbox.createTime }}</div>
<div v-html="workInbox.inboxContent" class="p-16 inboxContent content"></div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import emitter from '@/utils/mitt'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const init = () => {
if (route.query && route.query.inboxId) {
emitter.emit('showLoading', '')
let inboxId = route.query.inboxId
getWorkInboxDetail(inboxId)
} else {
router.push({ name: '404' })
}
}
let workInbox = ref({
inboxTitle: '',
stockCode: '',
stockName: '',
inboxTypeSecondName: '',
firstChargeUserName: '',
createTime: '',
inboxContent: ''
})
import { workInboxInfo } from '@/utils/api'
const getWorkInboxDetail = async (inboxId: any) => {
const { data } = await workInboxInfo(inboxId)
workInbox.value = data.workInbox
emitter.emit('setTitle', { title: workInbox.value.inboxTitle, type: 'comment' })
emitter.emit('hiddenLoading', '')
}
init()
</script>
<style lang='scss' scoped>
.inboxContent {
margin: 12px 16px 0;
width: calc(100vw - 32px);
line-height: 20px;
font-size: 16px;
overflow-y: auto;
height: calc(100vh - 140px);
@media screen and (min-width: 678px) {
margin: 12px 0;
width: calc(674px);
}
}
:deep(table) {
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
:deep(td) {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
white-space: nowrap;
}
:deep(img) {
width: 100%;
}
:deep(pre) {
white-space: pre-wrap;
word-wrap: break-word;
}
.inboxTitle {
max-width: 678px;
word-break: break-all;
}
</style>

125
src/views/comment/list.vue Normal file
View File

@@ -0,0 +1,125 @@
<template>
<div class="page-content" ref="pageContent">
<div class="head">
<van-image class="bg" src="/img/commentBg.jpg" fit="cover" />
<div class="headDiv p-16">
<div class="headTop">
<div class="d-flex justify-content-between">
<div>
<div class="border-bottom-1">
<span class="day">{{ day }}</span>
<span class="month1 ms-10">{{ month1 }}</span>
</div>
<div class="textSize text-end">{{ year }}</div>
</div>
<van-image class="logo border-radius-4" src="/img/completeLogo.jpg" />
</div>
</div>
<div class="d-flex justify-content-end">
<div class="border-radius-4 border-1 text-center p-8">
<div>{{ month2 }}</div>
<div>{{ week }}</div>
</div>
</div>
<div class="textSize">
<div>第一上海</div>
<div class="mt-2">精选点评让你了解更多行业新闻</div>
</div>
</div>
</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
<van-list class="mt-10" v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
:immediate-check="false">
<van-cell v-for="item in list" :key="item.inboxId"
@click="$router.push({ name: 'comment-detail', query: { inboxId: item.inboxId } })">
<div class="normal-title text-start balck-text-color f-b ellipsis-one">{{ item.inboxTitle }}</div>
<div class="d-flex justify-content-between">
<div class="content">{{ item.stockName ? item.stockName : '' }}</div>
<div class="content">{{ item.createTime }}</div>
</div>
</van-cell>
</van-list>
</van-pull-refresh>
<van-empty v-else />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { getWeek } from '@/utils/index'
import moment from 'moment'
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
import { scrollList } from '@/mixins/scroll-list'
import { onBeforeRouteLeave } from 'vue-router'
let year = ref(moment().format('YYYY'))
let month1 = ref(moment().format('MMM'))
let month2 = ref(moment().format('M') + '月')
let week = ref(getWeek(moment().format('d')))
let day = ref(moment().format('DD'))
let pageContent = ref(null)
const scrollPosition = ref(0)
const refresh = () => {
onRefresh()
}
import { workInboxList } from '@/utils/api'
const getData = async () => {
const { data } = await workInboxList({
curPage: curPage.value,
limit: 20,
inboxType: 4
})
return data
}
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'comment')
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
setScrollTop()
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'comment-detail')
})
</script>
<style lang='scss' scoped>
.page-content {
.head {
position: relative;
.bg {
width: 100%;
height: 220px;
}
.headDiv {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: #fff;
background: #171b2eb3;
.headTop {
.day {
font-size: 60px;
}
.month1 {
font-size: 26px;
}
.logo {
height: 57px;
width: 132px;
border: 2px solid #fff;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,11 @@
<template>
<div class="page-content">
<van-empty description="404" image="network">
<van-button size="mini" type="primary" plain @click="$router.go(-1)" class="bottom-button">返回上一页</van-button>
<van-button size="mini" type="success" @click="$router.push({ name: 'home' })"
class="bottom-button">进入首页</van-button>
</van-empty>
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss"></style>

View File

@@ -0,0 +1,195 @@
<template>
<div>
<div class="loading-page">
<div>
<div class="grid">
<div class="item-animation item-animation1">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="item-animation item-animation2">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="item-animation item-animation3">
<img
src="data:image/jpeg;base64,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" />
</div>
<div class="item-animation item-animation4">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="item-animation item-animation5">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="item-animation item-animation6">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="item-animation item-animation7">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="item-animation item-animation8">
<img
src="data:image/jpeg;base64,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">
</div>
<div class="item-animation item-animation9">
<img
src="data:image/jpeg;base64,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">
</div>
</div>
<div class="normal-title f-b mb-5 mt-5">第一上海證券有限公司/第一上海期貸有限公司</div>
<div class="sub-title mb-5">FIRST SHANGHAI SECURITIES LIMITED</div>
<div class="tip mb-5">香港中環德輔道中 71 號永安集團大廈 19 樓D</div>
<div class="tip mb-5">19/F, Wing On House, 71 Des Voeux Road Central, Hong Kong</div>
<div class="tip mb-5">電話 Tel:8522532 1580 傳真 Fax:8522537 6911</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
import userInfoStore from '@/stores/userInfo'
const userInfo = userInfoStore()
const getCode = () => {
const code = getUrlParam('code') // 截取路径中的code如果没有就去微信授权如果已经获取到了就直接传code给后台获取openId
const local = window.location.href
if (code == null || code === '') {
window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1d52f91f259a691f&redirect_uri=' + encodeURIComponent(local) + '&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect'
} else {
userInfo.code = code
window.location.href = 'http://h5.szzztec.com/login'
}
}
const getUrlParam = (name: string) => {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
var r = window.location.search.substr(1).match(reg)
if (r != null) return unescape(r[2])
return null
}
getCode()
</script>
<style lang='scss' scoped>
.loading-page {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
max-width: 678px;
height: 100vh;
margin: 0 auto;
.grid {
height: 120px;
width: 120px;
margin: 0 auto;
.item-animation {
width: 33%;
height: 33%;
float: left;
-webkit-animation: itemFlicker 1.3s infinite ease-in-out;
animation: itemFlicker 1.3s infinite ease-in-out;
background-size: cover;
img {
width: 100%;
}
}
.item-animation1 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.item-animation2 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.item-animation3 {
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.item-animation4 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.item-animation5 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.item-animation6 {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.item-animation7 {
-webkit-animation-delay: 0;
animation-delay: 0;
}
.item-animation8 {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.item-animation9 {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
}
.text1 {
font-size: 18px;
margin-top: 20px;
font-weight: bold;
}
.text2 {
font-size: 14px;
}
.text3 {
font-size: 12px;
}
}
@-webkit-keyframes itemFlicker {
0%,
70%,
100% {
-webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1);
}
35% {
-webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1);
}
}
@keyframes itemFlicker {
0%,
70%,
100% {
-webkit-transform: scale3D(1, 1, 1);
transform: scale3D(1, 1, 1);
}
35% {
-webkit-transform: scale3D(0, 0, 1);
transform: scale3D(0, 0, 1);
}
}
</style>

112
src/views/common/log.vue Normal file
View File

@@ -0,0 +1,112 @@
<template>
<div class="page-content">
<div class="title">請求詳情</div>
<div class="request">
<div class="request-detail">
<div class="name">note</div>
<div class="content">{{ logData.note }}</div>
</div>
<div class="request-detail">
<div class="name">userName</div>
<div class="content">{{ logData.userName }}</div>
</div>
</div>
<div class="title mt-16">日志详情</div>
<div class="card mt-16" v-for="item in logList" :key="item.id">
<div class="d-flex pb-16 border-bottom-1 justify-content-between">
<div>类型<span :style="{ color: item.logLevelColor }">{{ item.logLevelName }}</span></div>
<div class="create-time">创建时间{{ item.createTime }}</div>
</div>
<div class="d-flex mt-16">
详情
<div class="note flex-1" :class="!item.showAll ? 'text-ellipsis' : ''">{{ item.note }}
<div style="margin-top: 4px;" v-if="item.logLevel === 40000">错误信息{{ item.errorMsg }}</div>
</div>
<span class="activeColor" @click="item.showAll = !item.showAll">{{ item.showAll ? '收起' : '展开' }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { type IUserLog } from '@/utils/types'
const logData = reactive<IUserLog>({
note: '',
userName: ''
})
const logList = ref<any[]>([])
import emitter from '@/utils/mitt'
import { sysLog } from '@/utils/api'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const requestId = ref()
const init = () => {
if (route.query && route.query.requestId) {
requestId.value = route.query.requestId
getData()
} else {
router.push({ name: '404' })
}
}
const getData = async () => {
emitter.emit('showLoading', '')
const { data } = await sysLog(requestId.value)
logData.note = data.sysUserLog.note
logData.userName = data.sysUserLog.userName
logList.value = data.sysUserLogDetail.map((ele: any) => {
ele.showAll = false
return ele
})
emitter.emit('hiddenLoading', '')
}
init()
</script>
<style lang="scss" scoped>
.page-content {
padding: 10px;
font-size: 13px;
}
.pb-10{
padding-bottom: 10px;
}
table {
width: 100vw;
overflow: auto;
}
.create-time {
font-size: 12px;
color: #909090;
}
.title {
font-weight: bold;
text-align: center;
padding: 4px 0;
background-color: #d9d9d9;
}
.request {
border: 1px solid #e2e2e2;
.request-detail {
display: flex;
border-bottom: 1px solid #e2e2e2;
.name {
width: 100px;
padding: 10px;
text-align: center;
border-right: 1px solid #e2e2e2;
}
.content {
word-break: break-all;
padding: 10px;
flex: 1;
text-align: center;
color: #1989fa;
}
}
}
.note {
word-break: break-all;
margin-right: 4px;
}
</style>

182
src/views/common/login.vue Normal file
View File

@@ -0,0 +1,182 @@
<template>
<div class="page-div">
<div class="form-container d-flex align-center">
<div class="form">
<div class="text-center pt-20">
<van-image height="100" width="100" src="/img/smallLogo.jpg" round />
</div>
<div class="text-center">
<h2 class="big-title f-b mt-16">第一上海</h2>
<div class="sub-title mt-16">欢迎登录数据管理系统</div>
</div>
<van-form class="control-area" @submit="onSubmit" validate-trigger="onSubmit">
<van-field v-model="username" name="username" placeholder="请输入您的账户" label="账户"
:rules="[{ required: true, message: '请输入您的账户' }]" autocomplete="new-password" />
<van-field v-model="password" name="password" placeholder="请输入您的密码" label="密码"
:rules="[{ required: true, message: '请输入您的密码' }]" type="password" autocomplete="new-password" />
<div class="opr-area">
<van-button round block type="primary" :disabled="submitFlag" native-type="submit">
确定
</van-button>
</div>
</van-form>
</div>
</div>
<div class="company-info">
<div class="text-center mt-16 mb-16">
<div class="mb-16 normal-title">第一上海证券有限公司</div>
<div class="mb-8 sub-title">FIRST SHANGHAI SECURITIES LIMITED</div>
<div class="mb-5 tip">香港中環德輔道中 71 號永安集團大廈 19 樓D</div>
<div class="mb-5 tip">19/F, Wing On House, 71 Des Voeux Road Central, Hong Kong</div>
<div class="mb-5 tip">电话 Tel:8522532 1580 传真 Fax:8522537 6911</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import userInfoStore from '@/stores/userInfo'
const userInfo = userInfoStore()
const router = useRouter()
const route = useRoute()
let username = ref(userInfo.username || '')
let password = ref(userInfo.password || '')
let submitFlag = ref(false)
import { encrypt } from '@/utils/index'
import { sysLogin, sysUserInfo, sysMenuNav } from '@/utils/api'
const onSubmit = async () => {
submitFlag.value = true
const data: any = await sysLogin(
{
username: username.value,
password: encrypt(password.value),
}
)
if (data && data.code === 0) {
userInfo.username = username.value
userInfo.password = password.value
userInfo.token = data.token
getUserInfo()
} else {
submitFlag.value = false
showToast('账号密码错误')
}
}
const getUserInfo = async () => {
const data: any = await sysUserInfo()
userInfo.id = data.user.userId
userInfo.realname = data.user.realname
userInfo.headPic = data.user.headPic
userInfo.roleId = data.user.roleId + ''
getsysMenuNav()
}
const getsysMenuNav = async () => {
const data: any = await sysMenuNav()
userInfo.permissions = data.permissions
let redirect = route.query.redirect || '/'
if (typeof redirect !== 'string') {
redirect = '/'
}
router.replace(redirect)
}
</script>
<style lang='scss' scoped>
.form-container {
flex: 1;
height: 100%;
.form {
width: 100%;
background-color: #fff;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
padding: 16px;
margin: 16px;
@media screen and (min-width: 768px) {
padding: 40px;
margin: 40px;
}
.pt-20 {
padding-top: 20px;
@media screen and (min-width: 768px) {
padding-top: 30px;
}
}
.control-area {
margin: 32px auto;
padding: 0 16px;
width: 100%;
@media screen and (min-width: 768px) {
width: 100%;
margin: 40px auto;
}
.van-cell {
padding: 16px;
font-size: 16px;
margin: 0 0 16px;
width: 100%;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .1);
@media screen and (min-width: 768px) {
padding: 20px;
font-size: 18px;
margin-bottom: 20px;
}
&:last-child {
margin-bottom: 0;
}
:deep(.van-field__label) {
padding-right: 16px;
label {
max-width: 5em;
margin-right: 8px;
}
}
}
.opr-area {
padding: 16px 0;
.van-button {
height: 32px;
line-height: 32px;
@media screen and (min-width: 768px) {
height: 40px;
line-height: 40px;
font-size: 16px;
}
}
.van-button--disabled {
color: rgba(0, 0, 0, 0.15);
background-color: rgba(0, 0, 0, 0.05);
height: 32px;
line-height: 32px;
}
}
}
}
}
.company-info {
margin-top: 30px;
width: 100%;
padding: 20px;
@media screen and (min-width: 768px) {
padding: 30px;
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<div class="page-content">
<van-list class="pt-10">
<van-cell v-for="item in subscriptionList" :key="item.id" :title="item.subscriptionItemName">
<template #value>
<van-switch size="16" :disabled="item.disabled" v-model="item.isSubscription" @change="changeSub(item)" />
</template>
</van-cell>
</van-list>
</div>
</template>
<script setup lang="ts">
import { getSubscriptionList, delSubscription, saveSubscription } from '@/utils/api'
import { ref } from 'vue'
const subscriptionList = ref()
const getSubList = async () => {
const { data } = await getSubscriptionList()
subscriptionList.value = data.map((item: any) => {
return {
...item,
disabled: false,
isSubscription: !!item.userId
}
})
}
getSubList()
const changeSub = async (item: any) => {
item.disabled = true
if (!item.isSubscription) {
await delSubscription(item.id)
} else {
await saveSubscription(item.subscriptionItem)
}
await getSubList()
item.disabled = false
}
</script>
<style lang="scss" scoped>
.pt-10 {
padding-top: 10px;
}
.van-cell {
margin: 12px 16px 0;
width: calc(100vw - 32px);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
@media screen and (min-width: 678px) {
width: 646px;
}
}
</style>

View File

@@ -0,0 +1,449 @@
<template>
<div class="page-content">
<div class="add-cusUser-detail detailClass">
<van-form @submit="onSubmit" validate-trigger="onSubmit">
<van-field name="cusName" v-model="popObj.cusName" label="机构名" placeholder="请输入机构名" :required="true"
:rules="[{ required: true, message: '请填写机构名' }]" />
<van-field clickable name="cusLevelName" v-model="popObj.cusLevelName" label="机构评级" placeholder="点击选择机构评级"
@click="popObj.handleShowPop1('cusLevel', '机构评级')" readonly />
<van-popup v-model:show="popObj.showPop1" position="bottom">
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj[`${popObj.type}List`]"
@confirm="popObj.onConfirm1($event)" @cancel="popObj.showPop1 = false" v-model="popObj.value1" />
</van-popup>
<van-field clickable name="cusUserName" v-model="popObj.cusUserName" label="核心联系人" placeholder="点击选择机构"
@click="popObj.handleShowPop2('cusUser', '核心联系人')" readonly />
<van-popup v-model:show="popObj.showPop2" position="bottom">
<div class="d-flex align-items-center">
<van-field v-model="popObj.searchValue" :placeholder="'请输入' + popObj.popTitle"
@input="popObj.onSearchInput" />
<van-button block type="success" size="small" native-type="button" @click="toAddCusUser"
style="width: 60px; margin: 12px 16px 0 0">
新增
</van-button>
</div>
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj.showPop2List"
@confirm="popObj.onConfirm2($event)" @cancel="popObj.showPop2 = false" />
</van-popup>
<van-field clickable name="companyTypeName" v-model="popObj.companyTypeName" label="机构类型" placeholder="点击选择机构类型"
@click="popObj.handleShowPop1('companyType', '机构类型')" readonly />
<van-field clickable name="cityName" v-model="popObj.cityName" label="城市" placeholder="点击选择城市"
@click="popObj.handleShowPop1('city', '城市')" readonly />
<van-field name="address" v-model="popObj.address" label="地址" placeholder="请输入地址" />
<van-field name="radio" label="是否开户">
<template #input>
<van-radio-group v-model="popObj.accountStatus" direction="horizontal">
<van-radio name="0"></van-radio>
<van-radio name="1"></van-radio>
</van-radio-group>
</template>
</van-field>
<van-field v-show="popObj.accountStatus === '1'" clickable name="accountTime" v-model="popObj.accountTime"
label="开户时间" placeholder="点击选择日期" @click="popObj.handleShowPop4('accountTime', '开户时间')" readonly />
<van-popup v-model:show="popObj.showPop4" position="bottom">
<van-date-picker v-model="popObj.value4" :title="popObj.popTitle" @confirm="popObj.onConfirm4($event)"
@cancel="popObj.showPop4 = false" />
</van-popup>
<van-field v-show="popObj.accountStatus === '1'" clickable name="accountTypeNames"
v-model="popObj.accountTypeNames" label="开户类型" placeholder="点击选择开户类型"
@click="popObj.handleShowPop3('accountType', '开户类型')" readonly />
<van-popup v-model:show="popObj.showPop3" position="bottom">
<div class="p-16 box-popup">
<van-row class="mb-16">
<van-col span="8">
<van-button class="van-picker__cancel" native-type="button"
@click="popObj.showPop3 = false">取消</van-button>
</van-col>
<van-col span="8" class="text-center">
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
</van-col>
<van-col span="8" class="text-end">
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm3">确认</van-button>
</van-col>
</van-row>
<van-checkbox-group v-model="popObj.value3" direction="horizontal">
<van-checkbox v-for="(item, index) in popObj[`${popObj.type}List`]" :key="index" :name="item.value"
shape="square">
{{ item.text }}
</van-checkbox>
</van-checkbox-group>
</div>
</van-popup>
<van-field v-show="popObj.accountStatus === '1'" name="radio" label="是否交易">
<template #input>
<van-radio-group v-model="popObj.isMoney" direction="horizontal">
<van-radio name="0"></van-radio>
<van-radio name="1"></van-radio>
</van-radio-group>
</template>
</van-field>
<van-field v-show="popObj.accountStatus === '1' && popObj.isMoney === '1'" name="tradeMoney"
v-model="popObj.tradeMoney" label="交易总金额" placeholder="请输入交易总金额" />
<van-field v-show="popObj.accountStatus === '1' && popObj.isMoney === '1'" clickable name="capitalScaleName"
v-model="popObj.capitalScaleName" label="资金规模(亿)" placeholder="点击选择资金规模"
@click="popObj.handleShowPop1('capitalScale', '资金规模')" readonly />
<van-field v-show="popObj.accountStatus === '1' && popObj.isMoney === '1'" name="capitalScaleValue"
v-model="popObj.capitalScaleValue" label="资金规模(亿)" placeholder="请输入资金规模" @blur="popObj.setAccountStatus" />
<van-field name="fundNum" v-model="popObj.fundNum" label="基金数量" placeholder="请输入基金数量" />
<van-field name="investOverseasPer" v-model="popObj.investOverseasPer" label="海外投资占比" placeholder="请输入海外投资占比" />
<van-field name="investResearchNum" v-model="popObj.investResearchNum" label="投研人数" placeholder="请输入投研人数" />
<van-field clickable name="investChannelName" v-model="popObj.investChannelName" label="投资渠道"
placeholder="点击选择投资渠道" @click="popObj.handleShowPop1('investChannel', '投资渠道')" readonly />
<van-field name="website" v-model="popObj.website" label="公司网站" placeholder="请输入公司网站" />
<van-field clickable name="companyCreateTime" v-model="popObj.companyCreateTime" label="开户时间"
placeholder="点击选择公司创建时间" @click="popObj.handleShowPop4('companyCreateTime', '公司创建时间')" readonly />
<van-field name="introduction" v-model="popObj.introduction" label="公司简介" rows="3" type="textarea" autosize />
<van-field clickable name="saleUserNames" v-model="popObj.saleUserNames" label="负责人" placeholder="点击选择负责人"
@click="popObj.handleShowPop3('saleUser', '负责人')" readonly />
<div class="m-16 d-flex justify-content-center">
<van-button round block type="primary" size="small" native-type="submit"
:disabled="submitFlag">提交</van-button>
</div>
</van-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import moment from 'moment'
import emitter from '@/utils/mitt'
import { sysDicListByType, sysUserGetSaleAndManagerList, cusUserLookUp, cusInfoSave } from '@/utils/api'
let submitFlag = ref(false)
import { debounceThrottle } from '@/mixins/debounce-throttle'
const { throttle } = debounceThrottle()
import { type CusObj } from '@/utils/types'
const popObj: CusObj = reactive({
showPop1: false,
showPop2: false,
showPop3: false,
showPop4: false,
popTitle: '',
type: '',
value1: [],
value3: [],
value4: [],
cusName: '',
cusLevelName: '',
selectcusLevel: '',
cusLevelList: [],
cusUserName: '',
selectcusUser: '',
searchValue: '',
showPop2List: [],
companyTypeName: '',
selectcompanyType: '',
companyTypeList: [],
cityName: '',
selectcity: '',
cityList: [],
address: '',
accountStatus: '1',
accountTime: '',
selectaccountTime: [],
accountTypeNames: '',
selectaccountTypeIds: [],
accountTypeList: [],
isMoney: '0',
tradeMoney: '',
capitalScaleName: '',
selectcapitalScale: '',
capitalScaleList: [],
capitalScaleValue: '',
fundNum: '',
investOverseasPer: '',
investResearchNum: '',
investChannelName: '',
selectinvestChannel: '',
investChannelList: [],
website: '',
companyCreateTime: '',
selectcompanyCreateTime: [],
introduction: '',
saleUserNames: '',
selectsaleUserIds: [],
saleUserList: [],
handleShowPop1: (val: string, title: string) => {
popObj.showPop1 = true
popObj.popTitle = title
popObj.type = val
popObj.value1 = [popObj[`select${val}`]]
},
handleShowPop2: (val: string, title: string) => {
popObj.showPop2 = true
popObj.popTitle = title
popObj.type = val
popObj.searchValue = popObj[`${val}Name`]
popObj.showPop2List = popObj[`select${val}`] ? [{ text: popObj[`${val}Name`], value: popObj[`select${val}`] }] : []
},
handleShowPop3: (val: string, title: string) => {
popObj.showPop3 = true
popObj.popTitle = title
popObj.type = val
popObj.value3 = popObj[`select${val}Ids`]
},
handleShowPop4: (val: string, title: string) => {
popObj.popTitle = title
popObj.type = val
if (popObj[`select${val}`].length === 0) {
let nowDay = moment().format('YYYY-MM-DD HH:mm:ss')
popObj.value4 = nowDay.slice(0, 10).split('-')
} else {
popObj.value4 = popObj[`select${val}`]
}
popObj.showPop4 = true
},
onConfirm1: ({ selectedOptions }) => {
if (selectedOptions) {
popObj[`select${popObj.type}`] = selectedOptions[0].value
popObj[`${popObj.type}Name`] = selectedOptions[0].text
if (popObj.type === 'capitalScale') {
if (popObj.capitalScale === '1') {
if (Number(popObj.capitalScaleValue) > 10) {
popObj.capitalScaleValue = ''
}
} else if (popObj.capitalScale === '2') {
if (Number(popObj.capitalScaleValue) > 100 || Number(popObj.capitalScale) < 10) {
popObj.capitalScaleValue = ''
}
} else if (popObj.capitalScale === '3') {
if (Number(popObj.capitalScaleValue) < 100) {
popObj.capitalScaleValue = ''
}
}
}
}
popObj.showPop1 = false
},
onSearchInput: throttle(
async () => {
if (popObj.searchValue) {
const { data } = await cusUserLookUp({ cusUserName: popObj.searchValue })
popObj.showPop2List = data.map((ele: any) => {
ele.text = `${ele.cusUserName}${ele.positionName ? '【' + ele.positionName + '】' : ''}`
ele.value = ele.cusUserId
return ele
})
} else {
popObj.showPop2List = []
}
},
500
),
onConfirm2: ({ selectedOptions }) => {
if (selectedOptions && selectedOptions[0]) {
popObj[`select${popObj.type}`] = selectedOptions[0].value
popObj[`${popObj.type}Name`] = selectedOptions[0].text
popObj.showPop2 = false
} else {
showToast(`请选择${popObj.popTitle}后,再确认`)
}
},
onConfirm3: () => {
popObj[`select${popObj.type}Ids`] = popObj.value3
const sameList = popObj[`${popObj.type}List`].filter((ele: any) => popObj.value3.some((res: any) => {
return ele.value === res
}))
let names = ''
sameList.forEach((ele: any) => {
names += `${ele.text},`
})
if (names) {
names = names.substring(0, names.length - 1)
}
popObj[`${popObj.type}Names`] = names
popObj.showPop3 = false
},
onConfirm4: ({ selectedValues }) => {
if (selectedValues) {
popObj[popObj.type] = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`
popObj[`select${popObj.type}`] = selectedValues
}
popObj.showPop4 = false
},
setAccountStatus: () => {
if (Number(popObj.capitalScaleValue) >= 0 && Number(popObj.capitalScaleValue) < 10) {
popObj.selectcapitalScale = '1'
} else if (Number(popObj.capitalScaleValue) >= 10 && Number(popObj.capitalScaleValue) < 100) {
popObj.selectcapitalScale = '2'
} else if (Number(popObj.capitalScaleValue) >= 100) {
popObj.selectcapitalScale = '3'
} else {
popObj.selectcapitalScale = ''
}
let index = popObj.capitalScaleList.findIndex((ele: any) => {
return ele.dicKey === popObj.selectcapitalScale
})
if (index > -1) {
popObj.capitalScaleName = popObj.capitalScaleList[index].dicValue
} else {
popObj.capitalScaleName = ''
}
}
})
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
import saveCusInfoStore from '@/stores/saveCusInfo'
const saveCusInfo: any = saveCusInfoStore()
import saveInfoStore from '@/stores/saveInfo'
const saveInfo: any = saveInfoStore()
let linkId: any = ''
const getSelectList = async () => {
linkId = route.query.linkId
emitter.emit('showLoading', '')
const data1 = await sysDicListByType({ dicType: 'cus_level' })
popObj.cusLevelList = data1.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data2 = await sysDicListByType({ dicType: 'sale_company_type' })
popObj.companyTypeList = data2.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data3 = await sysDicListByType({ dicType: 'cus_city' })
popObj.cityList = data3.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data4 = await sysDicListByType({ dicType: 'account_type' })
popObj.accountTypeList = data4.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data5 = await sysDicListByType({ dicType: 'invest_channel' })
popObj.investChannelList = data5.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data6 = await sysUserGetSaleAndManagerList()
popObj.saleUserList = data6.data.map((ele: any) => {
ele.value = ele.userId
ele.text = ele.userName
return ele
})
const data7 = await sysDicListByType({ dicType: 'sale_capital_scale' })
popObj.capitalScaleList = data7.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
for (let i in saveCusInfo) {
if (Object.prototype.hasOwnProperty.call(popObj, i)) {
popObj[i] = saveCusInfo[i]
}
}
if (saveInfo.type === 'cusUser') {
popObj.cusUserName = `${saveInfo.name}${saveInfo.positionName ? '【' + saveInfo.positionName + '】' : ''}`
popObj.selectcusUser = saveInfo.id
saveInfo.$reset()
}
emitter.emit('hiddenLoading', '')
}
const handleKeyUp = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
}
}
onMounted(() => {
window.addEventListener('keyup', handleKeyUp)
})
onUnmounted(() => {
window.removeEventListener('keyup', handleKeyUp)
})
const toAddCusUser = () => {
for (let i in saveCusInfo) {
if (Object.prototype.hasOwnProperty.call(popObj, i)) {
saveCusInfo[i] = popObj[i]
}
}
if (linkId) {
router.push({ name: 'add-cusUser', query: { linkId: linkId, from: 'cus' } })
} else {
router.push({ name: 'add-cusUser', query: { linkId: linkId, from: 'cus' } })
}
}
const onSubmit = async () => {
submitFlag.value = true
const res: any = {
cusName: popObj.cusName,
cusLevel: popObj.selectcusLevel || 'E',
cusUserId: popObj.selectcusUser || null,
cusUserIdList: popObj.selectcusUser ? [popObj.selectcusUser] : [],
companyType: popObj.selectcompanyType || null,
city: popObj.selectcity || null,
address: popObj.address,
accountStatus: popObj.accountStatus,
accountTypeList: popObj.accountStatus === '1' && popObj.selectaccountTypeIds.length > 0 ?
popObj.selectaccountTypeIds : null,
accountTime: popObj.accountStatus === '1' && popObj.accountTime ?
popObj.accountTime : null,
tradeMoney: popObj.accountStatus === '1' && (popObj.tradeMoney === '1' || popObj.tradeMoney === '0') ?
popObj.tradeMoney : null,
capitalScaleValue: popObj.capitalScaleValue || null,
capitalScale: popObj.selectcapitalScale || null,
fundNum: popObj.fundNum || null,
investOverseasPer: popObj.investOverseasPer || null,
investResearchNum: popObj.investResearchNum || null,
investChannel: popObj.selectinvestChannel || null,
website: popObj.website || null,
companyCreateTime: popObj.companyCreateTime || null,
introduction: popObj.introduction || null,
saleUserIdList: popObj.selectsaleUserIds || null
}
const data: any = await cusInfoSave(res)
if (data.code === 0) {
showToast('提交成功')
saveInfo.id = data.data.cusId
saveInfo.name = data.data.cusName
saveInfo.type = 'cus'
saveCusInfo.$reset()
router.go(-1)
} else {
showToast(data.msg)
submitFlag.value = false
}
}
getSelectList()
</script>
<style lang='scss' scoped>
.add-cusUser-detail {
height: 100vh;
overflow-y: auto;
}
.detailClass {
.van-cell {
margin: 12px 16px 0;
width: calc(100vw - 32px);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
@media screen and (min-width: 678px) {
width: 646px;
}
}
}
:deep(.van-field__control) {
color: #000 !important;
}
:deep(.van-checkbox) {
margin-right: 0 !important;
width: 50%;
margin-bottom: 5px;
}
</style>

View File

@@ -0,0 +1,222 @@
<template>
<div class="page-content">
<div class="add-cus-detail detailClass">
<van-form @submit="onSubmit" validate-trigger="onSubmit">
<van-field name="cusUserName" v-model="popObj.cusUserName" label="联系人" placeholder="请输入联系人" :required="true"
:rules="[{ required: true, message: '请填写联系人' }]" />
<van-field clickable name="positionName" v-model="popObj.positionName" label="职位" placeholder="点击选择职位"
@click="popObj.handleShowPop1('position', '职位')" readonly :required="true"
:rules="[{ required: true, message: '请填写职位' }]" />
<van-popup v-model:show="popObj.showPop1" position="bottom">
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj[`${popObj.type}List`]"
@confirm="popObj.onConfirm1($event)" @cancel="popObj.showPop1 = false" v-model="popObj.value1" />
</van-popup>
<van-field clickable name="saleUserNames" v-model="popObj.saleUserNames" label="跟进人" placeholder="点击选择跟进人"
@click="popObj.handleShowPop3('saleUser', '跟进人')" readonly />
<van-popup v-model:show="popObj.showPop3" position="bottom">
<div class="p-16 box-popup">
<van-row class="mb-16">
<van-col span="8">
<van-button class="van-picker__cancel" native-type="button"
@click="popObj.showPop3 = false">取消</van-button>
</van-col>
<van-col span="8" class="text-center">
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
</van-col>
<van-col span="8" class="text-end">
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm3">确认</van-button>
</van-col>
</van-row>
<van-checkbox-group v-model="popObj.value3" direction="horizontal">
<van-checkbox v-for="(item, index) in popObj[`${popObj.type}List`]" :key="index" :name="item.value"
shape="square">
{{ item.text }}
</van-checkbox>
</van-checkbox-group>
</div>
</van-popup>
<van-field name="phone" v-model="popObj.phone" label="座机号" placeholder="请输入座机号" />
<van-field name="mobile" v-model="popObj.mobile" label="手机号" placeholder="请输入手机号" />
<van-field name="email" v-model="popObj.email" label="邮箱" placeholder="请输入邮箱" />
<van-field name="wxName" v-model="popObj.wxName" label="微信名" placeholder="请输入微信名" />
<van-field name="address" v-model="popObj.address" label="地址" placeholder="请输入地址" />
<van-field name="preference" v-model="popObj.preference" label="擅长" placeholder="请输入擅长" />
<div class="m-16 d-flex justify-content-center">
<van-button round block type="primary" size="small" native-type="submit"
:disabled="submitFlag">提交</van-button>
</div>
</van-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import emitter from '@/utils/mitt'
import { cusUserCusPositionList, sysUserGetSaleAndManagerList, cusUserSave } from '@/utils/api'
let submitFlag = ref(false)
import { type CusUserObj } from '@/utils/types'
const popObj: CusUserObj = reactive({
showPop1: false,
showPop3: false,
popTitle: '',
type: '',
value1: [],
value3: [],
cusUserName: '',
positionName: '',
selectposition: '',
positionList: [],
saleUserNames: '',
selectsaleUserIds: [],
saleUserList: [],
selectcusUser: '',
phone: '',
mobile: '',
email: '',
wxName: '',
address: '',
preference: '',
handleShowPop1: (val: string, title: string) => {
popObj.showPop1 = true
popObj.popTitle = title
popObj.type = val
popObj.value1 = [popObj[`select${val}`]]
},
handleShowPop3: (val: string, title: string) => {
popObj.showPop3 = true
popObj.popTitle = title
popObj.type = val
popObj.value3 = popObj[`select${val}Ids`]
},
onConfirm1: ({ selectedOptions }) => {
if (selectedOptions) {
popObj[`select${popObj.type}`] = selectedOptions[0].value
popObj[`${popObj.type}Name`] = selectedOptions[0].text
}
popObj.showPop1 = false
},
onConfirm3: () => {
popObj[`select${popObj.type}Ids`] = popObj.value3
const sameList = popObj[`${popObj.type}List`].filter((ele: any) => popObj.value3.some((res: any) => {
return ele.value === res
}))
let names = ''
sameList.forEach((ele: any) => {
names += `${ele.text},`
})
if (names) {
names = names.substring(0, names.length - 1)
}
popObj[`${popObj.type}Names`] = names
popObj.showPop3 = false
}
})
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
let cusId: any = ''
let linkId: any = ''
let from: any = ''
const getSelectList = async () => {
if (route.query && route.query.from) {
cusId = route.query && route.query.cusId ? route.query.cusId : ''
linkId = route.query && route.query.linkId ? route.query.linkId : ''
from = route.query.from
emitter.emit('showLoading', '')
const data1 = await cusUserCusPositionList()
popObj.positionList = data1.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data2 = await sysUserGetSaleAndManagerList()
popObj.saleUserList = data2.data.map((ele: any) => {
ele.value = ele.userId
ele.text = ele.userName
return ele
})
emitter.emit('hiddenLoading', '')
} else {
router.push({ name: '404' })
}
}
const handleKeyUp = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
}
}
onMounted(() => {
window.addEventListener('keyup', handleKeyUp)
})
onUnmounted(() => {
window.removeEventListener('keyup', handleKeyUp)
})
import saveInfoStore from '@/stores/saveInfo'
const saveInfo: any = saveInfoStore()
const onSubmit = async () => {
submitFlag.value = true
const res: any = {
cusUserName: popObj.cusUserName,
positionId: popObj.selectposition || null,
saleUserIdList: popObj.selectsaleUserIds || null,
phone: popObj.phone || null,
mobile: popObj.mobile || null,
email: popObj.email || null,
wxName: popObj.wxName || null,
address: popObj.address || null,
preference: popObj.preference || null
}
if (cusId) {
res.cusId = cusId
}
const data: any = await cusUserSave(res)
if (data.code === 0) {
showToast('提交成功')
saveInfo.id = data.data.cusUserId
saveInfo.name = data.data.cusUserName
saveInfo.positionName = data.data.positionName
saveInfo.type = 'cusUser'
router.go(-1)
} else {
submitFlag.value = false
}
}
getSelectList()
</script>
<style lang='scss' scoped>
.add-cus-detail {
height: 100vh;
overflow-y: auto;
}
.detailClass {
.van-cell {
margin: 12px 16px 0;
width: calc(100vw - 32px);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
@media screen and (min-width: 678px) {
width: 646px;
}
}
}
:deep(.van-field__control) {
color: #000 !important;
}
:deep(.van-checkbox) {
margin-right: 0 !important;
width: 50%;
margin-bottom: 5px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
<template>
<div class="page-content top-select-list" ref="pageContent">
<div v-if="userInfo.roleId === '1' || userInfo.roleId === '9' || userInfo.roleId === '10' || userInfo.roleId === '6'
|| userInfo.roleId === '7' || userInfo.roleId === '16'" class="addDiv success-background-color"
@click="$router.push({ name: 'communicate-detail', query: { addDay: selectDay } })">
<div class="text-center addDiv1">新增</div>
<div class="text-center addDiv2">沟通</div>
</div>
<div class="top-select border-bottom-1">
<van-dropdown-menu>
<van-dropdown-item :title="cusName" ref="itemRef">
<van-field v-model="searchValue" placeholder="请输入机构" @input="onSearchInput"
:right-icon="searchValue ? 'clear' : ''" @click-right-icon.stop="clearSearch" />
<van-picker show-toolbar title="机构列表" :columns="list" @confirm="onConfirm" @cancel="onCancel" />
</van-dropdown-item>
<van-dropdown-item v-model="linkType" :options="linkTypeList" @change="getLinkList" />
<van-dropdown-item v-model="saleId" :options="saleList" @change="getLinkList" />
</van-dropdown-menu>
</div>
<van-calendar title="" :poppable="false" :show-confirm="false" switch-mode="year-month" first-day-of-week="1"
:style="{ height: '410px' }" :formatter="formatter" :default-date="defaultDate" @panel-change="panelChange"
@select="onDateConfirm" />
<communicate-item class="pb-16" :list="list" />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { scrollList } from '@/mixins/scroll-list'
import { convertToUrl } from '@/utils'
import userInfoStore from '@/stores/userInfo'
const userInfo = userInfoStore()
import { cusLinkInfo, cusLinkList, sysUserGetSaleAndManagerList, cusInfoLookUp } from '@/utils/api'
let cusName = ref('机构')
let cusId = ref('')
let searchValue = ref('')
let itemRef = ref(null)
import { debounceThrottle } from '@/mixins/debounce-throttle'
const { throttle } = debounceThrottle()
const onSearchInput = throttle(
async () => {
if (searchValue.value) {
const { data } = await cusInfoLookUp({ cusName: searchValue.value })
list.value = data.map((ele: any) => {
ele.text = ele.cusName
ele.value = ele.cusId
return ele
})
} else {
list.value = []
}
},
500
)
const clearSearch = () => {
searchValue.value = ''
}
const onConfirm = (e: any) => {
console.log(e)
if (e.selectedValues.length > 0) {
cusName.value = e.selectedOptions[0].cusName
cusId.value = e.selectedOptions[0].cusId
searchValue.value = e.selectedOptions[0].cusName
getLinkList()
}
itemRef.value.toggle()
}
const onCancel = () => {
itemRef.value.toggle()
}
let linkType = ref('31,32,34')
let linkTypeList: any = ref([{
text: '全部类型',
value: '31,32,34'
}, {
text: '上市公司按需会议',
value: '31'
}, {
text: '机构路演',
value: '32'
}, {
text: '机构沟通',
value: '34'
}])
let saleId = ref('')
let saleList: any = ref([])
const getSaleList = async () => {
const { data } = await sysUserGetSaleAndManagerList()
saleList.value = data.map((ele: any) => {
ele.text = ele.userName
ele.value = ele.userId
return ele
})
saleList.value.unshift({ text: '全部负责人', value: '' })
}
getSaleList()
let defaultDate = ref()
import moment from 'moment'
const panelChange = (e: any) => {
dateBegin.value = moment(e.date).startOf('month').format('YYYY-MM-DD')
dateEnd.value = moment(e.date).endOf('month').format('YYYY-MM-DD')
getLinkList()
}
const onDateConfirm = (e: any) => {
defaultDate.value = e
getList()
}
let list: any = ref([])
let selectDay = ref('')
const getList = () => {
list.value = []
selectDay.value = moment(defaultDate.value).format('YYYY-MM-DD')
linkList.value.forEach((ele: any) => {
if (ele.sysDay === selectDay.value) {
list.value = ele.list
}
})
}
const formatter = computed(() => {
if (linkList.value.length === 0) {
return (day: any) => day
}
return (day: any) => {
let time = moment(day.date).format('YYYY-MM-DD')
linkList.value.forEach((ele: any) => {
if (ele.sysDay === time) {
day.topInfo = ele.num
}
})
return day
}
})
const dateBegin = ref()
const dateEnd = ref()
const linkList: any = ref([])
const getLinkList = async () => {
let url = getLinkParams()
const data = await cusLinkList(url)
let map = new Map()
data.forEach((item: any, index: any, arr: any) => {
if (!map.has(item.sysDay)) {
map.set(
item.sysDay,
arr.filter((a: any) => a.sysDay == item.sysDay)
)
}
})
let res = Array.from(map).map((item) => [...item[1]])
linkList.value = []
res.forEach((ele: any) => {
linkList.value.push({ sysDay: ele[0].sysDay, num: ele.length, list: ele })
})
getList()
}
const getLinkParams = () => {
const res = {
dateBegin: dateBegin.value,
dateEnd: dateEnd.value,
limit: 999,
dataStatus: 1,
curPage: 1,
saleId: saleId.value,
linkTypes: linkType.value,
cusId: cusId.value
}
const url = convertToUrl(res)
return url
}
let pageContent = ref(null)
const scrollPosition = ref(0)
const refresh = async (type: any, id: any) => {
if (type === 'delete' || type === 'refresh') {
let dayIndex = linkList.value.findIndex((ele: any) => {
return ele.sysDay === selectDay.value
})
if (dayIndex > -1) {
let index = linkList.value[dayIndex].list.findIndex((ele: any) => {
return Number(ele.linkId) === Number(id)
})
if (type === 'delete') {
if (index > -1) {
linkList.value[dayIndex].list.splice(index, 1)
linkList.value[dayIndex].num -= 1
}
} else {
if (index > -1) {
const { data } = await cusLinkInfo(id)
switch (data.linkTypeMenu) {
case '32':
data.tagColor = 'danger-background-color'
break
case '34':
data.tagColor = 'primary-background-color'
break
case '31':
data.tagColor = 'success-background-color'
break
}
linkList.value[dayIndex].list[index] = data
}
getList()
}
}
} else {
defaultDate.value = new Date()
dateBegin.value = moment().startOf('month').format('YYYY-MM-DD')
dateEnd.value = moment().endOf('month').format('YYYY-MM-DD')
getLinkList()
}
}
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
setScrollTop()
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'communicate-detail')
})
</script>
<style lang='scss' scoped>
:deep(.van-calendar__header-title) {
display: none;
}
:deep(.van-calendar__top-info) {
width: 16px;
height: 16px;
left: calc(100vw / 7 - 20px);
border-radius: 100%;
background-color: #f56c6c;
color: #fff;
@media screen and (min-width: 678px) {
left: 58px;
}
}
:deep(.van-calendar__day) {
border: 1px solid #f2f2f2;
margin-bottom: 0;
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div class="page-content">
<div class="tabbar-list" ref="pageContent">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
:immediate-check="false">
<van-grid :border="false" :clickable="true" :column-num="3">
<van-grid-item v-for="item in list" :key="item.id"
@click="$router.push({ name: 'flow', query: { stockCode: item.stockCode } })">
<div class="itemImage">
<svg class="stockIcon" aria-hidden="true">
<use :xlink:href="`#icon-${item.companyIcon}`"></use>
</svg>
</div>
<div class="itemName">{{ item.stockName }}</div>
</van-grid-item>
</van-grid>
</van-list>
</van-pull-refresh>
<van-empty v-else />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { stockCompanyFlowInfoListCompany } from '@/utils/api'
let pageContent = ref(null)
const scrollPosition = ref(0)
const getData = async () => {
const { data } = await stockCompanyFlowInfoListCompany()
return { list: data, totalCount: data.length }
}
const refresh = () => {
onRefresh()
}
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
const { refreshing, finished, loading, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'stock')
import { scrollList } from '@/mixins/scroll-list'
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
setScrollTop()
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'track')
})
</script>
<style lang='scss' scoped>
.tabbar-list {
padding: 0 20px;
}
:deep(.van-grid-item) {
padding-right: 20px;
padding-bottom: 26px;
}
:deep(.van-grid-item:nth-child(3n)) {
padding-right: 0;
}
:deep(.van-grid-item__content) {
padding: 0;
}
.itemImage {
margin-top: 18px;
height: 36px;
.stockIcon {
max-height: 36px;
width: auto;
max-width: 100%;
}
}
.itemName {
height: 20px;
line-height: 20px;
color: #666;
font-size: 12px;
text-align: center;
margin-top: 15px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div class="page-content p-10">
<div class="d-flex justify-content-between">
<van-tag type="primary" size="large">{{ flowData.followType }}</van-tag>
<div>{{ flowData.sysDate }}</div>
</div>
<div class="mt-10" v-if="flowData.fileId">
<audio :id="`auto${flowData.id}`" :src="flowData.fileUrl" controls></audio>
</div>
<div class="mt-10">
{{ flowData.introduce }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import emitter from '@/utils/mitt'
const route = useRoute()
const router = useRouter()
let id: any = ref('')
let flowData: any = ref({})
const init = () => {
if (route.query && route.query.id) {
id.value = route.query.id
emitter.emit('showLoading', '')
getData()
} else {
router.push({ name: '404' })
}
}
import { stockCompanyFlowInfoInfo } from '@/utils/api'
const getData = async () => {
const { data }: any = await stockCompanyFlowInfoInfo(id.value)
data.playFlag = false
flowData.value = data
emitter.emit('setTitle', { title: data.sysDate + data.stockName + data.flowType, type: 'comment' })
emitter.emit('hiddenLoading', '')
}
init()
</script>
<style lang='scss' scoped>
audio {
height: 36px;
}
</style>

60
src/views/flow/flow.vue Normal file
View File

@@ -0,0 +1,60 @@
<template>
<div class="page-content">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
:immediate-check="false">
<van-cell v-for="item in list" :key="item.id"
@click="$router.push({ name: 'flow-detail', query: { id: item.id } })">
<div class="text-start"><van-tag type="primary" size="large">{{ item.followType }}</van-tag></div>
<van-text-ellipsis class="text-start mt-10" :content="item.introduce" rows="3" expand-text="展开"
collapse-text="收起" @click-action="showOrHiddenText" />
<div>{{ item.sysDate }}</div>
</van-cell>
</van-list>
</van-pull-refresh>
<van-empty v-else />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router'
let pageContent = ref(null)
const scrollPosition = ref(0)
const route = useRoute()
const router = useRouter()
let stockCode: any = ref('')
const refresh = () => {
if (route.query && route.query.stockCode) {
stockCode.value = route.query.stockCode
onRefresh()
} else {
router.push({ name: '404' })
}
}
import { stockCompanyFlowInfoList } from '@/utils/api'
const getData = async () => {
const { data }: any = await stockCompanyFlowInfoList({
stockCode: stockCode.value,
curPage: curPage.value,
limit: 20
})
data.list.map((ele: any) => {
ele.playFlag = false
})
return data
}
const showOrHiddenText = (event: any) => {
event.stopPropagation()
}
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'stock')
import { scrollList } from '@/mixins/scroll-list'
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
setScrollTop()
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'track-detail')
})
</script>
<style lang='scss' scoped></style>

202
src/views/home/index.vue Normal file
View File

@@ -0,0 +1,202 @@
<template>
<div class="page-content tabbar-list">
<div class="home-top" :class="isNight ? 'night' : ''">
<div class="title">{{ tips }}</div>
<div class="top-content">
<van-image class="banner" src="/img/home-banner.jpg" alt="" />
<div class="name">第一上海</div>
<div class="welcome">数据管理系统欢迎您</div>
</div>
</div>
<van-grid :column-num="4" :border=false>
<van-grid-item v-if="userInfo.roleId === '10' || userInfo.roleId === '1'" class="btn"
@click="$router.push({ name: 'organization-list' })">
<div class="icon-box organization"><van-icon name="newspaper-o" size="24" /></div>
<span>机构</span>
</van-grid-item>
<van-grid-item class="btn" @click="$router.push({ name: 'meeting-list' })">
<div class="icon-box meeting"><van-icon name="calendar-o" size="24" /></div>
<span>会议</span>
</van-grid-item>
<van-grid-item class="btn" @click="$router.push({ name: 'communicate-list' })">
<div class="icon-box communicate"><van-icon name="comment-o" size="24" /></div>
<span>沟通</span>
</van-grid-item>
<van-grid-item class="btn" @click="$router.push({ name: 'comment-list' })">
<div class="icon-box comment"><van-icon name="diamond-o" size="24" /></div>
<span>点评</span>
</van-grid-item>
<van-grid-item class="btn" @click="$router.push({ name: 'report-list' })">
<div class="icon-box report"><van-icon name="description-o" size="24" /></div>
<span>报告</span>
</van-grid-item>
<van-grid-item class="btn" @click="$router.push({ name: 'target-table' })">
<div class="icon-box cash"><van-icon name="guide-o" size="24" /></div>
<span>指标</span>
</van-grid-item>
</van-grid>
<div class="card">
<div class="card-title">今日沟通</div>
<communicate-item :list="communicateList" />
</div>
<tabbar />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import userInfoStore from '@/stores/userInfo'
const userInfo = userInfoStore()
const time = moment().hour()
const isNight = computed(() => time < 6 || time >= 22)
const tips = computed(() => {
if (time < 6 || time >= 22) {
return '夜深了,早点休息'
} else if (time >= 19) {
return `晚上好,${userInfo.realname}`
} else if (time >= 12) {
return `下午好,${userInfo.realname}`
} else {
return `早上好,${userInfo.realname}`
}
})
import { cusLinkList } from '@/utils/api'
import { convertToUrl } from '@/utils'
import moment from 'moment'
const isLogin = computed(() => !!userInfo.token)
const communicateList = ref([])
const getCusLink = async () => {
if (!isLogin.value) return
const url = convertToUrl({
limit: 3,
curPage: 1,
dateBegin: moment().format('YYYY-MM-DD')
})
const data = await cusLinkList(url)
communicateList.value = data
}
getCusLink()
import { workMeetingList } from '@/utils/api'
const meetingList = ref<Array<any>>([])
const getMeetingList = async () => {
if (!isLogin.value) return
const url = convertToUrl({
dateBegin: moment().format('YYYY-MM-DD'),
dateEnd: moment().format('YYYY-MM-DD'),
meetingType: '1,2,3,16,17,4,5,15,6,10,11,12,13,14,7'
})
const { data } = await workMeetingList(url)
meetingList.value = data.slice(0, 3)
}
getMeetingList()
</script>
<style lang="scss" scoped>
.night {
background-image: url('/img/night.png');
background-repeat: no-repeat;
padding-top: 30px;
color: #fff;
}
.home-top {
padding: 20px 10px 0;
.top-content {
height: 120px;
z-index: 3;
position: relative;
@media screen and (min-width: 678px) {
height: 160px;
}
}
.title {
font-size: 16px;
margin-bottom: 20px;
font-weight: 600;
}
.name {
padding-top: 30px;
font-size: 18px;
color: #fff;
margin-left: 20px;
}
.welcome {
font-size: 14px;
color: #eee;
margin-left: 40px;
margin-top: 10px;
}
.banner {
border-radius: 6px;
width: 100%;
position: absolute;
border-radius: 6px;
overflow: hidden;
z-index: -1;
top: 0;
bottom: 0;
left: 0;
}
}
.btn {
text-align: center;
font-size: 13px;
transition: all 0.2s ease;
&:active {
transform: scale(0.95);
opacity: 0.8;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.icon-box {
width: 36px;
height: 36px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
margin-bottom: 4px;
}
.organization {
background-color: #ff8f00;
}
.meeting {
background-color: #a3cf7d;
}
.communicate {
background-color: #ffc700;
padding-top: 2px;
}
.comment {
background-color: #ff758f;
}
.report {
background-color: #5bb1ff;
}
.cash {
background-color: #f15b6c;
}
}
.card {
padding: 10px 0;
// margin-top: 10px;
margin: 10px 12px;
.card-title {
margin-left: 16px;
padding: 0 4px;
font-weight: 600;
border-left: 4px solid #5bb1ff;
margin-bottom: 10px;
font-size: 16px;
}
}
.empty {
color: #808080;
font-size: 14px;
text-align: center;
}
:deep(.van-empty__image) {
width: 80px;
height: 80px;
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<div class="page-content">
<div class="tabbar-list top-select-list" ref="pageContent">
<div class="top-select border-bottom-1">
<van-grid :border="false" :gutter="10" :column-num="2">
<van-grid-item>
<top-select1 :type="'speak_type'" :initialName="'类型'" @refresh="refreshBySpeakType"
:selectName="speakTypeName" :selectValue="speakType" />
</van-grid-item>
</van-grid>
</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
:immediate-check="false">
<van-cell v-for="(item, index) in list" :key="item.speakId" @click="showDetail(item, index)"
:id="item.speakId">
<div class="d-flex justify-content-between">
<div class="name1 text-start ">
<van-tag :class="item.tagColor" size="medium">{{ item.speakTypeName }}</van-tag>
</div>
<div class="name1 stockNames balck-text-color">{{ item.sysDate }}</div>
</div>
</van-cell>
</van-list>
</van-pull-refresh>
<van-empty v-else />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick, onUnmounted } from 'vue'
import TopSelect1 from '@/components/top-select1.vue'
import emitter from '@/utils/mitt'
import { workSpeakList } from '@/utils/api'
import speakInfoStore from '@/stores/speakInfo'
const speakInfo = speakInfoStore()
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
const scrollPage = () => {
nextTick(() => {
if (speakInfo.speakId && speakInfo.index) {
speakTypeName.value = speakInfo.speakTypeName
speakType.value = speakInfo.speakType
let pageIndex1 = speakInfo.index / 20
let pageIndex2 = speakInfo.index % 20
if (pageIndex2 > 1) {
pageIndex1++
}
if (pageIndex1 > curPage.value) {
onLoad()
} else {
const el = document.getElementById(speakInfo.speakId)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
speakInfo.speakId = ''
speakInfo.index = 0
emitter.emit('hiddenLoading', '')
}
} else {
emitter.emit('hiddenLoading', '')
}
})
}
const getData = async () => {
const { data } = await workSpeakList({
curPage: curPage.value,
limit: 20,
speakType: speakType.value || null
})
data.list.map((ele: any) => {
switch (ele.speakType) {
case 'CH':
ele.tagColor = 'primary-background-color'
break
case 'CF':
ele.tagColor = 'danger-background-color'
break
case 'YJZH':
ele.tagColor = 'success-background-color'
break
case 'SALE':
ele.tagColor = 'warning-background-color'
break
}
})
return data
}
const { refreshing, finished, loading, list, onLoad, onRefresh, curPage } = listLoadAndRefresh(getData, 'internal', scrollPage)
let speakTypeName = ref('类型')
let speakType = ref('')
const refreshBySpeakType = (e: any) => {
speakType.value = e.value
speakTypeName.value = e.name
onRefresh()
}
import { showFile } from '@/mixins/show-file'
const { loadingFile } = showFile()
const showDetail = (item: any, index: number) => {
speakInfo.index = index
speakInfo.speakId = item.speakId
speakInfo.speakTypeName = speakTypeName.value
speakInfo.speakType = speakType.value
loadingFile(item.tencentRecordUrl)
}
const onRefresh1 = () => {
speakTypeName.value = speakInfo.speakTypeName
speakType.value = speakInfo.speakType
onRefresh()
}
onRefresh1()
onUnmounted(() => {
speakInfo.speakTypeName = '类型'
speakInfo.speakType = ''
})
</script>
<style lang='scss' scoped>
.grid-item-text {
max-width: calc(50vw - 28px) !important;
@media screen and (min-width: 678px) {
max-width: 310px !important;
}
}
</style>

View File

@@ -0,0 +1,683 @@
<template>
<div class="page-content">
<div class="add-meeting" :class="disabledFlag ? 'detailClass2' : 'detailClass1'">
<van-form @submit="onSubmit" validate-trigger="onSubmit">
<van-field name="meetingTitle" v-model="popObj.meetingTitle" label="会议标题"
:placeholder="disabledFlag ? '' : '请输入会议标题'" :required="true"
:rules="[{ required: true, message: '请填写会议标题' }]" :disabled="disabledFlag" />
<van-field clickable name="sysDay" v-model="popObj.sysDay" label="会议日期"
:placeholder="disabledFlag ? '' : '点击选择日期'" @click="popObj.handleShowPop4('sysDay', '选择日期', 4)"
:required="true" :rules="[{ required: true, message: '请选择会议日期' }]" readonly :disabled="disabledFlag" />
<van-popup v-model:show="popObj.showPop4" position="bottom">
<van-date-picker v-model="popObj.value4" :title="popObj.popTitle" @confirm="popObj.onConfirm4($event)"
@cancel="popObj.showPop4 = false" />
</van-popup>
<van-field clickable name="sysTime" v-model="popObj.sysTime" label="会议时间"
:placeholder="disabledFlag ? '' : '点击选择时间'" @click="popObj.handleShowPop4('sysTime', '会议时间', 5)"
:required="true" :rules="[{ required: true, message: '请选择会议时间' }]" readonly :disabled="disabledFlag" />
<van-popup v-model:show="popObj.showPop5" position="bottom">
<van-time-picker v-model="popObj.value5" :title="popObj.popTitle" @confirm="popObj.onConfirm4($event)"
@cancel="popObj.showPop5 = false" />
</van-popup>
<van-field clickable name="meetingTypeName" v-model="popObj.meetingTypeName" label="会议类型"
:placeholder="disabledFlag ? '' : '点击选择会议类型'" @click="popObj.handleShowPop1('meetingType', '会议类型')"
:required="true" :rules="[{ required: true, message: '请选择会议类型' }]" readonly :disabled="disabledFlag" />
<van-popup v-model:show="popObj.showPop1" position="bottom">
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj[`${popObj.type}List`]"
@confirm="popObj.onConfirm1($event)" @cancel="popObj.showPop1 = false" v-model="popObj.value1" />
</van-popup>
<van-field clickable name="meetingByName" v-model="popObj.meetingByName" label="会议形式"
:placeholder="disabledFlag ? '' : '点击选择会议形式'" @click="popObj.handleShowPop1('meetingBy', '会议形式')"
:required="true" :rules="[{ required: true, message: '请选择会议形式' }]" readonly :disabled="disabledFlag" />
<div v-for="(item, index) in popObj.stockCodeList" :key="index">
<van-field clickable name="text" v-model="item.text" :label="`股票代码${index + 1}`"
:placeholder="disabledFlag ? '' : '点击选择股票'" @click="popObj.handleShowPop6('stockCode', '股票', index)"
type="text" autosize readonly :right-icon="disabledFlag ? '' : 'close'"
@click-right-icon.stop="popObj.deleteItemByType('stockCode', index)" :disabled="disabledFlag" />
</div>
<van-field clickable name="newItem" v-model="newItem" label="股票代码" placeholder="点击新增股票"
@click="popObj.handleShowPop6('stockCode', '股票', -1)" readonly v-show="!disabledFlag" />
<van-popup v-model:show="popObj.showPop2" position="bottom">
<van-field v-model="popObj.searchValue" :placeholder="'请输入' + popObj.popTitle"
@input="popObj.onSearchInput" />
<van-picker show-toolbar :title="popObj.popTitle" :columns="popObj.showPop2List"
@confirm="popObj.onConfirm2($event)" @cancel="popObj.showPop2 = false" />
</van-popup>
<div v-for="(item, index) in popObj.meetingGuestList" :key="index">
<van-field clickable name="text" v-model="item.text" :label="`嘉宾${index + 1}`"
:placeholder="disabledFlag ? '' : '点击选择宾'" @click="popObj.handleShowPop6('meetingGuest', '嘉宾', index)"
type="text" autosize readonly :right-icon="disabledFlag ? '' : 'close'"
@click-right-icon.stop="popObj.deleteItemByType('meetingGuest', index)" :disabled="disabledFlag" />
</div>
<van-field clickable name="newItem" v-model="newItem" label="嘉宾" placeholder="点击新增嘉宾"
@click="popObj.handleShowPop6('meetingGuest', '嘉宾', -1)" readonly v-show="!disabledFlag" />
<van-field clickable name="linkFromName" v-model="popObj.linkFromName" label="发起人"
:placeholder="disabledFlag ? '' : '点击添加发起人'" @click="popObj.handleShowPop5('linkFrom', '发起人')" readonly
:disabled="disabledFlag" />
<van-popup v-model:show="popObj.showPop6" position="bottom">
<div class="p-16 box-popup">
<van-row class="mb-16">
<van-col span="8">
<van-button class="van-picker__cancel" native-type="button"
@click="popObj.showPop6 = false">取消</van-button>
</van-col>
<van-col span="8" class="text-center">
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
</van-col>
<van-col span="8" class="text-end">
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm5">确认</van-button>
</van-col>
</van-row>
<van-radio-group v-model="popObj.linkFrom" @click="popObj.changeLinkFrom">
<van-radio :name="1">
<div class="d-flex align-items-center">
<div class="radioLabel">机构人员</div>
<van-field class="flex-1 radioContent1 border-1 ms-10" v-model="popObj.linkFromField"
placeholder="录入发起人" :disabled="popObj.linkFrom !== 1" />
</div>
</van-radio>
<van-radio :name="2" class="align-items-start-radio">
<div class="d-flex align-items-start mt-10">
<div class="radioLabel">第一上海</div>
<div class="flex-1 radioContent2">
<van-radio-group class="ms-10" v-model="popObj.linkFromRadio" direction="horizontal"
:disabled="popObj.linkFrom !== 2">
<van-radio v-for="(item, index) in popObj.linkFromList" :key="index" :name="item.userId">
{{ item.userName }}
</van-radio>
</van-radio-group>
</div>
</div>
</van-radio>
</van-radio-group>
</div>
</van-popup>
<van-field name="phoneBy" v-model="popObj.phoneBy" label="电话拨入方式" :placeholder="disabledFlag ? '' : '请输入电话拨入方式'"
:disabled="disabledFlag" />
<van-field name="netBy" v-model="popObj.netBy" label="网络端登录方式" :placeholder="disabledFlag ? '' : '请输入网络端登录方式'"
:disabled="disabledFlag" />
<van-field v-if="popObj.netBy" name="meetingPassword" v-model="popObj.meetingPassword" label="会议密码"
placeholder="请输入会议密码" :disabled="disabledFlag" />
<van-field clickable name="organizerNames" v-model="popObj.organizerNames" label="相关研究员"
:placeholder="disabledFlag ? '' : '点击选择相关研究员'" @click="popObj.handleShowPop3('organizer', '相关研究员')" readonly
:disabled="disabledFlag" />
<van-popup v-model:show="popObj.showPop3" position="bottom">
<div class="p-16 box-popup">
<van-row class="mb-16">
<van-col span="8">
<van-button class="van-picker__cancel" native-type="button"
@click="popObj.showPop3 = false">取消</van-button>
</van-col>
<van-col span="8" class="text-center">
<span class="sub-title f-b">请选择{{ popObj.popTitle }}</span>
</van-col>
<van-col span="8" class="text-end">
<van-button class="van-picker__confirm" native-type="button" @click="popObj.onConfirm3">确认</van-button>
</van-col>
</van-row>
<van-checkbox-group v-model="popObj.value3" direction="horizontal">
<van-checkbox v-for="(item, index) in popObj[`${popObj.type}List`]" :key="index" :name="item.value"
shape="square">
{{ item.text }}
</van-checkbox>
</van-checkbox-group>
</div>
</van-popup>
<van-field name="content" v-model="popObj.content" label="备注" rows="3" type="textarea" autosize
:disabled="disabledFlag" />
<van-field clickable name="meetingByName" v-model="popObj.visibleName" label="可见范围"
:placeholder="disabledFlag ? '' : '点击选择可见范围'" @click="popObj.handleShowPop1('visible', '可见范围')" readonly
:disabled="disabledFlag" />
<van-field v-if="popObj.selectvisible === '3'" clickable name="staffNames" v-model="popObj.staffNames"
label="可见员工" :placeholder="disabledFlag ? '' : '点击选择可见员工'" @click="popObj.handleShowPop3('staff', '可见员工')"
readonly :rules="[{ required: popObj.selectvisible === '3', message: '请选择可见员工' }]"
:required="popObj.selectvisible === '3'" :disabled="disabledFlag" />
<div class="m-16 d-flex justify-content-center">
<van-button round block type="warning" size="small" native-type="button" @click="changeDisabled"
v-if="meetingId">
{{ disabledFlag ? '修改' : '取消' }}
</van-button>
<van-button v-show="!disabledFlag" round block type="primary" size="small" native-type="submit"
:disabled="submitFlag">提交</van-button>
</div>
</van-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import emitter from '@/utils/mitt'
import {
sysDicListByType, sysUserGetStaffAndManagerList, sysUserGetCompanyUserList, stockStockInfoLookUp,
cusUserLookUp, workMeetingUpdateOrSave, workMeetingInfo, workMeetingUserList
} from '@/utils/api'
let submitFlag = ref(false)
let disabledFlag = ref(true)
let newItem = ref('')
let listIndex = ref(0)
let meetingId: any = ref('')
import { debounceThrottle } from '@/mixins/debounce-throttle'
const { throttle } = debounceThrottle()
import { type MeetingObj } from '@/utils/types'
const popObj: MeetingObj = reactive({
showPop1: false,
showPop2: false,
showPop3: false,
showPop4: false,
showPop5: false,
showPop6: false,
popTitle: '',
type: '',
value1: [],
value3: [],
meetingTitle: '',
sysDay: '',
selectsysDay: [],
sysTime: '',
selectsysTime: [],
meetingTypeName: '',
selectmeetingType: '',
meetingTypeList: [],
meetingByName: '',
selectmeetingBy: '',
meetingByList: [],
stockCodeList: [],
searchValue: '',
showPop2List: [],
meetingGuestList: [],
linkFromName: '',
linkFrom: 1,
linkFromRadio: '',
linkFromField: '',
linkFromList: [],
phoneBy: '',
netBy: '',
meetingPassword: '',
organizerNames: '',
selectorganizerIds: [],
organizerList: [],
content: '',
visibleName: '公开',
selectvisible: '1',
visibleList: [{
value: '1',
text: '公开'
}, {
value: '3',
text: '员工'
}],
staffNames: '',
staffList: [],
selectstaffIds: [],
handleShowPop1: (val: string, title: string) => {
if (!disabledFlag.value) {
popObj.showPop1 = true
popObj.popTitle = title
popObj.type = val
popObj.value1 = [popObj[`select${val}`]]
}
},
handleShowPop3: (val: string, title: string) => {
if (!disabledFlag.value) {
popObj.showPop3 = true
popObj.popTitle = title
popObj.type = val
popObj.value3 = popObj[`select${val}Ids`]
}
},
handleShowPop4: (val: string, title: string, index: number) => {
if (!disabledFlag.value) {
popObj.popTitle = title
popObj.type = val
popObj[`value${index}`] = popObj[`select${val}`]
popObj[`showPop${index}`] = true
}
},
handleShowPop5: (val: string, title: string) => {
if (!disabledFlag.value) {
if (popObj.linkFrom === 1) {
popObj.linkFromRadio = ''
popObj.linkFromField = popObj.linkFromName
} else {
const index = popObj.linkFromList.findIndex((ele: any) => {
return ele.userName === popObj.linkFromName
})
if (index > -1) {
popObj.linkFromRadio = popObj.linkFromList[index].userId
}
}
popObj.showPop6 = true
popObj.popTitle = title
popObj.type = val
}
},
handleShowPop6: (val: string, title: string, index: number) => {
if (!disabledFlag.value) {
popObj.showPop2 = true
popObj.popTitle = title
popObj.type = val
popObj.searchValue = index > -1 ?
popObj[`${val}List`][index][`${val === 'stockCode' ? 'value' : 'cusUserName'}`] : ''
popObj.showPop2List = index > -1 && popObj[`${val}List`][index].value ?
[{ text: popObj[`${val}List`][index].text, value: popObj[`${val}List`][index].value }] : []
listIndex.value = index
}
},
onSearchInput: throttle(
async () => {
if (popObj.searchValue) {
if (popObj.type === 'stockCode') {
const { data } = await stockStockInfoLookUp({ stockCode: popObj.searchValue })
popObj.showPop2List = data.map((ele: any) => {
ele.text = `${ele.stockCode}${ele.stockName}`
ele.value = ele.stockCode
return ele
})
} else if (popObj.type === 'meetingGuest') {
const { data } = await cusUserLookUp({ cusUserName: popObj.searchValue })
popObj.showPop2List = data.map((ele: any) => {
ele.text = `${ele.cusUserName}${ele.cusName ? '【' + ele.cusName + '】' : ''}`
ele.value = ele.cusUserId
return ele
})
}
} else {
popObj.showPop2List = []
}
},
500
),
deleteItemByType: (type: string, index: number) => {
if (!disabledFlag.value) {
popObj[`${type}List`].splice(index, 1)
}
},
onConfirm1: ({ selectedOptions }) => {
if (selectedOptions) {
popObj[`select${popObj.type}`] = selectedOptions[0].value
popObj[`${popObj.type}Name`] = selectedOptions[0].text
if (popObj.type === 'meetingBy') {
if (selectedOptions[0].value === '1') {
popObj.phoneBy = '香港852-3005-1318\n大陆400-810-5222\n'
} else if (selectedOptions[0].value == 2) {
popObj.phoneBy = '+8675536550000\n+85230018898'
} else {
popObj.phoneBy = ''
}
}
}
popObj.showPop1 = false
},
onConfirm2: ({ selectedOptions }) => {
if (selectedOptions && selectedOptions[0]) {
if (listIndex.value > -1) {
popObj[`${popObj.type}List`][listIndex.value].value = selectedOptions[0].value
popObj.stockCodeList[listIndex.value][`${popObj.type === 'stockCode' ? 'stockName' : 'cusUserName'}`] =
selectedOptions[0][`${popObj.type === 'stockCode' ? 'stockName' : 'cusUserName'}`]
popObj[`${popObj.type}List`][listIndex.value].text = selectedOptions[0].text
} else {
let data: any = {
value: selectedOptions[0].value,
text: selectedOptions[0].text
}
if (popObj.type === 'stockCode') {
data.stockName = selectedOptions[0].stockName
} else if (popObj.type === 'meetingGuest') {
data.cusUserName = selectedOptions[0].cusUserName
}
popObj[`${popObj.type}List`].push(data)
}
popObj.showPop2 = false
} else {
if (popObj.type === 'stockCode') {
showToast(`请选择${popObj.popTitle}后,再确认`)
} else {
popObj.meetingGuestList.push({
value: '',
cusUserName: popObj.searchValue,
text: popObj.searchValue
})
popObj.showPop2 = false
}
}
},
onConfirm3: () => {
popObj[`select${popObj.type}Ids`] = popObj.value3
const sameList = popObj[`${popObj.type}List`].filter((ele: any) => popObj.value3.some((res: any) => {
return ele.value === res
}))
let names = ''
sameList.forEach((ele: any) => {
names += `${ele.text},`
})
if (names) {
names = names.substring(0, names.length - 1)
}
popObj[`${popObj.type}Names`] = names
popObj.showPop3 = false
},
onConfirm4: ({ selectedValues }) => {
if (selectedValues) {
popObj[popObj.type] = `${selectedValues[0]}${popObj.type === 'sysDay' ? '-' + selectedValues[1] + '-' + selectedValues[2] : ':' + selectedValues[1]}`
popObj[`select${popObj.type}`] = selectedValues
}
if (popObj.type === 'sysDay') {
popObj.showPop4 = false
} else {
popObj.showPop5 = false
}
},
changeLinkFrom: () => {
if (popObj.linkFrom === 1) {
popObj.linkFromRadio = ''
} else if (popObj.linkFrom === 2) {
popObj.linkFromField = ''
}
},
onConfirm5: () => {
if (popObj.linkFrom === 1) {
popObj.linkFromName = popObj.linkFromField
popObj.linkFromRadio = ''
popObj.showPop6 = false
} else if (popObj.linkFrom === 2) {
popObj.linkFromField = ''
const index = popObj.linkFromList.findIndex((ele: any) => {
return ele.userId === popObj.linkFromRadio
})
if (index > -1) {
popObj.linkFromName = popObj.linkFromList[index].userName
popObj.showPop6 = false
} else {
showToast('请选择发起人')
}
}
}
})
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
import moment from 'moment'
const getSelectList = async () => {
emitter.emit('showLoading', '')
const data1 = await sysDicListByType({ dicType: 'meeting_type' })
popObj.meetingTypeList = data1.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data2 = await sysDicListByType({ dicType: 'meeting_by' })
popObj.meetingByList = data2.data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
const data3 = await sysUserGetStaffAndManagerList()
popObj.organizerList = data3.data.map((ele: any) => {
ele.value = ele.userId
ele.text = ele.userName
return ele
})
const data4 = await sysUserGetCompanyUserList()
popObj.staffList = data4.data.map((ele: any) => {
ele.value = ele.userId
ele.text = ele.userName
return ele
})
popObj.linkFromList = data4.data
if (route.query && route.query.addDay) {
disabledFlag.value = false
popObj.sysDay = route.query.addDay
popObj.selectsysDay = popObj.sysDay.split('-')
popObj.sysTime = '08:00'
popObj.selectsysTime = ['08', '00']
emitter.emit('hiddenLoading', '')
} else if (route.query && route.query.meetingId) {
disabledFlag.value = true
meetingId.value = route.query.meetingId
getMeeingInfo()
emitter.emit('setTitle', { title: '修改会议', type: 'meetingType' })
} else {
disabledFlag.value = false
let nowDay = moment().format('YYYY-MM-DD')
popObj.sysDay = nowDay
popObj.selectsysDay = nowDay.split('-')
popObj.sysTime = '08:00'
popObj.selectsysTime = ['08', '00']
emitter.emit('setTitle', { title: '新增会议', type: 'meetingType' })
emitter.emit('hiddenLoading', '')
}
}
const getMeeingInfo = async () => {
const { data } = await workMeetingInfo(meetingId.value)
popObj.meetingTitle = data.meetingTitle
popObj.sysDay = data.meetingTime.slice(0, 10)
popObj.selectsysDay = data.meetingTime.split('-')
popObj.sysTime = data.meetingTime.slice(11, 16)
popObj.selectsysTime = data.meetingTime.split(':')
popObj.meetingTypeName = data.meetingTypeName
popObj.selectmeetingType = data.meetingType + ''
popObj.meetingByName = data.meetingByName
popObj.selectmeetingBy = data.meetingBy + ''
let codeList = data.stockCodes ? data.stockCodes.split(',') : []
let nameList = data.stockNames ? data.stockNames.split(',') : []
popObj.stockCodeList = codeList.map((ele: any, index: number) => {
return {
value: ele,
text: `${ele}${nameList.length > index ? '【' + nameList[index] + '】' : ''}`,
stockName: nameList.length > index ? nameList[index] : '',
}
})
let guestNameList = data.meetingGuestNames ? data.meetingGuestNames.split(',') : []
let guestIdList = data.meetingGuestIds ? data.meetingGuestIds.split(',') : []
popObj.meetingGuestList = guestNameList.map((ele: any, index: number) => {
return {
value: guestIdList.length > index ? guestIdList[index] : '',
text: ele,
cusUserName: ele,
}
})
popObj.linkFrom = data.oranizerId && data.oranizerId !== -1 ? 2 : 1
popObj.linkFromName = data.oranizerName
popObj.phoneBy = data.phoneBy
popObj.netBy = data.netBy
popObj.meetingPassword = data.meetingPassword
popObj.organizerNames = data.oranizerName
popObj.selectorganizerIds = data.oranizerId ? data.oranizerId.split(',').map(Number) : []
popObj.content = data.content
popObj.visibleName = data.visible === 1 ? '公开' : '员工'
popObj.selectvisible = data.visible + ''
if (data.visible === 3) {
getVisible()
}
emitter.emit('hiddenLoading', '')
}
const getVisible = async () => {
const { data } = await workMeetingUserList({ meetingId: meetingId.value })
let staffNames = ''
let selectstaffIds: any = []
data.forEach((ele: any, index: number) => {
staffNames += `${ele.userName}${index < data.length - 1 ? ',' : ''}`
selectstaffIds.push(ele.userId)
})
popObj.staffNames = staffNames
popObj.selectstaffIds = selectstaffIds
}
const handleKeyUp = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
}
}
onMounted(() => {
window.addEventListener('keyup', handleKeyUp)
})
onUnmounted(() => {
window.removeEventListener('keyup', handleKeyUp)
})
import listRefreshInfoStore from '@/stores/listRefreshInfo'
const listRefreshInfo = listRefreshInfoStore()
const onSubmit = async () => {
submitFlag.value = true
let stockCodeList: any = []
popObj.stockCodeList.forEach((ele: any) => {
if (ele.value) {
stockCodeList.push({
stockCode: ele.value,
stockName: ele.stockName
})
}
})
let meetingGuestList: any = []
popObj.meetingGuestList.forEach((ele: any) => {
if (ele.cusUserName) {
meetingGuestList.push({
cusUserId: ele.value || -1,
cusUserName: ele.cusUserName
})
}
})
const res: any = {
meetingTitle: popObj.meetingTitle,
meetingTime: `${popObj.sysDay} ${popObj.sysTime}:00`,
meetingType: popObj.selectmeetingType,
meetingBy: popObj.selectmeetingBy,
stockCodeList: stockCodeList || [],
meetingGuestList: meetingGuestList || [],
linkFrom: popObj.linkFromName || null,
linkFromId: popObj.linkFrom === 2 && popObj.linkFromRadio ? popObj.linkFromRadio : null,
phoneBy: popObj.phoneBy || null,
netBy: popObj.netBy || null,
meetingPassword: popObj.netBy && popObj.meetingPassword ? popObj.meetingPassword : null,
organizerIdList: popObj.selectorganizerIds || [],
content: popObj.content || null,
visible: popObj.selectvisible,
userIdList: popObj.selectvisible === '3' ? popObj.selectstaffIds : [],
meetingId: meetingId.value || null
}
const data: any = await workMeetingUpdateOrSave(meetingId.value, res)
if (data.code === 0) {
showToast('提交成功')
if (meetingId.value) {
listRefreshInfo.refreshId = meetingId.value
} else {
listRefreshInfo.addId = '1'
}
router.go(-1)
} else {
submitFlag.value = false
}
}
const changeDisabled = () => {
disabledFlag.value = !disabledFlag.value
if (disabledFlag.value) {
getMeeingInfo()
}
}
getSelectList()
</script>
<style lang='scss' scoped>
.add-meeting {
height: 100vh;
overflow-y: auto;
}
.detailClass1 {
.van-cell {
margin: 12px 16px 0;
width: calc(100vw - 32px);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
@media screen and (min-width: 678px) {
width: 646px;
}
}
}
.detailClass2 {
.van-cell {
margin: 0 16px;
width: calc(100vw - 32px);
box-shadow: 0 0;
@media screen and (min-width: 678px) {
width: 646px;
}
}
}
:deep(.van-field--disabled) .van-field__label {
color: #646566;
}
:deep(.van-field__control:disabled) {
-webkit-text-fill-color: #000 !important;
}
:deep(.van-field__control) {
color: #000 !important;
}
:deep(.van-checkbox) {
margin-right: 0 !important;
width: 50%;
margin-bottom: 5px;
}
:deep(.van-radio) {
width: 100%;
}
.radioLabel {
width: 60px;
}
.radioContent1 {
max-width: 220px;
margin-left: 10px !important;
}
.radioContent2 {
max-height: 400px;
overflow-y: auto;
.van-radio {
margin-right: 0 !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 50%;
margin-bottom: 5px;
}
}
:deep(.align-items-start-radio) {
align-items: flex-start !important;
max-height: 300px;
.van-radio__icon {
margin-top: 10px;
}
}
.van-radio-group {
.van-cell {
margin-top: 0;
}
}
:deep(.ms-10) {
.van-radio__icon {
margin-top: 0;
}
}
</style>

View File

@@ -0,0 +1,436 @@
<template>
<div class="page-content">
<div class="meeting-detail">
<div class="normal-title text-center p-10 f-b">
{{ meetingData.meetingTitle }}
<van-icon v-if="isAuth('calendar:update') || userInfo.id === meetingData.createUser" name="edit"
class="warning-text-color"
@click="$router.push({ name: 'add-or-update-meeting', query: { meetingId: meetingId } })" />
<van-icon v-if="isAuth('calendar:update') || userInfo.id === meetingData.createUser" name="delete-o"
class="danger-text-color" @click="deleteMeeting" />
</div>
<div class="text-center m-5 f-b">{{ meetingData.meetingTime }}</div>
<div class=" text-center m-5">
<van-tag type="success" size="medium">{{ meetingData.meetingTypeName }}</van-tag>
</div>
<van-grid class="baseData" :column-num="2">
<van-grid-item>会议形式</van-grid-item>
<van-grid-item>{{ meetingData.meetingByName }}</van-grid-item>
<van-grid-item>股票名称</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.stockNames }}</div>
</van-grid-item>
<van-grid-item>股票代码</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.stockCodes }}</div>
</van-grid-item>
<van-grid-item>电话拨入方式</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.phoneBy }}</div>
</van-grid-item>
<van-grid-item>网络端登录方式</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.netBy }}</div>
</van-grid-item>
<van-grid-item>会议密码</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.meetingPassword }}</div>
</van-grid-item>
<van-grid-item>嘉宾</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.meetingGuestList }}</div>
</van-grid-item>
<van-grid-item>研究员</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.oranizerName }}</div>
</van-grid-item>
<van-grid-item>发起人</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.organizer }}</div>
</van-grid-item>
<van-grid-item>IS参会人数</van-grid-item>
<van-grid-item>{{ meetingData.meetingCusUserNum }}</van-grid-item>
<van-grid-item>IS提问人数</van-grid-item>
<van-grid-item>{{ meetingData.questionNum }}</van-grid-item>
<van-grid-item>总参会人数</van-grid-item>
<van-grid-item>{{ meetingData.userNumHigh }}</van-grid-item>
<van-grid-item>总提问人数</van-grid-item>
<van-grid-item>{{ meetingData.queationNumAll }}</van-grid-item>
<van-grid-item>可见范围</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.visible === 1 ? '公开' : meetingData.visibleName }}</div>
</van-grid-item>
<van-grid-item>备注</van-grid-item>
<van-grid-item>
<div class="grid-item-text">{{ meetingData.content }}</div>
</van-grid-item>
</van-grid>
<div class="sub-title m-16">会议资料</div>
<file-list ref="meetingFile" />
<div class="sub-title m-16">参会资料</div>
<file-list ref="joinMeetingFile" />
<div class="sub-title m-16 d-flex align-items-center">
参会人员
<van-button type="primary" size="mini" @click="addJoinPerson(1)" style="margin-left: 4px">报名</van-button>
<van-button type="primary" size="mini" @click="addJoinPerson(2)">签到</van-button>
</div>
<van-popup v-model:show="showPersonPopup" position="bottom">
<div class="p-10 box-popup">
<div class="personTitle">当前添加联系人{{ addPerson }}</div>
<van-field v-model="searchCuseUser" placeholder="查找联系人" @input="onSearchInput"
class="border-1 border-radius-8" />
<div class="tip">
<van-row v-if="personType === 2 || personType === 3">
<van-col span="12">
<van-field name="switch" label="签到">
<template #input>
<van-switch v-model="attend" size="20" :disabled="personType === 2" />
</template>
</van-field>
</van-col>
<van-col span="12">
<van-field name="switch" label="提问" v-show="attend">
<template #input>
<van-switch v-model="question" size="20" />
</template>
</van-field>
</van-col>
</van-row>
<van-picker show-toolbar title="" :columns="cusUserList" @click-option="onCusUserConfirm"
@cancel="showPersonPopup = false">
<template #confirm>
<span></span>
</template>
</van-picker>
</div>
</div>
</van-popup>
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<van-swipe-cell v-for="(item, index) in joinPersonList" :key="item.cusUserId">
<van-cell>
<div class="sub-title text-start balck-text-color">{{ item.cusUserName }}</div>
<div class="d-flex">
<div class="flex-1">
<div class="content text-start">{{ item.cusName }}</div>
<div class="content text-start">{{ item.positionName }}</div>
</div>
<div class="flex-1 text-end">
<div class="content" :class="item.attend ? 'primary-text-color' : ''">
{{ item.attend ? '已到场' : '已报名' }}
</div>
<div class="content" v-if="item.attend" :class="item.question ? 'danger-text-color' : ''">
{{ item.attend ? item.question ? '已提问' : '未提问' : '' }}
</div>
</div>
</div>
</van-cell>
<template #right>
<van-button square type="danger" text="删除" @click="deleteJoinPerson(item, index)" />
<van-button square type="primary" text="修改" @click="changeJoinPerson(item)" />
</template>
</van-swipe-cell>
</van-list>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import FileList from '@/components/file-list.vue'
import emitter from '@/utils/mitt'
import { isAuth } from '@/utils/index'
import userInfoStore from '@/stores/userInfo'
const userInfo = userInfoStore()
const route = useRoute()
const router = useRouter()
let meetingData = ref({
meetingTime: '',
meetingTitle: '',
meetingTypeName: '',
meetingByName: '',
stockCodes: '',
stockNames: '',
phoneBy: '',
netBy: '',
meetingPassword: '',
meetingCusUserNum: '',
questionNum: '',
userNumHigh: '',
queationNumAll: '',
meetingGuestList: '',
organizer: '',
oranizerName: '',
visible: undefined,
visibleName: '',
content: '',
createUser: ''
})
let meetingId: any = ref('')
const meetingFile: any = ref(null)
const joinMeetingFile: any = ref(null)
import fileInfoStore from '@/stores/fileInfo'
const fileInfo = fileInfoStore()
const init = () => {
if (route.query && route.query.meetingId) {
meetingId.value = route.query.meetingId
emitter.emit('showLoading', '')
getMeetingDetail()
} else {
router.push({ name: '404' })
}
}
import {
workMeetingInfo, workMeetingUserList, workMeetingGetFileId, workMeetinGetUserFileMenuId,
cusUserMeetingLogList, cusUserMeetingLogDelete, cusUserLookUp, cusUserMeetingLogUpdateOrSave,
workMeetingDelete
} from '@/utils/api'
const getMeetingDetail = async () => {
const data1: any = await workMeetingInfo(meetingId.value)
let meetingGuestNameList = data1.data.meetingGuestNames
? data1.data.meetingGuestNames.split(',')
: []
let meetingGuestList = ''
meetingGuestNameList.forEach((item: any, index: number) => {
meetingGuestList +=
`
${item}
${index < meetingGuestNameList.length - 1 ? ',' : ''}
`
})
data1.data.meetingGuestList = meetingGuestList
meetingData.value = data1.data
emitter.emit('setTitle', { title: meetingData.value.meetingTitle, type: 'meeting' })
if (data1.data.visible === 3) {
getVisible()
}
const data2: any = await workMeetingGetFileId({ meetingId: meetingId.value })
if (meetingFile.value) {
meetingFile.value.init(data2.data)
}
const data3: any = await workMeetinGetUserFileMenuId({ meetingId: meetingId.value })
if (joinMeetingFile.value) {
joinMeetingFile.value.init(data3.data)
}
nextTick(() => {
if (fileInfo.fileId) {
const el = document.getElementById(fileInfo.fileId)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
fileInfo.fileId = ''
emitter.emit('hiddenLoading', '')
} else {
emitter.emit('hiddenLoading', '')
}
})
}
const getVisible = async () => {
const { data } = await workMeetingUserList({ meetingId: meetingId.value })
let name = ''
data.forEach((ele: any, index: number) => {
name += `${ele.userName}${index < data.length - 1 ? ',' : ''}`
})
meetingData.value.visibleName = name
}
let finished = ref(false)
let loading = ref(false)
let curPage = ref(1)
let joinPersonList = ref<{ cusUserId: string, cusUserName: string, cusName: string, positionName: string, attend: boolean, question: boolean }[]>([])
const onLoad = async () => {
loading.value = true
const { data } = await cusUserMeetingLogList({
curPage: curPage.value,
limit: 20,
meetingId: meetingId.value
})
data.list.map((ele: any) => {
ele.question = !!ele.question
ele.attend = !!ele.attend
})
joinPersonList.value = joinPersonList.value.concat(data.list)
if (joinPersonList.value.length >= data.totalCount) {
finished.value = true
} else {
curPage.value++
}
loading.value = false
}
let attend = ref(false)
let question = ref(false)
let searchCuseUser = ref('')
let meetingUserId = ref('')
let personType = ref()
let showPersonPopup = ref(false)
let addPerson = ref('')
let cusUserList = ref<{ text: string, value: string, cusUserName: string, cusName: string, positionName: string, cusUserId: string }[]>([])
const addJoinPerson = (index: number) => {
personType.value = index
searchCuseUser.value = ''
attend.value = false
if (index === 2) {
attend.value = true
}
question.value = false
cusUserList.value = []
meetingUserId.value = ''
showPersonPopup.value = true
}
const changeJoinPerson = (e: any) => {
personType.value = 3
searchCuseUser.value = e.cusUserName
attend.value = e.attend
question.value = e.question
cusUserList.value = [{
cusUserName: e.cusUserName,
cusName: e.cusName,
positionName: e.positionName,
cusUserId: e.cusUserId,
text: `${e.cusUserName}${e.cusName ? '【' + e.cusName + '】' : ''}${e.positionName ? '(' + e.positionName + ')' : ''}`,
value: e.cusUserId
}]
meetingUserId.value = e.meetingUserId
showPersonPopup.value = true
}
import { debounceThrottle } from '@/mixins/debounce-throttle'
const { throttle } = debounceThrottle()
const onSearchInput = throttle(
async () => {
if (searchCuseUser.value) {
const { data } = await cusUserLookUp({ cusUserName: searchCuseUser.value })
cusUserList.value = data.map((ele: any) => {
ele.text = `${ele.cusUserName}${ele.cusName ? '【' + ele.cusName + '】' : ''}${ele.positionName ? '(' + ele.positionName + ')' : ''}`
ele.value = ele.cusUserId
return ele
})
if (data.length === 1) {
const res = {
cusUserId: data[0].cusUserId,
attend: attend.value ? 1 : 0,
question: question.value ? 1 : 0,
meetingId: meetingId.value,
meetingUserId: meetingUserId.value || null
}
await cusUserMeetingLogUpdateOrSave(meetingUserId.value, res)
addPerson.value = `${data[0].cusUserName},${addPerson.value}`
searchCuseUser.value = ''
cusUserList.value = []
}
} else {
cusUserList.value = []
}
},
500
)
const onCusUserConfirm = async (e: any) => {
if (e.selectedIndexes[0] > -1) {
const res = {
cusUserId: e.selectedOptions[0].value,
attend: attend.value ? 1 : 0,
question: question.value ? 1 : 0,
meetingId: meetingId.value,
meetingUserId: meetingUserId.value || null
}
await cusUserMeetingLogUpdateOrSave(meetingUserId.value, res)
addPerson.value = `${e.selectedOptions[0].cusUserName},${addPerson.value}`
searchCuseUser.value = ''
cusUserList.value = []
}
}
watch(() => showPersonPopup.value, (val) => {
if (!val) {
curPage.value = 1
joinPersonList.value = []
finished.value = false
addPerson.value = ''
onLoad()
}
})
const deleteJoinPerson = (e: any, index: number) => {
showConfirmDialog({
title: '提示',
message:
`是否确认删除${e.cusUserName}参加${meetingData.value.meetingTime} ${meetingData.value.meetingTitle}记录`,
}).then(async () => {
const data: any = await cusUserMeetingLogDelete([e.meetingUserId])
if (data.code === 0) {
showToast('删除成功')
joinPersonList.value.splice(index, 1)
}
}).catch(() => {
})
}
init()
import listRefreshInfoStore from '@/stores/listRefreshInfo'
const listRefreshInfo = listRefreshInfoStore()
const deleteMeeting = () => {
showConfirmDialog({
title: '提示',
message:
`是否确认删除会议${meetingData.value.meetingTitle}`,
}).then(async () => {
const data: any = await workMeetingDelete(meetingId.value)
if (data.code === 0) {
showToast('删除成功')
listRefreshInfo.deleteId = meetingId.value
router.go(-1)
}
}).catch(() => {
})
}
</script>
<style lang='scss' scoped>
.meeting-detail {
height: 100vh;
overflow-y: auto;
}
.baseData {
:deep(.van-grid-item:nth-child(2n)) {
.van-grid-item__content {
color: #1989fa;
}
}
:deep(.van-grid-item__content) {
font-size: 14px;
font-weight: 400;
padding: 8px 4px;
font-size: 12px;
}
}
.grid-item-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
max-width: calc(50vw - 16px);
@media screen and (min-width: 678px) {
max-width: 320px;
}
}
:deep(.van-swipe-cell__right) {
.van-button {
height: 100%;
}
}
.personTitle {
height: 60px;
overflow-y: auto;
}
</style>

237
src/views/meeting/list.vue Normal file
View File

@@ -0,0 +1,237 @@
<template>
<div class="page-content tabbar-list top-select-list" ref="pageContent">
<div class="addDiv success-background-color" v-if="isAuth('calendar:update')"
@click=" $router.push({ name: 'add-or-update-meeting' , query: { addDay: selectDay } })">
<div class="text-center addDiv1">新增</div>
<div class="text-center addDiv2">会议</div>
</div>
<div class="top-select border-bottom-1">
<van-dropdown-menu>
<van-dropdown-item :title="searchContent" ref="itemRef">
<van-field v-model="search" placeholder="搜索" :right-icon="search ? 'clear' : ''"
@click-right-icon.stop="clearSearch" />
<div style="padding: 5px 16px;">
<van-button type="primary" size="small" block round @click="onConfirm">
确认
</van-button>
</div>
</van-dropdown-item>
<van-dropdown-item v-model="meetingType" :options="meetingTypeList" @change="getMeetingList" />
<van-dropdown-item v-model="createUser" :options="createUserList" @change="getMeetingList" />
</van-dropdown-menu>
</div>
<van-calendar title="" :poppable="false" :show-confirm="false" switch-mode="year-month" first-day-of-week="1"
:style="{ height: '410px' }" :formatter="formatter" :default-date="defaultDate" @panel-change="panelChange"
@select="onDateConfirm">
</van-calendar>
<div v-if="list.length > 0">
<van-cell v-for="item in list" :key="item.meetingId"
@click="$router.push({ name: 'meeting-detail', query: { meetingId: item.meetingId } })">
<div class="text-start activeColor sub-title">{{ item.meetingHour }}</div>
<div class="normal-title text-start balck-text-color mb-5 mt-5">{{ item.meetingTitle }}</div>
<div class="text-end">
<van-tag type="primary" size="medium">{{ item.meetingTypeName }}</van-tag>
<van-tag type="danger" class="ms-10" size="medium">{{ item.meetingByName }}</van-tag>
</div>
</van-cell>
</div>
<van-empty v-else description="暂无会议" />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { sysDicListByType, workMeetingList, workMeetingInfo, workStaffLookup } from '@/utils/api'
import { convertToUrl } from '@/utils'
import { scrollList } from '@/mixins/scroll-list'
import { isAuth } from '@/utils/index'
let search = ref('')
let searchContent = ref('搜索')
let itemRef = ref()
const clearSearch = () => {
search.value = ''
searchContent.value = '搜索'
}
const onConfirm = () => {
searchContent.value = search.value || '搜索'
itemRef.value.toggle()
getMeetingList()
}
let meetingType = ref('1,2,3,16,17,4,5,15,6,10,11,12,13,14,7')
let meetingTypeList: any = ref([])
const getMeetingTypeList = async () => {
const { data } = await sysDicListByType({ dicType: 'meeting_type' })
meetingTypeList.value = data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
meetingTypeList.value.unshift({ text: '全部类型', value: '1,2,3,16,17,4,5,15,6,10,11,12,13,14,7' })
}
getMeetingTypeList()
let createUser = ref('')
let createUserList: any = ref([])
const getCreateUserList = async () => {
const { data } = await workStaffLookup()
createUserList.value = data.map((ele: any) => {
ele.text = ele.staffName
ele.value = ele.staffId
return ele
})
createUserList.value.unshift({ text: '全部创建人', value: '' })
}
getCreateUserList()
let defaultDate = ref()
import moment from 'moment'
const panelChange = (e: any) => {
dateBegin.value = moment(e.date).startOf('month').format('YYYY-MM-DD')
dateEnd.value = moment(e.date).endOf('month').format('YYYY-MM-DD')
getMeetingList()
}
const onDateConfirm = (e: any) => {
defaultDate.value = e
getList()
}
let list: any = ref([])
let selectDay = ref('')
const getList = () => {
list.value = []
selectDay.value = moment(defaultDate.value).format('YYYY-MM-DD')
meetingList.value.forEach((ele: any) => {
if (ele.meetingDate === selectDay.value) {
list.value = ele.list
}
})
}
const formatter = computed(() => {
if (meetingList.value.length === 0) {
return (day: any) => day
}
return (day: any) => {
let time = moment(day.date).format('YYYY-MM-DD')
meetingList.value.forEach((ele: any) => {
if (ele.meetingDate === time) {
day.topInfo = ele.num
}
})
return day
}
})
const dateBegin = ref()
const dateEnd = ref()
const meetingList: any = ref([])
const getMeetingList = async () => {
let url = getMeetingParams()
const { data } = await workMeetingList(url)
let map = new Map()
data.forEach((item: any, index: any, arr: any) => {
if (!map.has(item.meetingDate)) {
map.set(
item.meetingDate,
arr.filter((a: any) => a.meetingDate == item.meetingDate)
)
}
})
let res = Array.from(map).map((item) => [...item[1]])
meetingList.value = []
res.forEach((ele: any) => {
meetingList.value.push({ meetingDate: ele[0].meetingDate, num: ele.length, list: ele })
})
getList()
}
const getMeetingParams = () => {
const res = {
dateBegin: dateBegin.value,
dateEnd: dateEnd.value,
createUser: createUser.value,
cusName: search.value,
meetingType: meetingType.value
}
const url = convertToUrl(res)
return url
}
let pageContent = ref(null)
const scrollPosition = ref(0)
const refresh = async (type: any, id: any) => {
if (type === 'delete' || type === 'refresh') {
let dayIndex = meetingList.value.findIndex((ele: any) => {
return ele.meetingDate === selectDay.value
})
if (dayIndex > -1) {
let index = meetingList.value[dayIndex].list.findIndex((ele: any) => {
return Number(ele.meetingId) === Number(id)
})
if (type === 'delete') {
if (index > -1) {
meetingList.value[dayIndex].list.splice(index, 1)
meetingList.value[dayIndex].num -= 1
}
} else {
if (index > -1) {
const { data } = await workMeetingInfo(id)
meetingList.value[dayIndex].list[index] = data
}
getList()
}
}
} else {
defaultDate.value = new Date()
dateBegin.value = moment().startOf('month').format('YYYY-MM-DD')
dateEnd.value = moment().endOf('month').format('YYYY-MM-DD')
getMeetingList()
}
}
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
setScrollTop()
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'meeting-detail')
})
</script>
<style lang='scss' scoped>
:deep(.van-calendar__header-title) {
display: none;
}
:deep(.van-calendar__day) {
border: 1px solid #f2f2f2;
margin-bottom: 0;
}
:deep(.van-calendar__top-info) {
width: 14px;
left: calc(100vw / 7 - 18px);
border-radius: 100%;
background-color: #f56c6c;
color: #fff;
@media screen and (min-width: 678px) {
left: 74px;
}
}
:deep(.van-calendar__selected-day) {
width: 100%;
height: 100%;
}
.item {
width: 100%;
text-align: center;
}
.meetingList {
overflow-y: auto;
height: calc(100vh - 160px);
}
</style>

View File

@@ -0,0 +1,487 @@
<template>
<div class="page-content">
<van-tabs v-model:active="active" sticky @change="changeTab">
<van-tab v-for="item in tabs" :key="item.title" :title="item.title">
<div class="p-10" v-if="active === 0">
<div class="base-info">
<div class="d-flex base-item">
<span class="base-title">机构名称</span>
<span class="base-desc">{{ cusInfo.cusName }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">机构类型</span>
<span class="base-desc">{{ cusInfo.companyTypeName }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">核心联系人</span>
<span class="base-desc">{{ cusInfo.cusUserName }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">跟进人</span>
<span class="base-desc">{{ cusInfo.saleUserName }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">城市</span>
<span class="base-desc">{{ cusInfo.cityName }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">城市</span>
<span class="base-desc">{{ cusInfo.cityName }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">是否开户</span>
<span class="base-desc">{{ cusInfo.accountStatusFlag ? '是' : '否' }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">开户时间</span>
<span class="base-desc">{{ cusInfo.accountStatusFlag && cusInfo.accountTime ? cusInfo.accountTime : ''
}}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">开户类型</span>
<span class="base-desc">{{ cusInfo.accountStatusFlag && cusInfo.accountTypeName ? cusInfo.accountTypeName
: '' }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">是否交易</span>
<span class="base-desc">{{ cusInfo.accountStatusFlag && cusInfo.isMoneyFlag ? '是' : '否' }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">交易总金额</span>
<span class="base-desc">{{ cusInfo.isMoneyFlag && cusInfo.accountStatusFlag && cusInfo.tradeMoney ?
cusInfo.tradeMoney : '' }}</span>
</div>
<div class="d-flex base-item">
<span class="base-title">资金规模</span>
<span class="base-desc">{{ cusInfo.capitalScaleValue ? cusInfo.capitalScaleValue :
cusInfo.capitalScaleName ? cusInfo.capitalScaleName : '' }}</span>
</div>
</div>
<div class="title">参会趋势</div>
<div class="chart d-flex align-items-center justify-content-center">
<van-loading v-show="chartLoading" type="spinner" color="#1989fa" class="loading" />
<div id="meeting_chart" class="chartDiv"></div>
</div>
</div>
<div v-if="active === 1">
<van-list v-if="cusUserList.length" finished-text="没有更多了" :immediate-check="false">
<van-cell v-for="item in cusUserList" :key="item.cusId">
<div class="name1 text-start stockNames balck-text-color">{{ item.cusUserName }}<span
class="mx-10">|</span>{{
item.positionName }}</div>
<div class="d-flex justify-content-between">
<div class="font-12">跟进人{{ item.saleNames }}
<span v-if="item.entryTime">({{ item.entryTime }}入职)</span>
</div>
</div>
<div class="d-flex font-12">
<div>更新时间</div>
<div class="activeColor">{{ item.updateTime }}</div>
</div>
<div class="d-flex font-12 justify-content-between flex-wrap">
<div class=" d-flex w-half">
<div>参会次数</div>
<div class="activeColor">{{ item.meetingNum || 0 }}</div>
</div>
<div class="d-flex justify-content-end w-half">
<div>沟通次数</div>
<div class="activeColor">{{ item.linkNum || 0 }}</div>
</div>
</div>
</van-cell>
</van-list>
<van-empty v-else />
</div>
<div v-if="active === 2">
<van-cell v-if="workList?.length" v-for="(item, index) in workList" :key="index"
@click="showDetail(item.linkNote)">
<div class="f-b activeColor text-start mb-5 mt-5">{{ item.saleNames }} - {{ item.linkTypeMenuName }}
</div>
<div class="balck-text-color text-ellipsis">{{ item.linkNote }}</div>
<div class=" d-flex font-12 flex-wrap">
<div>相关研究人{{ item.staffName }}</div>
<div>联系人{{ item.cusUserName }}</div>
</div>
<div class="d-flex justify-content-end">
<div class="activeColor font-12 text-start flex-1">{{ item.sysDate }}</div>
<van-tag type="danger" class="ms-10" size="medium">{{ item.linkTypeName }}</van-tag>
</div>
</van-cell>
<van-empty v-else />
</div>
<van-overlay :show="showOverLay" @click="showOverLay = false">
<div class="wrapper">
<div class="block" @click.stop>{{ note }}</div>
</div>
</van-overlay>
<div v-if="active === 3">
<van-cell v-if="meetingList?.length" v-for="item in meetingList" :key="item.meetingId"
@click="$router.push({ name: 'meeting-detail', query: { meetingId: item.meetingId } })">
<div class="f-b text-start balck-text-color mb-5 mt-5">{{ item.meetingTitle }}</div>
<div class="d-flex">
<div class="font-12 text-start activeColor flex-1">{{ item.meetingTime }}</div>
<van-tag type="primary" size="medium">{{ item.meetingTypeName }}</van-tag>
<van-tag type="danger" class="ms-10" size="medium">{{ item.meetingByName }}</van-tag>
</div>
</van-cell>
<van-empty v-else description="暂无会议" />
</div>
<div v-if="active === 4">
<communicate-item v-if="communicateList" class="pb-16" :list="communicateList" />
<van-empty v-else />
</div>
<div v-if="active === 5">
<van-cell v-if="serviceList?.length" v-for="item in serviceList" :key="item.id"
@click="showDetail(item.content)">
<div class="d-flex align-items-center">
<div class="f-b activeColor text-ellipsis">{{ item.content }}</div>
</div>
<div class="d-flex justify-content-between">
<div class="text-start font-12">开始时间<span class="activeColor">{{ item.serviceBeginDate }}</span></div>
<div class="text-start font-12">状态<span class="activeColor">{{ item.serviceStatus }}</span>
</div>
</div>
</van-cell>
<van-empty v-else description="暂无数据" />
</div>
<div v-if="active === 6">
<van-cell v-if="yearList?.length" v-for="(item, index) in yearList" :key="index">
<div class="d-flex align-items-center">
<div class="f-b activeColor">{{ item.sysYear }}</div>
</div>
<div class="d-flex justify-content-between">
<div class="text-start font-12">目标销售额<span class="activeColor">{{ item.targetMoney || 0 }}</span></div>
<div class="text-start font-12">年度交易额<span class="activeColor">{{ item.tradeMoneyYear || 0 }}</span>
</div>
</div>
<div class="text-start font-12 ">
完成率<span class="activeColor">{{ item.finishPer || 0 }}%</span>
</div>
</van-cell>
<van-empty v-else description="暂无数据" />
</div>
<div v-if="active === 7">
<van-cell v-if="accountList?.length" v-for="(item, index) in accountList" :key="index">
<div class="f-b text-start balck-text-color mb-5">姓名{{ item.accountName }}</div>
<div class="text-start font-12">账号{{ item.accountNo }}</div>
</van-cell>
<van-empty v-else description="暂无数据" />
</div>
</van-tab>
</van-tabs>
</div>
</template>
<script setup lang="ts">
import { nextTick, ref } from 'vue'
const showOverLay = ref(false)
const note = ref()
const showDetail = (value: string) => {
note.value = value
showOverLay.value = true
}
const active = ref(0)
const tabs = [{
name: 'base',
title: '基本信息'
}, {
name: 'cusUser',
title: '员工列表'
}, {
name: 'work',
title: '工作日报'
}, {
name: 'meeting',
title: '参会记录'
}, {
name: 'communicate',
title: '沟通记录'
}, {
name: 'service',
title: '服务目标'
}, {
name: 'year',
title: '年度数据'
}, {
name: 'account',
title: '账号列表'
}, {
name: '关注股票',
title: '关注股票'
}]
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
import emitter from '@/utils/mitt'
const cusId = ref()
const init = () => {
if (route.query && route.query.cusId) {
cusId.value = route.query.cusId
getData()
} else {
router.push({ name: '404' })
}
}
import {
cusInfoInfo, cusUserListByCusId, cusLinkList, cusServiceGetByCusId,
cusYearList, cusAccountList, cusMeetingList, cusInfoCountByMonth
} from '@/utils/api'
import { convertToUrl } from '@/utils'
const getData = async () => {
emitter.emit('showLoading', '')
try {
getCusInfo()
getChartList()
getCusUserList()
getMeetingList()
getCusLinkList()
getWorkList()
getServiceList()
getYearList()
getAccountList()
} finally {
emitter.emit('hiddenLoading', '')
}
}
import * as echarts from 'echarts'
const chartLoading = ref(false)
const getChartList = async () => {
chartLoading.value = false
const { data } = await cusInfoCountByMonth({ cusId: cusId.value })
const sysDate: any = []
const seriesList: any = []
const legendList = ['FSH电话会议+调研+其他', '周末会+精英会', '按需会议', '机构路演', '机构沟通', '交易量']
const colorList = ['#f26522', '#45b97c', '#009ad6', '#8552a1', '#6c4c49', '#ed1941']
legendList.forEach((ele, index) => {
if (index < 5) {
seriesList.push({
type: 'line',
name: ele,
data: [],
yAxisIndex: 0,
xAxisIndex: 0,
connectNulls: false,
symbol: 'none',
symbolSize: 0
})
} else {
seriesList.push({
type: 'bar',
name: ele,
data: [],
emphasis: {
focus: 'series'
},
yAxisIndex: 1,
xAxisIndex: 1,
showEmptyData: false,
barWidth: 10
})
}
})
data.forEach((ele: any) => {
sysDate.push(ele.sysMonth)
seriesList[0].data.push(ele.meetingNum34 + ele.meetingNum7)
seriesList[1].data.push(ele.meetingNum5 + ele.meetingNum6)
seriesList[2].data.push(ele.linkNum31)
seriesList[3].data.push(ele.linkNum32)
seriesList[4].data.push(ele.linkNum34)
seriesList[5].data.push(ele.tradeMoney)
})
const xAxis = {
type: 'category',
name: '',
data: [],
show: true,
axisLabel: {
show: true,
showMaxLabel: true,
formatter: '{value}'
},
gridIndex: 0
}
const yAxis = {
type: 'value',
name: '',
nameLocation: 'end',
scale: true,
show: true,
axisLabel: {
show: true,
formatter: '{value}'
},
axisLine: {
show: true,
lineStyle: {
color: data.color || '#000'
}
},
position: 'left',
}
const option = {
yAxis: [{ ...yAxis, name: '次数', minInterval: 1 }, { ...yAxis, name: '交易数', minInterval: 1, nameLocation: 'start', gridIndex: 1 }],
xAxis: [{ ...xAxis, data: sysDate, name: '时间' }, { ...xAxis, data: sysDate, showFlag: false, gridIndex: 1 }],
series: seriesList,
tooltip: {
show: true,
trigger: 'axis',
formatter: function (data: any) {
let res = `${data[0].name}</br>`
data.forEach((ele: any) => {
res += `<span style="color: ${ele.color}">${ele.seriesName}:${ele.value}${ele.componentSubType === 'bar' ? '百万' : ''}</span></br>`
})
return res
}
},
legend: { data: legendList, top: '10px' },
color: colorList,
grid: [{
top: '84px',
bottom: '35%',
left: '40px',
right: '40px'
}, {
top: '72%',
bottom: '40px',
left: '40px',
right: '40px'
}]
}
nextTick(() => {
const chart = echarts.init(document.getElementById('meeting_chart'))
chart.setOption(option)
chartLoading.value = false
})
}
const cusInfo = ref<any>({})
const getCusInfo = async () => {
const { data } = await cusInfoInfo(cusId.value)
cusInfo.value = data
emitter.emit('setTitle', { title: cusInfo.value.cusName, type: 'meeting' })
}
const cusUserList = ref<any[]>([])
const getCusUserList = async () => {
const { data } = await cusUserListByCusId({ cusId: cusId.value })
cusUserList.value = data
}
const workList = ref<any[]>()
const getWorkList = async () => {
const url = convertToUrl({
limit: 50,
cusId: cusId.value,
requestFrom: 'DETAIL_RB'
})
const data = await cusLinkList(url)
workList.value = data
}
const communicateList = ref<any[]>()
const getCusLinkList = async () => {
const url = convertToUrl({
limit: 100,
cusId: cusId.value,
requestFrom: 'DETAIL_GTJL',
dataStatus: 1
})
const data = await cusLinkList(url)
communicateList.value = data
}
const meetingList = ref<any[]>()
const getMeetingList = async () => {
const { data } = await cusMeetingList({ cusId: cusId.value, meetingType: '' })
meetingList.value = data
}
const serviceList = ref<any[]>()
const getServiceList = async () => {
const { data } = await cusServiceGetByCusId({ cusId: cusId.value })
data.forEach((ele: any) => {
ele.selectId = ele.id
ele.serviceStatus = ele.serviceStatus === 0 ? '历史' : '当前'
})
serviceList.value = data
}
const yearList = ref<any[]>()
const getYearList = async () => {
const { data } = await cusYearList({ cusId: cusId.value, curPage: 1, limit: 999 })
yearList.value = data.list
}
const accountList = ref<any[]>()
const getAccountList = async () => {
const { data } = await cusAccountList({ cusId: cusId.value, curPage: 1, limit: 999 })
accountList.value = data.list
}
init()
const changeTab = (num: number) => {
if (num === 0) {
getChartList()
}
}
</script>
<style lang="scss" scoped>
$border-color: #e0e0e0;
.base-info {
overflow: hidden;
border-radius: 4px;
border: 1px solid $border-color;
.base-item {
font-size: 13px;
border-bottom: 1px solid $border-color;
&:last-child {
border-bottom: 0;
}
}
.base-title {
width: 30%;
border-right: 1px solid $border-color;
padding: 6px 10px;
background-color: #fafafa;
}
.base-desc {
background-color: #f2f4f7;
padding: 6px 10px;
width: 70%;
color: #1989fa;
}
}
.chartDiv {
height: 50vh;
width: 100vh;
}
.title {
margin-top: 20px;
font-size: 14px;
font-weight: 600;
}
:deep(.van-tab--active) {
.van-tab__text {
color: #1989fa;
}
}
.w-half {
width: 50%;
}
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
.block {
width: 80%;
height: 50%;
overflow: auto;
padding: 10px;
font-size: 14px;
background-color: #fff;
}
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<div class="page-content top-select-list" ref="pageContent">
<div class="top-select border-bottom-1">
<van-dropdown-menu>
<van-dropdown-item :title="searchContent" ref="itemRef">
<van-field v-model="search" placeholder="搜索" :right-icon="search ? 'clear' : ''"
@click-right-icon.stop="clearSearch" />
<div style="padding: 5px 16px;">
<van-button type="primary" size="small" block round @click="onConfirm">
确认
</van-button>
</div>
</van-dropdown-item>
<van-dropdown-item v-model="companyType" :options="companyTypeList" @change="onRefresh" />
<van-dropdown-item v-model="cusLevel" :options="cusLevelList" @change="onRefresh" />
</van-dropdown-menu>
</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
:immediate-check="false">
<van-cell v-for="item in list" :key="item.cusId"
@click="$router.push({ name: 'organization-detail', query: { cusId: item.cusId } })">
<div class="name1 text-start stockNames balck-text-color">{{ item.cusName }}</div>
<div class="d-flex justify-content-between">
<div class="font-12">负责人{{ item.saleUserName }}</div>
<div class="name1 text-start flex1">
<van-tag :style="{ backgroundColor: item.cusLevelColor}" size="medium">{{ item.companyTypeName }}</van-tag>
<van-tag class="tag" size="medium">{{ item.cusLevelName }}</van-tag>
</div>
</div>
</van-cell>
</van-list>
</van-pull-refresh>
<van-empty v-else />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { cusInfoList, sysDicListByType } from '@/utils/api'
let searchContent = ref('搜索')
let itemRef = ref()
const clearSearch = () => {
search.value = ''
searchContent.value = '搜索'
}
const onConfirm = () => {
searchContent.value = search.value || '搜索'
itemRef.value.toggle()
onRefresh()
}
const getData = async () => {
const { data } = await cusInfoList({
companyType: companyType.value || null,
cusLevel: cusLevel.value || null,
cusName: search.value,
curPage: curPage.value,
limit: 20
})
console.log(data);
return data
}
const companyType = ref('')
const cusLevel = ref('')
const search = ref('')
const companyTypeList = ref<any[]>([])
const cusLevelList = ref<any[]>([])
const getDicList = async () => {
const { data } = await sysDicListByType({ dicType: 'sale_company_type' })
companyTypeList.value = data.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
companyTypeList.value.unshift({ text: '全部类型', value: '' })
const { data: data1 } = await sysDicListByType({ dicType: 'sale_customer_type' })
cusLevelList.value = data1.map((ele: any) => {
ele.text = ele.dicValue
ele.value = ele.dicKey
return ele
})
cusLevelList.value.unshift({ text: '全部评级', value: '' })
}
getDicList()
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'organization')
import { scrollList } from '@/mixins/scroll-list'
const pageContent = ref()
const scrollPosition = ref(0)
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, onRefresh)
setScrollTop()
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'organization-detail')
})
</script>
<style lang='scss' scoped>
.top-select-list {
padding-top: 60px;
}
.item {
width: 100%;
text-align: center;
}
.tag {
margin-left: 10px;
background-color: #ff758f;
}
</style>

267
src/views/report/list.vue Normal file
View File

@@ -0,0 +1,267 @@
<template>
<div class="page-content top-select-list">
<div class="top-select border-bottom-1">
<van-grid :border="false" :gutter="10" :column-num="3">
<van-grid-item>
<top-select1 :type="'report_type'" :initialName="'类型'" @refresh="refreshByReportType"
:selectName="reportTypeName" :selectValue="reportType" />
</van-grid-item>
<van-grid-item>
<top-date1 :initialName="'日期'" @refresh="refreshByDate" :selectDate="date" />
</van-grid-item>
<van-grid-item>
<top-select2 :type="'stock'" :initialName="'股票'" @refresh="refreshByStock" :selectName="stockName"
:selectValue="stockCode" />
</van-grid-item>
</van-grid>
</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
:immediate-check="false">
<van-cell class="cell" v-for="(item, index) in list" :key="index" :id="item.reportId"
@click="showDetail(item, index)">
<div class="img-box">
<van-image :src="item.url" class="img"></van-image>
<div class="title">{{ item.reportName }}</div>
</div>
<div class="road-info flex-1 text-start d-flex justify-content-between">
<div class="title">{{ item.reportName }}</div>
<div class="flex-1">
<div>
<span>{{ item.stockName }}</span>
<span class="line">|</span>
<van-tag size="medium" :class="item.reportColor" class="white-text-color">
{{ item.reportTypeName }}
</van-tag>
</div>
<div class="report-desc">
<div class="detail text-ellipsis" v-for="(row, idx) in item.reportDescList" :key="idx">{{ row }}</div>
</div>
</div>
<div class="report-time">{{ item.reportDate }}</div>
</div>
</van-cell>
</van-list>
</van-pull-refresh>
<van-empty v-else />
</div>
</template>
<script setup lang="ts">
import { ref, nextTick, onUnmounted } from 'vue'
import TopSelect1 from '@/components/top-select1.vue'
import TopSelect2 from '@/components/top-select2.vue'
import TopDate1 from '@/components/top-date1.vue'
import emitter from '@/utils/mitt'
import { stockReportList } from '@/utils/api'
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
const getData = async () => {
const { data } = await stockReportList(`${reportType.value ? '?reportType=' + reportType.value : ''}`, {
curPage: curPage.value,
limit: 20,
stockCode: stockCode.value || null,
reportDate: date.value !== '日期' ? date.value : null
})
data.list.map((ele: any) => {
ele.url = `/img/${ele.reportType}.jpeg`
ele.reportDescList = ele.reportTitle?.split('') || []
ele.reportName = ele.reportName
? ele.reportName.substring(0, ele.reportName.length - 4)
: ''
switch (ele.reportType) {
case 'SFBG':
ele.reportColor = 'primary-background-color'
break
case 'GXBG':
ele.reportColor = 'danger-background-color'
break
case 'XLL':
ele.reportColor = 'success-background-color'
break
case 'CLBG':
ele.reportColor = 'warning-background-color'
break
}
})
return data
}
import reportInfoStore from '@/stores/reportInfo'
const reportInfo = reportInfoStore()
const scrollPage = () => {
nextTick(() => {
if (reportInfo.reportId && reportInfo.index) {
stockCode.value = reportInfo.stockCode
stockName.value = reportInfo.stockName
date.value = reportInfo.date
reportTypeName.value = reportInfo.reportTypeName
reportType.value = reportInfo.reportType
let pageIndex1 = reportInfo.index / 20
let pageIndex2 = reportInfo.index % 20
if (pageIndex2 > 1) {
pageIndex1++
}
if (pageIndex1 > curPage.value) {
onLoad()
} else {
const el = document.getElementById(reportInfo.reportId)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
reportInfo.reportId = ''
reportInfo.index = 0
emitter.emit('hiddenLoading', '')
}
} else {
emitter.emit('hiddenLoading', '')
}
})
}
const { refreshing, finished, loading, list, onLoad, onRefresh, curPage } = listLoadAndRefresh(getData, 'report', scrollPage)
import { showFile } from '@/mixins/show-file'
import userInfoStore from '@/stores/userInfo'
const { loadingFile } = showFile()
const showDetail = (item: any, index: number) => {
reportInfo.index = index
reportInfo.reportId = item.reportId
reportInfo.stockCode = stockCode.value
reportInfo.stockName = stockName.value
reportInfo.date = date.value
reportInfo.reportTypeName = reportTypeName.value
reportInfo.reportType = reportType.value
const userInfo = userInfoStore()
loadingFile(`${import.meta.env.VITE_BASE_URL}/stock/report/${item.reportId}?token=${userInfo.token}`)
}
let reportType = ref('')
let reportTypeName = ref('类型')
let date = ref('日期')
let stockCode = ref('')
let stockName = ref('股票')
const refreshByReportType = (e: any) => {
reportType.value = e.value
reportTypeName.value = e.name
onRefresh()
}
const refreshByDate = (e: any) => {
date.value = e
onRefresh()
}
const refreshByStock = (e: any) => {
stockCode.value = e.value
stockName.value = e.name
onRefresh()
}
const onRefresh1 = () => {
stockCode.value = reportInfo.stockCode
stockName.value = reportInfo.stockName
date.value = reportInfo.date
reportTypeName.value = reportInfo.reportTypeName
reportType.value = reportInfo.reportType
onRefresh()
}
onRefresh1()
onUnmounted(() => {
reportInfo.stockCode = ''
reportInfo.stockName = '股票'
reportInfo.date = '日期'
reportInfo.reportTypeName = '类型'
reportInfo.reportType = ''
})
</script>
<style lang='scss' scoped>
.van-cell {
width: 100%;
max-width: 678px;
margin: 0;
box-shadow: 0 0;
}
.cell {
:deep(.van-cell__value) {
display: flex;
}
.img-box {
width: 30%;
max-width: 100PX;
max-height: 130PX;
position: relative;
.img {
width: 100%;
height: 100%;
}
.title {
position: absolute;
top: 50%;
transform: translateY(-50%);
padding: 10px;
zoom: 0.8;
height: 40px;
overflow: hidden;
color: #fff;
text-align: center;
font-size: 12px;
}
}
.road-info {
margin-left: 10px;
flex-direction: column;
width: 70%;
.report-time {
font-size: 12px;
line-height: 16px;
}
.title {
color: #000;
font-weight: 600;
}
.report-desc {
color: #999;
height: 48px;
margin-top: 6px;
overflow: hidden;
font-size: 12px;
.detail {
line-height: 16px;
height: 16px;
padding-left: 8px;
position: relative;
&::before {
content: '';
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #999;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
.line {
margin: 0 10px;
}
}
}
</style>

189
src/views/stock/detail.vue Normal file
View File

@@ -0,0 +1,189 @@
<template>
<div class="page-content">
<div class="stock-detail" ref="pageContent">
<van-steps direction="vertical" :active="-1" v-if="eventList.length > 0" @click-step="showInfo">
<van-step v-for="item in eventList" :key="item.id" :id="item.id">
<template v-slot:inactive-icon>
<div style="background-color: #000" class="slotIcon"></div>
</template>
<div class="stepItem">
<van-row class="mb-16">
<van-col span="12">
<van-tag size="medium" :stlye="{ backgroundColor: item.eventTypeColor }">
{{ item.eventTypeName }}
</van-tag>
</van-col>
<van-col span="12">
<div class="text-end tip">{{ item.sysDate }}</div>
</van-col>
</van-row>
<div v-if="item.eventType === 'CHDP'">
<div v-if="item.eventNote">
<div v-if="!item.playFlag" class="audioDiv d-flex align-items-center">
<van-icon name="play-circle" @click="playAudio(item)" />
</div>
<audio v-else :id="`auto${item.id}`" :src="item.eventNote" controls @play="playAudio(item)"></audio>
</div>
</div>
<div class="eventNote" v-else>{{ item.eventNote }}</div>
</div>
</van-step>
</van-steps>
<van-empty v-else />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import emitter from '@/utils/mitt'
const route = useRoute()
const router = useRouter()
import stockInfoStore from '@/stores/stockInfo'
const stockInfo = stockInfoStore()
let stockCode: any = ref('')
const init = () => {
if (route.query && route.query.stockCode) {
stockCode.value = route.query.stockCode
emitter.emit('showLoading', '')
getStockEventList()
} else {
router.push({ name: '404' })
}
}
let eventList: any = ref([])
import { stockStockinfoEventList } from '@/utils/api'
const getStockEventList = async () => {
const { data }: any = await stockStockinfoEventList({
stockCode: stockCode.value
})
data.map((ele: any) => {
ele.playFlag = false
})
eventList.value = data
nextTick(() => {
if (stockInfo.id) {
const el = document.getElementById(stockInfo.id)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
stockInfo.id = ''
emitter.emit('hiddenLoading', '')
} else {
emitter.emit('hiddenLoading', '')
}
})
}
init()
import { showFile } from '@/mixins/show-file'
const { loadingFile } = showFile()
const showInfo = (e: any) => {
if (eventList.value[e].eventType === 'DP') {
router.push({ name: 'comment-detail', query: { inboxId: eventList.value[e].inboxId } })
} else if (eventList.value[e].eventType === 'UPDATE' || eventList.value[e].eventType === 'FIRST') {
stockInfo.id = eventList.value[e].id
loadingFile(`${import.meta.env.VITE_BASE_URL}/api/file/${eventList.value[e].reportFileId}`)
}
}
import { scrollList } from '@/mixins/scroll-list'
import { onBeforeRouteLeave } from 'vue-router'
let pageContent = ref(null)
const scrollPosition = ref(0)
const refresh = () => {
init()
}
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
setScrollTop()
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'comment-detail')
})
const playAudio = (item: any) => {
eventList.value.map((ele: any) => {
if (ele.eventType === 'CHDP' && ele.eventNote) {
if (item.id === ele.id) {
ele.playFlag = true
nextTick(() => {
const el = document.getElementById(`auto${item.id}`)
if (el) {
el.play()
}
})
} else {
if (ele.playFlag) {
const el = document.getElementById(`auto${item.id}`)
if (el) {
el.pause()
}
ele.playFlag = false
}
}
}
})
}
</script>
<style lang='scss' scoped>
.stock-detail {
height: 100vh;
overflow-y: auto;
}
:deep(.van-step__circle-container) {
top: 29px
}
.van-steps--vertical {
padding-left: 48px;
}
.van-steps__items {
padding-right: 32px
}
.slotIcon {
height: 14px;
width: 14px;
border-radius: 100%;
}
.stepItem {
padding: 8px 16px;
}
.van-step--vertical:not(:last-child):after {
border-color: #b2b2b2;
margin-right: 46px;
margin-left: 16px;
}
.eventNote {
font-weight: 400;
font-size: 14px;
line-height: 20px;
}
.audioDiv {
height: 28px;
width: 300px;
border-radius: 32px;
background-color: #f1f3f4;
padding-left: 10px;
margin: 4px 0;
.van-icon {
color: #000;
}
}
audio {
height: 28px;
margin: 4px 0;
}
</style>

121
src/views/stock/list.vue Normal file
View File

@@ -0,0 +1,121 @@
<template>
<div class="page-content">
<div class="tabbar-list" ref="pageContent">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" v-if="list.length > 0">
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
:immediate-check="false">
<van-cell v-for="item in list" :key="item.stockCode"
@click="$router.push({ name: 'stock-detail', query: { stockCode: item.stockCode } })">
<van-row class="mb-1">
<van-col span="12">
<div class="text-start">
<van-tag :class="item.eventTypeColor" size="medium">{{ item.eventTypeName }}</van-tag>
</div>
</van-col>
<van-col span="12">
<div class="text-end">
<div class="sub-title balck-text-color">
{{ item.stockCode }}{{ item.stockName ? '【' + item.stockName + '】' : '' }}
</div>
</div>
</van-col>
</van-row>
<div v-if="item.eventType === 'CHDP'">
<div v-if="item.eventNote">
<div class="audioDiv d-flex align-items-center">
<van-icon name="play-circle" />
</div>
</div>
</div>
<div class="eventNote text-start" v-else>{{ item.eventNote }}</div>
<div class="text-end tip">{{ item.sysDate }}</div>
</van-cell>
</van-list>
</van-pull-refresh>
<van-empty v-else />
<tabbar />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { listLoadAndRefresh } from '@/mixins/list-load-and-refresh'
import { scrollList } from '@/mixins/scroll-list'
import { onBeforeRouteLeave } from 'vue-router'
import Tabbar from '@/components/tabbar.vue'
let pageContent = ref(null)
const scrollPosition = ref(0)
const refresh = () => {
onRefresh()
}
import { stockStockinfoEventNews } from '@/utils/api'
const getData = async () => {
const { data } = await stockStockinfoEventNews({
curPage: curPage.value,
limit: 20
})
data.list.map((ele: any) => {
switch (ele.eventType) {
case 'DP':
ele.eventTypeColor = 'primary-background-color'
break
case 'SFBG':
ele.eventTypeColor = 'danger-background-color'
break
case 'GXBG':
ele.eventTypeColor = 'warning-background-color'
break
case 'XLL':
ele.eventTypeColor = 'pink-background-color'
break
case 'CLBG':
ele.eventTypeColor = 'other-background-color'
break
case 'JOIN':
ele.eventTypeColor = 'success-background-color'
break
case 'CHDP':
ele.eventTypeColor = 'purple-background-color'
break
case 'DATA':
ele.eventTypeColor = 'yellow-background-color'
break
}
})
return data
}
const { refreshing, finished, loading, curPage, list, onLoad, onRefresh } = listLoadAndRefresh(getData, 'stock')
const { setScrollTop, setScrollPositionAndRefreshFlag } = scrollList(pageContent, scrollPosition, refresh)
setScrollTop()
onBeforeRouteLeave((to, from) => {
setScrollPositionAndRefreshFlag(to, 'stock-detail')
})
</script>
<style lang='scss' scoped>
.eventNote {
font-weight: 400;
font-size: 14px;
line-height: 20px;
}
.audioDiv {
height: 28px;
width: 300px;
border-radius: 32px;
background-color: #f1f3f4;
padding-left: 10px;
margin: 4px 0;
.van-icon {
color: #000;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More