├── .babelrc ├── .gitignore ├── .idea ├── .gitignore ├── frontend.iml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── bash.exe.stackdump ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── server ├── nginx.conf └── vqa_web_api.py ├── src ├── Router │ └── RouterConfig.js ├── actions │ └── actionCreators.js ├── assets │ ├── pic │ │ ├── add_picture.png │ │ ├── app_scan.jpg │ │ ├── login.png │ │ ├── vqa.png │ │ └── wechat.jpg │ ├── scss │ │ ├── components │ │ │ ├── calendar.scss │ │ │ ├── foot.scss │ │ │ ├── logo.scss │ │ │ ├── page.scss │ │ │ ├── page │ │ │ │ ├── access.scss │ │ │ │ ├── charts.scss │ │ │ │ ├── datetime.scss │ │ │ │ ├── introduce.scss │ │ │ │ ├── login.scss │ │ │ │ ├── modify.scss │ │ │ │ ├── record.scss │ │ │ │ ├── register.scss │ │ │ │ └── upload.scss │ │ │ ├── sider.scss │ │ │ └── toptitle.scss │ │ ├── index.scss │ │ └── variables.scss │ └── sodagreen.mp3 ├── components │ ├── calendar.jsx │ ├── foot.jsx │ ├── logo.jsx │ ├── sider.jsx │ └── toptitle.jsx ├── constant │ └── constants.js ├── containers │ ├── datetime.jsx │ ├── echartsAnalysis.jsx │ ├── introduce.jsx │ ├── record.jsx │ ├── user │ │ ├── login.jsx │ │ ├── modify.jsx │ │ └── register.jsx │ └── vqa │ │ ├── access.jsx │ │ └── upload.jsx ├── index.js ├── index.scss ├── redux │ ├── menu.js │ ├── reducers.js │ ├── update.js │ └── user.js ├── serviceWorker.js ├── setupProxy.js ├── setupTests.js └── store │ └── store.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react-app" 5 | ], 6 | "plugins": [ 7 | "transform-decorators-legacy", 8 | [ 9 | "import", 10 | { 11 | "libraryName": "antd", 12 | "style": "css" 13 | } 14 | ] 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Default ignored files 3 | /workspace.xml -------------------------------------------------------------------------------- /.idea/frontend.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | This project is a BlindVQA Mannagement System based on tech stacks as follows. 3 | ### Frontend 4 | * [React.js](https://github.com/facebook/react) 5 | * react-router 6 | * [Redux](https://github.com/reduxjs/redux) 7 | * [axios](https://github.com/axios/axios) 8 | * [Ant Design](https://ant.design/docs/react/introduce-cn) 9 | * [Sass](https://www.sass.hk/docs/) 10 | * [Echarts](https://www.echartsjs.com/zh/index.html) 11 | ### Server 12 | * [Nginx](https://github.com/nginx/nginx) 13 | * [Docker](https://github.com/yeasy/docker_practice) 14 | * [FRP](https://github.com/fatedier/frp) 15 | * [uWSGI](https://github.com/tiangolo/uwsgi-nginx-flask-docker) 16 | ### Backend 17 | * [Flask](https://github.com/pallets/flask) 18 | * MySQL 19 | * [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) 20 | * [Pytorch](https://github.com/yunjey/pytorch-tutorial) for Deep Learning and [VQA model](https://github.com/MILVLG/mcan-vqa) 21 | 22 | ## Contents 23 | ```js 24 | ### 目录结构介绍 25 | ***├── build // 放在Flask所在目录下的static文件夹内*** 26 | ***├── server // 后端Flask web应用程序*** 27 | ***├── public *** 28 | ***├── node_modules // 项目的包依赖*** 29 | ***├── src // 源码目录*** 30 | ***│ ├── assets // 存放项目的图片资源和SCSS文件*** 31 | ***│ ├── components // 页面自定义组件*** 32 | ***│ ├── containers // 页面容器*** 33 | ***│ ├── constant // 变量全局配置*** 34 | ***│ ├── store // 创建store仓库*** 35 | ***│ ├── redux // 组合reducers*** 36 | ***│ ├── actions // 管理actions操作*** 37 | ***│ ├── Router // 页面路由配置 *** 38 | ***│ ├── setupProxy.js // 设置跨域请求代理*** 39 | ***│ ├── index.js // 程序入口文件,加载各种公共组件*** 40 | ***├── .babelrc // babel配置文件 *** 41 | ``` 42 | ## Project Deployment 43 | ```bash 44 | npm run/yarn build 45 | rsync /path/to/build root@cloud_server_ip:/build 46 | cd /path/to/vqa_flask_project 47 | python vqa_web_api.py 48 | cd /path/to/frpc 49 | ./frpc -c ./frpc.ini 50 | ssh -o ServerAliveInterval=60 root@cloud_server_ip 51 | nginx -s reload 52 | docker start/restart frps_container_name 53 | ``` 54 | ## Login Page 55 | ![login page](https://github.com/JerryWisdom/react-vqa-master/blob/master/src/assets/pic/login.png) 56 | -------------------------------------------------------------------------------- /bash.exe.stackdump: -------------------------------------------------------------------------------- 1 | Stack trace: 2 | Frame Function Args 3 | 000FFFFA1A8 0018005D19E (0018024212D, 00180223C26, 000FFFFA1A8, 000FFFF90A0) 4 | 000FFFFA1A8 001800463F9 (00000000000, 00000000000, 00000000000, 001005F0D44) 5 | 000FFFFA1A8 00180046432 (001802421E9, 000FFFFA058, 000FFFFA1A8, 00000000000) 6 | 000FFFFA1A8 001800A9EEF (00000000000, 00000000000, 00000000000, 00000000000) 7 | 000FFFFA1A8 001800AA13D (000FFFFA1C0, 00000000000, 00000000000, 00000000000) 8 | 000FFFFA430 001800AB3A4 (000FFFFA1C0, 00000000000, 00000000000, 00000000000) 9 | End of stack trace 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "antd": "^3.26.7", 7 | "antd-mobile": "^2.3.1", 8 | "axios": "^0.19.2", 9 | "babel-cli": "^6.26.0", 10 | "blob": "^0.0.5", 11 | "cssnano": "^4.1.10", 12 | "echarts": "^4.6.0", 13 | "express": "^4.17.1", 14 | "history": "^4.10.1", 15 | "moment": "^2.24.0", 16 | "node-sass": "^4.13.1", 17 | "postcss-aspect-ratio-mini": "^1.0.1", 18 | "postcss-cssnext": "^3.1.0", 19 | "postcss-px-to-viewport": "^1.1.1", 20 | "postcss-viewport-units": "^0.1.6", 21 | "postcss-write-svg": "^3.0.1", 22 | "prop-types": "^15.7.2", 23 | "react": "^16.12.0", 24 | "react-dom": "^16.12.0", 25 | "react-highlight-words": "^0.16.0", 26 | "react-materialize": "^3.5.9", 27 | "react-redux": "^7.1.3", 28 | "react-router": "^5.1.2", 29 | "react-router-dom": "^5.1.2", 30 | "react-router-redux": "^4.0.8", 31 | "react-scripts": "3.3.0", 32 | "react-tooltip": "^3.11.5", 33 | "reactjs-swiper": "^0.1.0", 34 | "redux": "^4.0.5", 35 | "redux-logger": "^3.0.6", 36 | "redux-persist": "^6.0.0", 37 | "redux-saga": "^1.1.3", 38 | "redux-thunk": "^2.3.0", 39 | "sass": "^1.25.0", 40 | "sass-loader": "^8.0.2", 41 | "whatwg-fetch": "^3.0.0", 42 | "yarn": "^1.22.0", 43 | "zrender": "^4.2.0" 44 | }, 45 | "scripts": { 46 | "start": "react-scripts start", 47 | "build": "react-scripts build", 48 | "test": "react-scripts test", 49 | "eject": "react-scripts eject", 50 | "prebuild": "rm -rf ../VizWiz-VQA-PyTorch-master/templates/index.html && rm -rf ../VizWiz-VQA-PyTorch-master/static/build", 51 | "postbuild": "cp build/index.html ../VizWiz-VQA-PyTorch-master/templates/ && cp -r build ../VizWiz-VQA-PyTorch-master/static/" 52 | }, 53 | "homepage": "/", 54 | "eslintConfig": { 55 | "extends": "react-app" 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | ">0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/nginx.conf: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes 4; 3 | 4 | #error_log logs/error.log; 5 | #error_log logs/error.log notice; 6 | #error_log logs/error.log info; 7 | 8 | #pid logs/nginx.pid; 9 | 10 | 11 | events { 12 | use epoll; 13 | #multi_accept on; 14 | worker_connections 1024; 15 | } 16 | 17 | 18 | http { 19 | include /usr/local/nginx/conf/mime.types; 20 | default_type application/octet-stream; 21 | 22 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 23 | # '$status $body_bytes_sent "$http_referer" ' 24 | # '"$http_user_agent" "$http_x_forwarded_for"'; 25 | 26 | #access_log logs/access.log main; 27 | 28 | sendfile on; 29 | #tcp_nopush on; 30 | keepalive_timeout 65; 31 | gzip on; 32 | 33 | #upstream myserver { 34 | #ip_hash; 35 | #server hducsrao.xyz:8880; # weight=1; 36 | #server hducsrao.xyz:8881 weight=1; 37 | #fair; 38 | #} 39 | 40 | server { 41 | # listen 80; 42 | listen 443 ssl; 43 | # listen 443 ssl http2 default_server; 44 | # listen [::]:443 ssl http2 default_server; 45 | server_name www.hducsrao.xyz; 46 | root /build; 47 | 48 | # charset koi8-r; 49 | # access_log logs/host.access.log main; 50 | client_max_body_size 20M; 51 | 52 | ssl_certificate "/usr/local/nginx/conf/1_www.hducsrao.xyz_bundle.crt"; 53 | ssl_certificate_key "/usr/local/nginx/conf/2_www.hducsrao.xyz.key"; 54 | ssl_session_cache shared:SSL:1m; 55 | ssl_session_timeout 10m; 56 | # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 57 | ssl_ciphers HIGH:!aNULL:!MD5; 58 | ssl_prefer_server_ciphers on; 59 | 60 | location / { 61 | root /build; 62 | index index.html index.htm; 63 | try_files $uri /index.html; 64 | 65 | # rewrite .* /index.html break; 66 | # proxy_pass http://hducsrao.xyz:8001/; 67 | # include uwsgi_params; 68 | # uwsgi_pass 127.0.0.1:8089; 69 | # uwsgi_param UWSGI_CHDIR /home/colin; 70 | # uwsgi_param UWSGI_SCRIPT test_flask:app; 71 | # uwsgi_read_timeout 300; 72 | # uwsgi_connect_timeout 300; 73 | # uwsgi_send_timeout 300; 74 | } 75 | 76 | location ^~ /usr_login/ { 77 | # root /build; 78 | # try_files $uri /index.html; 79 | proxy_pass http://hducsrao.xyz:8001/login/; 80 | } 81 | location ^~ /usr_register/ { 82 | proxy_pass http://hducsrao.xyz:8001/register/; 83 | } 84 | location ^~ /usr_modify/ { 85 | proxy_pass http://hducsrao.xyz:8001/modify/; 86 | } 87 | location ^~ /usr_record/ { 88 | proxy_pass http://hducsrao.xyz:8001/record/; 89 | } 90 | location ^~ /api { 91 | proxy_pass http://hducsrao.xyz:8001/api/; 92 | } 93 | 94 | # location / { 95 | # root /build; 96 | # try_files $uri /index.html; 97 | # # rewrite .* /index.html break; 98 | # } 99 | 100 | 101 | # location ^~ /register/ { 102 | # #include uwsgi_params; 103 | # #uwsgi_pass 127.0.0.1:8001/register/; 104 | # #uwsgi_param UWSGI_CHDIR /home/colin/VizWiz-VQA-PyTorch-master; 105 | # #uwsgi_param UWSGI_SCRIPT vqa_web_api:app; 106 | # #uwsgi_read_timeout 300; 107 | # #uwsgi_connect_timeout 300; 108 | # #uwsgi_send_timeout 300; 109 | # #proxy_pass http://myserver/register/; 110 | # proxy_pass http://hducsrao.xyz:8001/register/; 111 | # proxy_read_timeout 300; 112 | # proxy_connect_timeout 300; 113 | # proxy_send_timeout 300; 114 | # } 115 | 116 | # location ^~ /question/ { 117 | # include uwsgi_params; 118 | # uwsgi_pass 127.0.0.1:8089; 119 | # uwsgi_param UWSGI_CHDIR /home/colin/VizWiz-VQA-PyTorch-master; 120 | # uwsgi_param UWSGI_SCRIPT Android_flaskWeb:app; 121 | # uwsgi_read_timeout 300; 122 | # uwsgi_connect_timeout 300; 123 | # uwsgi_send_timeout 300; 124 | # #proxy_pass http://hducsrao.xyz:8001/question/; 125 | # #proxy_read_timeout 240s; 126 | # #proxy_pass http://myserver/question/; 127 | # # include uwsgi_params; 128 | # # uwsgi_pass unix:/root/myproject/myproject.sock; 129 | # # uwsgi_pass http://127.0.0.1:8880; 130 | # } 131 | 132 | error_page 500 502 503 504 /50x.html; 133 | location = /50x.html { 134 | root html; 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /server/vqa_web_api.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os.path 4 | import time 5 | import numpy as np 6 | import torch 7 | import torch.backends.cudnn as cudnn 8 | import torch.nn as nn 9 | import yaml 10 | from torch.autograd import Variable 11 | from tqdm import tqdm 12 | from flask_cors import CORS 13 | from pyspark import SparkContext 14 | import models 15 | import predict_dataset 16 | import os 17 | import os.path 18 | import cv2 19 | import torch.utils.data as data 20 | import torchvision.transforms as transforms 21 | from PIL import Image 22 | from one_predict_forWeb import predict 23 | from flask import Flask, render_template, request, redirect, url_for, make_response,jsonify 24 | from werkzeug.utils import secure_filename 25 | import os 26 | from preprocessing import image_features_extraction as imf 27 | import time 28 | from datetime import timedelta,datetime 29 | 30 | """ 31 | 模型预处理部分 32 | """ 33 | # Load config yaml file 34 | parser = argparse.ArgumentParser() 35 | parser.add_argument('--path_config', default='config/default.yaml', type=str, 36 | help='path to a yaml config file') 37 | args = parser.parse_args() 38 | 39 | if args.path_config is not None: 40 | with open(args.path_config, 'r') as handle: 41 | config = yaml.load(handle, Loader=yaml.FullLoader) 42 | 43 | class ImageDataset(data.Dataset): 44 | 45 | def __init__(self, img_filename, img_filepath, transform=None): # , path 46 | # self.path = path 47 | self.transform = transform 48 | # Load the paths to the images available in the folder 49 | img_path = [] 50 | self.filename = img_filename 51 | self.file_path = img_filepath 52 | img_path.append(self.file_path) 53 | self.image_names = img_path 54 | 55 | def __getitem__(self, index): 56 | item = {} 57 | item['name'] = self.filename 58 | item['path'] = self.file_path 59 | # item['path'] = os.path.join(self.path, item['name']) 60 | 61 | # 使用PIL加载图片数据再提取特征, Image.oopen后维度例如(3, 640, 722) 62 | item['visual'] = Image.open(item['path']).convert('RGB') 63 | if self.transform is not None: 64 | item['visual'] = self.transform(item['visual']) 65 | return item 66 | 67 | def __len__(self): 68 | return len(self.image_names) 69 | 70 | def get_path(self): 71 | return os.path.basename(self.file_path) 72 | 73 | def get_transform(img_size): 74 | return transforms.Compose([ 75 | transforms.Resize(img_size), 76 | transforms.CenterCrop(img_size), 77 | transforms.ToTensor(), 78 | # TODO : Compute mean and std of VizWiz 79 | # ImageNet normalization 80 | transforms.Normalize(mean=[0.485, 0.456, 0.406], 81 | std=[0.229, 0.224, 0.225]), 82 | ]) 83 | 84 | """ 85 | Flask后端配置、数据库连接表 86 | """ 87 | from flask_sqlalchemy import SQLAlchemy 88 | from flask import Blueprint 89 | import pymysql 90 | # from sqlalchemy.orm import sessionmaker 91 | pymysql.install_as_MySQLdb() 92 | app = Flask(__name__,template_folder="templates",static_folder="static",static_url_path="/VizWiz-VQA-PyTorch-master/static") 93 | # static_url_path:前端访问资源文件的前缀目录 static_folder:后端存储资源文件的目录。默认是/static,就是指明你后端的资源文件 94 | app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123@localhost:3306/vqa_web?charset=utf8' 95 | # app.config['SQLACHEMY_COMMIT_ON_TEARDOWN']=True # 自动提交 96 | db = SQLAlchemy(app) # 初始化 MySQL database 97 | import time # 格式化成2016-03-20 11:45:39形式:time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 98 | 99 | class Vqaer(db.Model): 100 | """ 创建用户注册登录模型,注意设置表的属性区分大小写 """ 101 | __tablename__ = 'login_register' 102 | user = db.Column(db.String(50), nullable=False, primary_key=True) 103 | pwd = db.Column(db.String(50), nullable=False) 104 | 105 | class QandA(db.Model): 106 | """ username, date_time, question, answer 设置为主键的query只会返回一个记录 db.DateTime """ 107 | __tablename__ = 'ques_ans' 108 | user = db.Column(db.String(50), nullable=False) 109 | date = db.Column(db.String(50), nullable=False, primary_key=True) 110 | vqa_ques = db.Column(db.String(50), nullable=False) 111 | vqa_ans = db.Column(db.String(50), nullable=False) 112 | interaction = db.Column(db.Float, nullable=False) 113 | 114 | # 单个对象方法 115 | def single_to_dict(self): 116 | return {c.name: getattr(self, c.name) for c in self.__table__.columns} 117 | # 多个对象 118 | def dobule_to_dict(self): 119 | result = {} 120 | for key in self.__mapper__.c.keys(): 121 | if getattr(self, key) is not None: 122 | result[key] = str(getattr(self, key)) 123 | else: 124 | result[key] = getattr(self, key) 125 | return result 126 | 127 | def to_json(self): 128 | dict = self.__dict__ 129 | if "_sa_instance_state" in dict: 130 | del dict["_sa_instance_state"] 131 | return dict 132 | 133 | ''' 134 | 路由所需函数 135 | ''' 136 | def add_user(username, password): # 添加用户 137 | newuser = Vqaer(user=username, pwd=password) 138 | db.session.add(newuser) 139 | db.session.commit() 140 | 141 | def find_pwd(username): 142 | # 获取对应用户密码 143 | res = db.session.query(Vqaer).filter(Vqaer.user == username).all() 144 | password = '' 145 | # print(res) 146 | if res: 147 | password = res[0].pwd 148 | return password 149 | 150 | def modify_pwd(username, password): 151 | # 修改用户密码 152 | db.session.query(Vqaer).filter(Vqaer.user == username).update({"pwd": password}) 153 | db.session.commit() 154 | 155 | # 先按条件查询再删除:session.query(QandA).filter(QandA.user == username).delete() 156 | # def to_json(all_vendors): 157 | # v = [ ven.dobule_to_dict() for ven in all_vendors ] 158 | # return v 159 | 160 | def find_record(username): 161 | # 获取对应用户密码 162 | res = db.session.query(QandA).filter(QandA.user == username).all() #返回的是object对象,注意这里以唯一的时间值作为主键而非用户名 163 | cnt = db.session.query(QandA).filter(QandA.user == username).count() #返回符合条件的记录条数 164 | # print(cnt, res) 165 | record = [] 166 | if cnt == 0: 167 | return jsonify({'auth': False, 'msg': '当前用户没有记录!'}) 168 | else: 169 | for item in res: 170 | record.append(item.to_json()) 171 | # print(record) 172 | return jsonify({'auth': True, 'nums': cnt, 'rec': record}) # json.dumps 173 | 174 | ''' 175 | 路由函数,与PC端Web网站的react-axios交互 176 | ''' 177 | # @app.route('/', methods=['POST','GET']) 178 | # # React.js的入口文件,若把build放在服务器根目录下在配置则不用响应flask的入口文件 179 | # def index(): 180 | # return render_template('index.html') 181 | 182 | @app.route('/login/', methods=['POST','GET']) 183 | def login(): 184 | # request.args.get("username") 获取Get请求参数, request.form.get 获取post请求 185 | username = request.args.get("username") 186 | userpwd = request.args.get("password") 187 | # print(username, userpwd) 188 | pwd = find_pwd(username) 189 | if pwd == "": 190 | return jsonify({'auth': False, 'msg': '用户名尚未注册!'}) 191 | if pwd != userpwd: 192 | return jsonify({'auth': False, 'msg': '请检查密码是否正确!'}) 193 | else: 194 | return jsonify({'auth': True }) 195 | 196 | @app.route('/register/', methods=['POST','GET']) 197 | def register(): 198 | username = request.args.get("username") 199 | userpwd = request.args.get("password") 200 | if find_pwd(username) != '': 201 | return jsonify({'auth': False, 'msg': '账户已注册,请返回登录!'}) 202 | else: 203 | add_user(username, userpwd) 204 | return jsonify({'auth': True}) 205 | 206 | @app.route('/modify/', methods=['POST','GET']) 207 | def modify(): 208 | username = request.args.get("username") 209 | userpwd = request.args.get("password") 210 | if find_pwd(username) == '': 211 | return jsonify({'auth': False, 'msg': '账户不存在,请先注册!'}) 212 | if find_pwd(username) == userpwd: 213 | return jsonify({'auth': False, 'msg': '新密码与旧密码一致!'}) 214 | else: 215 | modify_pwd(username, userpwd) 216 | return jsonify({'auth': True}) 217 | 218 | @app.route('/record/', methods=['POST','GET']) 219 | def record(): 220 | username = request.args.get("username") 221 | return find_record(username) 222 | 223 | ''' 224 | VQA预测模型模块 225 | ''' 226 | #设置允许的文件格式 227 | ALLOWED_EXTENSIONS = set(['png', 'jpg', 'JPG', 'PNG', 'bmp', 'jpeg']) 228 | def allowed_file(filename): 229 | return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS 230 | 231 | @app.route('/api/', methods=['POST','GET']) 232 | def upload(): 233 | a = datetime.now() 234 | nowtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 235 | # nowtime = time.strptime(nowtime, "%Y-%m-%d %H:%M:%S") 236 | question = request.form.get("ques") # 前端获取问题字符串,Base64编码图片可以上传到七牛云存储然后返回url链接 237 | username = request.form.get("username") 238 | print(username, nowtime, question) 239 | 240 | f = request.files['image'] 241 | if not (f and allowed_file(f.filename)): 242 | return jsonify({"error": 1001, "msg": "请检查上传的图片类型"}) 243 | p = os.path.dirname(__file__) # 当前文件所在路径 244 | 245 | # 图片路径用于PIL加载图片的RGB数据,图片名字无所谓 246 | basepath = os.path.join(p, 'static') 247 | img_filename = f.filename 248 | 249 | upload_path = os.path.join(p, 'static', secure_filename(f.filename)) 250 | f.save(upload_path) 251 | # 使用Opencv转换一下图片格式和名称 252 | img = cv2.imread(upload_path) 253 | cv2.imwrite(os.path.join(p,'static', 'test.jpg'), img) 254 | 255 | #调用函数进行预测 256 | transform = get_transform(config['images']['img_size']) 257 | dataset = ImageDataset(img_filename, upload_path, transform=transform) 258 | att_fea, noatt_fea, img_namee = imf.feature(dataset) 259 | answer = predict(img_filename, question, att_fea, noatt_fea, img_namee) 260 | 261 | b = datetime.now() 262 | during = float((b-a).seconds) 263 | # 向 MySQL 添加用户问答记录 264 | newRecord = QandA(user=username, date=nowtime, vqa_ques=question, vqa_ans=answer, interaction=during) 265 | db.session.add(newRecord) 266 | db.session.commit() 267 | 268 | return jsonify({'res': answer}) 269 | # return render_template('index.html') 270 | # return render_template('upload_ok.html',userinput1=question,userinput2=res,val1=time.time()) 271 | 272 | if __name__ == '__main__': 273 | # app.debug = True 8880 274 | app.run(host='0.0.0.0', port=8880, debug=True) # 127.0.0.1:8987/predict 本地测试地址 275 | 276 | 277 | 278 | # if __name__ == '__main__': 279 | # img_name = get_feature() 280 | # res = predict(img_name) 281 | # return res 282 | # print(web_predict()) 283 | # fig = {"image": str(img.tolist()).encode('base64')} img是ndarray,无法直接用base64编码,否则会报错,这行是处理本地的图片,不能用 284 | # 从客户端获取图片json数据,预处理从客户端获取的图片,比如我们的 /http://192.168.2.126:5000/predict 285 | # fig = request.form.get("img_name") 286 | # 获取推过来的json,也可以用data然后转换成json再使用 res = json.loads(request.data) -------------------------------------------------------------------------------- /src/Router/RouterConfig.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | // Link, 4 | Switch, 5 | Route, 6 | BrowserRouter, 7 | Redirect 8 | } from 'react-router-dom' 9 | import Access from '../containers/vqa/access' 10 | import VQAUpload from '../containers/vqa/upload' 11 | import Login from '../containers/user/login' 12 | import Register from '../containers/user/register' 13 | import Modify from '../containers/user/modify' 14 | import Record from '../containers/record' 15 | import Introduce from '../containers/introduce' 16 | import MyCalendar from '../containers/datetime' 17 | import MyCharts from "../containers/echartsAnalysis"; 18 | import { 19 | // createHashHistory, 20 | createBrowserHistory 21 | } from 'history' 22 | // 利用react-router-redux提供的syncHistoryWithStore我们可以结合store同步导航事件 23 | import { syncHistoryWithStore } from 'react-router-redux' 24 | import store from '../store/store' 25 | 26 | // const browserHistory = createHashHistory(); 27 | // 使用 URL 中的 hash #部分去创建路由 28 | const browserHistory = createBrowserHistory(); 29 | // hash不需要服务器配置,实际生产环境用browserHistory 30 | const history = syncHistoryWithStore(browserHistory, store) 31 | // 创建一个增强版的history来结合store同步导航事件 32 | 33 | 34 | class RouterConfig extends Component { 35 | componentDidMount() { // 组件渲染后调用 36 | document.title = 'BlindVQA' 37 | } 38 | render() { 39 | return( 40 | // basename='/build' 41 | 42 | 43 | ( 44 | 45 | )}/> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ) 58 | } 59 | } 60 | 61 | export default RouterConfig -------------------------------------------------------------------------------- /src/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | import { INPUT_QUES,CLEAR_INPUT_QUES,SET_IMAGE_URL,SET_ANSWER,REGI_SUCCESS,AUTH_SUCCESS,MODI_SUCCESS,LOAD_DATA,ERROR_MSG,LOGOUT,CHANGE_MODE,CHANGE_THEME } from "../constant/constants"; 2 | import axios from "axios"; 3 | 4 | // 这些方法都返回一个action对象 5 | export const handleGetInputValue = ques =>({ 6 | type: INPUT_QUES, 7 | ques 8 | }) 9 | 10 | export const handleSetImageUrl = imgurl =>({ 11 | type: SET_IMAGE_URL, 12 | imgurl 13 | }) 14 | 15 | export const handleSetAnswer = ans =>({ 16 | type: SET_ANSWER, 17 | ans 18 | }) 19 | 20 | export const handleClearInques = () => ({ 21 | type: CLEAR_INPUT_QUES 22 | }) 23 | 24 | export const handleSetMode = data =>({ 25 | type: CHANGE_MODE, 26 | payload: data 27 | }) 28 | 29 | export const handleSetTheme = data =>({ 30 | type: CHANGE_THEME, 31 | payload: data 32 | }) 33 | 34 | // 注册登录模块,访问与Flask后端连接的MySQL数据库 35 | function authSuccess(obj){ 36 | const {...data} = obj 37 | return { 38 | type: AUTH_SUCCESS, 39 | payload: data 40 | } 41 | } 42 | function regiSuccess(obj){ 43 | const {pwd,...data} = obj 44 | return { 45 | type: REGI_SUCCESS, 46 | payload: data 47 | } 48 | } 49 | function modiSuccess(obj){ 50 | const {pwd,...data} = obj 51 | return { 52 | type: MODI_SUCCESS, 53 | payload: data 54 | } 55 | } 56 | function errorMsg(msg){ 57 | return { 58 | msg, 59 | type: ERROR_MSG 60 | } 61 | } 62 | 63 | export function loadData(obj){ 64 | const { ...data } = obj 65 | console.log(data) 66 | return { 67 | type: LOAD_DATA, 68 | payload: data 69 | } 70 | } 71 | 72 | export function logoutSubmit(){ 73 | return { 74 | type: LOGOUT 75 | } 76 | } 77 | 78 | //判断字符串密码是否为数字和字母的组合 79 | function checkPwd (str){ 80 | var zg = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]*$/; 81 | if (!zg.test(str)) { 82 | return false; 83 | } else { 84 | return true; 85 | } 86 | } 87 | 88 | // 注册 89 | export function register({user,pwd,repeatpwd}){ 90 | if(!user||!pwd){ 91 | return errorMsg('用户名密码必须输入!') 92 | } 93 | if(pwd!==repeatpwd){ 94 | return errorMsg('密码不一致!') 95 | } 96 | if(!checkPwd(pwd)){ 97 | return errorMsg("密码必须只包含字母和数字!") 98 | } 99 | if(pwd.length<6){ 100 | return errorMsg("密码不能少于六位数!") 101 | } 102 | return async dispatch=>{ 103 | // let form_data = new FormData(); // 创建form对象 104 | // form_data.append('username', user); 105 | // form_data.append('password', pwd); 106 | // console.log(form_data) 107 | await axios.get(`/usr_register?username=${user}&password=${pwd}` 108 | // { 109 | // headers: { 110 | // 'Access-Control-Allow-Origin':'*', //解决cors头问题, 不同端口存在跨域问题,使用proxy代理 111 | // 'Access-Control-Allow-Credentials':'true', //解决session问题 112 | // 'Content-Type': 'application/form-data; charset=UTF-8' 113 | // }, 114 | // withCredentials : true} 115 | ).then(res=>{ 116 | if(res.data['auth']){ //res.status===200&&res.data.code===0 117 | dispatch(regiSuccess({user,pwd})) 118 | }else{ 119 | dispatch(errorMsg(res.data['msg'])) 120 | } 121 | }).catch( error => { 122 | dispatch(errorMsg('服务器未响应!')) 123 | }); 124 | } 125 | } 126 | 127 | // 登录 128 | export function login({user,pwd}){ 129 | if(!user||!pwd){ 130 | return errorMsg('用户名密码必须输入!') 131 | } 132 | return async dispatch=>{ 133 | await axios.get(`/usr_login?username=${user}&password=${pwd}` 134 | ).then(res=>{ 135 | if(res.data['auth']){ 136 | dispatch(authSuccess({user,pwd})) 137 | }else{ 138 | dispatch(errorMsg(res.data['msg'])) 139 | } 140 | }).catch( error => { 141 | dispatch(errorMsg('服务器未响应!')) 142 | }); 143 | } 144 | } 145 | 146 | // 忘记密码?用户修改密码 147 | export function modify({user,pwd,repeatpwd}){ 148 | if(!user||!pwd){ 149 | return errorMsg('用户名密码必须输入!') 150 | } 151 | if(pwd!==repeatpwd){ 152 | return errorMsg('密码不一致!') 153 | } 154 | if(!checkPwd(pwd)){ 155 | return errorMsg("密码必须只包含字母和数字!") 156 | } 157 | if(pwd.length<6){ 158 | return errorMsg("密码不能少于六位数!") 159 | } 160 | return async dispatch=>{ 161 | await axios.get(`/usr_modify?username=${user}&password=${pwd}` 162 | ).then(res=>{ 163 | if(res.data['auth']){ 164 | dispatch(modiSuccess({user,pwd})) 165 | }else{ 166 | dispatch(errorMsg(res.data['msg'])) 167 | } 168 | }).catch( error => { 169 | dispatch(errorMsg('服务器未响应!')) 170 | }); 171 | } 172 | } 173 | 174 | // 查询某用户使用记录 175 | export function search_by_user({user}){ 176 | return async dispatch=>{ 177 | await axios.get(`/usr_record?username=${user}` 178 | ).then(res=>{ 179 | if(res.data['auth']){ 180 | let nums = res.data['nums'] 181 | let rec = res.data['rec'] 182 | dispatch(loadData({nums,rec})) 183 | } 184 | else{ 185 | dispatch(errorMsg(res.data['msg'])) 186 | } 187 | }).catch( () => { 188 | dispatch(errorMsg('服务器响应出错 !')) 189 | }); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/assets/pic/add_picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/add_picture.png -------------------------------------------------------------------------------- /src/assets/pic/app_scan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/app_scan.jpg -------------------------------------------------------------------------------- /src/assets/pic/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/login.png -------------------------------------------------------------------------------- /src/assets/pic/vqa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/vqa.png -------------------------------------------------------------------------------- /src/assets/pic/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/pic/wechat.jpg -------------------------------------------------------------------------------- /src/assets/scss/components/calendar.scss: -------------------------------------------------------------------------------- 1 | .box { 2 | position: relative; 3 | width: $mini-side-calendar; 4 | border: 1px solid #d9d9d9; 5 | border-radius: 4; 6 | } -------------------------------------------------------------------------------- /src/assets/scss/components/foot.scss: -------------------------------------------------------------------------------- 1 | .foot { 2 | // flex: 1; 3 | height: 180px; 4 | // position: absolute; 5 | // bottom: 0; 6 | // width: 100%; 7 | color: $footer-info-text-color; 8 | background-color: $footer-background-color; 9 | display: flex; 10 | flex-direction: row; 11 | justify-content: space-between; 12 | align-items: center; 13 | &-app { 14 | width: 50%; 15 | display: flex; 16 | flex-direction: row; 17 | justify-content: space-between; 18 | align-items: center; 19 | &-left { 20 | width: 60%; 21 | text-align: right; 22 | } 23 | &-right { 24 | text-align: left; 25 | flex: 1; 26 | p { 27 | padding-left: 30px; 28 | } 29 | &-intro { 30 | color: RGB(169,184,202); 31 | } 32 | } 33 | } 34 | &-developer { 35 | text-align: left; 36 | flex: 1; 37 | border-left: 1px solid #646f7c; 38 | p { 39 | color: RGB(169,184,202); 40 | padding-left: 40px; 41 | a { 42 | color: RGB(169,184,202); 43 | &:hover { 44 | color: #ffffff; 45 | } 46 | } 47 | } 48 | &-link { 49 | color: RGB(169,184,202); 50 | &:hover { 51 | color: #ffffff; 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/assets/scss/components/logo.scss: -------------------------------------------------------------------------------- 1 | .logo-container{ 2 | margin-top: 50px; 3 | text-align: center; 4 | margin-bottom: 20px; 5 | img { 6 | width: 260px; 7 | height: 260px; 8 | border-radius: 50%; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/scss/components/page.scss: -------------------------------------------------------------------------------- 1 | @import './page/login'; 2 | @import './page/register'; 3 | @import './page/modify'; 4 | @import './page/record'; 5 | @import './page/introduce'; 6 | @import './page/upload'; 7 | @import './page/access'; 8 | @import './page/charts'; 9 | @import './page/datetime'; 10 | 11 | -------------------------------------------------------------------------------- /src/assets/scss/components/page/access.scss: -------------------------------------------------------------------------------- 1 | .whole { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .sider-vqa2 { 7 | flex: 1; 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .vqa2 { 14 | /* display: inline-block; */ 15 | /* position: absolute; */ 16 | // float: right; 17 | // position: relative; 18 | flex: 1; 19 | display: flex; 20 | flex-direction: row; 21 | justify-content: space-between; 22 | text-align: center; 23 | margin-right: $access-margin-right; 24 | // width: $vqa-access-view; 25 | margin-top: $access-view-margin-top; 26 | color: green; 27 | &-empty { 28 | margin-left: $access-empty-margin-left; 29 | } 30 | 31 | .show_img { 32 | // display: inline-block; 33 | // position: relative; 34 | // position: absolute; 35 | width: $access-image-width; 36 | h2 { 37 | font-size: $access-image-title-font-size; 38 | } 39 | } 40 | 41 | .response { 42 | // float: right; 43 | // position: relative; 44 | // position: absolute; 45 | // text-align: center; 46 | flex: 1; 47 | width: $access-response-width; 48 | // color: green; 49 | margin-top: $access-response-margin-top; 50 | &-ans { 51 | background: $access-response-background; 52 | Paragraph { 53 | font-size: $access-response-ans-font-size; 54 | } 55 | } 56 | h3 { 57 | font-weight: bold; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/assets/scss/components/page/charts.scss: -------------------------------------------------------------------------------- 1 | .whole { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .sider-charts { 7 | flex: 1; 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .charts { 14 | // float: right; 15 | // position: relative; 16 | flex: 1; 17 | display: flex; 18 | flex-direction: row; 19 | justify-content: space-between; 20 | text-align: center; 21 | margin-right: $access-margin-right; 22 | // width: $vqa-access-view; 23 | margin-top: $user-echarts-margin-top; 24 | 25 | .echart-bar { 26 | // display: inline-block; 27 | //position: relative; 28 | // position: absolute; 29 | width: $access-image-width; 30 | } 31 | 32 | .echart-pie { 33 | // float: right; 34 | //position: relative; 35 | // position: absolute; 36 | //text-align: center; 37 | flex: 1; 38 | width: $access-response-width; 39 | } 40 | } -------------------------------------------------------------------------------- /src/assets/scss/components/page/datetime.scss: -------------------------------------------------------------------------------- 1 | .whole { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .sider-calen { 7 | flex: 1; 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .calen { 14 | /* display: inline-block; */ 15 | // float: right; 16 | // position: relative; 17 | flex: 1; 18 | text-align: center; 19 | margin-right: $date-calendar-margin-right; 20 | // width: $date-calendar-view; 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/scss/components/page/introduce.scss: -------------------------------------------------------------------------------- 1 | .whole { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .sider-intro { 7 | flex: 1; 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .intro { 14 | /* display: inline-block; */ 15 | // float: right; 16 | // position: relative; 17 | flex: 1; 18 | text-align: center; 19 | // width: $vqa-introduce-view; 20 | margin-right: $introduce-margin-right; 21 | &-title { 22 | font-weight: bold; 23 | font-size: $introduce-title-text-size; 24 | color: $introduce-title-text-color; 25 | } 26 | &-card { 27 | background: $introduce-card-background; 28 | padding: $introduce-card-padding; 29 | &-list { 30 | font-size: $introduce-list-font-size; 31 | /* text-align: center; */ 32 | background: $introduce-ul-list-background; 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/assets/scss/components/page/login.scss: -------------------------------------------------------------------------------- 1 | .login { 2 | text-align: center; 3 | color: black; 4 | // position: relative; 5 | // top: 0; 6 | // bottom: 0; 7 | // width: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | .error-msg { 11 | @include error-msg(); 12 | } 13 | &-auth { 14 | // position: absolute; 15 | // top: 0; 16 | // bottom: 0; 17 | // width: 100%; 18 | // height: 500; 19 | flex: 1; 20 | background-color: $login-auth-background-color; 21 | &-form { 22 | margin-right: $user-info-label-width; 23 | } 24 | &-preInput { 25 | text-align: right; 26 | padding-right: 10px; 27 | width: $user-info-label-width; 28 | display: inline-block; 29 | vertical-align: top; 30 | } 31 | } 32 | &-user { 33 | width: $user-info-input-width; 34 | } 35 | &-password { 36 | width: $user-info-input-width; 37 | } 38 | } 39 | .forget{ 40 | &:hover { 41 | color: $login-hover-text-color; 42 | } 43 | } 44 | .required { 45 | color: #FF0000; 46 | } 47 | 48 | .login::before { 49 | @include login-background(); 50 | } 51 | -------------------------------------------------------------------------------- /src/assets/scss/components/page/modify.scss: -------------------------------------------------------------------------------- 1 | .modify { 2 | text-align: center; 3 | color: black; 4 | // position: relative; 5 | // top: 0; 6 | // bottom: 0; 7 | // width: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | &-auth { 11 | // position: absolute; 12 | // top: 0; 13 | // bottom: 0; 14 | //height: 500; 15 | flex: 1; 16 | // width: 100%; 17 | background-color: $login-auth-background-color; 18 | &-form { 19 | margin-right: $user-info-label-width; 20 | } 21 | &-preInput { 22 | text-align: right; 23 | padding-right: 10px; 24 | width: $user-info-label-width; 25 | display: inline-block; 26 | vertical-align: top; 27 | } 28 | } 29 | .error-msg { 30 | @include error-msg(); 31 | } 32 | &-user { 33 | width: $user-info-input-width; 34 | } 35 | &-password { 36 | width: $user-info-input-width; 37 | } 38 | &-checkpwd { 39 | width: $user-info-input-width; 40 | } 41 | } 42 | .required { 43 | color: #FF0000; 44 | } 45 | .forget{ 46 | &:hover { 47 | color: $login-hover-text-color; 48 | } 49 | } 50 | 51 | .modify::before { 52 | @include login-background(); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/assets/scss/components/page/record.scss: -------------------------------------------------------------------------------- 1 | .whole { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .sider-reco { 7 | flex: 1; 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .reco { 14 | /* display: inline-block; */ 15 | // float: right; 16 | // position: relative; 17 | flex: 1; 18 | text-align: center; 19 | margin-right: $record-margin-right; 20 | // width: $user-record-view; 21 | color: green; 22 | h1 { 23 | color: $record-title-text-color; 24 | } 25 | .error-msg { 26 | @include error-msg(10px); 27 | } 28 | } 29 | 30 | .search_input { 31 | width: 100%; 32 | } 33 | 34 | .search_button { 35 | width: 47.5%; 36 | } 37 | 38 | .reset_button { 39 | width: 47.5%; 40 | } -------------------------------------------------------------------------------- /src/assets/scss/components/page/register.scss: -------------------------------------------------------------------------------- 1 | .register { 2 | text-align: center; 3 | color: black; 4 | // position: absolute; 5 | // top: 0; 6 | // bottom: 0; 7 | // width: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | &-auth { 11 | // position: absolute; 12 | // top: 0; 13 | // bottom: 0; 14 | // width: 100%; 15 | flex: 1; 16 | // height: 500; 17 | background-color: $login-auth-background-color; 18 | &-form { 19 | margin-right: $user-info-label-width; 20 | } 21 | &-preInput { 22 | text-align: right; 23 | padding-right: 10px; 24 | width: $user-info-label-width; 25 | display: inline-block; 26 | vertical-align: top; 27 | } 28 | } 29 | .error-msg { 30 | @include error-msg(); 31 | } 32 | &-user { 33 | width: $user-info-input-width; 34 | } 35 | &-password { 36 | width: $user-info-input-width; 37 | } 38 | &-checkpwd { 39 | width: $user-info-input-width; 40 | } 41 | } 42 | .required { 43 | color: #FF0000; 44 | } 45 | 46 | .register::before { 47 | @include login-background(); 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/scss/components/page/upload.scss: -------------------------------------------------------------------------------- 1 | .whole { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .sider-vqa1 { 7 | flex: 1; 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | } 12 | 13 | .vqa1 { 14 | /* display: inline-block; */ 15 | // float: right; 16 | // position: relative; 17 | flex: 1; 18 | text-align: center; 19 | margin-right: $upload-margin-right; 20 | // width: $vqa-upload-view; 21 | color: green; 22 | .h2 { 23 | font-size: $upload-title-size; 24 | font-weight: bold; 25 | } 26 | } 27 | 28 | #form1 { 29 | .upload_file { 30 | margin-top: $upload-image-margin-top; 31 | width: $upload-image-file; 32 | 33 | } 34 | .ques_input { 35 | margin-top: $upload-ques-margin-top; 36 | width: $upload-ques-width; 37 | background: #dddddd; 38 | border: none; 39 | font-size: 1.3em; 40 | font-weight: 700; 41 | outline: none; 42 | border-radius: 30px; 43 | } 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/assets/scss/components/sider.scss: -------------------------------------------------------------------------------- 1 | .sider { 2 | // display: inline-block; 3 | // position: relative; 4 | width: $side-whole-menu; 5 | /* height: 100%; */ 6 | &-wrap { 7 | width: $side-wrap-view; 8 | &-content { 9 | width: $side-wrap-content; 10 | } 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/assets/scss/components/toptitle.scss: -------------------------------------------------------------------------------- 1 | .nav { 2 | // position: relative; 3 | height: $top-title-height; 4 | background: $top-title-background; 5 | &-title { 6 | // display: inline-block; 7 | /* float: left; */ 8 | display: flex; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | &-name { 12 | text-align: center; 13 | width: $top-title-width; 14 | color: $top-title-text-color; 15 | line-height: $top-title-height; // 文字高度居中对齐 16 | font-size: $top-title-font-size; 17 | } 18 | &-link { 19 | // float: right; 20 | flex: 1; 21 | text-align: right; 22 | color: $top-title-text-color; 23 | line-height: $top-title-height; 24 | margin-right: $top-title-margin-right; 25 | font-size: $top-clock-font-size; 26 | a { 27 | color: $top-title-text-color; 28 | &:hover { 29 | color: $record-title-text-color; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | .modal { 37 | text-align: center 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/assets/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | 3 | // 4 | // All components and pages(containers) 5 | // 6 | @import "./components/page"; 7 | @import "./components/calendar"; 8 | @import "./components/logo"; 9 | @import "./components/sider"; 10 | @import "./components/toptitle"; 11 | @import "./components/foot"; 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/scss/variables.scss: -------------------------------------------------------------------------------- 1 | // 2 | // define Variables or style function 3 | // 4 | // @extend .another_className 继承其它选择器风格 5 | 6 | @mixin error-msg($msg-padding-top: 0px) { 7 | color: $error-message-hint; 8 | padding-left: 10px; 9 | padding-top: $msg-padding-top; 10 | } 11 | 12 | @mixin login-background($background-pic: '', $img-opacity: 0.5) { 13 | // background-image: url( $background-pic ); '../pic/bg.png' 14 | background-color: $background-pic; 15 | background-size: cover; 16 | content: ""; 17 | /*-webkit-filter: opacity(50%); 18 | filter: opacity(50%); */ 19 | opacity: $img-opacity; 20 | z-index: -1; 21 | width: 100%; 22 | height: 100%; 23 | position: absolute; 24 | top: 0px; 25 | left: 0px; 26 | } 27 | 28 | // color 29 | $error-message-hint: #f50; 30 | $access-response-background: lightgoldenrodyellow; 31 | $top-title-background: RGB(61,68,76); 32 | $introduce-title-text-color: RGB(0,165,241); 33 | $introduce-ul-list-background: lightgoldenrodyellow; 34 | $introduce-card-background: #ECECEC; 35 | $record-title-text-color: MediumTurquoise; 36 | $wechat-mini-title-text-color: #000000; 37 | $login-auth-background-color: RGB(240,240,240); 38 | $footer-background-color: RGB(42,47,53); 39 | $footer-info-text-color: RGB(255,255,255); 40 | $top-title-link-ahover: #ffff00; 41 | $top-title-text-color: #ffffff; 42 | $login-hover-text-color: rgb(36, 190, 36); 43 | 44 | // font 45 | $upload-title-size: 1.6em; 46 | $top-clock-font-size: 1.3em; 47 | $top-title-font-size: 1.8em; 48 | $introduce-list-font-size: 1.3em; 49 | $access-response-ans-font-size: 1.1em; 50 | $access-image-title-font-size: 1.7em; 51 | $introduce-title-text-size: 1.6em; 52 | $login-foot-text-size: 1.2em; 53 | 54 | // height 55 | $top-title-height: 60px; 56 | 57 | // width 58 | $user-info-input-width: 290px; //20%; 59 | $user-info-label-width: 150px; 60 | $login-title-text-width: 370px; 61 | 62 | $vqa-introduce-view: 60%; 63 | $user-record-view: 64.6%; 64 | 65 | $upload-ques-width: 40%; 66 | $upload-image-file: 30%; 67 | $vqa-upload-view: 58.6%; 68 | 69 | $vqa-access-view: 63.6%; 70 | $access-image-width: 50%; 71 | $access-response-width: 50%; 72 | 73 | $side-whole-menu: 26.4%; 74 | $side-wrap-view: 100%; 75 | $side-wrap-content: 55%; 76 | 77 | $top-title-width: 70.5%; 78 | $date-calendar-view: 64%; 79 | $mini-side-calendar: 20%; 80 | 81 | $record-search-input: 100%; 82 | $record-search-button: 47.5%; 83 | $record-reset-button: 47.5%; 84 | 85 | $record-echarts-view: 73%; 86 | 87 | // margin 88 | $access-empty-margin-left: 28%; 89 | 90 | $record-margin-right: 9%; 91 | $upload-margin-right: 15%; 92 | $introduce-margin-right: 13.6%; 93 | $access-margin-right: 10%; 94 | $top-title-margin-right: 1%; 95 | $date-calendar-margin-right: 9.6%; 96 | $echarts-margin-right: 0.6%; 97 | 98 | $upload-image-margin-top: 20px; 99 | $upload-ques-margin-top: 10px; 100 | $top-clock-margin-top: 10px; 101 | $access-view-margin-top: 40px; 102 | $access-response-margin-top: 120px; 103 | $wechat-app-margin-top: 80px; 104 | $user-echarts-margin-top: 90px; 105 | 106 | // padding 107 | $introduce-card-padding: 30px; 108 | 109 | -------------------------------------------------------------------------------- /src/assets/sodagreen.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryWisdom/react-vqa-master/46a24a66ffdef76cebbbc90ce3668626230f52c2/src/assets/sodagreen.mp3 -------------------------------------------------------------------------------- /src/components/calendar.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { Calendar } from 'antd' 3 | 4 | function onPanelChange(value, mode) { 5 | console.log(value, mode); 6 | } 7 | 8 | class SideCalendar extends Component { 9 | constructor(props) { 10 | super(props); 11 | } 12 | render() { 13 | return ( 14 | // width: 300, 15 |
16 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | export default SideCalendar 26 | -------------------------------------------------------------------------------- /src/components/foot.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { 3 | AUTHOR, 4 | FOOTER, 5 | GITHUB, 6 | ICP, 7 | APP_GITHUB, 8 | ICP_address, 9 | VQA 10 | } from "../constant/constants"; 11 | import appScan from '../assets/pic/app_scan.jpg' 12 | import sodagreen from "../assets/sodagreen.mp3" //assets文件先import 13 | import { 14 | Icon, 15 | } from 'antd' 16 | 17 | class LOGINFOOT extends Component{ 18 | componentDidMount() { 19 | var audio = document.getElementById('music'); 20 | audio.pause();//打开页面时无音乐 21 | } 22 | play_audio = () => { 23 | var audio = document.getElementById('music'); 24 | if (audio.paused) { 25 | audio.play(); 26 | }else{ 27 | audio.pause(); 28 | // audio.currentTime = 0; //音乐从头播放 29 | } 30 | } 31 | 32 | render(){ 33 | return ( 34 |
35 |
36 |
37 | 42 |
43 |
44 |

  微信扫一扫 BlindVQA 小程序

