Commit 0c2e1451 by HoMeTown

Initial commit

parents
NODE_ENV = 'dev'
VITE_NODE_ENV = 'dev'
VITE_APP_BASE_API = 'https://sit-sdream.insurbank.cn/sdream-api'
#企业微信ID
VITE_APP_WECHAT_APP_ID = 'ww4a5a2a9b6170e5dc'
#企业微信内应用ID
VITE_APP_WECHAT_SUB_APP_ID = '1000028'
NODE_ENV = 'pre'
VITE_NODE_ENV = 'pre'
VITE_APP_BASE_API = 'https://pre-lfsplanning-platform.guominpension.com'
NODE_ENV = 'prod'
VITE_NODE_ENV = 'prod'
VITE_APP_BASE_API = 'https://sit-sdream.insurbank.cn/sdream-api'
#企业微信ID
VITE_APP_WECHAT_APP_ID = 'ww4a5a2a9b6170e5dc'
#企业微信内应用ID
VITE_APP_WECHAT_SUB_APP_ID = '1000028'
NODE_ENV = 'sit'
VITE_NODE_ENV = 'sit'
VITE_APP_BASE_API = 'https://sit-sdream.insurbank.cn/sdream-api'
#企业微信ID
VITE_APP_WECHAT_APP_ID = 'ww00ea9bd48f98ec24'
#企业微信内应用ID
VITE_APP_WECHAT_SUB_APP_ID = '1000003'
NODE_ENV = 'sit'
VITE_NODE_ENV = 'sit'
VITE_APP_BASE_API = 'https://sit-sdream.insurbank.cn/sdream-api'
#企业微信ID
VITE_APP_WECHAT_APP_ID = 'ww00ea9bd48f98ec24'
#企业微信内应用ID
VITE_APP_WECHAT_SUB_APP_ID = '1000003'
NODE_ENV = 'uat'
VITE_NODE_ENV = 'uat'
VITE_APP_BASE_API = 'https://jianyan-uat.guominpension.com'
#企业微信ID
VITE_APP_WECHAT_APP_ID = 'ww00ea9bd48f98ec24'
#企业微信内应用ID
VITE_APP_WECHAT_SUB_APP_ID = '1000004'
*.sh
node_modules
lib
*.md
*.woff
*.ttf
.vscode
.idea
/dist/
/public
/docs
.vscode
.local
package.json
!.env-config.ts
components.d.ts
# common
.DS_Store
dist/
jianyan/
sdream-h5/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.txt
node_modules
{
"singleQuote": true,
"semi": false,
"insertPragma": false,
"trailingComma": "none",
"arrowParens": "avoid"
}
FROM ccr-2h88r0v7-pub.cnc.bj.baidubce.com/guomin-pension/basenginx:v1.0.0-fix-20240411181122
EXPOSE 8080
# 添加时区环境变量,亚洲,上海
ENV TimeZone=Asia/Shanghai
# 使用软连接,并且将时区配置覆盖/etc/timezone
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone
# hide nginx version
RUN sed -i "/default_type/i\ server_tokens off;\n" /etc/nginx/nginx.conf
# static file
COPY output/ /home/work/jianyan-fe-h5/
# nginx
COPY output/deploy/default.conf /etc/nginx/conf.d/
# start.sh
COPY output/deploy/start.sh /start.sh
RUN rm -rf /home/work/jianyan-fe-h5/deploy
RUN chmod 755 /start.sh
CMD ["/start.sh"]
# 国民养老 H5 项目,作为微信小程序 webview 使用
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
server {
listen 8080;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
gzip on;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
location ^~ /jianyan-h5 {
expires -1;
alias /home/work/jianyan-fe-h5/;
index index.html index.html;
try_files $uri $uri/ /jianyan-h5/index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
#上传文件的大小限制 1G
client_max_body_size 1G;
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
#!/bin/sh
exec nginx -g 'daemon off;'
// eslint.config.js
import antfu from '@antfu/eslint-config'
export default antfu({
typescript: true,
vue: true,
formatters: {
css: true,
html: true,
},
})
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<title>晓得</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
</body>
</html>
{
"name": "guomin-jianyan-h5-fe",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "cross-env vite --mode=development",
"build": "cross-env vite build --mode=production",
"build:pre": "cross-env vite build --mode=pre",
"build:sit": "cross-env vite build --mode=sit",
"build:uat": "cross-env vite build --mode=uat",
"build:test": "cross-env vite build --mode=test",
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"typecheck": "vue-tsc --noEmit",
"postinstall": "npx simple-git-hooks",
"sizecheck": "npx vite-bundle-visualizer"
},
"dependencies": {
"@antv/g6": "^4.8.24",
"@antv/x6": "^2.18.1",
"@antv/x6-plugin-export": "^2.1.6",
"@opentiny/vue": "3",
"@types/echarts": "^4.9.16",
"@types/node": "^18.11.9",
"@vueuse/core": "^10.9.0",
"animate.css": "^4.1.1",
"axios": "^1.2.0",
"cross-env": "^7.0.3",
"echarts": "^5.4.0",
"html2canvas": "^1.4.1",
"markdown-it": "^14.1.0",
"markdown-it-image": "^1.0.0",
"mermaid": "^11.4.1",
"mind-elixir": "^4.3.6",
"moment": "^2.30.1",
"prettier": "^2.8.0",
"sass": "^1.56.1",
"swiper": "^11.1.4",
"typeit": "^8.8.3",
"url": "^0.11.0",
"vant": "^4.9.0",
"vconsole": "^3.15.1",
"vue": "^3.2.41",
"vue-router": "^4.1.6",
"weixin-js-sdk": "^1.6.5"
},
"devDependencies": {
"@antfu/eslint-config": "^2.8.1",
"@vitejs/plugin-vue": "^3.2.0",
"eslint": "^8.56.0",
"eslint-plugin-format": "^0.1.0",
"less": "^4.1.3",
"lint-staged": "^15.2.2",
"postcss-plugin-px2rem": "^0.8.1",
"postcss-px-to-viewport": "^1.1.1",
"postcss-px-to-viewport-8-plugin": "^1.2.0",
"simple-git-hooks": "^2.10.0",
"typescript": "^4.6.4",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.22.9",
"vite": "^3.2.3",
"vite-bundle-visualizer": "^1.0.1",
"vite-plugin-require-transform": "^1.0.4",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.0.9"
},
"lint-staged": {
"*": "eslint --fix"
}
}
module.exports = {
plugins: {
'postcss-px-to-viewport-8-plugin': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 375, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*'], // 能转化为vw的属性列表,!font-size表示font-size后面的单位不会被转换
viewportUnit: 'vw', // 希望使用的视口单位
fontViewportUnit: 'vw', // 字体使用的视口单位
// 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
// 下面配置表示类名中含有'keep-px'都不会被转换
selectorBlackList: ['keep-px]'],
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: [/src/], // 如果设置了include,那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1338, // 横屏时使用的视口宽度
},
},
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
<script setup lang="ts">
import * as echarts from 'echarts'
import { provide } from 'vue'
import { fetchGetUserInfo } from './api/user'
import { getToken } from './utils/user'
import { EnumStorageKey } from './enum'
import { getLocal } from './utils/storage'
import { fetchGetAgreementList, fetchGetAppConfig } from './api/config'
provide('echarts', echarts)
const userInfo = ref<any>({})
const agreementList = ref([])
const isShow = ref(false)
const token = getToken()
function getUserInfo() {
fetchGetUserInfo().then((res) => {
if (res.data) {
userInfo.value = res.data
isShow.value = true
}
})
fetchGetAgreementList().then((res) => {
if (res.data)
agreementList.value = res.data
})
fetchGetAppConfig().then((res) => {
if (res.data)
window.localStorage.setItem('__APP_CONFIG__', JSON.stringify(res.data))
})
}
const route = useRoute()
onMounted(() => {
if (!token) {
userInfo.value = getLocal(EnumStorageKey['user-info'])
return
}
getUserInfo()
})
</script>
<template>
<div v-if="isShow" id="app_inner">
<router-view />
<!-- <Watermark :user-name="userInfo.userName" /> -->
<GrantPopup v-if="route.path !== '/agreement'" :grant-flag="userInfo.grantFlag" :agreement-list="agreementList" />
</div>
</template>
<style scoped></style>
import http from '../utils/request'
/**
* 查询推荐问题
* @param conversationId
* @returns
*/
export function fetchQueryRecommendQuestion(conversationId: string, recordId: string) {
return http.post('/conversation/api/conversation/mobile/v1/query_recommend_question', {
conversationId,
recordId,
})
}
/**
* 查询用户收藏列表
* @param params
* @returns
*/
export function fetchQueryCollectionList<T>(params: T) {
return http.post('/conversation/api/collection/mobile/v1/query_user_collection_page', params)
}
/**
* 提交收藏
* @param recordId
* @returns
*/
export function fetchSubmitCollection(recordId: string) {
return http.post('/conversation/api/collection/mobile/v1/submit_collection', { recordId })
}
/**
* 删除收藏
* @param collectionIdList
* @returns
*/
export function fetchDelCollection(collectionIdList: string[]) {
return http.post('/conversation/api/collection/mobile/v1/delete_user_collection', { collectionIdList })
}
/**
* 取消收藏
* @param recordId
* @returns
*/
export function fetchCancelCollection(recordId: string) {
return http.post('/conversation/api/collection/mobile/v1/cancel_user_collection', { recordId })
}
/**
* 获取文档链接
* @param recordId
* @returns
*/
export function fetchGetDocumentLink(docId: string) {
return http.post('/conversation/api/conversation/mobile/v1/get_document', {
ossType: 'private',
docId,
})
}
/**
* 获取文档链接s
* @param recordId
* @returns
*/
export function fetchGetDocumentLinks(docIdList: string[]) {
return http.post('/conversation/api/conversation/mobile/v1/query_batch_document', {
ossType: 'private',
docIdList,
})
}
/**
* 获取反馈配置
* 场景:01:用户问答反馈
* @param recordId
* @returns
*/
export function fetchGetFeedbackConfig() {
return http.post('/conversation/api/feedback/mobile/v1/get_config', {
scene: '01',
})
}
/**
* 提交反馈
* feedbackStatus 问答反馈状态 00:初始(取消) 01:点赞 02:点踩
* recordId 会话问答ID
* scene 场景:01:用户问答反馈
* content 反馈内容
* @param params
* @returns
*/
export function fetchSubmitFeedback(params: any) {
return http.post('/conversation/api/feedback/mobile/v1/submit_feedback', {
...params,
scene: '01',
})
}
import { List } from 'echarts'
import http from '../utils/request'
/**
* 查询token合法
* @params
*/
export function checkTokenApi() {
return http.post('/user/api/user_center/mobile/v1/check_token', {})
}
/**
* 消息配置信息
* @params messageType [首页欢迎语 01]
*/
export function fetchMessageConfig(messageType: string) {
return http.post('/config-center/api/message/mobile/v1/get_message_config', { messageType })
}
/**
* 查询工具
* @params
*/
export function fetchToolList() {
return http.post('/config-center/api/tool/mobile/v1/get_tool_list', { })
}
/**
* 查询推荐问题列表
* @params
*/
export function fetchRecommendQuestionList() {
return http.post('/config-center/api/question/mobile/v1/get_recommend_question_list')
}
/**
* 获取协议列表
* @returns
*/
export function fetchGetAgreementList() {
return http.post('/config-center/api/commonconfig/mobile/v1/query_agreement_list')
}
/**
* 获取配置信息
* @returns
*/
export function fetchGetAppConfig() {
return http.post('/config-center/api/commonconfig/mobile/v1/get_app_config')
}
/*
* @Author: weiyudumei wangxu9681@126.com
* @Date: 2024-05-20 10:56:58
* @LastEditors: weiyudumei wangxu9681@126.com
* @LastEditTime: 2024-06-24 15:10:47
* @FilePath: /guomin-jianyan-h5-fe/src/api/conversation.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import http from '../utils/request'
/**
* 查询场景配置接口
* @params { * }
*/
export function fetchSceneConfig() {
return http.post('/conversation/api/scene_config/mobile/v1/query_scene_config', {})
}
/**
* 查询当前会话
* @params { * }
*/
export function fetchCurrentConversation() {
return http.post('/conversation/api/conversation/mobile/v1/get_user_conversation_session', {})
}
/**
* 创建会话
* @params { * }
*/
export function createConversation() {
return http.post('/conversation/api/conversation/mobile/v1/create_conversation', {})
}
/**
* 查询用户问答历史
* @params { * }
*/
export function fetchUserQaRecordRage(data: object) {
return http.post('/conversation/api/conversation/mobile/v1/query_user_qa_record_list', data)
}
/**
* 提交问题
* @params
*/
export function submitQuestionStream(data: object) {
return http.post('/conversation/api/conversation/mobile/v1/submit_question_stream', data, {
responseType: 'stream',
})
}
/**
* 查询用户历史会话列表
* @params
*/
export function queryUserConversationPage(data: object) {
return http.post('/conversation/api/conversation/mobile/v1/query_user_conversation_page', data, {
responseType: 'stream',
})
}
/**
* 删除用户历史会话
* @params
*/
export function deleteUserConversation(data: object) {
return http.post('/conversation/api/conversation/mobile/v1/delete_user_conversation', data, {
responseType: 'stream',
})
}
/**
* 停止问答
* @param params
* @returns
*/
export function fetchTerminateQuestion(params: any) {
return http.post('/conversation/api/conversation/mobile/v1/terminate_question', params)
}
import http from '../utils/request'
import type { ProductVipItem } from '@/typings/vip'
/**
* 查询会员产品列表接口
* @returns
*/
export function fetchGetProductVipList() {
return http.post<ProductVipItem[]>('/api/chonglin-api/order/api/product/mobile/v1/query_product_vip_list')
}
/* eslint-disable node/prefer-global/process */
import http from '../utils/request'
/**
* 查询三方用户信息接口
* @returns
*/
export function fetchGetThirdUserInfo(code: string) {
const params = {
code,
appId: process.env.VITE_APP_WECHAT_APP_ID,
subAppId: process.env.VITE_APP_WECHAT_SUB_APP_ID,
}
return http.post('/user/api/login/mobile/v1/get_third_user_info', params)
}
/**
* 获取用户信息
* @returns
*/
export function fetchGetUserInfo() {
return http.post('/user/api/user_center/mobile/v1/get_user')
}
/**
* 登录
* @returns
*/
export function fetchLogin(loginCode: string) {
return http.post('/user/api/login/mobile/v1/login', {
loginCode,
appId: process.env.VITE_APP_WECHAT_APP_ID,
subAppId: process.env.VITE_APP_WECHAT_SUB_APP_ID,
})
}
/**
* 用户业务操作公共接口
* 操作类型 01:用户协议已读通知
* @returns
*/
export function fetchCommonOperate(operateType: string) {
return http.post('/user/api/user_center/mobile/v1/common_operate', {
operateType,
})
}
/**
* 登录
* @returns
*/
export function fetchLoginByUid(uid: string) {
return http.post('/user/api/login/mobile/v1/guest/login', {
userId: uid,
})
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 3</title>
<g id="H5" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="我的收藏1" transform="translate(-32.000000, -152.000000)">
<g id="1" transform="translate(16.000000, 104.000000)">
<g id="编组-19" transform="translate(16.000000, 48.000000)">
<path d="M8,0 C12.418278,-8.11624501e-16 16,3.581722 16,8 L16,14.4 C16,15.2836556 15.2836556,16 14.4,16 L8,16 C3.581722,16 5.41083001e-16,12.418278 0,8 C-5.41083001e-16,3.581722 3.581722,8.11624501e-16 8,0 Z" id="矩形备份-3" fill="#393D46"></path>
<path d="M5.66696035,12.8 L6.41409692,10.0845714 L9.57180617,10.0845714 L10.3189427,12.8 L12.8,12.8 L9.93832599,3.2 L6.06167401,3.2 L3.2,12.8 L5.66696035,12.8 Z M9.12070485,8.43885714 L6.86519824,8.43885714 L7.97885463,4.42057143 L8.00704846,4.42057143 L9.12070485,8.43885714 Z" id="A" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="7px" height="10px" viewBox="0 0 7 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形@2x</title>
<g id="0613-版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="简言---重新生成4" transform="translate(-290.000000, -540.000000)" fill="rgba(41,182,253,0.6)">
<g id="编组-4" transform="translate(15.000000, -84.000000)">
<g id="编组-12备份" transform="translate(0.000000, 524.000000)">
<g id="切换" transform="translate(275.000000, 97.000000)">
<g id="左箭头" transform="translate(3.500000, 8.000000) scale(-1, 1) translate(-3.500000, -8.000000) translate(0.000000, 2.000000)">
<path d="M-0.550252532,2.44974747 C0.00203221814,2.44974747 0.449747468,2.89746272 0.449747468,3.44974747 L0.449747468,7.44974747 L0.449747468,7.44974747 L4.44974747,7.44974747 C5.00203222,7.44974747 5.44974747,7.89746272 5.44974747,8.44974747 C5.44974747,9.00203222 5.00203222,9.44974747 4.44974747,9.44974747 L-0.550252532,9.44974747 C-1.10253728,9.44974747 -1.55025253,9.00203222 -1.55025253,8.44974747 L-1.55025253,3.44974747 C-1.55025253,2.89746272 -1.10253728,2.44974747 -0.550252532,2.44974747 Z" id="矩形" transform="translate(1.949747, 5.949747) scale(-1, 1) rotate(45.000000) translate(-1.949747, -5.949747) "></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="375px" height="320px" viewBox="0 0 375 320" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>bg</title>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#E6EFF8" offset="0%"></stop>
<stop stop-color="#F8F8F8" offset="100%"></stop>
</linearGradient>
<radialGradient cx="0%" cy="0%" fx="0%" fy="0%" r="100%" gradientTransform="translate(0.000000,0.000000),scale(0.853333,1.000000),rotate(90.000000),scale(1.000000,1.523066),translate(-0.000000,-0.000000)" id="radialGradient-2">
<stop stop-color="#AAA7FF" stop-opacity="0.79661464" offset="0%"></stop>
<stop stop-color="#C1E3FF" stop-opacity="0" offset="100%"></stop>
</radialGradient>
<radialGradient cx="100%" cy="0%" fx="100%" fy="0%" r="65.0249672%" gradientTransform="translate(1.000000,0.000000),scale(0.853333,1.000000),rotate(90.000000),scale(1.000000,0.985386),translate(-1.000000,-0.000000)" id="radialGradient-3">
<stop stop-color="#CDE8FF" stop-opacity="0.79661464" offset="0%"></stop>
<stop stop-color="#C1E3FF" stop-opacity="0" offset="100%"></stop>
</radialGradient>
<rect id="path-4" x="0" y="0" width="375" height="320"></rect>
<linearGradient x1="0%" y1="34.5258713%" x2="92.2393799%" y2="50%" id="linearGradient-6">
<stop stop-color="#FFFFFF" stop-opacity="0.302849616" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="0%" y1="29.6439852%" x2="91.3160597%" y2="57.1683339%" id="linearGradient-7">
<stop stop-color="#FFFFFF" stop-opacity="0.599625594" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
</defs>
<g id="H5" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="主页" transform="translate(0.000000, -88.000000)">
<g id="bg" transform="translate(0.000000, 88.000000)">
<mask id="mask-5" fill="white">
<use xlink:href="#path-4"></use>
</mask>
<g id="矩形">
<use fill="url(#linearGradient-1)" xlink:href="#path-4"></use>
<use fill="url(#radialGradient-2)" xlink:href="#path-4"></use>
<use fill="url(#radialGradient-3)" xlink:href="#path-4"></use>
</g>
<circle id="椭圆形" stroke="url(#linearGradient-7)" stroke-width="0.5" fill="url(#linearGradient-6)" mask="url(#mask-5)" cx="300" cy="80" r="99.75"></circle>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><circle fill='#29B6FD' stroke='#29B6FD' stroke-width='2' r='15' cx='40' cy='65'><animate attributeName='cy' calcMode='spline' dur='1' values='65;135;65;' keySplines='.5 0 .5 1;.5 0 .5 1' repeatCount='indefinite' begin='-.4'></animate></circle><circle fill='#29B6FD' stroke='#29B6FD' stroke-width='2' r='15' cx='100' cy='65'><animate attributeName='cy' calcMode='spline' dur='1' values='65;135;65;' keySplines='.5 0 .5 1;.5 0 .5 1' repeatCount='indefinite' begin='-.2'></animate></circle><circle fill='#29B6FD' stroke='#29B6FD' stroke-width='2' r='15' cx='160' cy='65'><animate attributeName='cy' calcMode='spline' dur='1' values='65;135;65;' keySplines='.5 0 .5 1;.5 0 .5 1' repeatCount='indefinite' begin='0'></animate></circle></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 3@2x</title>
<defs>
<rect id="path-1" x="4" y="6" width="12" height="8"></rect>
</defs>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="简言---收藏3" transform="translate(-16.000000, -742.000000)">
<g id="删除按钮" transform="translate(0.000000, 725.500000)">
<g id="编组-3" transform="translate(16.000000, 16.500000)">
<circle id="未勾选" fill="#F4F4F4" cx="10" cy="10" r="10"></circle>
<g id="✅已勾选">
<circle id="椭圆形" fill-opacity="0.6" fill="#16A39A" cx="10" cy="10" r="10"></circle>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="蒙版"></g>
<path d="M7.88914675,11.3942068 L10.6598346,11.3339929 L10.6598346,11.3339929 L10.7917235,4.63800131 C10.8064567,3.8900021 11.4093004,3.28715833 12.1572996,3.27242521 C12.8683451,3.25841996 13.4561149,3.82348272 13.4701202,4.53452821 C13.4704531,4.55143241 13.4704531,4.56834153 13.4701202,4.58524572 L13.3213027,12.1406928 C13.3012849,13.1569982 12.48286,13.976432 11.4665801,13.997702 L7.8364141,14.0736786 C7.12626257,14.0885415 6.53852221,13.5248988 6.52365928,12.8147472 C6.52329488,12.7973362 6.52328415,12.7799195 6.5236271,12.762508 C6.53836859,12.014084 7.14075433,11.4104712 7.88914675,11.3942068 Z" id="矩形" fill="#FFFFFF" mask="url(#mask-2)" transform="translate(9.996874, 8.673877) rotate(45.000000) translate(-9.996874, -8.673877) "></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 7</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="H5" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="主页5" transform="translate(-145.000000, -576.000000)">
<g id="编组-2" transform="translate(16.000000, 190.000000)">
<g id="操作按钮" transform="translate(0.000000, 373.500000)">
<g id="编组-12" transform="translate(16.000000, 12.500000)">
<g id="收藏" transform="translate(113.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份-7"></g>
<path d="M7,4 L15,4 C16.1045695,4 17,4.8954305 17,6 L17,16.017926 C17,17.1224955 16.1045695,18.017926 15,18.017926 C14.6458907,18.017926 14.2981301,17.9239092 13.9922579,17.7454838 L11,16 L11,16 L8.00774205,17.7454838 C7.05363822,18.3020444 5.82900276,17.9797719 5.2724422,17.0256681 C5.0940168,16.7197959 5,16.3720353 5,16.017926 L5,6 C5,4.8954305 5.8954305,4 7,4 Z" id="星形-2" fill="#29B6FD" fill-rule="nonzero" mask="url(#mask-2)"></path>
<path d="M11,7.75 C11.4142136,7.75 11.75,8.08578644 11.75,8.5 L11.75,10 L13.25,10 C13.6642136,10 14,10.3357864 14,10.75 C14,11.1642136 13.6642136,11.5 13.25,11.5 L11.75,11.5 L11.75,13 C11.75,13.4142136 11.4142136,13.75 11,13.75 C10.5857864,13.75 10.25,13.4142136 10.25,13 L10.25,11.5 L8.75,11.5 C8.33578644,11.5 8,11.1642136 8,10.75 C8,10.3357864 8.33578644,10 8.75,10 L10.25,10 L10.25,8.5 C10.25,8.08578644 10.5857864,7.75 11,7.75 Z" id="形状结合" fill="#FFFFFF" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 7</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="H5" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="主页5" transform="translate(-145.000000, -576.000000)">
<g id="编组-2" transform="translate(16.000000, 190.000000)">
<g id="操作按钮" transform="translate(0.000000, 373.500000)">
<g id="编组-12" transform="translate(16.000000, 12.500000)">
<g id="收藏" transform="translate(113.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份-7"></g>
<path d="M7,4 L15,4 C16.1045695,4 17,4.8954305 17,6 L17,16.017926 C17,17.1224955 16.1045695,18.017926 15,18.017926 C14.6458907,18.017926 14.2981301,17.9239092 13.9922579,17.7454838 L11,16 L11,16 L8.00774205,17.7454838 C7.05363822,18.3020444 5.82900276,17.9797719 5.2724422,17.0256681 C5.0940168,16.7197959 5,16.3720353 5,16.017926 L5,6 C5,4.8954305 5.8954305,4 7,4 Z" id="星形-2" fill="#B2B8C1" fill-rule="nonzero" mask="url(#mask-2)"></path>
<path d="M11,7.75 C11.4142136,7.75 11.75,8.08578644 11.75,8.5 L11.75,10 L13.25,10 C13.6642136,10 14,10.3357864 14,10.75 C14,11.1642136 13.6642136,11.5 13.25,11.5 L11.75,11.5 L11.75,13 C11.75,13.4142136 11.4142136,13.75 11,13.75 C10.5857864,13.75 10.25,13.4142136 10.25,13 L10.25,11.5 L8.75,11.5 C8.33578644,11.5 8,11.1642136 8,10.75 C8,10.3357864 8.33578644,10 8.75,10 L10.25,10 L10.25,8.5 C10.25,8.08578644 10.5857864,7.75 11,7.75 Z" id="形状结合" fill="#FFFFFF" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 8@2x</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="简言03.4.3" transform="translate(-283.000000, -546.000000)">
<g id="编组-4" transform="translate(15.000000, -228.000000)">
<g id="编组-12备份" transform="translate(0.000000, 524.000000)">
<g id="操作按钮" transform="translate(20.000000, 250.000000)">
<g id="复制" transform="translate(248.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份-8"></g>
<path d="M13,7 C14.1045695,7 15,7.8954305 15,9 L15,16 C15,17.1045695 14.1045695,18 13,18 L6,18 C4.8954305,18 4,17.1045695 4,16 L4,9 C4,7.8954305 4.8954305,7 6,7 L13,7 Z M12.3429719,13.5 L6.64435739,13.5 C6.46511258,13.499974 6.29396092,13.5868744 6.17203111,13.7398187 C6.05010129,13.8927629 5.98841661,14.0979238 6.00180022,14.306 C6.024057,14.6641818 6.26675176,14.9446777 6.56579034,14.9927168 L6.65702814,15 L12.3556426,15 C12.5348874,15.000026 12.7060391,14.9131256 12.8279689,14.7601813 C12.9498987,14.6072371 13.0115834,14.4020762 12.9981998,14.194 C12.9737173,13.8 12.6825049,13.5 12.3429719,13.5 Z M15,4 C16.6568542,4 18,5.34314575 18,7 L18,13.0007411 C18,14.0547122 17.1844245,14.9181954 16.1499482,14.9945163 L16.0007411,15 L16.0007411,9 C16.0007411,7.34314575 14.6575954,6 13.0007411,6 L7,6 C7,4.8954305 7.8954305,4 9,4 L15,4 Z M12.3429719,9.99999999 L6.64435739,9.99999999 C6.46511258,9.99997397 6.29396092,10.0868744 6.17203111,10.2398187 C6.05010129,10.3927629 5.98841661,10.5979238 6.00180022,10.806 C6.024057,11.1641818 6.26675176,11.4446777 6.56579034,11.4927167 L6.65702814,11.5 L12.3556426,11.5 C12.5348874,11.500026 12.7060391,11.4131256 12.8279689,11.2601813 C12.9498987,11.1072371 13.0115834,10.9020762 12.9981998,10.694 C12.9737173,10.3 12.6825049,9.99999999 12.3429719,9.99999999 Z" id="形状结合" fill="#B2B8C1" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形@2x</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22"></rect>
</defs>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.800637381">
<g id="简言---收藏" transform="translate(-282.000000, -388.000000)">
<g id="收藏&amp;工具" transform="translate(20.000000, 104.000000)">
<g id="1" transform="translate(0.000000, 164.000000)">
<g id="删除" transform="translate(262.000000, 120.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形"></g>
<path d="M16.7272727,6.8 L16.2273329,15.5989407 C16.1507677,16.9464888 15.0356987,18 13.6859772,18 L8.3140228,18 C6.96430127,18 5.84923232,16.9464888 5.77266708,15.5989407 L5.27272727,6.8 L16.7272727,6.8 Z M8.13636364,9.425 C7.7849097,9.425 7.5,9.7099097 7.5,10.0613636 L7.5,10.0613636 L7.5,15.1522727 C7.5,15.5037267 7.7849097,15.7886364 8.13636364,15.7886364 C8.48781757,15.7886364 8.77272727,15.5037267 8.77272727,15.1522727 L8.77272727,15.1522727 L8.77272727,10.0613636 C8.77272727,9.7099097 8.48781757,9.425 8.13636364,9.425 Z M11,9.425 C10.6485461,9.425 10.3636364,9.7099097 10.3636364,10.0613636 L10.3636364,10.0613636 L10.3636364,15.1522727 C10.3636364,15.5037267 10.6485461,15.7886364 11,15.7886364 C11.3514539,15.7886364 11.6363636,15.5037267 11.6363636,15.1522727 L11.6363636,15.1522727 L11.6363636,10.0613636 C11.6363636,9.7099097 11.3514539,9.425 11,9.425 Z M13.8636364,9.425 C13.5121824,9.425 13.2272727,9.7099097 13.2272727,10.0613636 L13.2272727,10.0613636 L13.2272727,15.1522727 C13.2272727,15.5037267 13.5121824,15.7886364 13.8636364,15.7886364 C14.2150903,15.7886364 14.5,15.5037267 14.5,15.1522727 L14.5,15.1522727 L14.5,10.0613636 C14.5,9.7099097 14.2150903,9.425 13.8636364,9.425 Z M12.2363707,4 L11.6363636,4.63636364 L16.5363636,4.63636364 C17.297158,4.63636364 17.9223772,5.21683019 17.9932999,5.95904205 L18,6.1 L4,6.1 C4,5.29165596 4.65529232,4.63636364 5.46363636,4.63636364 L10.3636364,4.63636364 L9.72727273,4 L12.2363707,4 Z" id="形状结合" fill="#B2B8C1" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编辑@2x</title>
<defs>
<rect id="path-1" x="0" y="0" width="16" height="16"></rect>
</defs>
<g id="0520版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="简言--点踩意见反馈" transform="translate(-32.000000, -636.000000)">
<g id="编组-19" transform="translate(0.000000, 326.000000)">
<g id="编组-3备份-9" transform="translate(20.000000, 296.000000)">
<g id="编辑" transform="translate(12.000000, 14.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="蒙版"></g>
<path d="M14,13 C14.5522847,13 15,13.4477153 15,14 C15,14.5522847 14.5522847,15 14,15 L2,15 C1.44771525,15 1,14.5522847 1,14 C1,13.4477153 1.44771525,13 2,13 L14,13 Z M12.2426407,0.646446609 L13.6568542,2.06066017 C14.0473785,2.45118446 14.0473785,3.08434944 13.6568542,3.47487373 L6.79988114,10.3318468 C6.6602806,10.4714474 6.48248162,10.5666025 6.2888905,10.6053207 L3.78568804,11.1059612 C3.51490816,11.1601172 3.25149561,10.9845088 3.19733963,10.713729 C3.18439342,10.6489979 3.18439342,10.5823439 3.19733963,10.5176128 L3.69798012,8.01441036 C3.73669835,7.82081924 3.83185347,7.64302026 3.97145402,7.50341971 L10.8284271,0.646446609 C11.2189514,0.255922318 11.8521164,0.255922318 12.2426407,0.646446609 Z" id="形状结合" fill="#B2B8C1" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份@2x</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="简言03.4.3" transform="translate(-35.000000, -546.000000)">
<g id="编组-4" transform="translate(15.000000, -228.000000)">
<g id="编组-12备份" transform="translate(0.000000, 524.000000)">
<g id="点赞" transform="translate(20.000000, 250.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份"></g>
<path d="M17.9861958,9.41122355 C17.4920104,8.81248083 16.6847281,8.81248083 16.4190189,8.81248083 L14.2899599,8.81248083 C14.4625863,7.88147807 14.6098265,6.79868139 14.2933448,5.65685373 C14.0767156,4.8708985 13.6789979,4.31263416 13.0748055,3.95170193 C12.7583237,3.76280282 12.4299951,3.66666667 12.0965892,3.66666667 C11.2199179,3.66666667 10.5632606,4.31432076 10.4651005,5.2807421 C10.4278673,5.64167433 10.394019,5.98405397 10.3077058,6.27752223 C9.98614684,7.35525912 9.20086596,8.17831953 8.47481963,8.83777982 C8.20403312,9.08065011 7.91463004,9.5157927 7.91124521,10.0049065 C7.90109072,11.3137075 7.8993983,12.6275682 7.8993983,13.9701012 L7.89768589,16.3549525 C7.89432105,17.1527139 8.32080981,17.7953082 9.0316244,18.0702239 C9.40734068,18.2220178 9.80505836,18.3063477 10.2180078,18.3181539 C10.8746651,18.3265869 11.5347072,18.3265869 12.1524389,18.3265869 L13.1154234,18.3265869 C13.7450021,18.3265869 14.3745807,18.3265869 15.0075442,18.3333333 L15.0278532,18.3333333 C15.764054,18.3333333 16.3428602,17.9673413 16.6170315,17.3298068 L16.6982675,17.1425943 C16.8895104,16.7040785 17.0858307,16.2503833 17.2076846,15.7460902 C17.580016,14.2129715 17.9675792,12.5719105 18.2806761,10.9595216 C18.4059149,10.3203005 18.3077548,9.79914137 17.9861958,9.41122355 Z M5.59263572,8.85970561 L5.39123825,8.85970561 C4.44010064,8.85970561 3.66666667,9.63048145 3.66666667,10.5783502 L3.66666667,16.4544618 C3.66666667,17.4023306 4.44010064,18.1731064 5.39123825,18.1731064 L5.59263572,18.1731064 C6.54377334,18.1731064 7.31721007,17.4023306 7.31721007,16.4544618 L7.31721007,10.5783502 C7.31889972,9.63048145 6.54377334,8.85970561 5.59263572,8.85970561 Z" id="形状" fill="#B2B8C1" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份@2x</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="简言03.4.2" transform="translate(-35.000000, -442.000000)">
<g id="编组-4" transform="translate(15.000000, -248.000000)">
<g id="编组-12备份" transform="translate(0.000000, 524.000000)">
<g id="点赞" transform="translate(20.000000, 166.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份"></g>
<path d="M17.9861958,9.41122355 C17.4920104,8.81248083 16.6847281,8.81248083 16.4190189,8.81248083 L14.2899599,8.81248083 C14.4625863,7.88147807 14.6098265,6.79868139 14.2933448,5.65685373 C14.0767156,4.8708985 13.6789979,4.31263416 13.0748055,3.95170193 C12.7583237,3.76280282 12.4299951,3.66666667 12.0965892,3.66666667 C11.2199179,3.66666667 10.5632606,4.31432076 10.4651005,5.2807421 C10.4278673,5.64167433 10.394019,5.98405397 10.3077058,6.27752223 C9.98614684,7.35525912 9.20086596,8.17831953 8.47481963,8.83777982 C8.20403312,9.08065011 7.91463004,9.5157927 7.91124521,10.0049065 C7.90109072,11.3137075 7.8993983,12.6275682 7.8993983,13.9701012 L7.89768589,16.3549525 C7.89432105,17.1527139 8.32080981,17.7953082 9.0316244,18.0702239 C9.40734068,18.2220178 9.80505836,18.3063477 10.2180078,18.3181539 C10.8746651,18.3265869 11.5347072,18.3265869 12.1524389,18.3265869 L13.1154234,18.3265869 C13.7450021,18.3265869 14.3745807,18.3265869 15.0075442,18.3333333 L15.0278532,18.3333333 C15.764054,18.3333333 16.3428602,17.9673413 16.6170315,17.3298068 L16.6982675,17.1425943 C16.8895104,16.7040785 17.0858307,16.2503833 17.2076846,15.7460902 C17.580016,14.2129715 17.9675792,12.5719105 18.2806761,10.9595216 C18.4059149,10.3203005 18.3077548,9.79914137 17.9861958,9.41122355 Z M5.59263572,8.85970561 L5.39123825,8.85970561 C4.44010064,8.85970561 3.66666667,9.63048145 3.66666667,10.5783502 L3.66666667,16.4544618 C3.66666667,17.4023306 4.44010064,18.1731064 5.39123825,18.1731064 L5.59263572,18.1731064 C6.54377334,18.1731064 7.31721007,17.4023306 7.31721007,16.4544618 L7.31721007,10.5783502 C7.31889972,9.63048145 6.54377334,8.85970561 5.59263572,8.85970561 Z" id="形状" fill="#29B6FD" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="21px" viewBox="0 0 20 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>未勾选@2x</title>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="简言---收藏2" transform="translate(-16.000000, -742.000000)" fill="#F4F4F4">
<g id="删除按钮" transform="translate(0.000000, 726.000000)">
<g id="编组-3" transform="translate(16.000000, 16.500000)">
<circle id="未勾选" cx="10" cy="10" r="10"></circle>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形</title>
<g id="H5" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="我的收藏1" transform="translate(-32.000000, -122.000000)">
<g id="1" transform="translate(16.000000, 104.000000)">
<g id="编组-20" transform="translate(16.000000, 18.000000)">
<path d="M8,0 C12.418278,-8.11624501e-16 16,3.581722 16,8 L16,14.4 C16,15.2836556 15.2836556,16 14.4,16 L8,16 C3.581722,16 5.41083001e-16,12.418278 0,8 C-5.41083001e-16,3.581722 3.581722,8.11624501e-16 8,0 Z" id="矩形" fill="#29B6FD"></path>
<path d="M11.1156727,12.8 C10.8959778,12.2886392 10.6884882,11.8451935 10.4932039,11.4696629 C11.2255201,11.1500624 11.7930652,10.6606742 12.1958391,10.0014981 C12.598613,9.3423221 12.8,8.54531835 12.8,7.61048689 C12.8,6.74756554 12.6148867,5.98052434 12.2446602,5.3093633 C11.8744337,4.63820225 11.349607,4.11885144 10.6701803,3.75131086 C9.99075358,3.38377029 9.23606103,3.2 8.40610264,3.2 C7.53546001,3.2 6.76449376,3.38576779 6.09320388,3.75730337 C5.42191401,4.12883895 4.90522423,4.65018727 4.54313454,5.32134831 C4.18104485,5.99250936 4,6.75555556 4,7.61048689 C4,8.48139825 4.17697642,9.23845194 4.53092926,9.88164794 C4.88488211,10.5248439 5.39750347,11.0222222 6.06879334,11.3737828 C6.74008322,11.7253433 7.51918632,11.9011236 8.40610264,11.9011236 C8.52001849,11.9011236 8.67055016,11.8931336 8.85769764,11.8771536 C8.97975035,12.1088639 9.13028202,12.4164794 9.30929265,12.8 L9.30929265,12.8 L11.1156727,12.8 Z M8.40610264,10.4149813 C7.90161812,10.4149813 7.46629681,10.2971286 7.1001387,10.0614232 C6.73398058,9.82571785 6.44715673,9.49812734 6.23966713,9.07865169 C6.03217753,8.65917603 5.92843273,8.16978777 5.92843273,7.61048689 C5.92843273,7.07515605 6.03421174,6.58576779 6.24576976,6.1423221 C6.45732779,5.6988764 6.74618585,5.33932584 7.11234397,5.06367041 C7.47850208,4.78801498 7.90161812,4.65018727 8.38169209,4.65018727 C8.89431345,4.65018727 9.3357374,4.79400749 9.70596394,5.08164794 C10.0761905,5.36928839 10.3609801,5.73483146 10.5603329,6.17827715 C10.7596856,6.62172285 10.859362,7.09912609 10.859362,7.61048689 C10.859362,8.16978777 10.7556172,8.65917603 10.5481276,9.07865169 C10.340638,9.49812734 10.0558484,9.82571785 9.69375867,10.0614232 C9.33166898,10.2971286 8.9024503,10.4149813 8.40610264,10.4149813 Z" id="Q" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>椭圆形@2x</title>
<defs>
<rect id="path-1" x="4" y="6" width="12" height="8"></rect>
</defs>
<g id="0520版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="简言--AI助手服务合规审查-勾选" transform="translate(-24.000000, -658.000000)">
<g id="编组-19" transform="translate(0.000000, 406.000000)">
<g id="✅已勾选" transform="translate(24.000000, 252.000000)">
<circle id="椭圆形" fill-opacity="0.6" fill="#29B6FD" cx="10" cy="10" r="10"></circle>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="蒙版"></g>
<path d="M7.88914675,11.3942068 L10.6598346,11.3339929 L10.6598346,11.3339929 L10.7917235,4.63800131 C10.8064567,3.8900021 11.4093004,3.28715833 12.1572996,3.27242521 C12.8683451,3.25841996 13.4561149,3.82348272 13.4701202,4.53452821 C13.4704531,4.55143241 13.4704531,4.56834153 13.4701202,4.58524572 L13.3213027,12.1406928 C13.3012849,13.1569982 12.48286,13.976432 11.4665801,13.997702 L7.8364141,14.0736786 C7.12626257,14.0885415 6.53852221,13.5248988 6.52365928,12.8147472 C6.52329488,12.7973362 6.52328415,12.7799195 6.5236271,12.762508 C6.53836859,12.014084 7.14075433,11.4104712 7.88914675,11.3942068 Z" id="矩形" fill="#FFFFFF" mask="url(#mask-2)" transform="translate(9.996874, 8.673877) rotate(45.000000) translate(-9.996874, -8.673877) "></path>
</g>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>未勾选@2x</title>
<g id="0520版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="简言--AI助手服务合规审查" transform="translate(-24.000000, -658.000000)" fill="#F4F4F4">
<g id="编组-19" transform="translate(0.000000, 406.000000)">
<g id="编组-3" transform="translate(24.000000, 252.000000)">
<circle id="未勾选" cx="10" cy="10" r="10"></circle>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>重新生成</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="0520版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="简言---纯文本" transform="translate(-183.000000, -546.000000)">
<g id="编组-4" transform="translate(15.000000, -228.000000)">
<g id="编组-12备份" transform="translate(0.000000, 524.000000)">
<g id="操作按钮" transform="translate(20.000000, 250.000000)">
<g id="编组-11" transform="translate(69.000000, 0.000000)">
<g id="重新生成" transform="translate(79.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份-7"></g>
<path d="M17.8228521,9.5 C18.9963941,15.2080307 14.0687747,17.198853 11.0061675,17.198853 L11.0061675,18.1870087 L11.0061675,18.1870087 C11.0061675,18.7392934 10.5584523,19.1870087 10.0061675,19.1870087 C9.81735588,19.1870087 9.63240197,19.1335546 9.47270106,19.0328299 L5.31700621,16.4117985 C4.84987205,16.1171731 4.71002608,15.499645 5.00465149,15.0325108 C5.08123263,14.91109 5.18305831,14.8075805 5.30320693,14.7290184 L9.45890178,12.0117171 C9.92114141,11.7094705 10.5408799,11.8391705 10.8431264,12.3014102 C10.9495101,12.4641077 11.0061675,12.6542846 11.0061675,12.8486759 L11.0061675,13.8374876 C14.7550171,14.0175008 17.0272453,12.5716716 17.8228521,9.5 Z M11.9938325,2.81299131 C12.1826441,2.81299131 12.367598,2.86644541 12.5272989,2.9671701 L16.6829938,5.58820151 C17.1501279,5.88282691 17.2899739,6.50035505 16.9953485,6.9674892 C16.9187674,7.08891004 16.8169417,7.19241953 16.6967931,7.2709816 L12.5410982,9.98828295 C12.0788586,10.2905295 11.4591201,10.1608295 11.1568736,9.69858982 C11.0504899,9.5358923 10.9938325,9.34571535 10.9938325,9.15132406 L10.9938325,8.16251237 C7.2449829,7.98249917 4.9727547,9.42832838 4.17714785,12.5 C3.00360594,6.79196935 7.93122535,4.80114698 10.9938325,4.80114698 L10.9938325,3.81299131 L10.9938325,3.81299131 C10.9938325,3.26070656 11.4415477,2.81299131 11.9938325,2.81299131 Z" id="形状结合" fill="#B2B8C1" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 5@2x</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="简言03.4.3" transform="translate(-74.000000, -546.000000)">
<g id="编组-4" transform="translate(15.000000, -228.000000)">
<g id="编组-12备份" transform="translate(0.000000, 524.000000)">
<g id="操作按钮" transform="translate(20.000000, 250.000000)">
<g id="踩" transform="translate(50.000000, 11.000000) scale(1, -1) translate(-50.000000, -11.000000) translate(39.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份-5"></g>
<path d="M17.9861958,9.41122355 C17.4920104,8.81248083 16.6847281,8.81248083 16.4190189,8.81248083 L14.2899599,8.81248083 C14.4625863,7.88147807 14.6098265,6.79868139 14.2933448,5.65685373 C14.0767156,4.8708985 13.6789979,4.31263416 13.0748055,3.95170193 C12.7583237,3.76280282 12.4299951,3.66666667 12.0965892,3.66666667 C11.2199179,3.66666667 10.5632606,4.31432076 10.4651005,5.2807421 C10.4278673,5.64167433 10.394019,5.98405397 10.3077058,6.27752223 C9.98614684,7.35525912 9.20086596,8.17831953 8.47481963,8.83777982 C8.20403312,9.08065011 7.91463004,9.5157927 7.91124521,10.0049065 C7.90109072,11.3137075 7.8993983,12.6275682 7.8993983,13.9701012 L7.89768589,16.3549525 C7.89432105,17.1527139 8.32080981,17.7953082 9.0316244,18.0702239 C9.40734068,18.2220178 9.80505836,18.3063477 10.2180078,18.3181539 C10.8746651,18.3265869 11.5347072,18.3265869 12.1524389,18.3265869 L13.1154234,18.3265869 C13.7450021,18.3265869 14.3745807,18.3265869 15.0075442,18.3333333 L15.0278532,18.3333333 C15.764054,18.3333333 16.3428602,17.9673413 16.6170315,17.3298068 L16.6982675,17.1425943 C16.8895104,16.7040785 17.0858307,16.2503833 17.2076846,15.7460902 C17.580016,14.2129715 17.9675792,12.5719105 18.2806761,10.9595216 C18.4059149,10.3203005 18.3077548,9.79914137 17.9861958,9.41122355 Z M5.59263572,8.85970561 L5.39123825,8.85970561 C4.44010064,8.85970561 3.66666667,9.63048145 3.66666667,10.5783502 L3.66666667,16.4544618 C3.66666667,17.4023306 4.44010064,18.1731064 5.39123825,18.1731064 L5.59263572,18.1731064 C6.54377334,18.1731064 7.31721007,17.4023306 7.31721007,16.4544618 L7.31721007,10.5783502 C7.31889972,9.63048145 6.54377334,8.85970561 5.59263572,8.85970561 Z" id="形状" fill="#B2B8C1" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 5@2x</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22" rx="7.33333333"></rect>
</defs>
<g id="0425版本" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.602618989">
<g id="简言03.4.2" transform="translate(-74.000000, -442.000000)">
<g id="编组-4" transform="translate(15.000000, -248.000000)">
<g id="编组-12备份" transform="translate(0.000000, 524.000000)">
<g id="操作按钮" transform="translate(20.000000, 166.000000)">
<g id="踩" transform="translate(50.000000, 11.000000) scale(1, -1) translate(-50.000000, -11.000000) translate(39.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形备份-5"></g>
<path d="M17.9861958,9.41122355 C17.4920104,8.81248083 16.6847281,8.81248083 16.4190189,8.81248083 L14.2899599,8.81248083 C14.4625863,7.88147807 14.6098265,6.79868139 14.2933448,5.65685373 C14.0767156,4.8708985 13.6789979,4.31263416 13.0748055,3.95170193 C12.7583237,3.76280282 12.4299951,3.66666667 12.0965892,3.66666667 C11.2199179,3.66666667 10.5632606,4.31432076 10.4651005,5.2807421 C10.4278673,5.64167433 10.394019,5.98405397 10.3077058,6.27752223 C9.98614684,7.35525912 9.20086596,8.17831953 8.47481963,8.83777982 C8.20403312,9.08065011 7.91463004,9.5157927 7.91124521,10.0049065 C7.90109072,11.3137075 7.8993983,12.6275682 7.8993983,13.9701012 L7.89768589,16.3549525 C7.89432105,17.1527139 8.32080981,17.7953082 9.0316244,18.0702239 C9.40734068,18.2220178 9.80505836,18.3063477 10.2180078,18.3181539 C10.8746651,18.3265869 11.5347072,18.3265869 12.1524389,18.3265869 L13.1154234,18.3265869 C13.7450021,18.3265869 14.3745807,18.3265869 15.0075442,18.3333333 L15.0278532,18.3333333 C15.764054,18.3333333 16.3428602,17.9673413 16.6170315,17.3298068 L16.6982675,17.1425943 C16.8895104,16.7040785 17.0858307,16.2503833 17.2076846,15.7460902 C17.580016,14.2129715 17.9675792,12.5719105 18.2806761,10.9595216 C18.4059149,10.3203005 18.3077548,9.79914137 17.9861958,9.41122355 Z M5.59263572,8.85970561 L5.39123825,8.85970561 C4.44010064,8.85970561 3.66666667,9.63048145 3.66666667,10.5783502 L3.66666667,16.4544618 C3.66666667,17.4023306 4.44010064,18.1731064 5.39123825,18.1731064 L5.59263572,18.1731064 C6.54377334,18.1731064 7.31721007,17.4023306 7.31721007,16.4544618 L7.31721007,10.5783502 C7.31889972,9.63048145 6.54377334,8.85970561 5.59263572,8.85970561 Z" id="形状" fill="#29B6FD" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
\ No newline at end of file
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AiAnswer: typeof import('./components/AiAnswer/index.vue')['default']
AiAnswerBox: typeof import('./components/AiAnswerBox/index.vue')['default']
AnswerLoading: typeof import('./components/AiAnswer/components/AnswerLoading/index.vue')['default']
AnswerParser: typeof import('./components/AiAnswer/components/AnswerParser/index.vue')['default']
AnswerShower: typeof import('./components/AiAnswer/components/AnswerShower/index.vue')['default']
AttachmentParser: typeof import('./components/AiAnswer/components/AttachmentParser/index.vue')['default']
BusinessDialog: typeof import('./components/BusinessDialog/index.vue')['default']
CollectionBtn: typeof import('./components/AiAnswer/components/OperateBar/components/CollectionBtn.vue')['default']
CopyBtn: typeof import('./components/AiAnswer/components/OperateBar/components/CopyBtn.vue')['default']
FixedBtoButton: typeof import('./components/FixedBtoButton/index.vue')['default']
GrantPopup: typeof import('./components/GrantPopup/index.vue')['default']
GuessULike: typeof import('./components/AiAnswer/components/GuessULike/index.vue')['default']
ImagesViewer: typeof import('./components/AiAnswer/components/ImagesViewer/index.vue')['default']
ItemBox: typeof import('./components/AiAnswer/components/AttachmentParser/components/ItemBox/index.vue')['default']
ItemCard: typeof import('./components/AiAnswer/components/AttachmentParser/components/ItemCard/index.vue')['default']
ItemProductDetail: typeof import('./components/AiAnswer/components/AttachmentParser/components/ItemProductDetail/index.vue')['default']
ItemReference: typeof import('./components/AiAnswer/components/AttachmentParser/components/ItemReference/index.vue')['default']
LikeBtn: typeof import('./components/AiAnswer/components/OperateBar/components/LikeBtn.vue')['default']
MindMap: typeof import('./components/MindMap/index.vue')['default']
OperateBar: typeof import('./components/AiAnswer/components/OperateBar/index.vue')['default']
ReloadBtn: typeof import('./components/AiAnswer/components/OperateBar/components/ReloadBtn.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SvgIcon: typeof import('./components/SvgIcon/index.vue')['default']
UnLike: typeof import('./components/AiAnswer/components/OperateBar/components/UnLike.vue')['default']
VanButton: typeof import('vant/es')['Button']
VanDialog: typeof import('vant/es')['Dialog']
VanField: typeof import('vant/es')['Field']
VanList: typeof import('vant/es')['List']
VanOverlay: typeof import('vant/es')['Overlay']
VanPopup: typeof import('vant/es')['Popup']
VanSkeleton: typeof import('vant/es')['Skeleton']
VanSpace: typeof import('vant/es')['Space']
VanSwipe: typeof import('vant/es')['Swipe']
VanSwipeItem: typeof import('vant/es')['SwipeItem']
VanWatermark: typeof import('vant/es')['Watermark']
Watermark: typeof import('./components/Watermark/index.vue')['default']
}
}
<template>
<!-- <SvgIcon size="29px" name="bouncingcircles" /> -->
<img class="answer-loading" src="@/assets/images/tetsloading.gif" alt="">
</template>
<style>
.answer-loading {
width: 20px !important;
height: 20px !important;
}
</style>
<script lang="ts" setup>
import ImagesViewer from '../ImagesViewer/index.vue'
import { fetchGetDocumentLinks } from '@/api/answer'
interface Props {
streamAnswer: string
isEnd: boolean
}
const props = defineProps<Props>()
const emits = defineEmits(['onTypingEnd', 'onSourceFileParserEnd'])
function generateUniqueID(length: number = 6): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
const charactersLength = characters.length
for (let i = 0; i < length; i++)
result += characters.charAt(Math.floor(Math.random() * charactersLength))
return result
}
const typedHtml = ref('')
const currentIndex = ref(0)
const isTyping = ref(false)
const timer = ref<any>()
const typingSpeed = 50 // Adjust as needed
function formatMarkdown(str: string) {
return str.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>').replace(/\n/g, '<br />')
}
function extractImageSources(htmlString: string): string[] {
const imgRegex = /<img[^>]+src="([^">]+)"/gi
const srcRegex = /src="([^">]+)"/i
const matches = htmlString.match(imgRegex)
const sources: string[] = []
if (matches) {
matches.forEach((match) => {
const srcMatch = match.match(srcRegex)
if (srcMatch && srcMatch[1])
sources.push(srcMatch[1])
})
}
return sources
}
function replaceImageSources(str: string, originalSrcs: string[], newSrcs: string[]): string {
if (originalSrcs.length !== newSrcs.length)
return str
return originalSrcs.reduce((acc, originalSrc, index) => {
const newSrc = newSrcs[index]
const regex = new RegExp(originalSrc, 'g')
return acc.replace(regex, newSrc)
}, str)
}
async function formatImgAnswer(str: string) {
const imagesSrc = extractImageSources(str)
const res = await fetchGetDocumentLinks(imagesSrc)
if (res.data) {
const arr = replaceImageSources(str, imagesSrc, res.data.map((item: any) => item.docUrl))
return arr
}
else { return replaceImageSources(str, imagesSrc, []) }
}
const isImgAnswer = ref(false)
const isMusicChar = ref(false)
const timern = ref<any>()
const isStop = ref(false)
function typeText() {
if (isStop.value)
return
const text = props.streamAnswer
if (currentIndex.value < text.length) {
const nextChar = text[currentIndex.value]
if (nextChar === '<') {
isImgAnswer.value = true
// 是图片,不继续打字,等接下来所有的内容拼接完成,并且之前的文字都打印完毕,然后直接展示
timer.value = setInterval(() => {
if (props.isEnd && isTyping.value) {
timern.value = setTimeout(async () => {
const res = await formatImgAnswer(props.streamAnswer)
typedHtml.value = res.split('♪')[0] || ''
bindImgEvent()
endTyping()
clearTimeout(timern.value)
}, 500)
clearInterval(timer.value)
}
}, 500)
}
if (nextChar === '♪' || isMusicChar.value) {
isMusicChar.value = true
if (isImgAnswer.value && !props.isEnd && isTyping.value) {
// 不做任何操作
}
else {
endTyping()
}
// splitIndex.value = currentIndex.value
// setTimeout(getSourceFileName, typingSpeed)
}
else {
currentIndex.value++
if (!isImgAnswer.value)
typedHtml.value = formatMarkdown(text.substring(0, currentIndex.value))
setTimeout(typeText, typingSpeed)
}
}
else {
if (!props.isEnd) {
setTimeout(typeText, typingSpeed)
}
else {
// 结束
endTyping()
}
}
}
const isShowImagesViewer = ref(false)
const imgsUrlList = ref<string[]>([])
const initialSwipeIdx = ref(0)
const classKey = ref('')
function bindImgEvent() {
nextTick(() => {
imgsUrlList.value = []
const imgDomList = [...document.querySelectorAll(`.event-${classKey.value} img`) as any]
imgDomList.forEach((img, idx) => {
imgsUrlList.value.push(img.src)
img.addEventListener('click', () => {
isShowImagesViewer.value = true
initialSwipeIdx.value = idx
})
})
})
}
function onClickStop() {
if (typedHtml.value === '')
typedHtml.value = '已停止回答'
isStop.value = true
isTyping.value = false
emits('onTypingEnd', { isStop: true })
clearInterval(timer.value)
clearTimeout(timern.value)
timer.value = null
timern.value = null
}
function endTyping() {
clearInterval(timer.value)
timer.value = null
isTyping.value = false
emits('onTypingEnd', { isStop: false })
clearInterval(timer.value)
clearTimeout(timern.value)
timer.value = null
timern.value = null
}
watch(() => props.streamAnswer, (_, oldVal) => {
if (oldVal.length === 0 && !isStop.value) {
typeText()
isTyping.value = true
}
}, {
deep: true,
})
defineExpose({
onClickStop,
})
onMounted(() => {
classKey.value = generateUniqueID()
})
</script>
<template>
<div class="answer-parser-wrap" :class="{ 'finished-typing': !isTyping }">
<ImagesViewer v-model:show="isShowImagesViewer" :initial-swipe="initialSwipeIdx" :imgs-url-list="imgsUrlList" />
<div class="answer-parser-content" :class="[`event-${classKey}`]" v-html="typedHtml" />
</div>
</template>
<style scoped lang="scss">
:deep(.answer-parser-content) {
img {
display: block !important;
width: 160px;
height: 160px;
object-fit: cover;
margin: 12px 0;
}
}
.answer-parser-wrap {
display: flex;
align-items: flex-end;
}
.answer-parser-content::after {
content: '';
right: 0;
bottom: 0;
width: 2px;
height: 14px;
background-color: black;
animation: cursor-blink-51baf6dc 1s infinite;
display: inline-block;
position: relative;
top: 2px;
margin-left: 2px;
}
.finished-typing .answer-parser-content::after {
display: none; /* 打字完成后隐藏光标 */
}
@keyframes cursor-blink {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>
<script lang="ts" setup>
const props = defineProps<Props>()
interface Props {
streamAnswer: string
}
const isShowImagesViewer = ref(false)
function formatMarkdown(str: string) {
return str.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>').replace(/\n/g, '<br />').split('♪')[0]
}
const initialSwipeIdx = ref(0)
function generateUniqueID(length: number = 6): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
const charactersLength = characters.length
for (let i = 0; i < length; i++)
result += characters.charAt(Math.floor(Math.random() * charactersLength))
return result
}
const imgsUrlList = ref<string[]>([])
const classKey = ref('')
function bindImgEvent() {
nextTick(() => {
imgsUrlList.value = []
const imgDomList = [...document.querySelectorAll(`.event-${classKey.value} img`) as any]
imgDomList.forEach((img, idx) => {
imgsUrlList.value.push(img.src)
img.addEventListener('click', () => {
isShowImagesViewer.value = true
initialSwipeIdx.value = idx
})
})
})
}
onMounted(() => {
// initAnswer()
classKey.value = generateUniqueID()
bindImgEvent()
})
</script>
<template>
<div class="answer-shower-wrap">
<div class="answer-shower-content" :class="[`event-${classKey}`]" v-html="formatMarkdown(props.streamAnswer || '已停止回答')" />
<ImagesViewer v-model:show="isShowImagesViewer" :initial-swipe="initialSwipeIdx" :imgs-url-list="imgsUrlList" />
</div>
</template>
<style lang="scss" scoped>
:deep(.answer-shower-content) {
img {
display: block !important;
width: 160px;
height: 160px;
object-fit: cover;
margin: 12px 0;
}
}
.answer-shower-wrap {
display: flex;
align-items: flex-end;
}
</style>
<!-- 产品列表 -->
<script lang="ts" setup>
import { AiAnswerProvideKey } from '../../../../type'
interface Props {
data: any
}
const props = defineProps<Props>()
const emits = defineEmits(['onClickBoxProductItem'])
const animated = inject(AiAnswerProvideKey)?.animated
const productCode = inject(AiAnswerProvideKey)?.productCode
const productList = computed(() => props.data?.content?.productList || [])
const activeCode = ref('')
function onClickItem(item: any) {
if (activeCode.value)
return
activeCode.value = item.productCode
emits('onClickBoxProductItem', item)
}
watch(() => productCode?.value, (val) => {
activeCode.value = val as string
}, {
immediate: true,
deep: true,
})
</script>
<template>
<div class="attachment-box-wrap" :class="{ 'animate__animated animate__fadeIn': animated }">
<div v-for="(item, index) in productList" :key="index" class="attachment-box-item" :class="{ active: activeCode === item.productCode }" @click="onClickItem(item)">
<span>{{ item.productName }}</span>
</div>
</div>
</template>
<style lang="scss" scoped>
.attachment-box-wrap {
// margin-top: 12px;
.attachment-box-item {
width: 305px;
// height: 32px;
background: #f6fcff;
border-radius: 16px;
display: flex;
align-items: center;
box-sizing: border-box;
padding: 6px 16px;
margin-bottom: 8px;
border: 1px solid #f6fcff;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #393d46;
line-height: 20px;
text-align: justify;
font-style: normal;
> span {
display: flex;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
&:last-child {
margin-bottom: 0;
}
&.active {
border: 1px solid rgba(41, 182, 253, 0.3);
background: #f6fcff;
color: #29b6fd;
}
}
}
</style>
<script lang="ts" setup>
import { AiAnswerProvideKey } from '../../../../type'
interface Props {
data: any
}
const props = defineProps<Props>()
const animated = inject(AiAnswerProvideKey)?.animated
function onRouteUrl() {
window.location.href = props.data?.url
}
</script>
<template>
<div class="attachment-card-wrap" :class="{ 'animate__animated animate__fadeIn': animated }">
<img v-if="props.data?.type === 'card-calculation'" class="item" src="@/assets/images/card-calculation.png" alt="" @click="onRouteUrl">
<img v-if="props.data?.type === 'card-nav'" class="item" src="@/assets/images/card-nav.png" alt="" @click="onRouteUrl">
<img v-if="props.data?.type === 'card-detail'" class="item" src="@/assets/images/card-detail.png" alt="" @click="onRouteUrl">
<img v-if="props.data?.type === 'card-product-compare'" class="item" src="@/assets/images/card-product2222.png" alt="" @click="onRouteUrl">
<img v-if="props.data?.type === 'card-plans'" class="item" src="@/assets/images/card-book1111.png" alt="" @click="onRouteUrl">
</div>
</template>
<style lang="scss" scoped>
.item {
// margin-top: 12px;
width: 100%;
// height: 92px;
}
</style>
<script lang="ts" setup>
import { AiAnswerProvideKey } from '../../../../type'
interface Props {
data: any
}
const props = defineProps<Props>()
const animated = inject(AiAnswerProvideKey)?.animated
</script>
<template>
<div class="attachment-product-detail-wrap" :class="{ 'animate__animated animate__fadeIn': animated }">
<div class="inner">
<img src="@/assets/images/a-product-detail-icon.png" alt="">
<span> {{ props.data.name }}</span>
</div>
</div>
</template>
<style lang="scss" scoped>
.attachment-product-detail-wrap {
// margin: 12px 0;
// margin-top: 12px;
.inner {
background: rgba(41, 182, 253, 0.04);
border-radius: 12px;
box-sizing: border-box;
padding: 4px 12px;
display: flex;
align-items: center;
width: fit-content;
margin: 0 !important;
img {
width: 14px;
height: 14px;
margin-right: 8px;
}
span {
font-size: 12px;
color: #6dd2fe;
line-height: 16px;
}
}
}
</style>
<!-- 参考文档 -->
<script lang="ts" setup>
import { AiAnswerProvideKey } from '../../../../type'
import { fetchGetDocumentLink } from '@/api/answer'
const props = defineProps<Props>()
interface Props {
data: any
}
const docList = computed(() => props.data?.content?.docList || [])
const animated = inject(AiAnswerProvideKey)?.animated
const isShowList = ref(true)
async function onClickDocItem(item: any) {
const res = await fetchGetDocumentLink(item.docId)
if (res.data)
window.location.href = res.data.docUrl
}
function toggleShow() {
isShowList.value = !isShowList.value
}
</script>
<template>
<div v-if="docList.length !== 0" class="attachment-reference-wrap">
<div v-if="docList.length !== 0" class="count" @click="toggleShow">
已为您找到{{ docList.length }}篇资料作为参考
<!-- <img :class="{ rotate: isShowList }" src="@/assets/images/downArrow.png" alt=""> -->
</div>
<div v-show="isShowList" class="doc-list" :class="{ 'animate__animated animate__fadeIn animate__fadeIn': animated }">
<div v-for="(item, index) in docList" :key="index" class="doc-item" @click="onClickDocItem(item)">
<img class="icon" src="@/assets/images/linkIcon.png" alt="">
<div class="text">
{{ item.docName }}
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.attachment-reference-wrap {
// margin-top: 12px;
width: 311px;
height: 72px;
background: #fff;
border-radius: 8px;
box-sizing: border-box;
padding: 4px 8px 8px 0;
.count {
padding-left: 12px;
width: fit-content;
border-radius: 12px;
box-sizing: border-box;
display: flex;
align-items: center;
// border: 1px solid rgba(178, 184, 193, 0.3);
font-size: 12px;
color: #8d9795;
line-height: 16px;
text-align: left;
font-style: normal;
> img {
width: 10px;
height: 6px;
margin-left: 4px;
transition: all 0.3s;
&.rotate {
transform: rotate(-180deg);
}
}
}
.doc-list::-webkit-scrollbar {
display: none;
}
.doc-list {
margin-top: 8px;
box-sizing: border-box;
// padding-left: 12px;
width: 100%;
overflow-y: hidden;
overflow-x: scroll;
display: flex;
.doc-item {
display: flex;
align-items: center;
width: 205px;
height: 32px;
background: #f8f8f8;
border-radius: 4px;
box-sizing: border-box;
padding: 8px 12px;
margin-right: 8px;
&:last-child {
margin-bottom: 0;
}
.icon {
width: 16px;
height: 16px;
margin-right: 6px;
}
.text {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 12px;
color: #27353c;
line-height: 16px;
text-align: justify;
font-style: normal;
overflow: hidden;
width: 250px;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
</style>
<script lang="ts" setup>
import ItemBox from './components/ItemBox/index.vue'
// import ItemReference from './components/ItemReference/index.vue'
import ItemCard from './components/ItemCard/index.vue'
import ItemProductDetail from './components/ItemProductDetail/index.vue'
interface Props {
streamAttachment: any[]
}
const props = defineProps<Props>()
const emits = defineEmits(['onClickBoxProductItem'])
function onClickBoxProductItem(item: any) {
emits('onClickBoxProductItem', item)
}
const isShow = computed(() => {
let show = false
props.streamAttachment?.forEach((item) => {
show = ['product-detail', 'box', 'reference'].includes(item.type || '') || item?.type?.includes('card-')
})
return show
})
</script>
<template>
<div v-if="isShow" class="attachment-parser-wrap">
<div class="attachment-content">
<van-space direction="vertical" fill size="8px">
<div v-for="(item, index) in props.streamAttachment" :key="index" class="attachment-item">
<ItemProductDetail v-if="item && item.type === 'product-detail'" :data="item" />
<ItemBox v-if="item && item.type === 'box'" :data="item" @on-click-box-product-item="onClickBoxProductItem" />
<ItemReference v-if="item && item.type === 'reference'" :data="item" />
<ItemCard v-if="item && item?.type?.includes('card-')" :data="item" />
</div>
</van-space>
</div>
</div>
</template>
<style lang="scss" scoped>
.attachment-parser-wrap {
margin-top: 12px;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.1s;
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 自定义显示时的渐显效果 */
.fade-enter-active {
transition: opacity 0.5s;
}
</style>
<script lang="ts" setup>
interface Props {
questionList: string[]
}
const props = defineProps<Props>()
const emits = defineEmits(['onClickLikeItem'])
function onOnClickLikeItem(question: string) {
emits('onClickLikeItem', question)
}
</script>
<template>
<div>
<div v-if="questionList.length > 0" class="guess-u-like animate__animated animate__fadeIn">
<div>你可继续问我:</div>
<div class="list">
<div v-for="(item, index) in props.questionList" :key="index" class="item ellipse" @click="onOnClickLikeItem(item)">
· {{ item }}
</div>
</div>
</div>
</div>
</template>
<style lang="scss" setup>
.guess-u-like {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 13px;
color: #8d9795;
line-height: 20px;
text-align: justify;
font-style: normal;
margin-top: 12px;
box-sizing: border-box;
padding-left: 12px;
.item {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 12px;
color: #29b6fd;
line-height: 18px;
text-align: justify;
font-style: normal;
margin-top: 8px;
}
}
</style>
<script lang="ts" setup>
interface Props {
show: boolean
imgsUrlList: string[]
initialSwipe: number
}
const props = defineProps<Props>()
const emits = defineEmits(['update:show'])
const currentIndex = ref(props.initialSwipe)
function onSwipeChange(idx: number) {
currentIndex.value = idx
}
watch(() => props.initialSwipe, (val) => {
currentIndex.value = val
}, {
immediate: true,
})
// function onSave() {
// useDownloadImage().downloadImg(props.imgsUrlList[currentIndex.value], `简言_${new Date().getTime()}`)
// showToast('下载完成')
// }
</script>
<template>
<van-overlay :show="props.show" teleport="body" class="van-popup-remove-bg" z-index="999999999">
<div class="wrap">
<div class="text-wrap">
长按保存相册
</div>
<div class="close-btn-wrap">
<img class="close-btn" src="@/assets/images/close-img.png" alt="" @click="emits('update:show', false)">
</div>
<div class="images-viewer-wrap">
<!-- :initial-swipe="props.initialSwipe" -->
<van-swipe :loop="false" :initial-swipe="props.initialSwipe" class="my-swipe" indicator-color="white" @change="onSwipeChange">
<van-swipe-item v-for="(item, index) in props.imgsUrlList" :key="index">
<div class="image-item-wrap">
<img :src="item" alt="">
</div>
</van-swipe-item>
</van-swipe>
</div>
</div>
<!-- <div class="save-btn" @click="onSave">
保存到相册
</div> -->
</van-overlay>
</template>
<style lang="scss" scoped>
.wrap {
width: 100%;
height: 100%;
// display: flex;
// flex-direction: column;
// align-items: center;
// justify-content: center;
position: relative;
box-sizing: border-box;
padding: 0 12px;
.text-wrap {
font-weight: 400;
font-size: 13px;
color: #ffffff;
line-height: 16px;
text-align: center;
font-style: normal;
margin-bottom: 18px;
margin-top: 40px;
}
.images-viewer-wrap {
margin-bottom: 24px;
}
.close-btn-wrap {
// position: absolute;
// right: 24px;
// top: 24px;
}
}
.my-swipe {
width: 100%;
height: 100%;
}
.image-item-wrap {
width: 100%;
height: calc(100vh - 100px);
overflow-y: scroll;
img {
max-width: 100%;
}
}
.image-item-wrap::-webkit-scrollbar {
display: none;
}
.close-btn-wrap {
width: 100%;
display: flex;
justify-content: flex-end;
padding-bottom: 12px;
padding-right: 8px;
box-sizing: border-box;
.close-btn {
width: 16px;
height: 16px;
}
}
.save-btn {
width: 160px !important;
height: 32px !important;
margin: 0 auto;
background: #29a794;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
width: 65px;
height: 18px;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 13px;
color: #ffffff;
line-height: 18px;
text-align: center;
font-style: normal;
margin-top: 16px;
}
</style>
<style lang="scss" scoped>
.van-swipe-item {
height: auto !important;
}
</style>
<!--
<style>
.van-popup-remove-bg {
background-color: rgba(255, 255, 255, 0) !important;
width: 100% !important;
margin: 0 !important;
width: 100vw;
height: 100vh;
}
</style> -->
<script lang="ts" setup>
import { useDebounceFn } from '@vueuse/core'
import { showToast } from 'vant'
import { fetchCancelCollection, fetchSubmitCollection } from '@/api/answer'
import { AiAnswerProvideKey } from '@/components/AiAnswer/type'
const props = withDefaults(defineProps<Props>(), {
recordId: '',
})
interface Props {
recordId: string
}
const isCollectionned = ref(false)
const collectionFlag = inject(AiAnswerProvideKey)?.collectionFlag
const onAddCollection = useDebounceFn(async () => {
const res = await fetchSubmitCollection(props.recordId)
if (res) {
isCollectionned.value = true
showToast('收藏成功')
}
}, 300)
const onDelCollection = useDebounceFn(async () => {
const res = await fetchCancelCollection(props.recordId)
if (res) {
isCollectionned.value = false
showToast('取消收藏成功')
}
}, 300)
watch(() => collectionFlag?.value, (val) => {
isCollectionned.value = val as boolean
}, {
immediate: true,
deep: true,
})
</script>
<template>
<div class="collection-btn-wrap">
<SvgIcon v-if="!isCollectionned" size="22px" name="collectionD" @click="onAddCollection" />
<SvgIcon v-else size="22px" name="collection" @click="onDelCollection" />
</div>
</template>
<script lang="ts" setup>
import { useClipboard } from '@vueuse/core'
import { showToast } from 'vant'
interface Props {
streamAnswer: string
}
const props = defineProps<Props>()
function removeImgTags(input: string): string {
return input.replace(/<img[^>]*\/>/g, '')
}
const { copy, isSupported } = useClipboard()
function onCopy() {
const copyText = `${removeImgTags(props.streamAnswer.split('♪')[0])}`
if (!isSupported) {
showToast('当前浏览器不支持复制')
return
}
copy(copyText)
showToast('复制成功')
}
</script>
<template>
<div class="copy-btn">
<SvgIcon size="22px" name="copyIcon" @click="onCopy" />
</div>
</template>
<script lang="ts" setup>
import { useDebounceFn } from '@vueuse/core'
import { showToast } from 'vant'
import { fetchSubmitFeedback } from '@/api/answer'
import { AiAnswerProvideKey } from '@/components/AiAnswer/type'
interface Props {
recordId: string
}
const props = defineProps<Props>()
const emits = defineEmits(['onAddLike', 'onDelLike'])
const isLike = ref(false)
const likeFlag = inject(AiAnswerProvideKey)?.likeFlag
// onAddLike 防抖处理
const onAddLike = useDebounceFn(async () => {
const res = await fetchSubmitFeedback({
recordId: props.recordId,
feedbackStatus: '01',
content: '',
})
if (res) {
showToast('感谢您的反馈')
isLike.value = true
emits('onAddLike')
}
}, 300)
function toggleFalse() {
isLike.value = false
}
// onDelLike 防抖处理
const onDelLike = useDebounceFn(async () => {
const res = await fetchSubmitFeedback({
recordId: props.recordId,
feedbackStatus: '00',
content: '',
})
if (res) {
isLike.value = false
emits('onDelLike')
showToast('已取消反馈')
}
}, 300)
defineExpose({
toggleFalse,
})
watch(() => likeFlag?.value, (val) => {
isLike.value = val as boolean
}, {
immediate: true,
deep: true,
})
</script>
<template>
<div class="like-btn-wrap">
<SvgIcon v-if="!isLike" size="22px" name="likeG" @click="onAddLike" />
<SvgIcon v-else size="22px" name="likeL" @click="onDelLike" />
</div>
</template>
<script lang="ts" setup>
import { useDebounceFn } from '@vueuse/core'
const emits = defineEmits(['onClickReload'])
const onClickReload = useDebounceFn(async () => {
emits('onClickReload')
}, 300)
</script>
<template>
<div class="reload-btn">
<SvgIcon size="22px" name="reloadIcon" @click="onClickReload" />
</div>
</template>
<script lang="ts" setup>
import { closeToast, showLoadingToast, showToast } from 'vant'
import { AiAnswerProvideKey } from '@/components/AiAnswer/type'
import { fetchGetFeedbackConfig, fetchSubmitFeedback } from '@/api/answer'
interface Props {
recordId: string
}
const props = defineProps<Props>()
const emits = defineEmits(['onAddUnLike', 'onDelUnLike'])
const isUnLike = ref(false)
const isShowPopup = ref(false)
const unLikeFlag = inject(AiAnswerProvideKey)?.unLikeFlag
const formData = ref<any>({})
async function onAddUnLike() {
const res = await fetchGetFeedbackConfig()
if (res.data) {
const objData = JSON.parse(res.data.content)
formData.value = objData
}
isShowPopup.value = true
}
function onClickOptionItem(item: any) {
item.answerValue = item.answerValue === '1' ? '0' : '1'
}
function toggleFalse() {
isUnLike.value = false
}
const disabledBtn = computed(() => formData.value.questionList[0].answerList.every((item: any) => item.answerValue === '0') && formData.value.questionList[1].answerValue.trim() === '')
async function onSubmit() {
if (disabledBtn.value)
return
const params = {
recordId: props.recordId,
feedbackStatus: '02',
content: JSON.stringify(formData.value),
}
showLoadingToast({
message: '提交中...',
duration: 0,
})
const res = await fetchSubmitFeedback(params)
if (res) {
closeToast()
showToast('感谢您的反馈')
isShowPopup.value = false
isUnLike.value = true
isShowPopup.value = false
emits('onAddUnLike')
}
}
async function onDelUnLike() {
const res = await fetchSubmitFeedback({
recordId: props.recordId,
feedbackStatus: '00',
content: '',
})
if (res) {
isUnLike.value = false
emits('onDelUnLike')
showToast('已取消反馈')
}
}
watch(() => unLikeFlag?.value, (val) => {
isUnLike.value = val as boolean
}, {
immediate: true,
deep: true,
})
defineExpose({
toggleFalse,
})
</script>
<template>
<div class="unlike-btn-wrap">
<SvgIcon v-if="!isUnLike" size="22px" name="unlikeG" @click="onAddUnLike" />
<SvgIcon v-else size="22px" name="unlikeL" @click="onDelUnLike" />
<van-popup
v-model:show="isShowPopup"
teleport="body"
style="height: 500px;"
round
position="bottom"
>
<div class="model-desc-pop-inner">
<img class="close" src="@/assets/images/close-b.png" alt="" @click="isShowPopup = false">
<div class="title">
{{ formData.title }}
</div>
<div class="content">
<div v-if="formData?.questionList[0].questionCode === '1001'" class="options-wrap">
<div v-for="item in formData.questionList[0].answerList" :key="item.answerCode" class="options-item" :class="{ active: item.answerValue === '1' }" @click="onClickOptionItem(item)">
{{ item.answerName }}
</div>
</div>
<div v-if="formData?.questionList[1].questionCode === '1099'" class="ipt-wrap">
<SvgIcon style="transform: translateY(2px);" size="18px" name="edit" />
<van-field
v-model="formData.questionList[1].answerValue"
style="background-color: #f6f7f9; padding: 0; margin-left: 8px;"
rows="2"
type="textarea"
autosize
:maxlength="100"
placeholder="留下更多反馈"
/>
</div>
</div>
<div class="footer-wrap">
<div class="submit-btn" :class="{ disabled: disabledBtn }" @click="onSubmit">
提交
</div>
</div>
</div>
</van-popup>
</div>
</template>
<style lang="scss" scoped>
.footer-wrap {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
.submit-btn {
&.disabled {
opacity: 0.5;
}
width: 280px;
height: 44px;
background: #29b6fd;
border-radius: 22px;
font-family:
PingFangSC,
PingFang SC;
font-weight: 500;
font-size: 15px;
color: #ffffff;
line-height: 22px;
display: flex;
justify-content: center;
align-items: center;
}
}
.content {
flex: 1;
box-sizing: border-box;
padding: 0 20px;
overflow-x: hidden;
overflow-y: scroll;
margin-top: 8px;
padding-bottom: 24px;
}
.ipt-wrap {
width: 335px;
background: #f6f7f9;
border-radius: 4px;
display: flex;
font-size: 14px;
box-sizing: border-box;
padding: 14px;
margin-top: 24px;
}
.options-wrap {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 8px;
.options-item {
width: 164px;
height: 32px;
background: #f6f7f9;
border-radius: 18px;
font-family: AlibabaPuHuiTi_2_55_Regular;
font-size: 14px;
color: #586462;
line-height: 18px;
text-align: center;
font-style: normal;
display: flex;
align-items: center;
justify-content: center;
margin-top: 16px;
box-sizing: border-box;
&.active {
color: #29b6fd;
background: #f6fcff;
border-radius: 18px;
border: 1px solid rgba(41, 182, 253, 0.3);
}
}
}
.model-desc-pop-inner {
width: 375px;
height: 100%;
background-size: 375px 148px;
background-repeat: no-repeat;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
padding-top: 48px;
position: relative;
.close {
position: absolute;
width: 16px;
height: 16px;
top: 16px;
right: 16px;
}
.title {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-family:
PingFangSC,
PingFang SC;
font-weight: 500;
font-size: 16px;
color: #333333;
line-height: 22px;
text-align: center;
font-style: normal;
position: relative;
}
}
</style>
<script lang="ts" setup>
import { AiAnswerProvideKey } from '../../type'
import CollectionBtn from './components/CollectionBtn.vue'
import LikeBtn from './components/LikeBtn.vue'
import UnLikeBtn from './components/UnLike.vue'
import CopyBtn from './components/CopyBtn.vue'
import ReloadBtn from './components/ReloadBtn.vue'
const props = withDefaults(defineProps<Props>(), {
recordId: '',
streamAnswer: '',
isLastAnswer: false,
})
const emits = defineEmits(['onClickReload'])
interface Props {
recordId: string
streamAnswer: string
isLastAnswer: boolean
}
const likeBtnRef = ref()
const unlikeBtnRef = ref()
const animated = inject(AiAnswerProvideKey)?.animated
function onAddUnLike() {
likeBtnRef.value.toggleFalse()
}
function onAddLike() {
unlikeBtnRef.value.toggleFalse()
}
function onClickReload() {
emits('onClickReload')
}
</script>
<template>
<div class="opetare-bar-wrap" :class="{ 'animate__animated animate__fadeIn': animated }">
<LikeBtn ref="likeBtnRef" :record-id="recordId" @on-add-like="onAddLike" />
<span class="line">|</span>
<UnLikeBtn ref="unlikeBtnRef" :record-id="recordId" @on-add-un-like="onAddUnLike" />
<span class="line">|</span>
<CopyBtn :stream-answer="props.streamAnswer" />
<span class="line">|</span>
<CollectionBtn :record-id="props.recordId" />
<span v-if="props.isLastAnswer" class="line">|</span>
<ReloadBtn v-if="props.isLastAnswer" :record-id="props.recordId" @on-click-reload="onClickReload" />
</div>
</template>
<style lang="scss" scoped>
.opetare-bar-wrap {
display: flex;
align-items: center;
margin-top: 8px;
.line {
font-size: 12px;
color: rgba(233, 235, 241, 1);
transform: translateY(-1px);
margin: 0 8px;
}
.collection-btn {
width: 22px;
height: 22px;
}
}
</style>
interface AiAnswerProvideKeyType {
animated: boolean
collectionFlag?: Ref<boolean>
likeFlag?: Ref<boolean>
unLikeFlag?: Ref<boolean>
isShow?: boolean
productCode?: Ref<string>
}
export const AiAnswerProvideKey: InjectionKey<AiAnswerProvideKeyType> = Symbol('AiAnswerProvideKeyType')
<script lang="ts" setup>
import { showToast } from 'vant'
interface Props {
answerList: any[]
showIndex: number
isLastAnswer: boolean
}
const props = defineProps<Props>()
const emits = defineEmits([
'onAnswerConcatEnd', // Answer的stream拼接完成
'onAttachmentConcatEnd', // Attachment的stream拼接完成
'onTypingEnd', // Answer打字机输出结束
'onClickBoxProductItem', // 点击产品列表中的产品
'onClickLikeItem', // 点击了猜你想问
'onClickStop', // 点击停止生成
'onClickReload', // 点击重新生成
'onLastAnswer', // 上一条答案
'onNextAnswer', // 下一条答案
'onFullEnd', // 所有内容输出完成
])
const curIdx = ref(0)
// const curAnswer = computed(() => props.answerList[curIdx.value] || {})
function getCurIdx() {
return curIdx.value
}
function setCurIdx(idx: number) {
curIdx.value = idx
}
function onFullEnd() {
emits('onFullEnd', {
data: null,
curAnswerIdx: curIdx.value,
})
}
function onClickLikeItem(data: any) {
emits('onClickLikeItem', data)
}
function onClickBoxProductItem(data: any) {
emits('onClickBoxProductItem', data)
}
function onClickStop() {
emits('onClickStop', {
data: props.answerList[curIdx.value],
curAnswerIdx: curIdx.value,
})
}
function onClickReload() {
const appConfig = JSON.parse(window.localStorage.getItem('__APP_CONFIG__') || '')
if (props.answerList.length === (appConfig?.repeatGenerateAnswerNum)) {
showToast(`当前最多支持 ${appConfig?.repeatGenerateAnswerNum} 次回答`)
return
}
const params = {
data: props.answerList[curIdx.value],
curAnswerIdx: curIdx.value,
}
emits('onClickReload', params)
}
function onLastAnswer() {
if (curIdx.value === 0)
return
const idx = curIdx.value
curIdx.value--
emits('onLastAnswer', {
data: props.answerList[curIdx.value],
curAnswerIdx: idx,
afterAnswerIdx: curIdx.value,
})
}
function onNextAnswer() {
if (curIdx.value === props.answerList.length - 1)
return
const idx = curIdx.value
curIdx.value++
emits('onLastAnswer', {
data: null,
curAnswerIdx: idx,
afterAnswerIdx: curIdx.value,
})
}
watch(() => props.showIndex, (val) => {
setCurIdx(val || 0)
}, {
immediate: true,
})
defineExpose({
getCurIdx,
setCurIdx,
})
</script>
<template>
<div class="ai-answer-box">
<div v-for="(item, index) in props.answerList" :key="index" class="ai-answer-item-wrap">
<AiAnswer
v-show="index === curIdx"
:conversation-id="item.conversationId"
:record-id="item.recordId"
:stream-data="item.streamData"
:is-show="item.isShow"
:is-last-answer="props.isLastAnswer"
:answer-list="props.answerList"
:cur-idx="index"
@on-full-end="onFullEnd"
@on-click-like-item="onClickLikeItem"
@on-click-box-product-item="onClickBoxProductItem"
@on-click-stop="onClickStop"
@on-click-reload="onClickReload"
@on-next-answer="onNextAnswer"
@on-last-answer="onLastAnswer"
/>
</div>
<!-- <div v-if="props.answerList.length !== 1" class="pagination-wrap">
<SvgIcon :style="{ opacity: curIdx === 0 ? 0.3 : 1 }" size="10px" name="arrowGreen" @click="onLastAnswer" />
<span>{{ curIdx + 1 }}/{{ props.answerList.length }}</span>
<SvgIcon :style="{ opacity: curIdx === props.answerList.length - 1 ? 0.3 : 1 }" style="transform: rotate(180deg);" size="10px" name="arrowGreen" @click="onNextAnswer" />
</div> -->
</div>
</template>
<style lang="scss" scoped>
.ai-answer-box {
position: relative;
}
.pagination-wrap {
font-family: alir, AlibabaPuHuiTi_2_55_Regular;
font-size: 12px;
color: rgba(57, 61, 70, 0.7);
line-height: 16px;
text-align: left;
font-style: normal;
position: absolute;
right: 12px;
bottom: 22px;
display: flex;
align-items: center;
> span {
margin: 0 6px;
}
}
</style>
<script lang="ts" setup>
import DefaultIcon from '@/images/vip/pay-success.png'
interface Props {
modelValue: boolean
zIndex?: number
primaryButtonText?: string
defaultButtonText?: string
desc?: string
icon?: string
showIcon?: boolean
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
zIndex: 999,
primaryButtonText: '确定',
defaultButtonText: '取消',
desc: 'ok了',
icon: DefaultIcon,
showIcon: true,
})
const emits = defineEmits(['update:modelValue', 'clickPrimaryButton', 'clickDefaultButton'])
</script>
<template>
<van-overlay :z-index="zIndex" :show="props.modelValue">
<div class="wrapper" @click.stop>
<div class="block">
<img v-if="props.showIcon" class="icon" :src="props.icon" alt="">
<div class="title">
{{ desc }}
</div>
<div class="btn-wrap">
<button class="default" @click="() => emits('clickDefaultButton')">
{{ defaultButtonText }}
</button>
<button class="primary" @click="() => emits('clickPrimaryButton')">
{{ primaryButtonText }}
</button>
</div>
</div>
</div>
</van-overlay>
</template>
<style lang="scss" scoped>
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.block {
width: 280px;
height: 175px;
background: #ffffff;
border-radius: 24px;
position: relative;
box-sizing: border-box;
padding: 0 24px;
.icon {
width: 72px;
height: 72px;
position: absolute;
left: 50%;
margin-left: -36px;
top: -27px;
}
.title {
width: 100%;
text-align: center;
font-family:
PingFangSC,
PingFang SC;
font-weight: 600;
font-size: 16px;
color: #0e2120;
line-height: 22px;
margin-top: 61px;
}
.btn-wrap {
margin-top: 32px;
display: flex;
align-items: center;
.default {
bottom: none;
width: 110px;
height: 36px;
border-radius: 22px;
border: 1px solid #d5d9d8;
font-family:
alir,
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #768180;
border-radius: 18px;
text-align: center;
font-style: normal;
margin: 0 8px;
background: #ffffff;
}
.primary {
border: none;
width: 110px;
height: 36px;
background: #16a39a;
border-radius: 18px;
font-family:
alir,
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #ffffff;
line-height: 19px;
text-align: center;
font-style: normal;
margin: 0 8px;
}
}
}
</style>
<script lang="ts" setup>
/**
* @description 固定底部按钮props
* @param {boolean} showPrimaryButton 是否显示主按钮
* @param {boolean} showDefaultButton 是否显示默认按钮
* @param {string} primaryButtonText 主按钮文案
* @param {string} defaultButtonText 默认按钮文案
*/
interface Props {
showPrimaryButton?: boolean
showDefaultButton?: boolean
showBorder?: boolean
primaryButtonText?: string
defaultButtonText?: string
}
const props = withDefaults(defineProps<Props>(), {
showBorder: false,
showPrimaryButton: true,
showDefaultButton: true,
primaryButtonText: '确定',
defaultButtonText: '取消',
})
/**
* @description 固定底部按钮emits
* @event clickPrimaryButton 点击主按钮
* @event clickDefaultButton 点击默认按钮
*/
const emits = defineEmits(['clickPrimaryButton', 'clickDefaultButton'])
</script>
<template>
<div class="fixed-bto-button" :style="{ 'border-top': showBorder ? '1px solid rgba(234, 235, 234, 0.5)' : 'none' }">
<div v-if="showDefaultButton" class="default" @click="() => emits('clickDefaultButton')">
{{ props.defaultButtonText }}
</div>
<div v-if="showPrimaryButton" class="primary" @click="() => emits('clickPrimaryButton')">
{{ props.primaryButtonText }}
</div>
</div>
</template>
<style lang="scss" scoped>
.fixed-bto-button {
width: 375px;
// height: 60px;
background: #ffffff;
box-shadow: 0px 0px 0px 0px rgba(224, 224, 224, 0.6);
position: fixed;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 18px 15px;
z-index: 99;
> div {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
&:active {
opacity: 0.9;
}
}
.default {
width: 165px;
height: 44px;
border-radius: 22px;
border: 1px solid #16a39a;
font-family:
alir,
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 16px;
color: #16a39a;
line-height: 22px;
text-align: center;
font-style: normal;
margin: 0 8px;
}
.primary {
width: 165px;
height: 44px;
background: #16a39a;
border-radius: 22px;
font-family:
alir,
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 16px;
color: #ffffff;
line-height: 19px;
text-align: center;
font-style: normal;
margin: 0 8px;
}
}
</style>
<script lang="ts" setup>
import { fetchCommonOperate } from '@/api/user'
const props = defineProps<{
grantFlag: boolean
agreementList: any[]
}>()
const isShowPopup = ref(false)
const checked = ref(false)
async function onSubmit() {
if (!checked.value)
return
const res = await fetchCommonOperate('01')
if (res) {
isShowPopup.value = false
window.localStorage.setItem('grantFlag', 'true')
}
}
function onHref(url: string) {
window.location.href = url
}
onMounted(async () => {
if (!props.grantFlag)
isShowPopup.value = true
})
</script>
<template>
<div class="grant-popup-wrap">
<van-popup
v-model:show="isShowPopup"
teleport="body"
round
:close-on-click-overlay="false"
position="bottom"
>
<div class="model-desc-pop-inner">
<!-- <img class="close" src="@/assets/images/close-b.png" alt="" @click="isShowPopup = false"> -->
<div class="title">
欢迎您使用晓得AI助手
</div>
<div class="content">
为帮助您更好了解晓得AI 助手服务内容,保障您的合法权益,请您认真阅读《晓得AI助手服务协议》《晓得AI助手隐私政策》,特别是其中有关使用限制、免责声明、个人信息保护等内容。您需在仔细阅读并确认同意相关协议后方可使用本服务。
</div>
<div class="radio-wrap">
<div @click="checked = !checked">
<SvgIcon v-show="checked" size="20px" name="radioChecked" />
<SvgIcon v-show="!checked" size="20px" name="radioNoCheck" />
</div>
<div>
我已阅读并同意
<span v-for="(item, index) in props.agreementList" :key="index" @click="onHref(item.value)">{{ item.key }}</span>
</div>
</div>
<div class="footer-wrap">
<div class="submit-btn" :class="{ disabled: !checked }" @click="onSubmit">
同意
</div>
</div>
</div>
</van-popup>
</div>
</template>
<style lang="scss" scoped>
.radio-wrap {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 13px;
color: #8d9795;
line-height: 20px;
box-sizing: border-box;
padding: 0 24px;
display: flex;
align-items: flex-start;
margin: 8px 0;
> div {
&:first-child {
margin-right: 8px;
}
}
span {
color: #158ad3;
}
}
.footer-wrap {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
.submit-btn {
&.disabled {
opacity: 0.5;
}
width: 280px;
height: 44px;
background: #29b6fd;
border-radius: 22px;
font-family:
PingFangSC,
PingFang SC;
font-weight: 500;
font-size: 15px;
color: #ffffff;
line-height: 22px;
display: flex;
justify-content: center;
align-items: center;
}
}
.model-desc-pop-inner {
width: 100%;
height: 100%;
background-size: 375px 148px;
background-repeat: no-repeat;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
padding-top: 38px;
position: relative;
.content {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 15px;
color: #393d46;
line-height: 22px;
text-align: justify;
font-style: normal;
box-sizing: border-box;
padding: 24px;
}
.close {
position: absolute;
width: 16px;
height: 16px;
top: 16px;
right: 16px;
}
.title {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-family:
PingFangSC,
PingFang SC;
font-weight: 500;
font-size: 16px;
line-height: 22px;
text-align: center;
font-style: normal;
position: relative;
font-family:
PingFangSC,
PingFang SC;
font-weight: 600;
font-size: 18px;
color: #222222;
line-height: 24px;
text-align: center;
font-style: normal;
}
}
</style>
/** 脑图theme */
export const mindTheme = {
name: 'Latte',
palette: [
'#4968a3',
'#3b88c4',
'#4fa3d4',
'#2b5b84',
'#367fa2',
'#5e93b7',
'#4a719c',
'#28567d',
'#214e6d',
'#336699',
],
cssVar: {
'--root-bgcolor': '',
'--root-border-color': '',
'--main-color': '#444446',
'--main-bgcolor': '#ffffff',
'--color': '#777777',
'--bgcolor': '#f6f6f6',
'--panel-color': '#444446',
'--panel-bgcolor': '#ffffff',
'--panel-border-color': '#eaeaea',
},
}
/**
* 生成基于时间戳的ID
*/
export function generateTimestampId(): string {
const timestamp = new Date().getTime()
const random = Math.floor(Math.random() * 10000)
return `${timestamp}_${random}`
}
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps({
// 在src/assets/svg目录下svg图标的名字
name: {
type: String,
required: true,
},
// 前缀,和vite.config.ts中配置的名称一致
prefix: {
type: String,
default: 'icon',
},
// 图标的颜色
color: {
type: String,
default: '#262626',
},
// 图标的大小
size: {
type: String,
default: '16px',
},
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>
<template>
<svg
aria-hidden="true"
:color="color"
:width="size"
:height="size"
>
<use
:xlink:href="symbolId"
/>
</svg>
</template>
<style scope>
.svg-icon {
width: 18px;
height: 18px;
color: #333;
fill: currentcolor;
}
</style>
<script lang="ts" setup>
const props = defineProps<{
userName: string
}>()
</script>
<template>
<van-watermark
:width="120"
:gap-x="12"
:gap-y="30"
:opacity="0.1"
:full-page="true"
>
<template #content>
<span
:style="{
fontSize: '12px',
color: '#00A0F2',
lineHeight: '16px',
textAlign: 'left',
fontStyle: 'normal',
}" class="watermark-label"
>{{ `-- 晓得 --` }}</span>
<!-- ${props.userName} -->
</template>
</van-watermark>
</template>
/**
* 微信小程序 App ID
*/
export const WX_APP_ID = 'wx66f5666df9f9007c'
/**
* App 模式 - h5
*/
export const APP_TYPE_H5 = '01'
/**
* App 模式 - 公众号
*/
export const APP_TYPE_OFFICIAL = '02'
/**
* App 模式 - 小程序
*/
export const APP_TYPE_MINI = '03'
/**
* 医生类型 普通
*/
export const DOCTOR_TYPE_CODE_NORMAL = '01'
export const DONT_NEED_WECHAT_AUTH_PATHS = ['/demo']
/**
* @description 订单状态 待支付
*/
export const ORDER_STATUS_WAIT_PAY = '00'
/**
* @description 订单状态 待联系客服
*/
export const ORDER_STATUS_WAIT_CONTACT = '02'
/**
* @description 订单状态 咨询中
*/
export const ORDER_STATUS_SAF = '03'
/**
* @description 订单状态 已完成
*/
export const ORDER_STATUS_COMPLETED = '04'
/**
* @description 订单状态 已关闭
*/
export const ORDER_STATUS_CLOSED = '05'
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment