mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	fix: 采用弹窗的方式显示验证码,解决验证码在低分辨率下被掩盖的Bug
This commit is contained in:
		@@ -70,7 +70,6 @@ func (h *UserHandler) Register(c *gin.Context) {
 | 
			
		||||
		var code int
 | 
			
		||||
		err := h.leveldb.Get(key, &code)
 | 
			
		||||
		if err != nil || code != data.Code {
 | 
			
		||||
			logger.Info(code)
 | 
			
		||||
			resp.ERROR(c, "短信验证码错误")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,11 @@ ALTER TABLE `chatgpt_chat_models`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
 (1, 'OpenAI', 'Bot GPT-3.5', 'gpt-3.5-turbo', 0, 1, '2023-08-23 12:06:36', '2023-09-02 16:49:36'),
 | 
			
		||||
 (2, 'Azure', 'Bot Azure-3.5', 'gpt-3.5-turbo', 0, 1, '2023-08-23 12:15:30', '2023-09-02 16:49:46'),
 | 
			
		||||
 (3, 'ChatGML', 'ChatGML-Pro', 'chatglm_pro', 3, 1, '2023-08-23 13:35:45', '2023-08-29 11:41:29'),
 | 
			
		||||
 (5, 'ChatGML', 'ChatGLM-Std', 'chatglm_std', 2, 1, '2023-08-24 15:05:38', '2023-08-29 11:41:28'),
 | 
			
		||||
 (6, 'ChatGML', 'ChatGLM-Lite', 'chatglm_lite', 4, 1, '2023-08-24 15:06:15', '2023-08-29 11:41:29');
 | 
			
		||||
 (1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo', 0, 1, '2023-08-23 12:06:36', '2023-09-02 16:49:36'),
 | 
			
		||||
 (2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 0, 1, '2023-08-23 12:15:30', '2023-09-02 16:49:46'),
 | 
			
		||||
 (3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 3, 1, '2023-08-23 13:35:45', '2023-08-29 11:41:29'),
 | 
			
		||||
 (5, 'ChatGLM', 'ChatGLM-Std', 'chatglm_std', 2, 1, '2023-08-24 15:05:38', '2023-08-29 11:41:28'),
 | 
			
		||||
 (6, 'ChatGLM', 'ChatGLM-Lite', 'chatglm_lite', 4, 1, '2023-08-24 15:06:15', '2023-08-29 11:41:29');
 | 
			
		||||
 | 
			
		||||
ALTER TABLE `chatgpt_users`
 | 
			
		||||
DROP `username`,
 | 
			
		||||
 
 | 
			
		||||
@@ -12,15 +12,15 @@ npm run build
 | 
			
		||||
cd ../docker
 | 
			
		||||
 | 
			
		||||
# remove docker image if exists
 | 
			
		||||
docker rmi -f registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-go:$version
 | 
			
		||||
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version
 | 
			
		||||
# build docker image for chatgpt-plus-go
 | 
			
		||||
docker build -t registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-go:$version -f dockerfile-api-go ../
 | 
			
		||||
docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version -f dockerfile-api-go ../
 | 
			
		||||
 | 
			
		||||
# build docker image for chatgpt-plus-vue
 | 
			
		||||
docker rmi -f registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version
 | 
			
		||||
docker build --platform linux/amd64 -t registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version -f dockerfile-vue ../
 | 
			
		||||
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version
 | 
			
		||||
docker build --platform linux/amd64 -t registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version -f dockerfile-vue ../
 | 
			
		||||
 | 
			
		||||
if [ "$2" = "push" ];then
 | 
			
		||||
  docker push registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version
 | 
			
		||||
  docker push registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-go:$version
 | 
			
		||||
  docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version
 | 
			
		||||
  docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version
 | 
			
		||||
fi
 | 
			
		||||
@@ -2,7 +2,7 @@ version: '3'
 | 
			
		||||
services:
 | 
			
		||||
  # 后端 API 程序
 | 
			
		||||
  chatgpt-plus-api:
 | 
			
		||||
    image: registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.0.7.4
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.1.0
 | 
			
		||||
    container_name: chatgpt-plus-api
 | 
			
		||||
    restart: always
 | 
			
		||||
    environment:
 | 
			
		||||
@@ -18,9 +18,9 @@ services:
 | 
			
		||||
      - ./static:/var/www/app/static
 | 
			
		||||
 | 
			
		||||
  # 前端应用
 | 
			
		||||
  chatgpt-plus-vue:
 | 
			
		||||
    image: registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:v3.0.7.4
 | 
			
		||||
    container_name: chatgpt-plus-vue
 | 
			
		||||
  chatgpt-plus-web:
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v3.1.0
 | 
			
		||||
    container_name: chatgpt-plus-web
 | 
			
		||||
    restart: always
 | 
			
		||||
    ports:
 | 
			
		||||
      - "8080:8080"
 | 
			
		||||
 
 | 
			
		||||
@@ -4,5 +4,5 @@ VUE_APP_USER=18575670125
 | 
			
		||||
VUE_APP_PASS=12345678
 | 
			
		||||
VUE_APP_ADMIN_USER=admin
 | 
			
		||||
VUE_APP_ADMIN_PASS=admin123
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
 | 
			
		||||
VUE_APP_TITLE="ChatGPT-PLUS V3"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,257 +1,265 @@
 | 
			
		||||
<template>
 | 
			
		||||
 | 
			
		||||
    <div class="wg-cap-wrap" :style="{width: width}">
 | 
			
		||||
      <div class="wg-cap-wrap__header">
 | 
			
		||||
        <span>请在下图<em>依次</em>点击:</span>
 | 
			
		||||
        <img class="wg-cap-wrap__thumb" v-if="thumbBase64Code" :src="thumbBase64Code" alt=" ">
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="wg-cap-wrap__body">
 | 
			
		||||
        <img class="wg-cap-wrap__picture" v-if="imageBase64Code" :src="imageBase64Code" alt=" " @click="handleClickPos($event)">
 | 
			
		||||
        <img class="wg-cap-wrap__loading" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiByZ2JhKDI0MSwgMjQyLCAyNDMsIDApOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjM2LjgxMDEiIHI9IjEzIiBmaWxsPSIjM2U3Y2ZmIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN5IiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgY2FsY01vZGU9InNwbGluZSIga2V5U3BsaW5lcz0iMC40NSAwIDAuOSAwLjU1OzAgMC40NSAwLjU1IDAuOSIga2V5VGltZXM9IjA7MC41OzEiIHZhbHVlcz0iMjM7Nzc7MjMiPjwvYW5pbWF0ZT4KICA8L2NpcmNsZT4KPC9zdmc+" alt="正在加载中...">
 | 
			
		||||
        <div v-for="(dot, key) in dots" :key="key" class="wg-cap-wrap__dot" :style="`top: ${dot.y}px; left:${dot.x}px;`">
 | 
			
		||||
          <span>{{ dot.index }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="wg-cap-wrap__footer">
 | 
			
		||||
        <div class="wg-cap-wrap__ico">
 | 
			
		||||
          <img @click="handleCloseEvent"
 | 
			
		||||
               src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDM5NDIzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijg2NzUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDIzLjI3MjcyN2MyNjkuOTE3MDkxIDAgNDg4LjcyNzI3MyAyMTguODEwMTgyIDQ4OC43MjcyNzMgNDg4LjcyNzI3M2E0ODYuNjMyNzI3IDQ4Ni42MzI3MjcgMCAwIDEtODQuOTQ1NDU1IDI3NS40MDk0NTUgNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDAgMS03Ni44NDY1NDUtNTIuNTI2NTQ2QTM5My41NDE4MTggMzkzLjU0MTgxOCAwIDAgMCA5MDcuNjM2MzY0IDUxMmMwLTIxOC41MDc2MzYtMTc3LjEyODcyNy0zOTUuNjM2MzY0LTM5NS42MzYzNjQtMzk1LjYzNjM2NFMxMTYuMzYzNjM2IDI5My40OTIzNjQgMTE2LjM2MzYzNiA1MTJzMTc3LjEyODcyNyAzOTUuNjM2MzY0IDM5NS42MzYzNjQgMzk1LjYzNjM2NGEzOTUuMTcwOTA5IDM5NS4xNzA5MDkgMCAwIDAgMTI1LjQ0LTIwLjI5MzgxOSA0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMCAxIDI5LjQ4NjU0NSA4OC4yOTY3MjhBNDg4LjI2MTgxOCA0ODguMjYxODE4IDAgMCAxIDUxMiAxMDAwLjcyNzI3M0MyNDIuMDgyOTA5IDEwMDAuNzI3MjczIDIzLjI3MjcyNyA3ODEuOTE3MDkxIDIzLjI3MjcyNyA1MTJTMjQyLjA4MjkwOSAyMy4yNzI3MjcgNTEyIDIzLjI3MjcyN3ogbS0xMTUuMiAzMDcuNzEyTDUxMiA0NDYuMTM4MTgybDExNS4yLTExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEgNjUuODE1MjczIDY1Ljg2MTgxOEw1NzcuODYxODE4IDUxMmwxMTUuMiAxMTUuMmE0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMSAxLTY1Ljg2MTgxOCA2NS44MTUyNzNMNTEyIDU3Ny44NjE4MThsLTExNS4yIDExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEtNjUuODE1MjczLTY1Ljg2MTgxOEw0NDYuMTM4MTgyIDUxMmwtMTE1LjItMTE1LjJhNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDEgMSA2NS44NjE4MTgtNjUuODE1MjczeiIgcC1pZD0iODY3NiIgZmlsbD0iIzcwNzA3MCI+PC9wYXRoPjwvc3ZnPg=="
 | 
			
		||||
               alt="关闭">
 | 
			
		||||
          <img @click="handleRefreshEvent"
 | 
			
		||||
               src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDk5NjM4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEzNjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTg3LjQ1NiA0MjUuMDI0YTMzNiAzMzYgMCAwIDAgMzY4LjM4NCA0MjAuMjI0IDQ4IDQ4IDAgMCAxIDEyLjU0NCA5NS4xNjggNDMyIDQzMiAwIDAgMS00NzMuNjY0LTU0MC4xNmwtNTcuMjgtMTUuMzZhMTIuOCAxMi44IDAgMCAxLTYuMjcyLTIwLjkyOGwxNTkuMTY4LTE3OS40NTZhMTIuOCAxMi44IDAgMCAxIDIyLjE0NCA1Ljg4OGw0OC4wNjQgMjM1LjA3MmExMi44IDEyLjggMCAwIDEtMTUuODA4IDE0LjkxMmwtNTcuMjgtMTUuMzZ6TTgzNi40OCA1OTkuMDRhMzM2IDMzNiAwIDAgMC0zNjguMzg0LTQyMC4yMjQgNDggNDggMCAxIDEtMTIuNTQ0LTk1LjE2OCA0MzIgNDMyIDAgMCAxIDQ3My42NjQgNTQwLjE2bDU3LjI4IDE1LjM2YTEyLjggMTIuOCAwIDAgMSA2LjI3MiAyMC45MjhsLTE1OS4xNjggMTc5LjQ1NmExMi44IDEyLjggMCAwIDEtMjIuMTQ0LTUuODg4bC00OC4wNjQtMjM1LjA3MmExMi44IDEyLjggMCAwIDEgMTUuODA4LTE0LjkxMmw1Ny4yOCAxNS4zNnoiIGZpbGw9IiM3MDcwNzAiIHAtaWQ9IjEzNjEiPjwvcGF0aD48L3N2Zz4="
 | 
			
		||||
               alt="刷新">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="wg-cap-wrap__btn">
 | 
			
		||||
          <button @click="handleConfirmEvent">确认</button>
 | 
			
		||||
        </div>
 | 
			
		||||
  <div class="wg-cap-wrap" :style="{width: width}">
 | 
			
		||||
    <div class="wg-cap-wrap__header">
 | 
			
		||||
      <span>请在下图<em>依次</em>点击:</span>
 | 
			
		||||
      <img class="wg-cap-wrap__thumb" v-if="thumbBase64Code" :src="thumbBase64Code" alt=" ">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="wg-cap-wrap__body">
 | 
			
		||||
      <img class="wg-cap-wrap__picture" v-if="imageBase64Code" :src="imageBase64Code" alt=" "
 | 
			
		||||
           @click="handleClickPos($event)">
 | 
			
		||||
      <img class="wg-cap-wrap__loading"
 | 
			
		||||
           src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiByZ2JhKDI0MSwgMjQyLCAyNDMsIDApOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjM2LjgxMDEiIHI9IjEzIiBmaWxsPSIjM2U3Y2ZmIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN5IiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgY2FsY01vZGU9InNwbGluZSIga2V5U3BsaW5lcz0iMC40NSAwIDAuOSAwLjU1OzAgMC40NSAwLjU1IDAuOSIga2V5VGltZXM9IjA7MC41OzEiIHZhbHVlcz0iMjM7Nzc7MjMiPjwvYW5pbWF0ZT4KICA8L2NpcmNsZT4KPC9zdmc+"
 | 
			
		||||
           alt="正在加载中...">
 | 
			
		||||
      <div v-for="(dot, key) in dots" :key="key" class="wg-cap-wrap__dot" :style="`top: ${dot.y}px; left:${dot.x}px;`">
 | 
			
		||||
        <span>{{ dot.index }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="wg-cap-wrap__footer">
 | 
			
		||||
      <div class="wg-cap-wrap__ico">
 | 
			
		||||
        <img @click="handleCloseEvent"
 | 
			
		||||
             src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDM5NDIzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijg2NzUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDIzLjI3MjcyN2MyNjkuOTE3MDkxIDAgNDg4LjcyNzI3MyAyMTguODEwMTgyIDQ4OC43MjcyNzMgNDg4LjcyNzI3M2E0ODYuNjMyNzI3IDQ4Ni42MzI3MjcgMCAwIDEtODQuOTQ1NDU1IDI3NS40MDk0NTUgNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDAgMS03Ni44NDY1NDUtNTIuNTI2NTQ2QTM5My41NDE4MTggMzkzLjU0MTgxOCAwIDAgMCA5MDcuNjM2MzY0IDUxMmMwLTIxOC41MDc2MzYtMTc3LjEyODcyNy0zOTUuNjM2MzY0LTM5NS42MzYzNjQtMzk1LjYzNjM2NFMxMTYuMzYzNjM2IDI5My40OTIzNjQgMTE2LjM2MzYzNiA1MTJzMTc3LjEyODcyNyAzOTUuNjM2MzY0IDM5NS42MzYzNjQgMzk1LjYzNjM2NGEzOTUuMTcwOTA5IDM5NS4xNzA5MDkgMCAwIDAgMTI1LjQ0LTIwLjI5MzgxOSA0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMCAxIDI5LjQ4NjU0NSA4OC4yOTY3MjhBNDg4LjI2MTgxOCA0ODguMjYxODE4IDAgMCAxIDUxMiAxMDAwLjcyNzI3M0MyNDIuMDgyOTA5IDEwMDAuNzI3MjczIDIzLjI3MjcyNyA3ODEuOTE3MDkxIDIzLjI3MjcyNyA1MTJTMjQyLjA4MjkwOSAyMy4yNzI3MjcgNTEyIDIzLjI3MjcyN3ogbS0xMTUuMiAzMDcuNzEyTDUxMiA0NDYuMTM4MTgybDExNS4yLTExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEgNjUuODE1MjczIDY1Ljg2MTgxOEw1NzcuODYxODE4IDUxMmwxMTUuMiAxMTUuMmE0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMSAxLTY1Ljg2MTgxOCA2NS44MTUyNzNMNTEyIDU3Ny44NjE4MThsLTExNS4yIDExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEtNjUuODE1MjczLTY1Ljg2MTgxOEw0NDYuMTM4MTgyIDUxMmwtMTE1LjItMTE1LjJhNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDEgMSA2NS44NjE4MTgtNjUuODE1MjczeiIgcC1pZD0iODY3NiIgZmlsbD0iIzcwNzA3MCI+PC9wYXRoPjwvc3ZnPg=="
 | 
			
		||||
             alt="关闭">
 | 
			
		||||
        <img @click="handleRefreshEvent"
 | 
			
		||||
             src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDk5NjM4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEzNjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTg3LjQ1NiA0MjUuMDI0YTMzNiAzMzYgMCAwIDAgMzY4LjM4NCA0MjAuMjI0IDQ4IDQ4IDAgMCAxIDEyLjU0NCA5NS4xNjggNDMyIDQzMiAwIDAgMS00NzMuNjY0LTU0MC4xNmwtNTcuMjgtMTUuMzZhMTIuOCAxMi44IDAgMCAxLTYuMjcyLTIwLjkyOGwxNTkuMTY4LTE3OS40NTZhMTIuOCAxMi44IDAgMCAxIDIyLjE0NCA1Ljg4OGw0OC4wNjQgMjM1LjA3MmExMi44IDEyLjggMCAwIDEtMTUuODA4IDE0LjkxMmwtNTcuMjgtMTUuMzZ6TTgzNi40OCA1OTkuMDRhMzM2IDMzNiAwIDAgMC0zNjguMzg0LTQyMC4yMjQgNDggNDggMCAxIDEtMTIuNTQ0LTk1LjE2OCA0MzIgNDMyIDAgMCAxIDQ3My42NjQgNTQwLjE2bDU3LjI4IDE1LjM2YTEyLjggMTIuOCAwIDAgMSA2LjI3MiAyMC45MjhsLTE1OS4xNjggMTc5LjQ1NmExMi44IDEyLjggMCAwIDEtMjIuMTQ0LTUuODg4bC00OC4wNjQtMjM1LjA3MmExMi44IDEyLjggMCAwIDEgMTUuODA4LTE0LjkxMmw1Ny4yOCAxNS4zNnoiIGZpbGw9IiM3MDcwNzAiIHAtaWQ9IjEzNjEiPjwvcGF0aD48L3N2Zz4="
 | 
			
		||||
             alt="刷新">
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="wg-cap-wrap__btn">
 | 
			
		||||
        <el-button type="primary" @click="handleConfirmEvent">确认</el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  export default {
 | 
			
		||||
    name: 'CaptchaPlus',
 | 
			
		||||
    mounted() {
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'CaptchaPlus',
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.$emit('refresh')
 | 
			
		||||
  },
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Boolean,
 | 
			
		||||
    width: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '300px'
 | 
			
		||||
    },
 | 
			
		||||
    calcPosType: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: 'dom',
 | 
			
		||||
      validator: value => ['dom', 'screen'].includes(value)
 | 
			
		||||
    },
 | 
			
		||||
    maxDot: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 5
 | 
			
		||||
      // validator: value => value > 10
 | 
			
		||||
    },
 | 
			
		||||
    imageBase64: String,
 | 
			
		||||
    thumbBase64: String
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      dots: [],
 | 
			
		||||
      imageBase64Code: '',
 | 
			
		||||
      thumbBase64Code: ''
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    value() {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.imageBase64Code = ''
 | 
			
		||||
      this.thumbBase64Code = ''
 | 
			
		||||
    },
 | 
			
		||||
    imageBase64(val) {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.imageBase64Code = val
 | 
			
		||||
    },
 | 
			
		||||
    thumbBase64(val) {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.thumbBase64Code = val
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 处理关闭事件
 | 
			
		||||
     */
 | 
			
		||||
    handleCloseEvent() {
 | 
			
		||||
      this.$emit('close')
 | 
			
		||||
      // this.dots = []
 | 
			
		||||
      // this.imageBase64Code = ''
 | 
			
		||||
      // this.thumbBase64Code = ''
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 处理刷新事件
 | 
			
		||||
     */
 | 
			
		||||
    handleRefreshEvent() {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.$emit('refresh')
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
      value: Boolean,
 | 
			
		||||
      width: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '300px'
 | 
			
		||||
      },
 | 
			
		||||
      calcPosType: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: 'dom',
 | 
			
		||||
        validator: value => ['dom', 'screen'].includes(value)
 | 
			
		||||
      },
 | 
			
		||||
      maxDot: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        default: 5
 | 
			
		||||
        // validator: value => value > 10
 | 
			
		||||
      },
 | 
			
		||||
      imageBase64: String,
 | 
			
		||||
      thumbBase64: String
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 处理确认事件
 | 
			
		||||
     */
 | 
			
		||||
    handleConfirmEvent() {
 | 
			
		||||
      this.$emit('confirm', this.dots)
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 处理dot
 | 
			
		||||
     * @param ev
 | 
			
		||||
     */
 | 
			
		||||
    handleClickPos(ev) {
 | 
			
		||||
      if (this.dots.length >= this.maxDot) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      const e = ev || window.event
 | 
			
		||||
      e.preventDefault()
 | 
			
		||||
      const dom = e.currentTarget
 | 
			
		||||
 | 
			
		||||
      const {domX, domY} = this.getDomXY(dom)
 | 
			
		||||
      // ===============================================
 | 
			
		||||
      // @notice 如 getDomXY 不准确可尝试使用 calcLocationLeft 或 calcLocationTop
 | 
			
		||||
      // const domX = this.calcLocationLeft(dom)
 | 
			
		||||
      // const domY = this.calcLocationTop(dom)
 | 
			
		||||
      // ===============================================
 | 
			
		||||
      let mouseX = (navigator.vendor === 'Netscape') ? e.pageX : e.x + document.body.offsetTop
 | 
			
		||||
      let mouseY = (navigator.vendor === 'Netscape') ? e.pageY : e.y + document.body.offsetTop
 | 
			
		||||
 | 
			
		||||
      if (this.calcPosType === 'screen') {
 | 
			
		||||
        mouseX = (navigator.vendor === 'Netscape') ? e.clientX : e.x
 | 
			
		||||
        mouseY = (navigator.vendor === 'Netscape') ? e.clientY : e.y
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 计算点击的相对位置
 | 
			
		||||
      const xPos = mouseX - domX
 | 
			
		||||
      const yPos = mouseY - domY
 | 
			
		||||
 | 
			
		||||
      // 转整形
 | 
			
		||||
      const xp = parseInt(xPos.toString())
 | 
			
		||||
      const yp = parseInt(yPos.toString())
 | 
			
		||||
 | 
			
		||||
      // 减去点的一半
 | 
			
		||||
      this.dots.push({
 | 
			
		||||
        x: xp - 11,
 | 
			
		||||
        y: yp - 11,
 | 
			
		||||
        index: this.dots.length + 1
 | 
			
		||||
      })
 | 
			
		||||
      return false
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 找到元素的屏幕位置
 | 
			
		||||
     * @param el
 | 
			
		||||
     */
 | 
			
		||||
    calcLocationLeft(el) {
 | 
			
		||||
      let tmp = el.offsetLeft
 | 
			
		||||
      let val = el.offsetParent
 | 
			
		||||
      while (val != null) {
 | 
			
		||||
        tmp += val.offsetLeft
 | 
			
		||||
        val = val.offsetParent
 | 
			
		||||
      }
 | 
			
		||||
      return tmp
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 找到元素的屏幕位置
 | 
			
		||||
     * @param el
 | 
			
		||||
     */
 | 
			
		||||
    calcLocationTop(el) {
 | 
			
		||||
      let tmp = el.offsetTop
 | 
			
		||||
      let val = el.offsetParent
 | 
			
		||||
      while (val != null) {
 | 
			
		||||
        tmp += val.offsetTop
 | 
			
		||||
        val = val.offsetParent
 | 
			
		||||
      }
 | 
			
		||||
      return tmp
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 找到元素的屏幕位置
 | 
			
		||||
     * @param dom
 | 
			
		||||
     */
 | 
			
		||||
    getDomXY(dom) {
 | 
			
		||||
      let x = 0
 | 
			
		||||
      let y = 0
 | 
			
		||||
      if (dom.getBoundingClientRect) {
 | 
			
		||||
        let box = dom.getBoundingClientRect();
 | 
			
		||||
        let D = document.documentElement;
 | 
			
		||||
        x = box.left + Math.max(D.scrollLeft, document.body.scrollLeft) - D.clientLeft;
 | 
			
		||||
        y = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop
 | 
			
		||||
      } else {
 | 
			
		||||
        while (dom !== document.body) {
 | 
			
		||||
          x += dom.offsetLeft
 | 
			
		||||
          y += dom.offsetTop
 | 
			
		||||
          dom = dom.offsetParent
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        dots: [],
 | 
			
		||||
        imageBase64Code: '',
 | 
			
		||||
        thumbBase64Code: ''
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
      value() {
 | 
			
		||||
        this.dots = []
 | 
			
		||||
        this.imageBase64Code = ''
 | 
			
		||||
        this.thumbBase64Code = ''
 | 
			
		||||
      },
 | 
			
		||||
      imageBase64(val) {
 | 
			
		||||
        this.dots = []
 | 
			
		||||
        this.imageBase64Code = val
 | 
			
		||||
      },
 | 
			
		||||
      thumbBase64(val) {
 | 
			
		||||
        this.dots = []
 | 
			
		||||
        this.thumbBase64Code = val
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      /**
 | 
			
		||||
       * @Description: 处理关闭事件
 | 
			
		||||
       */
 | 
			
		||||
      handleCloseEvent() {
 | 
			
		||||
        this.$emit('close')
 | 
			
		||||
        // this.dots = []
 | 
			
		||||
        // this.imageBase64Code = ''
 | 
			
		||||
        // this.thumbBase64Code = ''
 | 
			
		||||
      },
 | 
			
		||||
      /**
 | 
			
		||||
       * @Description: 处理刷新事件
 | 
			
		||||
       */
 | 
			
		||||
      handleRefreshEvent() {
 | 
			
		||||
        this.dots = []
 | 
			
		||||
        this.$emit('refresh')
 | 
			
		||||
      },
 | 
			
		||||
      /**
 | 
			
		||||
       * @Description: 处理确认事件
 | 
			
		||||
       */
 | 
			
		||||
      handleConfirmEvent() {
 | 
			
		||||
        this.$emit('confirm', this.dots)
 | 
			
		||||
      },
 | 
			
		||||
      /**
 | 
			
		||||
       * @Description: 处理dot
 | 
			
		||||
       * @param ev
 | 
			
		||||
       */
 | 
			
		||||
      handleClickPos(ev) {
 | 
			
		||||
        if (this.dots.length >= this.maxDot) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        const e = ev || window.event
 | 
			
		||||
        e.preventDefault()
 | 
			
		||||
        const dom = e.currentTarget
 | 
			
		||||
 | 
			
		||||
        const {domX, domY} = this.getDomXY(dom)
 | 
			
		||||
        // ===============================================
 | 
			
		||||
        // @notice 如 getDomXY 不准确可尝试使用 calcLocationLeft 或 calcLocationTop
 | 
			
		||||
        // const domX = this.calcLocationLeft(dom)
 | 
			
		||||
        // const domY = this.calcLocationTop(dom)
 | 
			
		||||
        // ===============================================
 | 
			
		||||
        let mouseX = (navigator.vendor === 'Netscape') ? e.pageX : e.x + document.body.offsetTop
 | 
			
		||||
        let mouseY = (navigator.vendor === 'Netscape') ? e.pageY : e.y + document.body.offsetTop
 | 
			
		||||
 | 
			
		||||
        if (this.calcPosType === 'screen') {
 | 
			
		||||
          mouseX = (navigator.vendor === 'Netscape') ? e.clientX : e.x
 | 
			
		||||
          mouseY = (navigator.vendor === 'Netscape') ? e.clientY : e.y
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 计算点击的相对位置
 | 
			
		||||
        const xPos = mouseX - domX
 | 
			
		||||
        const yPos = mouseY - domY
 | 
			
		||||
 | 
			
		||||
        // 转整形
 | 
			
		||||
        const xp = parseInt(xPos.toString())
 | 
			
		||||
        const yp = parseInt(yPos.toString())
 | 
			
		||||
 | 
			
		||||
        // 减去点的一半
 | 
			
		||||
        this.dots.push({
 | 
			
		||||
          x: xp - 11,
 | 
			
		||||
          y: yp - 11,
 | 
			
		||||
          index: this.dots.length + 1
 | 
			
		||||
        })
 | 
			
		||||
        return false
 | 
			
		||||
      },
 | 
			
		||||
      /**
 | 
			
		||||
       * @Description: 找到元素的屏幕位置
 | 
			
		||||
       * @param el
 | 
			
		||||
       */
 | 
			
		||||
      calcLocationLeft(el) {
 | 
			
		||||
        let tmp = el.offsetLeft
 | 
			
		||||
        let val = el.offsetParent
 | 
			
		||||
        while (val != null) {
 | 
			
		||||
          tmp += val.offsetLeft
 | 
			
		||||
          val = val.offsetParent
 | 
			
		||||
        }
 | 
			
		||||
        return tmp
 | 
			
		||||
      },
 | 
			
		||||
      /**
 | 
			
		||||
       * @Description: 找到元素的屏幕位置
 | 
			
		||||
       * @param el
 | 
			
		||||
       */
 | 
			
		||||
      calcLocationTop(el) {
 | 
			
		||||
        let tmp = el.offsetTop
 | 
			
		||||
        let val = el.offsetParent
 | 
			
		||||
        while (val != null) {
 | 
			
		||||
          tmp += val.offsetTop
 | 
			
		||||
          val = val.offsetParent
 | 
			
		||||
        }
 | 
			
		||||
        return tmp
 | 
			
		||||
      },
 | 
			
		||||
      /**
 | 
			
		||||
       * @Description: 找到元素的屏幕位置
 | 
			
		||||
       * @param dom
 | 
			
		||||
       */
 | 
			
		||||
      getDomXY(dom){
 | 
			
		||||
        let x = 0
 | 
			
		||||
        let y = 0
 | 
			
		||||
        if (dom.getBoundingClientRect) {
 | 
			
		||||
          let box = dom.getBoundingClientRect();
 | 
			
		||||
          let D = document.documentElement;
 | 
			
		||||
          x = box.left + Math.max(D.scrollLeft, document.body.scrollLeft) - D.clientLeft;
 | 
			
		||||
          y = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop
 | 
			
		||||
        }
 | 
			
		||||
        else{
 | 
			
		||||
          while (dom !== document.body) {
 | 
			
		||||
            x += dom.offsetLeft
 | 
			
		||||
            y += dom.offsetTop
 | 
			
		||||
            dom = dom.offsetParent
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
          domX: x,
 | 
			
		||||
          domY: y
 | 
			
		||||
        }
 | 
			
		||||
        domX: x,
 | 
			
		||||
        domY: y
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
  .wg-cap-wrap{
 | 
			
		||||
    background: #ffffff;
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
.wg-cap-wrap {
 | 
			
		||||
  background: #ffffff;
 | 
			
		||||
 | 
			
		||||
    -webkit-border-radius: 10px;
 | 
			
		||||
    -moz-border-radius: 10px;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
  -webkit-border-radius: 10px;
 | 
			
		||||
  -moz-border-radius: 10px;
 | 
			
		||||
  border-radius: 10px;
 | 
			
		||||
 | 
			
		||||
    -webkit-touch-callout: none;
 | 
			
		||||
    -webkit-user-select: none;
 | 
			
		||||
    -moz-user-select: none;
 | 
			
		||||
    -ms-user-select: none;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__header{
 | 
			
		||||
  -webkit-touch-callout: none;
 | 
			
		||||
  -webkit-user-select: none;
 | 
			
		||||
  -moz-user-select: none;
 | 
			
		||||
  -ms-user-select: none;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
 | 
			
		||||
  .wg-cap-wrap__header {
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-size: 15px;
 | 
			
		||||
 | 
			
		||||
    display:-webkit-box;
 | 
			
		||||
    display:-webkit-flex;
 | 
			
		||||
    display:-ms-flexbox;
 | 
			
		||||
    display:flex;
 | 
			
		||||
    -webkit-box-align:center;
 | 
			
		||||
    -webkit-align-items:center;
 | 
			
		||||
    -ms-flex-align:center;
 | 
			
		||||
    display: -webkit-box;
 | 
			
		||||
    display: -webkit-flex;
 | 
			
		||||
    display: -ms-flexbox;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    -webkit-box-align: center;
 | 
			
		||||
    -webkit-align-items: center;
 | 
			
		||||
    -ms-flex-align: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__header span{
 | 
			
		||||
    padding-right: 5px;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__header span em{
 | 
			
		||||
    padding: 0 3px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    color: #3e7cff;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__header .wg-cap-wrap__image{
 | 
			
		||||
    -webkit-border-radius: 5px;
 | 
			
		||||
    -moz-border-radius: 5px;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__header .wg-cap-wrap__thumb{
 | 
			
		||||
    min-width: 150px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    max-height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__header .wg-cap-wrap__thumb.wg-cap-wrap__hidden{
 | 
			
		||||
    display: none;
 | 
			
		||||
 | 
			
		||||
    span {
 | 
			
		||||
      padding-right: 5px;
 | 
			
		||||
 | 
			
		||||
      em {
 | 
			
		||||
        padding: 0 3px;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        color: #3e7cff;
 | 
			
		||||
        font-style: normal;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__image {
 | 
			
		||||
      -webkit-border-radius: 5px;
 | 
			
		||||
      -moz-border-radius: 5px;
 | 
			
		||||
      border-radius: 5px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      line-height: 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__thumb {
 | 
			
		||||
      min-width: 150px;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      line-height: 1;
 | 
			
		||||
      max-height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__thumb.wg-cap-wrap__hidden {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .wg-cap-wrap__body{
 | 
			
		||||
  .wg-cap-wrap__body {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: -webkit-box;
 | 
			
		||||
    display: -moz-box;
 | 
			
		||||
@@ -264,106 +272,78 @@
 | 
			
		||||
    -moz-border-radius: 5px;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__body .wg-cap-wrap__picture{
 | 
			
		||||
    position: relative;
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
 | 
			
		||||
    /*height: 100%;*/
 | 
			
		||||
    /*max-width: 100%;*/
 | 
			
		||||
    /*max-height: 100%;*/
 | 
			
		||||
    /*object-fit: cover;*/
 | 
			
		||||
    /*text-align: center;*/
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__body .wg-cap-wrap__picture.wg-cap-wrap__hidden{
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__body .wg-cap-wrap__loading{
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    z-index: 9;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    width: 68px;
 | 
			
		||||
    height: 68px;
 | 
			
		||||
    margin-left: -34px;
 | 
			
		||||
    margin-top: -34px;
 | 
			
		||||
    line-height: 68px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__body .wg-cap-wrap__dot{
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
    width: 22px;
 | 
			
		||||
    height: 22px;
 | 
			
		||||
    color: #cedffe;
 | 
			
		||||
    background: #3e7cff;
 | 
			
		||||
    border: 2px solid #f7f9fb;
 | 
			
		||||
    line-height: 20px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    -webkit-border-radius: 22px;
 | 
			
		||||
    -moz-border-radius: 22px;
 | 
			
		||||
    border-radius: 22px;
 | 
			
		||||
    cursor: default;
 | 
			
		||||
    .wg-cap-wrap__picture {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      z-index: 10;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__picture.wg-cap-wrap__hidden {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__loading {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      z-index: 9;
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      left: 50%;
 | 
			
		||||
      width: 68px;
 | 
			
		||||
      height: 68px;
 | 
			
		||||
      margin-left: -34px;
 | 
			
		||||
      margin-top: -34px;
 | 
			
		||||
      line-height: 68px;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__dot {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      z-index: 10;
 | 
			
		||||
      width: 22px;
 | 
			
		||||
      height: 22px;
 | 
			
		||||
      color: #cedffe;
 | 
			
		||||
      background: #3e7cff;
 | 
			
		||||
      border: 2px solid #f7f9fb;
 | 
			
		||||
      line-height: 20px;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      -webkit-border-radius: 22px;
 | 
			
		||||
      -moz-border-radius: 22px;
 | 
			
		||||
      border-radius: 22px;
 | 
			
		||||
      cursor: default;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .wg-cap-wrap__footer {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 40px;
 | 
			
		||||
    color: #34383e;
 | 
			
		||||
    display:-webkit-box;
 | 
			
		||||
    display:-webkit-flex;
 | 
			
		||||
    display:-ms-flexbox;
 | 
			
		||||
    display:flex;
 | 
			
		||||
    -webkit-box-align:center;
 | 
			
		||||
    -webkit-align-items:center;
 | 
			
		||||
    -ms-flex-align:center;
 | 
			
		||||
    display: -webkit-box;
 | 
			
		||||
    display: -webkit-flex;
 | 
			
		||||
    display: -ms-flexbox;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    -webkit-box-align: center;
 | 
			
		||||
    -webkit-align-items: center;
 | 
			
		||||
    -ms-flex-align: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding-top: 15px;
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__ico {
 | 
			
		||||
      flex: 1;
 | 
			
		||||
 | 
			
		||||
      img {
 | 
			
		||||
        width: 24px;
 | 
			
		||||
        height: 24px;
 | 
			
		||||
        color: #34383e;
 | 
			
		||||
        margin: 0 5px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .wg-cap-wrap__btn {
 | 
			
		||||
      display flex
 | 
			
		||||
      width: 120px;
 | 
			
		||||
      justify-content right
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__footer .wg-cap-wrap__ico{
 | 
			
		||||
    flex: 1;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__footer .wg-cap-wrap__ico img{
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    color: #34383e;
 | 
			
		||||
    margin: 0 5px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__footer .wg-cap-wrap__btn{
 | 
			
		||||
    width: 120px;
 | 
			
		||||
    height: 40px;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__footer .wg-cap-wrap__btn button{
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 40px;
 | 
			
		||||
    letter-spacing: 2px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    padding: 9px 15px;
 | 
			
		||||
    font-size: 15px;
 | 
			
		||||
    -webkit-border-radius: 5px;
 | 
			
		||||
    -moz-border-radius: 5px;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    background-color: #409eff;
 | 
			
		||||
    border: 1px solid #409eff;
 | 
			
		||||
    -webkit-appearance: none;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    transition: .1s;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    -moz-user-select: none;
 | 
			
		||||
    -webkit-user-select: none;
 | 
			
		||||
  }
 | 
			
		||||
  .wg-cap-wrap__footer .wg-cap-wrap__btn button:hover {
 | 
			
		||||
    background: #66b1ff;
 | 
			
		||||
    border-color: #66b1ff;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,26 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container>
 | 
			
		||||
    <el-popover
 | 
			
		||||
        :visible="showCaptcha"
 | 
			
		||||
        :hide-after="0"
 | 
			
		||||
        placement="top"
 | 
			
		||||
        :width="325"
 | 
			
		||||
        trigger="click"
 | 
			
		||||
        content="this is content, this is content, this is content"
 | 
			
		||||
  <el-container class="captcha-box">
 | 
			
		||||
    <el-button type="primary" class="send-btn" :size="props.size" :disabled="!canSend" @click="loadCaptcha" plain>
 | 
			
		||||
      {{ btnText }}
 | 
			
		||||
    </el-button>
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="showCaptcha"
 | 
			
		||||
        :close-on-click-modal="true"
 | 
			
		||||
        :show-close="false"
 | 
			
		||||
        style="width:90%;max-width: 360px;"
 | 
			
		||||
    >
 | 
			
		||||
      <captcha-plus
 | 
			
		||||
          v-if="showCaptcha"
 | 
			
		||||
          :max-dot="maxDot"
 | 
			
		||||
          :image-base64="imageBase64"
 | 
			
		||||
          :thumb-base64="thumbBase64"
 | 
			
		||||
          width="300"
 | 
			
		||||
          @close="showCaptcha = false"
 | 
			
		||||
          @refresh="handleRequestCaptCode"
 | 
			
		||||
          @confirm="handleConfirm"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <template #reference>
 | 
			
		||||
        <el-button type="primary" :size="props.size" :disabled="!canSend" @click="loadCaptcha" plain>
 | 
			
		||||
          {{ btnText }}
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-popover>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </el-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -75,6 +72,7 @@ const handleConfirm = (dots) => {
 | 
			
		||||
    dots: dots.value,
 | 
			
		||||
    key: captKey.value
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    // ElMessage.success('人机验证成功')
 | 
			
		||||
    showCaptcha.value = false
 | 
			
		||||
    sendMsg()
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
@@ -120,6 +118,21 @@ const sendMsg = () => {
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 | 
			
		||||
.captcha-box {
 | 
			
		||||
  .send-btn {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-dialog {
 | 
			
		||||
    .el-dialog__header {
 | 
			
		||||
      padding: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-dialog__body {
 | 
			
		||||
      //padding 0
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
          placeholder="请输入短信验证码"
 | 
			
		||||
      >
 | 
			
		||||
        <template #button>
 | 
			
		||||
          <send-msg-mobile size="small" :mobile="form.mobile"/>
 | 
			
		||||
          <send-msg size="small" :mobile="form.mobile"/>
 | 
			
		||||
        </template>
 | 
			
		||||
      </van-field>
 | 
			
		||||
    </van-cell-group>
 | 
			
		||||
@@ -30,7 +30,7 @@ import {computed, ref} from "vue";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {validateMobile} from "@/utils/validate";
 | 
			
		||||
import {showNotify} from "vant";
 | 
			
		||||
import SendMsgMobile from "@/components/mobile/SendMsgMobile.vue";
 | 
			
		||||
import SendMsg from "@/components/SendMsg.vue";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,137 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="captcha-box">
 | 
			
		||||
    <el-button type="primary" class="send-btn" :size="props.size" :disabled="!canSend" @click="loadCaptcha" plain>
 | 
			
		||||
      {{ btnText }}
 | 
			
		||||
    </el-button>
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="showCaptcha"
 | 
			
		||||
        :close-on-click-modal="true"
 | 
			
		||||
        :show-close="false"
 | 
			
		||||
        style="width:90%;max-width: 800px;"
 | 
			
		||||
    >
 | 
			
		||||
      <captcha-plus
 | 
			
		||||
          :max-dot="maxDot"
 | 
			
		||||
          :image-base64="imageBase64"
 | 
			
		||||
          :thumb-base64="thumbBase64"
 | 
			
		||||
          width="100%"
 | 
			
		||||
          @close="showCaptcha = false"
 | 
			
		||||
          @refresh="handleRequestCaptCode"
 | 
			
		||||
          @confirm="handleConfirm"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </el-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
// 发送短信验证码组件
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import lodash from 'lodash'
 | 
			
		||||
import {validateMobile} from "@/utils/validate";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import CaptchaPlus from "@/components/CaptchaPlus.vue";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  mobile: String,
 | 
			
		||||
  size: String,
 | 
			
		||||
});
 | 
			
		||||
const btnText = ref('发送验证码')
 | 
			
		||||
const canSend = ref(true)
 | 
			
		||||
const showCaptcha = ref(false)
 | 
			
		||||
const maxDot = ref(5)
 | 
			
		||||
const imageBase64 = ref('')
 | 
			
		||||
const thumbBase64 = ref('')
 | 
			
		||||
const captKey = ref('')
 | 
			
		||||
const dots = ref(null)
 | 
			
		||||
 | 
			
		||||
const handleRequestCaptCode = () => {
 | 
			
		||||
 | 
			
		||||
  httpGet('/api/captcha/get').then(res => {
 | 
			
		||||
    const data = res.data
 | 
			
		||||
    imageBase64.value = data.image
 | 
			
		||||
    thumbBase64.value = data.thumb
 | 
			
		||||
    captKey.value = data.key
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error('获取人机验证数据失败:' + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleConfirm = (dots) => {
 | 
			
		||||
  if (lodash.size(dots) <= 0) {
 | 
			
		||||
    return ElMessage.error('请进行人机验证再操作')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let dotArr = []
 | 
			
		||||
  lodash.forEach(dots, (dot) => {
 | 
			
		||||
    dotArr.push(dot.x, dot.y)
 | 
			
		||||
  })
 | 
			
		||||
  dots.value = dotArr.join(',')
 | 
			
		||||
  httpPost('/api/captcha/check', {
 | 
			
		||||
    dots: dots.value,
 | 
			
		||||
    key: captKey.value
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    showCaptcha.value = false
 | 
			
		||||
    sendMsg()
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error('人机验证失败')
 | 
			
		||||
    handleRequestCaptCode()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const loadCaptcha = () => {
 | 
			
		||||
  if (!validateMobile(props.mobile)) {
 | 
			
		||||
    return ElMessage.error("请输入合法的手机号")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  showCaptcha.value = true
 | 
			
		||||
  handleRequestCaptCode() // 每次点开都刷新验证码
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const sendMsg = () => {
 | 
			
		||||
  if (!canSend.value) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  canSend.value = false
 | 
			
		||||
  httpPost('/api/sms/code', {mobile: props.mobile, key: captKey.value, dots: dots.value}).then(() => {
 | 
			
		||||
    ElMessage.success('短信发送成功')
 | 
			
		||||
    let time = 120
 | 
			
		||||
    btnText.value = time
 | 
			
		||||
    const handler = setInterval(() => {
 | 
			
		||||
      time = time - 1
 | 
			
		||||
      if (time <= 0) {
 | 
			
		||||
        clearInterval(handler)
 | 
			
		||||
        btnText.value = '重新发送'
 | 
			
		||||
        canSend.value = true
 | 
			
		||||
      } else {
 | 
			
		||||
        btnText.value = time
 | 
			
		||||
      }
 | 
			
		||||
    }, 1000)
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    canSend.value = true
 | 
			
		||||
    ElMessage.error('短信发送失败:' + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 | 
			
		||||
.captcha-box {
 | 
			
		||||
  .send-btn {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-dialog {
 | 
			
		||||
    .el-dialog__header {
 | 
			
		||||
      padding: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-dialog__body {
 | 
			
		||||
      //padding 0
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
        <div class="header">{{ title }}</div>
 | 
			
		||||
        <div class="content">
 | 
			
		||||
          <div class="block">
 | 
			
		||||
            <el-input placeholder="手机号" size="large" v-model="username" autocomplete="off">
 | 
			
		||||
            <el-input placeholder="手机号" size="large" maxlength="11" v-model="username" autocomplete="off">
 | 
			
		||||
              <template #prefix>
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <UserFilled/>
 | 
			
		||||
@@ -57,6 +57,7 @@ import FooterBar from "@/components/FooterBar.vue";
 | 
			
		||||
import {isMobile} from "@/utils/libs";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
import {validateMobile} from "@/utils/validate";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const title = ref('ChatGPT-PLUS 用户登录');
 | 
			
		||||
@@ -81,8 +82,8 @@ onMounted(() => {
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const login = function () {
 | 
			
		||||
  if (username.value === '') {
 | 
			
		||||
    return ElMessage.error('请输入用户名');
 | 
			
		||||
  if (!validateMobile(username.value)) {
 | 
			
		||||
    return ElMessage.error('请输入合法的手机号');
 | 
			
		||||
  }
 | 
			
		||||
  if (password.value.trim() === '') {
 | 
			
		||||
    return ElMessage.error('请输入密码');
 | 
			
		||||
 
 | 
			
		||||
@@ -64,8 +64,7 @@
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                  <el-col :span="12">
 | 
			
		||||
                    <send-msg-mobile size="large" :mobile="formData.mobile" v-if="isMobile()"/>
 | 
			
		||||
                    <send-msg size="large" :mobile="formData.mobile" v-else/>
 | 
			
		||||
                    <send-msg size="large" :mobile="formData.mobile"/>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                </el-row>
 | 
			
		||||
              </div>
 | 
			
		||||
@@ -109,7 +108,7 @@ import FooterBar from "@/components/FooterBar.vue";
 | 
			
		||||
import SendMsg from "@/components/SendMsg.vue";
 | 
			
		||||
import {validateMobile} from "@/utils/validate";
 | 
			
		||||
import {isMobile} from "@/utils/libs";
 | 
			
		||||
import SendMsgMobile from "@/components/mobile/SendMsgMobile.vue";
 | 
			
		||||
import SendMsgMobile from "@/components/SendMsg.vue";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const title = ref('ChatGPT-PLUS 用户注册');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user