45 |

体验视觉问答的多模态智能交互

46 |
47 |
48 | 114 |
115 | ) 116 | } 117 | } 118 | 119 | export default LOGINFOOT 120 | 121 | -------------------------------------------------------------------------------- /src/components/logo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LogoImg from '../assets/pic/vqa.png' 3 | 4 | class Logo extends React.Component{ 5 | render(){ 6 | return ( 7 |
8 | 12 |
13 | ) 14 | } 15 | } 16 | 17 | export default Logo -------------------------------------------------------------------------------- /src/components/sider.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { 3 | Menu, 4 | Icon, 5 | // Switch 6 | } from 'antd'; 7 | import { 8 | withRouter, 9 | Link 10 | } from 'react-router-dom' 11 | import { connect } from "react-redux" 12 | import PropTypes from 'prop-types' 13 | import { 14 | logoutSubmit, 15 | handleSetMode, 16 | handleSetTheme, 17 | search_by_user 18 | } from '../actions/actionCreators' 19 | // import SideCalendar from '../components/calendar' 20 | const { SubMenu } = Menu; 21 | 22 | class Sider extends Component { 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | collapsed: false, 27 | }; 28 | } 29 | componentDidMount() { 30 | if(document.body.clientWidth < 500 ) { //宽度不够时将导航栏折叠 31 | this.setState({ 32 | collapsed: true 33 | }) 34 | } 35 | } 36 | 37 | handleLogout = () => { 38 | this.props.logoutSubmit() 39 | } 40 | handleSearchRecord = () => { 41 | const { user } = this.props; 42 | this.props.search_by_user({'user': user}) 43 | } 44 | 45 | handleChangeMode = value => { 46 | let nowmode = value ? 'vertical' : 'inline' 47 | this.props.handleSetMode({ 48 | 'mode': nowmode 49 | }) 50 | }; 51 | handleChangeTheme = value => { 52 | let nowtheme = value ? 'dark' : 'light' 53 | this.props.handleSetTheme({ 54 | 'theme': nowtheme 55 | }) 56 | }; 57 | 58 | render() { 59 | const { 60 | user, 61 | // mode, 62 | theme 63 | } = this.props; 64 | return ( 65 |
66 |
67 | {/* 切换导航
68 | */} 69 | {/* 70 |  切换导航背景 */}
71 | 80 | 81 | 82 | { user }  欢迎您 83 | 84 | 85 | 86 | 87 | 退出登录 88 | 93 | 94 | 95 | 96 | 97 | 我的日历 98 | 102 | 103 | 104 | 108 | 109 | 系统管理 110 | 111 | } 112 | > 113 | 114 | 115 | VQA主页 116 | 120 | 121 | 122 | 123 | 124 | 问答结果 125 | 129 | 130 | 131 | 132 | 技术栈介绍 133 | 137 | 138 | 139 | 140 | 144 | 145 | 用户管理 146 | 147 | } 148 | > 149 | 150 | 151 | 查询记录 152 | 157 | 158 | 159 | 160 | 161 | 图表分析 162 | 167 | 168 | 169 | 170 | 注册新用户 171 | 176 | 177 | 178 | 179 | 修改密码 180 | 185 | 186 | 187 | 188 | 189 |
190 | {/* */} 191 |
192 | ); 193 | } 194 | } 195 | Sider.propTypes = { 196 | user: PropTypes.string.isRequired, 197 | mode: PropTypes.string.isRequired, 198 | theme: PropTypes.string.isRequired 199 | } 200 | 201 | const mapStateToProps = (state) => { 202 | return { 203 | user: state.vqa_user.user, 204 | mode: state.side_menu.mode, 205 | theme: state.side_menu.theme 206 | } 207 | } 208 | 209 | export default withRouter( 210 | connect( 211 | mapStateToProps, { 212 | logoutSubmit, 213 | handleSetMode, 214 | handleSetTheme, 215 | search_by_user 216 | })(Sider) 217 | ); -------------------------------------------------------------------------------- /src/components/toptitle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | Icon, 4 | Modal 5 | } from 'antd' 6 | // import { Link } from 'react-router-dom' 7 | import { 8 | APP_GITHUB, 9 | MINI_GITHUB, 10 | VQA 11 | } from '../constant/constants' 12 | import AppImg from '../assets/pic/wechat.jpg' 13 | 14 | class Toptitle extends React.Component{ 15 | constructor(props){ 16 | super(props); 17 | var time = new Date() 18 | function PrefixInteger(num, length) { 19 | // 规定从何处开始选取,如果是负数,那么从数组尾部开始算起的位置,-2指倒数第二个元素 20 | return (Array(length).join('0') + num).slice(-length); 21 | } 22 | this.state = { //先初始化再设置timer计时 23 | year: PrefixInteger(time.getFullYear(), 4), 24 | month: PrefixInteger(time.getMonth()+1, 2), // 1月为0 25 | day: PrefixInteger(time.getDate(), 2), 26 | hour : PrefixInteger(time.getHours(), 2), 27 | minute : PrefixInteger(time.getMinutes(), 2), 28 | second : PrefixInteger(time.getSeconds(), 2), 29 | visible: false 30 | } 31 | } 32 | componentDidMount(){ 33 | this.timer = setInterval( ()=>this.GetTime(), 1000 ) // 每秒刷新一次时钟 34 | } 35 | 36 | GetTime = () => { 37 | var time = new Date() 38 | function PrefixInteger(num, length) { 39 | // 规定从何处开始选取,如果是负数,那么从数组尾部开始算起,-2指倒数第二个元素 40 | return (Array(length).join('0') + num).slice(-length); 41 | } 42 | this.setState({ 43 | year: PrefixInteger(time.getFullYear(), 4), 44 | month: PrefixInteger(time.getMonth()+1, 2), 45 | day: PrefixInteger(time.getDate(), 2), 46 | hour : PrefixInteger(time.getHours(), 2), 47 | minute : PrefixInteger(time.getMinutes(), 2), 48 | second : PrefixInteger(time.getSeconds(), 2), 49 | }) 50 | } 51 | 52 | showModal = () => { 53 | this.setState({ 54 | visible: true, 55 | }); 56 | }; 57 | 58 | handleOk = e => { 59 | console.log(e); 60 | this.setState({ 61 | visible: false, 62 | }); 63 | }; 64 | handleCancel = e => { 65 | this.setState({ 66 | visible: false, 67 | }); 68 | }; 69 | 70 | render(){ 71 | const { 72 | year, 73 | month, 74 | day, 75 | hour, 76 | minute, 77 | second 78 | } = this.state 79 | return ( 80 |
81 |
82 |
83 | 面向盲人辅助的 BlindVQA 视觉问答系统 84 |
85 |
86 | 91 | 关于VQA 92 |      93 | 97 | 微信小程序 98 | 99 |      100 | 105 | Android客户端 106 |      107 | 108 |  {hour}:{minute}:{second} 109 | {/*  {year}年{month}月{day}日 */} 110 |
111 |
112 | 118 |

119 | 融合语音合成、语音识别和翻译,实现图像问答的多模态智能交互  120 | 124 | 128 | 129 |

130 |
131 | 小程序仍在完善中~ 138 |
139 |
140 |
141 | ) 142 | } 143 | } 144 | 145 | export default Toptitle 146 | 147 | 148 | 149 | // class App extends React.Component { 150 | // state = { 151 | // current: 'mail', 152 | // }; 153 | 154 | // handleClick = e => { 155 | // console.log('click ', e); 156 | // this.setState({ 157 | // current: e.key, 158 | // }); 159 | // }; 160 | 161 | // render() { 162 | // return ( 163 | // 164 | // 165 | // 166 | // Navigation One 167 | // 168 | // 169 | // 170 | // Navigation Two 171 | // 172 | // 175 | // 176 | // Navigation Three - Submenu 177 | // 178 | // } 179 | // > 180 | // 181 | // Option 1 182 | // Option 2 183 | // 184 | // 185 | // Option 3 186 | // Option 4 187 | // 188 | // 189 | // 190 | // 191 | // Navigation Four - Link 192 | // 193 | // 194 | // 195 | // ); 196 | // } 197 | // } -------------------------------------------------------------------------------- /src/constant/constants.js: -------------------------------------------------------------------------------- 1 | // action操作常量 2 | export const INPUT_QUES = 'INPUT_QUES' 3 | export const SET_IMAGE_URL = 'SET_IMAGE_URL' 4 | export const SET_ANSWER = 'SET_ANSWER' 5 | export const CLEAR_INPUT_QUES = 'CLEAR_INPUT_QUES' 6 | 7 | //登录页页脚和图标链接 8 | export const AUTHOR = '1085031736@qq.com' 9 | export const GITHUB = 'https://github.com/JerryWisdom/react-vqa-master' 10 | export const VQA = 'https://visualqa.org/' 11 | export const ICP = '赣ICP备20001362号-1' 12 | export const FOOTER = 'Copyright © 2020. ALL RIGHT RESERVED.' 13 | export const LOGIN_TITLE = '欢迎使用 BlindVQA 视觉问答系统 ' 14 | export const APP_GITHUB = 'https://github.com/JerryWisdom/Multifunctional_APP_with_computer_vision' 15 | export const MINI_GITHUB = 'https://github.com/JerryWisdom/BlindVQA' 16 | export const ICP_address = 'http://www.beian.miit.gov.cn/state/outPortal/loginPortal.action' 17 | 18 | // 导航侧栏风格设置 19 | export const CHANGE_MODE = 'CHANGE_MODE' 20 | export const CHANGE_THEME = 'CHANGE_THEME' 21 | 22 | //用户信息操作,包括登录、注册、修改、访问记录 23 | export const REGI_SUCCESS= 'REGI_SUCCESS' 24 | export const AUTH_SUCCESS = 'AUTH_SUCCESS' 25 | export const MODI_SUCCESS = 'MODI_SUCCESS' 26 | export const LOAD_DATA = 'LOAD_DATA' 27 | export const ERROR_MSG = 'ERROR_MSG' 28 | export const LOGOUT = 'LOGOUT' 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/containers/datetime.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Calendar, 3 | // Alert 4 | } from 'antd'; 5 | // import moment from 'moment'; 6 | import React, {Component} from 'react' 7 | import { withRouter } from 'react-router-dom' 8 | import Sider from '../components//sider' 9 | import Toptitle from '../components/toptitle' 10 | 11 | class MyCalendar extends Component { 12 | onPanelChange = (value, mode) => { 13 | console.log(value, mode) 14 | }; 15 | 16 | render() { 17 | return ( 18 |
19 | 20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | export default withRouter(MyCalendar) -------------------------------------------------------------------------------- /src/containers/echartsAnalysis.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | withRouter, 4 | } from 'react-router-dom' 5 | import { connect }from "react-redux"; 6 | import Toptitle from '../components/toptitle' 7 | import Sider from '../components/sider' 8 | import echarts from 'echarts' 9 | import PropTypes from 'prop-types' 10 | 11 | class MyCharts extends Component { 12 | componentDidMount() { 13 | const myBar = echarts.init(document.getElementById("bar")) 14 | const myPie = echarts.init(document.getElementById("pie")) 15 | const { 16 | user, 17 | rec, 18 | // nums 19 | } = this.props; 20 | 21 | var ques = ['what', 'what color', 'how many', 'yes/no', 'else'] 22 | var ans = ['unanswerable', 'yes', 'no', 'black', 'blue', 'keyboard', 'else'] 23 | var ques_cnt = [] // new Array() 24 | var ans_cnt = [] 25 | 26 | function prepro_data(obj) { 27 | let what_cnt = 0; 28 | let what_color_cnt = 0; 29 | let how_many_cnt = 0; 30 | let is_not_cnt = 0; 31 | let else_ques_cnt = 0; 32 | let unanswer_cnt = 0; 33 | let yes_cnt = 0; 34 | let no_cnt = 0; 35 | let black_cnt = 0; 36 | let blue_cnt = 0; 37 | let keyboard_cnt = 0; 38 | let else_ans_cnt = 0; 39 | 40 | for(let item of rec) { 41 | console.log(item) 42 | let eachques = item.vqa_ques; 43 | let eachans = item.vqa_ans; 44 | if (eachques.includes('what is') || eachques.includes('What is') 45 | || eachques.includes('What\'s') || eachques.includes('what\'s')) { 46 | what_cnt = what_cnt + 1 47 | } 48 | else if (eachques.includes('what color') || eachques.includes('What color')) { 49 | what_color_cnt = what_color_cnt + 1 50 | } 51 | else if (eachques.includes('how many') || eachques.includes('How many')) { 52 | how_many_cnt = how_many_cnt + 1 53 | } 54 | else if (eachques.slice(0,2)==="Is" || eachques.slice(0,2)==="is" 55 | || eachques.slice(0,3)==="Are" || eachques.slice(0,3)==="are") { 56 | is_not_cnt = is_not_cnt + 1 57 | } 58 | else { 59 | else_ques_cnt = else_ques_cnt + 1 60 | } 61 | if (eachans.includes('unanswerable')) { 62 | unanswer_cnt = unanswer_cnt + 1 63 | } 64 | else if (eachans.includes('yes')) { 65 | yes_cnt = yes_cnt + 1 66 | } 67 | else if (eachans.includes('no')) { 68 | no_cnt = no_cnt + 1 69 | } 70 | else if (eachans.includes('black')) { 71 | black_cnt = black_cnt + 1 72 | } 73 | else if (eachans.includes('blue')) { 74 | blue_cnt = blue_cnt + 1 75 | } 76 | else if (eachans.includes('keyboard')) { 77 | keyboard_cnt = keyboard_cnt + 1 78 | } 79 | else { 80 | else_ans_cnt = else_ans_cnt + 1 81 | } 82 | } 83 | ques_cnt = [what_cnt, what_color_cnt, how_many_cnt, is_not_cnt, else_ques_cnt] 84 | ans_cnt = [unanswer_cnt, yes_cnt, no_cnt, black_cnt, blue_cnt, keyboard_cnt, else_ans_cnt] 85 | } 86 | prepro_data(rec) 87 | 88 | myBar.setOption({ 89 | title: { 90 | text: `用户 ${user} VQA 历史回答分析`, 91 | // subtext: '' 92 | }, 93 | tooltip: { 94 | trigger: 'axis' 95 | }, 96 | // legend: { 97 | // data: ['answer'] 98 | // }, 99 | toolbox: { 100 | show: true, 101 | feature: { 102 | dataView: { show: true, readOnly: false }, 103 | magicType:{ show: true, type:['line', 'bar'] }, 104 | restore: { show: true }, 105 | saveAsImage: { show: true } 106 | } 107 | }, 108 | xAxis:[ 109 | { 110 | type: 'category', 111 | data: ans 112 | } 113 | ], 114 | yAxis: [{ 115 | type: 'value' 116 | }], 117 | series: [ 118 | { 119 | name: 'answer', 120 | type: 'bar', 121 | data: ans_cnt 122 | } 123 | ] 124 | }) 125 | 126 | var pie_data = [] 127 | var index = 0 128 | for(let item of ques_cnt){ 129 | let ques_obj = {} 130 | ques_obj['value'] = item 131 | ques_obj['name'] = ques[index] 132 | pie_data.push(ques_obj) 133 | index = index + 1 134 | } 135 | myPie.setOption({ 136 | title: { 137 | text: `VQA 历史问题分析`, 138 | // subtext: '', 139 | x: 'center' 140 | }, 141 | tooltip: { 142 | trigger: 'item', 143 | formatter: "{a} {b} : {c} ({d}%)" 144 | }, 145 | toolbox: { 146 | show: true, 147 | feature: { 148 | dataView: { show: true, readOnly: false }, 149 | restore: { show: true }, 150 | saveAsImage: { show: true } 151 | } 152 | }, 153 | legend: { 154 | orient: 'vertical', 155 | left: 'left', 156 | data: ques 157 | }, 158 | series: [{ 159 | name: 'question', 160 | type: 'pie', 161 | radius: '55%', 162 | center: ['50%', '60%'], 163 | data: pie_data, 164 | itemStyle: { 165 | emphasis: { 166 | shadowBlur: 10, 167 | shadowOffsetX: 0, 168 | shadowColor: 'rgba(0,0,0,0.5)' 169 | } 170 | } 171 | }] 172 | }) 173 | } 174 | 175 | render() { 176 | return ( 177 |
178 |
179 |
180 | 181 |
182 |
187 |
188 |
193 |
194 |
195 |
196 |
197 | ); 198 | } 199 | } 200 | // 验证组件中的参数类型 201 | MyCharts.propTypes = { 202 | user: PropTypes.string.isRequired, 203 | rec: PropTypes.array.isRequired, 204 | nums: PropTypes.number.isRequired 205 | } 206 | 207 | const mapStateToProps = (state) => { 208 | return (state => state.vqa_user) 209 | } 210 | 211 | export default withRouter( 212 | connect( 213 | mapStateToProps, 214 | ) 215 | (MyCharts) 216 | ); -------------------------------------------------------------------------------- /src/containers/introduce.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { 3 | withRouter, 4 | // Link 5 | } from 'react-router-dom' 6 | import { 7 | Tooltip, 8 | // Icon, 9 | Card, 10 | Col, 11 | Row 12 | } from 'antd' 13 | import Toptitle from '../components/toptitle' 14 | import Sider from '../components/sider' 15 | import 'antd/dist/antd.css' 16 | 17 | class Introduce extends Component { 18 | 19 | render () { 20 | return ( 21 |
22 | 23 |
24 |
25 | 26 |
27 |

28 | BlindVQA 是帮助盲人与外界基于图片进行视觉交互问答的平台  29 |

30 | {/* 以卡片的形式呈现介绍 */} 31 |
32 | 33 | 34 | 35 |
    36 | 40 |
  • React.js
  • 41 |

    42 | 43 | 47 |
  • react-router
  • 48 |

    49 | 50 | 54 |
  • Redux
  • 55 |

    56 | 57 | 61 |
  • axios后台交互
  • 62 |

    63 | 64 | 68 |
  • antd
  • 69 |

    70 | 71 | 75 |
  • Sass
  • 76 |

    77 | 78 | 82 |
  • Echarts
  • 83 |
    84 |
85 |
86 | 87 | 88 | 89 | 90 |
    91 | 95 |
  • Flask框架
  • 96 |

    97 | 98 | 102 |
  • MySQL数据库
  • 103 |

    104 | 105 | 109 |
  • SQLAlchemy
  • 110 |

    111 | 112 | 116 |
  • Pytorch框架
  • 117 |

    118 | 119 | 123 |
  • 深度学习模型
  • 124 |
    125 |
126 |
127 | 128 | 129 | 130 | 131 |
    132 | 136 |
  • Nginx代理服务器
  • 137 |

    138 | 139 | 143 |
  • Docker容器化
  • 144 |

    145 | 146 | 150 |
  • Frp内网渗透
  • 151 |

    152 | 153 | 157 |
  • uWSGI服务器
  • 158 |
    159 |
160 |
161 | 162 |
163 |
164 |
165 |
166 |
167 | ) 168 | } 169 | } 170 | 171 | export default withRouter(Introduce) 172 | -------------------------------------------------------------------------------- /src/containers/record.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | withRouter, 4 | } from 'react-router-dom' 5 | import { connect } from "react-redux" 6 | import { 7 | Table, 8 | Input, 9 | message, 10 | Button, 11 | Icon 12 | } from 'antd' 13 | import { search_by_user } from '../actions/actionCreators' 14 | import PropTypes from 'prop-types' 15 | import Toptitle from '../components/toptitle' 16 | import Sider from '../components/sider' 17 | import Highlighter from 'react-highlight-words' 18 | import 'antd/dist/antd.css' 19 | 20 | class Record extends Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | data: [], 25 | loading: false, 26 | search: false, 27 | searchText: '', 28 | searchedColumn: '', 29 | } 30 | } 31 | 32 | // 列内查询并展示结果 33 | getColumnSearchProps = (dataIndex) => ({ 34 | filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( 35 |
36 | { 39 | this.searchInput = node; 40 | }} 41 | placeholder={`Search ${dataIndex}`} 42 | value={selectedKeys[0]} 43 | onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])} 44 | onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)} 45 | style={{ marginBottom: 8, display: 'block' }} 46 | // width: 188, 47 | /> 48 | 59 | 67 |
68 | ), 69 | filterIcon: filtered => ( 70 | 71 | ), 72 | onFilter: (value, record) => 73 | record[dataIndex] 74 | .toString() 75 | .toLowerCase() 76 | .includes(value.toLowerCase()), 77 | onFilterDropdownVisibleChange: visible => { 78 | if (visible) { 79 | setTimeout(() => this.searchInput.select()); 80 | } 81 | }, 82 | render: text => 83 | this.state.searchedColumn === dataIndex ? ( 84 | 90 | ) : ( 91 | text 92 | ), 93 | }); 94 | 95 | // 列内查询操作 96 | handleSearch = (selectedKeys, confirm, dataIndex) => { 97 | confirm(); 98 | this.setState({ 99 | searchText: selectedKeys[0], 100 | searchedColumn: dataIndex, 101 | }); 102 | }; 103 | 104 | // 清楚查询操作 105 | handleReset = clearFilters => { 106 | clearFilters(); 107 | this.setState({ searchText: '' }); 108 | }; 109 | 110 | // 查询后台数据库中用户访问记录 111 | handleRecordSearch = () => { 112 | this.setState({ loading: true }); 113 | const { 114 | user, 115 | nums, 116 | rec, 117 | msg 118 | } = this.props 119 | this.props.search_by_user({'user': user}) 120 | const pagination = { ...this.state.pagination }; 121 | pagination.total = nums 122 | this.setState({ 123 | loading: false, 124 | search: true, 125 | data: rec, 126 | pagination, 127 | }); 128 | if(msg){ 129 | message.error(msg) 130 | } 131 | } 132 | 133 | handleTableChange = (pagination, filters, sorter, extra) => { // Chrome-F12中打印信息 134 | console.log('params', pagination, filters, sorter, extra) 135 | } 136 | 137 | render () { 138 | const { 139 | user, 140 | nums 141 | } = this.props 142 | const columns = [ 143 | { 144 | title: '名称', 145 | dataIndex: 'user', // 此处应和后端数据库表的对应列名一致 146 | key: 'user', 147 | width: '10%' 148 | }, 149 | { 150 | title: '访问时间', 151 | dataIndex: 'date', 152 | key: 'date', 153 | sorter: (a,b) => { 154 | var date_a = new Date(a.date) 155 | var date_b = new Date(b.date) 156 | var da = date_a.getTime() 157 | var db = date_b.getTime() //日期字符串转换为时间戳排序 158 | return da > db ? 1 : -1; 159 | }, 160 | width: '20%', 161 | defaultSortOrder: 'descend', 162 | ...this.getColumnSearchProps('date'), 163 | }, 164 | { 165 | title: '问题', 166 | dataIndex: 'vqa_ques', 167 | key: 'vqa_ques', 168 | width: '39%', 169 | ...this.getColumnSearchProps('vqa_ques'), 170 | }, 171 | { 172 | title: '回答', 173 | dataIndex: 'vqa_ans', 174 | key: 'vqa_ans', 175 | width: '16%', 176 | ...this.getColumnSearchProps('vqa_ans'), 177 | }, 178 | { 179 | title: '响应时间(秒)', 180 | dataIndex: 'interaction', 181 | key: 'interaction', 182 | width: '15%', 183 | sorter: (a,b) => a.interaction-b.interaction, 184 | // sorter: true, 185 | defaultSortOrder: 'ascend' 186 | } 187 | ]; 188 | return ( 189 |
190 |
191 |
192 | 193 |
194 |

195 |  用户 {user} 的访问记录   196 | 202 |    203 | { this.state.search ? `共 ${nums} 条` : null } 204 |

205 | `${range[0]}-${range[1]} of ${nums} items`} 212 | /> 213 | 214 | 215 | 216 | ) 217 | } 218 | } 219 | // 验证组件中的参数类型 220 | Record.propTypes = { 221 | user: PropTypes.string.isRequired, 222 | rec: PropTypes.array.isRequired, 223 | nums: PropTypes.number.isRequired, 224 | msg: PropTypes.string.isRequired 225 | } 226 | 227 | const mapStateToProps = (state) => { 228 | return (state => state.vqa_user) 229 | } 230 | 231 | export default withRouter(connect( 232 | mapStateToProps, 233 | { search_by_user } 234 | )(Record) 235 | ) -------------------------------------------------------------------------------- /src/containers/user/login.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import Logo from '../../components/logo' 3 | import { 4 | Input, 5 | Button, 6 | Icon, 7 | message, 8 | Checkbox, 9 | //List,Form,Card,Switch,Radio,Mentions,slider,Select,Cascader,TreeSlectDataPicker,Transfer 10 | } from 'antd' 11 | import { 12 | LOGIN_TITLE 13 | } from '../../constant/constants' 14 | import { 15 | withRouter, 16 | Link 17 | } from 'react-router-dom' 18 | import { connect } from 'react-redux' 19 | import { login } from '../../actions/actionCreators' 20 | import LOGINFOOT from "../../components/foot" 21 | import PropTypes from 'prop-types' 22 | 23 | //设置cookie 24 | function setCookie(name, value, day) { 25 | var date = new Date(); 26 | date.setDate(date.getDate() + day); 27 | document.cookie = name + '=' + value + ';expires=' + date; 28 | } 29 | //获取cookie 30 | function getCookie(name) { 31 | var reg = RegExp(name + '=([^;]+)'); 32 | var arr = document.cookie.match(reg); 33 | if (arr) { 34 | return arr[1]; 35 | } else { 36 | return ''; 37 | } 38 | } 39 | //删除cookie 40 | function delCookie(name) { 41 | setCookie(name, null, -1); 42 | } 43 | 44 | class Login extends Component{ 45 | constructor(props){ 46 | super(props) 47 | this.state = { 48 | 'user': '', 49 | 'pwd': '', 50 | ischecked: false 51 | } 52 | this.register = this.register.bind(this) 53 | this.handleLogin = this.handleLogin.bind(this) 54 | this.handleChange1 = this.handleChange1.bind(this) 55 | this.handleChange2 = this.handleChange2.bind(this) 56 | } 57 | onCheckboxChange = (e) => { 58 | if (!e.target.checked) { 59 | delCookie('username'); 60 | delCookie('password'); 61 | } 62 | else { 63 | this.setState({ 64 | ischecked: true 65 | }) 66 | } 67 | } 68 | 69 | register(){ 70 | this.props.history.push('/register') 71 | } 72 | handleLogin(){ 73 | const { redirectTo } = this.props 74 | const { user, pwd, ischecked } = this.state 75 | this.props.login(this.state) 76 | if( redirectTo && redirectTo!=='/login') { 77 | message.success(`登录成功,当前用户为 ${this.props.user}`) 78 | this.props.history.push(redirectTo) 79 | if(ischecked) { 80 | setCookie('username', user, 7); 81 | setCookie('password', pwd, 7); //保存密码到cookie,有效期7天 82 | } 83 | } 84 | } 85 | handleChange1(val) { 86 | if(!val){ 87 | document.getElementById('username').innerHTML = getCookie('username') 88 | this.setState({ 89 | 'user': getCookie('username') 90 | }) 91 | } 92 | else{ 93 | this.setState({ 94 | 'user': val 95 | }) 96 | } 97 | } 98 | handleChange2(val) { 99 | if(!val){ 100 | document.getElementById('password').innerHTML = getCookie('password') 101 | this.setState({ 102 | 'pwd': getCookie('password') 103 | }) 104 | } 105 | else{ 106 | this.setState({ 107 | 'pwd': val 108 | }) 109 | } 110 | } 111 | 112 | render(){ 113 | const { msg } = this.props 114 | return ( 115 |
116 |
117 | 118 |

119 | { LOGIN_TITLE } 120 |

121 | {/* label为表单标签,用for绑定输入框的id,再在css中设置宽度和左/右对齐 122 | 还可以设置tr内嵌td制表对齐内容,} 128 | id='username' 129 | className='login-user' 130 | placeholder="请输入用户名" 131 | allowClear 132 | onChange={v=>this.handleChange1(v.target.value)} 133 | /> 134 |

135 |

136 | 137 | } 139 | id='password' 140 | className='login-password' 141 | placeholder="请输入密码" 142 | allowClear 143 | onChange={v=>this.handleChange2(v.target.value)} 144 | /> 145 |

146 | 147 |

148 | 记住密码 149 |                    150 | 151 | 忘记密码? 152 | 153 |


154 | 155 |          161 | 162 | 168 |




169 |
170 | 171 |
172 | ) 173 | } 174 | } 175 | // 验证组件中的参数类型 176 | Login.propTypes = { 177 | redirectTo: PropTypes.string.isRequired, 178 | msg: PropTypes.string.isRequired, 179 | user: PropTypes.string.isRequired, 180 | pwd: PropTypes.string.isRequired 181 | } 182 | 183 | export default withRouter(connect( 184 | state=>state.vqa_user, 185 | {login} 186 | )(Login)) 187 | 188 | 189 | 190 | // 如果使用Form组件登录 191 | // const FormItem = Form.Item; 192 | // class FormLogin extends React.Component{ 193 | // handleSubmit = ()=>{ 194 | // let userInfo = this.props.form.getFieldsValue(); 195 | // this.props.form.validateFields((err,values)=>{ 196 | // if(!err){ 197 | // message.success(`${userInfo.userName}欢迎您 ,当前密码为:${userInfo.userPwd}`) 198 | // } 199 | // }) 200 | // } 201 | // render(){ 202 | // const { getFieldDecorator } = this.props.form; 203 | // return ( 204 | //
205 | // 206 | //
207 | // 208 | // { 209 | // getFieldDecorator('userName',{ 210 | // initialValue:'', 211 | // rules:[ 212 | // { 213 | // required:true, 214 | // message:'用户名不能为空' 215 | // }, 216 | // { 217 | // min:5,max:10, 218 | // message:'长度不在范围内' 219 | // }, 220 | // { 221 | // pattern:new RegExp('^\\w+$','g'), 222 | // message:'用户名必须为字母或者数字' 223 | // } 224 | // ] 225 | // })( 226 | // } placeholder="请输入用户名" /> 227 | // ) 228 | // } 229 | // 230 | // 231 | // { 232 | // getFieldDecorator('userPwd', { 233 | // initialValue: '', 234 | // rules: [] 235 | // })( 236 | // } type="password" placeholder="请输入密码" /> 237 | // ) 238 | // } 239 | // 240 | // 241 | // { 242 | // getFieldDecorator('remember', { 243 | // valuePropName:'checked', 244 | // initialValue: true 245 | // })( 246 | // 记住密码 247 | // ) 248 | // } 249 | // 忘记密码 250 | // 251 | // 252 | // 253 | // 254 | // 255 | //
256 | //
257 | // ); 258 | // } 259 | // } 260 | // export default Form.create()(FormLogin); -------------------------------------------------------------------------------- /src/containers/user/modify.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import Logo from '../../components/logo' 3 | import { 4 | List, 5 | Input, 6 | Button, 7 | Icon 8 | } from 'antd' 9 | import { connect } from 'react-redux' 10 | import { 11 | withRouter, 12 | Link 13 | } from 'react-router-dom' 14 | import { 15 | LOGIN_TITLE, 16 | } from '../../constant/constants' 17 | import { modify } from '../../actions/actionCreators' 18 | import LOGINFOOT from "../../components/foot" 19 | import PropTypes from 'prop-types' 20 | 21 | class Modify extends Component{ 22 | constructor(props){ 23 | super(props); 24 | this.state = { 25 | 'user': '', 26 | 'pwd': '', 27 | 'repeatpwd': '' 28 | } 29 | this.handleModify = this.handleModify.bind(this) 30 | this.login = this.login.bind(this) 31 | this.handleChange1 = this.handleChange1.bind(this) 32 | this.handleChange2 = this.handleChange2.bind(this) 33 | this.handleChange3 = this.handleChange3.bind(this) 34 | } 35 | 36 | login(){ 37 | this.props.history.push('/login') 38 | } 39 | handleModify(){ 40 | // console.log(this.state) 41 | this.props.modify(this.state) 42 | } 43 | 44 | handleChange1(val) { //不是箭头函数就需要声明.bind(this) 45 | this.setState({ 46 | 'user': val 47 | }) 48 | } 49 | handleChange2(val) { 50 | this.setState({ 51 | 'pwd': val 52 | }) 53 | } 54 | handleChange3(val) { 55 | this.setState({ 56 | 'repeatpwd': val 57 | }) 58 | } 59 | 60 | render(){ 61 | const { msg } = this.props 62 | return ( 63 |
64 |
65 | {/* {redirectTo && redirectTo!=='/login' ? :null} */} 66 | 67 |

68 | { LOGIN_TITLE } 69 |

70 | { msg ?

{ msg }

: null } 71 |

72 | 73 | } 75 | id='username' 76 | className='modify-user' 77 | placeholder="请输入用户名" 78 | allowClear 79 | onChange={v=>this.handleChange1(v.target.value)} 80 | />

81 | 82 |

83 | 84 | } 86 | id='password' 87 | className='modify-password' 88 | placeholder="请输入新密码" 89 | allowClear 90 | onChange={v=>this.handleChange2(v.target.value)} 91 | />

92 | 93 |

94 | 95 | } 97 | id='password' 98 | className='modify-checkpwd' 99 | placeholder="请确认新密码" 100 | allowClear 101 | onChange={v=>this.handleChange3(v.target.value)} 102 | />

103 | 104 | {/*
*/} 105 | 111 | 112 |            113 | 114 | 返回注册 115 | 116 |

117 | 118 | 124 |




125 |
126 | 127 |
128 | ) 129 | } 130 | } 131 | // 验证组件中的参数类型 132 | Modify.propTypes = { 133 | redirectTo: PropTypes.string.isRequired, 134 | msg: PropTypes.string.isRequired, 135 | user: PropTypes.string.isRequired, 136 | pwd: PropTypes.string.isRequired 137 | } 138 | 139 | export default withRouter(connect( 140 | state => state.vqa_user, 141 | {modify} 142 | )(Modify)) -------------------------------------------------------------------------------- /src/containers/user/register.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import Logo from '../../components/logo' 3 | import { 4 | List, 5 | Input, 6 | Button, 7 | Icon 8 | } from 'antd' 9 | import { connect } from 'react-redux' 10 | import { 11 | withRouter 12 | } from 'react-router-dom' 13 | import { 14 | LOGIN_TITLE, 15 | } from '../../constant/constants' 16 | import { register } from '../../actions/actionCreators' 17 | import LOGINFOOT from "../../components/foot" 18 | import PropTypes from 'prop-types' 19 | 20 | class Register extends Component{ 21 | constructor(props){ 22 | super(props); 23 | this.state = { 24 | 'user': '', 25 | 'pwd': '', 26 | 'repeatpwd': '' 27 | } 28 | this.handleRegister = this.handleRegister.bind(this) 29 | this.login = this.login.bind(this) 30 | this.handleChange1 = this.handleChange1.bind(this) 31 | this.handleChange2 = this.handleChange2.bind(this) 32 | this.handleChange3 = this.handleChange3.bind(this) 33 | } 34 | login(){ 35 | this.props.history.push('/login') 36 | } 37 | handleRegister(){ 38 | // console.log(this.state) 39 | this.props.register(this.state) 40 | } 41 | handleChange1(val) { 42 | this.setState({ 43 | 'user': val 44 | }) 45 | } 46 | handleChange2(val) { 47 | this.setState({ 48 | 'pwd': val 49 | }) 50 | } 51 | handleChange3(val) { 52 | this.setState({ 53 | 'repeatpwd': val 54 | }) 55 | } 56 | 57 | render(){ 58 | const { msg } = this.props 59 | return ( 60 |
61 |
62 | 63 |

64 | { LOGIN_TITLE } 65 |

66 | { msg ?

{ msg }

: null } 67 |

68 | 69 | } 71 | id='username' 72 | className='register-user' 73 | placeholder="请输入用户名" 74 | allowClear 75 | onChange={v=>this.handleChange1(v.target.value)} 76 | />

77 | 78 |

79 | 80 | } 82 | id='password' 83 | className='register-password' 84 | placeholder="请输入密码" 85 | allowClear 86 | onChange={v=>this.handleChange2(v.target.value)} 87 | />

88 | 89 |

90 | 91 | } 93 | id='password' 94 | className='register-checkpwd' 95 | placeholder="请确认密码" 96 | allowClear 97 | onChange={v=>this.handleChange3(v.target.value)} 98 | />

99 | 100 | {/*
*/} 101 | 107 |

108 | 114 |




115 |
116 | 117 |
118 | ) 119 | } 120 | } 121 | // 验证组件中的参数类型 122 | Register.propTypes = { 123 | redirectTo: PropTypes.string.isRequired, 124 | msg: PropTypes.string.isRequired, 125 | user: PropTypes.string.isRequired, 126 | pwd: PropTypes.string.isRequired 127 | } 128 | 129 | export default withRouter(connect( 130 | state => state.vqa_user, 131 | {register} 132 | )(Register)) -------------------------------------------------------------------------------- /src/containers/vqa/access.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { 3 | withRouter 4 | } from 'react-router-dom' 5 | import { connect } from 'react-redux' 6 | import PropTypes from 'prop-types' 7 | import { 8 | Result, 9 | Icon, 10 | Typography, 11 | Rate, 12 | // message 13 | } from 'antd' 14 | import Toptitle from '../../components/toptitle' 15 | import Sider from '../../components/sider' 16 | const { Paragraph } = Typography; 17 | 18 | const desc = ['terrible', 'bad', 'normal', 'good', 'wonderful']; 19 | 20 | class Access extends Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | value: 3, 25 | }; 26 | } 27 | handleChange = value => { 28 | this.setState({ 29 | value 30 | }); 31 | }; 32 | 33 | render () { 34 | const { 35 | InputValue, 36 | answer, 37 | image_url 38 | } = this.props 39 | const { value } = this.state 40 | const success = ( 41 |
42 |
43 |

44 | 48 |  已上传的图片 49 |

50 | oh, 图片不见了~~ 57 |
58 |
59 |
60 |

61 | 针对图片提出的问题:
62 | 66 | {InputValue} 67 | 68 |

69 |

70 | 回答: 71 | 75 | {answer} 76 | 77 |

78 |

79 |

80 | 请给结果评分吧~ 81 |

82 | 83 | 88 | {value ? {desc[value - 1]} : ''} 89 | 90 |
91 |
92 | ) 93 | const failure = ( 94 |
95 | 101 |
102 | ) 103 | return ( 104 |
105 | 106 |
107 |
108 | 109 | {/* 选择的视图要被完整div包围,margin-right是div外的,right是距离右内边距离 */} 110 | { answer ? success : failure } 111 |
112 |
113 | ) 114 | } 115 | } 116 | // 验证组件中的参数类型 117 | Access.propTypes = { 118 | InputValue: PropTypes.string.isRequired, 119 | image_url: PropTypes.string.isRequired, 120 | answer: PropTypes.string.isRequired 121 | } 122 | 123 | const getElements = (state) => { 124 | return { 125 | InputValue: state.update.InputValue, 126 | answer: state.update.answer, 127 | image_url: state.update.image_url 128 | } 129 | } 130 | 131 | export default withRouter( 132 | connect(getElements)(Access) 133 | ) 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /src/containers/vqa/upload.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import axios from 'axios' //前端ajax插件 3 | import { withRouter } from 'react-router-dom' 4 | import { connect } from "react-redux" 5 | import { 6 | // Upload, 7 | Input, 8 | Button, 9 | Icon, 10 | Spin, 11 | message, 12 | // Modal 13 | } from 'antd' 14 | import { 15 | handleGetInputValue, 16 | handleSetImageUrl, 17 | handleSetAnswer, 18 | handleClearInques 19 | } from '../../actions/actionCreators' 20 | import PropTypes from 'prop-types' 21 | import Sider from '../../components/sider' 22 | import Toptitle from '../../components/toptitle' 23 | import 'antd/dist/antd.css' 24 | 25 | class VQAUpload extends Component { 26 | constructor(props) { 27 | super(props); 28 | this.state = { 29 | loading: false 30 | } 31 | } 32 | handleInputQues = (e) => { 33 | const { handleGetInputValue } = this.props 34 | handleGetInputValue(e.target.value) 35 | } 36 | handleClear = () => { 37 | const { handleClearInques } = this.props 38 | handleClearInques() 39 | } 40 | 41 | handleGetImage = () => { 42 | var file = document.getElementById("upload_image").files[0]; 43 | var reads = new FileReader() 44 | reads.readAsDataURL(file); //把文件对象读成base64,读完直接放到src中 45 | const { handleSetImageUrl } = this.props 46 | reads.onload = (e) => { 47 | document.getElementById('img').src = e.target.result 48 | handleSetImageUrl(e.target.result) 49 | }; 50 | } 51 | 52 | handlePost = () => { 53 | var _this = this; 54 | const { 55 | user, 56 | handleSetAnswer 57 | } = this.props; 58 | let q = document.getElementById("ques").value 59 | let i = document.getElementById("upload_image").files[0] 60 | if(q && !i) { 61 | message.error("未选择图片!") 62 | } 63 | else if(i && !q) { 64 | message.error("未输入问题!") 65 | } 66 | else if(!i && !q) { 67 | message.error("请先选择图片并输入问题!") 68 | } 69 | else { 70 | var iq_form = document.getElementById("form1") 71 | var form_data = new FormData(iq_form) 72 | form_data.append("username", user) 73 | this.setState({ 74 | loading: true 75 | }) 76 | message.loading('图片和问题已上传,请稍后~'); //message.info 77 | axios.post('/api', form_data, { 78 | headers: { 79 | 'Access-Control-Allow-Origin':'*', //解决cors头问题, 不同端口存在跨域问题,使用proxy代理 80 | 'Access-Control-Allow-Credentials':'true', //解决session问题 81 | 'Content-Type': 'application/form-data; charset=UTF-8' //将表单数据传递转化为form-data类型 82 | }, 83 | withCredentials : true 84 | }).then(response => { 85 | handleSetAnswer(response.data['res']) 86 | _this.props.history.push({ 87 | pathname: '/access' // state: { ... } 跳转组件用this.props.location.state.xxx获取 88 | }) 89 | message.success("获取问答结果成功~") 90 | }).catch( error => { 91 | // alert(error) 92 | console.log(error) 93 | message.error("服务器出错!") 94 | this.setState({ 95 | loading: false 96 | }) 97 | }); 98 | } 99 | } 100 | 101 | render () { 102 | const { 103 | InputValue, 104 | image_url, 105 | // answer 106 | } = this.props 107 | const { loading } = this.state 108 | return ( 109 |
110 |
111 |
112 | 113 |
114 |

115 | 点击拍照或上传本地图片 116 |

117 |
123 | 130 |
134 | ~~请选择图片~~ 141 |

142 |

143 | 请输入针对上传图片的问题: 144 |

145 | 154 |
155 | 161 |       162 | 168 |

169 | { InputValue && image_url && loading ? : null } 170 |
171 |
172 |
173 | ) 174 | } 175 | } 176 | // 验证组件中的参数类型 177 | VQAUpload.propTypes = { 178 | InputValue: PropTypes.string.isRequired, 179 | image_url: PropTypes.string.isRequired, 180 | answer: PropTypes.string.isRequired 181 | } 182 | 183 | const mapStateToProps = (state) => { // (state => state.update) 184 | return { 185 | InputValue: state.update.InputValue, 186 | image_url: state.update.image_url, 187 | answer: state.update.answer, 188 | user: state.vqa_user.user 189 | } 190 | } 191 | 192 | export default withRouter( 193 | connect( 194 | mapStateToProps, { 195 | handleGetInputValue, 196 | handleSetImageUrl, 197 | handleClearInques, 198 | handleSetAnswer 199 | } 200 | )(VQAUpload) 201 | ) 202 | 203 | 204 | // ant design:UI组件参考 https://ant.design/docs/react/introduce-cn 205 | // react-redux详细文档:https://www.redux.org.cn/docs/react-redux/api.html 206 | // connect第一个参数建立一个从外部的state对象到UI组件的props对象的映射关系 207 | // URL.createObjectURL(file)得到本地内存容器的URL地址,方便预览,多次使用需要注意手动释放内存的问题,性能优秀 208 | // var URL = window.URL || window.webkitURL; // 如果当前document消失,比如页面被刷新,URL就失效了 209 | // _this.setState({ 210 | // image_url: URL.createObjectURL(file) 211 | // }) 212 | // FileReader.readAsDataURL(file)胜在直接转为base64格式,可以直接用于业务,无需二次转换格式 213 | // 注意将 px 换算成各层内外 div 的包含比例,以适应页面伸缩 214 | 215 | // import { bindActionCreators } from 'redux' 216 | // const mapDispatchToProps = (dispatch) => { 217 | // return { 218 | // handleGetInputValue: bindActionCreators(handleGetInputValue, dispatch), 219 | // handleSetImageUrl: bindActionCreators(handleSetImageUrl, dispatch), 220 | // handleSetAnswer: bindActionCreators(handleSetAnswer, dispatch) 221 | // } 222 | // } 223 | // const params = { 224 | // name: 'image', 225 | // onChange: this.handleGetImage.bind(this) 226 | // } ...params -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.scss'; 4 | import * as serviceWorker from './serviceWorker'; 5 | import RouterConfig from './Router/RouterConfig' 6 | import 'antd/dist/antd.css'; 7 | import { Provider } from "react-redux"; 8 | import store from './store/store' 9 | import './assets/scss/index.scss' 10 | 11 | import { persistStore } from 'redux-persist' 12 | import { PersistGate } from 'redux-persist/integration/react' 13 | const persistor = persistStore(store) 14 | 15 | ReactDOM.render( 16 | 17 | 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | 27 | serviceWorker.unregister(); 28 | 29 | // If you want your app to work offline and load faster, you can change unregister() to register() below. 30 | // Note this comes with some pitfalls. Learn more about service workers: https://bit.ly/CRA-PWA 31 | 32 | // 在项目目录下,运行npm run build,进行打包,打包完成后会在目录下生成一个build文件夹,build生成的这些东西要放在服务器root下 33 | // 部署的时候你可以把build里的文件直接放到服务器的根路径下,比如,你的服务器IP是47.96.134.256,应用服务器端口为8080, 34 | // 这样就保证http://47.96.134.256:8080这种访问方式,访问到的是你的build下的文件。 35 | // 如果你希望以http://47.96.134.256:8080/build/index.htm这种方式访问应用 36 | // 那么你可以在package.json文件中增加一个homepage字段:"homepage": "." 或 "/" 37 | 38 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | min-width: 1300px; //这样缩小或打开调试页面不影响界面显示 4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | code { 10 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 11 | } -------------------------------------------------------------------------------- /src/redux/menu.js: -------------------------------------------------------------------------------- 1 | import { 2 | CHANGE_MODE, 3 | CHANGE_THEME 4 | } from "../constant/constants"; 5 | 6 | const initState={ 7 | mode: 'inline', //inline 8 | theme: 'light' 9 | } 10 | 11 | const side_menu = (state=initState, action) => { 12 | switch(action.type){ 13 | case CHANGE_MODE: 14 | return {...state, ...action.payload } 15 | case CHANGE_THEME: 16 | return {...state, ...action.payload } 17 | default: 18 | return state 19 | } 20 | } 21 | 22 | export default side_menu 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' // 利用combineReducers 合并reducers 2 | import { routerReducer } from 'react-router-redux' // 将routerReducer一起合并管理 3 | import { persistReducer } from 'redux-persist' //使redux和store管理的数据持久化,刷新页面数据不丢失 4 | import storage from 'redux-persist/lib/storage' 5 | 6 | import update from './update' 7 | import vqa_user from './user' 8 | import side_menu from './menu' 9 | 10 | const combine_reducer = combineReducers({ 11 | update, 12 | side_menu, 13 | vqa_user, 14 | routing: routerReducer 15 | }) 16 | 17 | const reducer = persistReducer({ 18 | key: 'root', 19 | storage 20 | }, combine_reducer) 21 | 22 | export default reducer -------------------------------------------------------------------------------- /src/redux/update.js: -------------------------------------------------------------------------------- 1 | import { 2 | INPUT_QUES, 3 | CLEAR_INPUT_QUES, 4 | SET_IMAGE_URL, 5 | SET_ANSWER 6 | } from '../constant/constants'; 7 | 8 | // 初始化state数据 9 | const initialState = { 10 | InputValue: "", 11 | image_url: "", 12 | answer: "" 13 | } 14 | 15 | // 通过dispatch action进入 16 | const update = (state = initialState, action) => { 17 | // 根据不同的action type进行state的更新 18 | switch(action.type) { 19 | case INPUT_QUES: 20 | return Object.assign({}, state, { InputValue: action.ques }) 21 | case SET_IMAGE_URL: 22 | return Object.assign({}, state, { image_url: action.imgurl }) 23 | case SET_ANSWER: 24 | return Object.assign({}, state, { answer: action.ans }) 25 | case CLEAR_INPUT_QUES: 26 | return Object.assign({}, state, { InputValue: '' }) 27 | default: 28 | return state 29 | } 30 | } 31 | 32 | export default update -------------------------------------------------------------------------------- /src/redux/user.js: -------------------------------------------------------------------------------- 1 | import { 2 | REGI_SUCCESS, 3 | MODI_SUCCESS, 4 | AUTH_SUCCESS, 5 | LOAD_DATA, 6 | ERROR_MSG, 7 | LOGOUT 8 | } from "../constant/constants"; 9 | 10 | const initState={ 11 | redirectTo:'/login', 12 | msg:'', 13 | user:'', 14 | pwd:'', 15 | nums: 0, 16 | rec: [] 17 | } 18 | 19 | const vqa_user = (state=initState, action) => { 20 | switch(action.type){ 21 | case AUTH_SUCCESS: 22 | return {...state, msg:'', redirectTo:'/upload', ...action.payload} //后面的为...state重新部分赋值 23 | case REGI_SUCCESS: 24 | return {...state, msg:'注册成功,请返回登录!', ...action.payload} 25 | case MODI_SUCCESS: 26 | return {...state, msg:'修改密码成功,请返回登录!', ...action.payload} 27 | case ERROR_MSG: 28 | return {...state, msg:action.msg} 29 | case LOGOUT: 30 | return {...initState, redirectTo:'/login', pwd:''} 31 | case LOAD_DATA: 32 | return {...state, msg:'', ...action.payload} 33 | default: 34 | return state 35 | } 36 | } 37 | 38 | export default vqa_user 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready.then(registration => { 134 | registration.unregister(); 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const proxy = require('http-proxy-middleware') // 代理中间件 2 | // 第一个参数为要代理的路由,第二参数中target为代理后的请求网址,changeOrigin是否改变请求头 3 | 4 | const Nginx_https_deploy = 'https://hducsrao.xyz:8880' 5 | // const Nginx_https_deploy = 'http://localhost:8880' 6 | 7 | module.exports = function(app) { 8 | app.use( 9 | proxy('/api', { 10 | target: Nginx_https_deploy, 11 | changeOrigin:true, 12 | pathRewrite:{ 13 | '^/api': '/' 14 | } 15 | }) 16 | ); 17 | app.use( 18 | proxy('/usr_login', { 19 | target: Nginx_https_deploy, 20 | changeOrigin:true, 21 | }) 22 | ); 23 | app.use( 24 | proxy('/usr_register', { 25 | target: Nginx_https_deploy, 26 | changeOrigin:true, 27 | }) 28 | ); 29 | app.use( 30 | proxy('/usr_modify', { 31 | target: Nginx_https_deploy, 32 | changeOrigin:true, 33 | }) 34 | ); 35 | app.use( 36 | proxy('/usr_record', { 37 | target: Nginx_https_deploy, 38 | changeOrigin:true, 39 | }) 40 | ) 41 | } -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | applyMiddleware, 4 | compose 5 | } from 'redux' 6 | import { routerMiddleware } from 'react-router-redux' 7 | import thunk from 'redux-thunk' 8 | // import createLogger from 'redux-logger' 9 | import reducer from '../redux/reducers' // 将reducer作为参数传给createStore创建store仓库 10 | import { 11 | // createHashHistory, 12 | createBrowserHistory 13 | } from 'history' 14 | 15 | // const browserHistory = createHashHistory(); 16 | const browserHistory = createBrowserHistory(); 17 | 18 | let routerWare = routerMiddleware(browserHistory); 19 | 20 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 21 | const store = createStore( 22 | reducer, 23 | composeEnhancers( 24 | applyMiddleware( 25 | thunk, 26 | routerWare 27 | ) 28 | ) 29 | ); 30 | 31 | export default store; --------------------------------------------------------------------------------