Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 5c42a22aa4
Bump url-parse from 1.5.1 to 1.5.10 in /fir_admin 3 years ago
  1. 5
      .gitignore
  2. 207
      README.md
  3. 12
      build.sh
  4. 7
      build_client.sh
  5. 102
      doc/docker.md
  6. BIN
      doc/images/img_1.png
  7. BIN
      doc/images/img_10.png
  8. BIN
      doc/images/img_11.png
  9. BIN
      doc/images/img_2.png
  10. BIN
      doc/images/img_3.png
  11. BIN
      doc/images/img_4.png
  12. BIN
      doc/images/img_5.png
  13. BIN
      doc/images/img_6.png
  14. BIN
      doc/images/img_7.png
  15. BIN
      doc/images/img_8.png
  16. BIN
      doc/images/img_9.png
  17. 166
      doc/local.md
  18. 51
      docker.md
  19. 67
      docker/build/docker-compose.yml
  20. 31
      docker/flyapps/docker-compose.only.yml
  21. 48
      docker/flyapps/docker-compose.yml
  22. 7
      docker/init/clean.sh
  23. 10
      docker/init/download.images
  24. 24
      docker/init/init.sh
  25. 1
      docker/init/timezone
  26. 43
      docker/mariadb/docker-compose.yml
  27. 108
      docker/mariadb/server.cnf
  28. 25
      docker/redis/docker-compose.yml
  29. 6
      docker/scripts/build.sh
  30. 9
      docker/scripts/start_all.sh
  31. 10
      docker/scripts/stop_all.sh
  32. 2
      fir_admin/.env.production
  33. 8
      fir_admin/src/api/user.js
  34. 20
      fir_admin/src/views/appinfos/list.vue
  35. 2
      fir_admin/src/views/login/index.vue
  36. 84
      fir_admin/src/views/userinfos/UserDetail.vue
  37. 5
      fir_admin/src/views/userinfos/list.vue
  38. 54
      fir_admin/src/views/wxbind/Detail.vue
  39. 17
      fir_admin/src/views/wxbind/list.vue
  40. 12
      fir_admin/yarn.lock
  41. 47
      fir_client/README.md
  42. 3
      fir_client/package.json
  43. 6
      fir_client/public/index.html
  44. 6
      fir_client/public/short.html
  45. BIN
      fir_client/src/assets/github.png
  46. BIN
      fir_client/src/assets/wxchat.jpg
  47. 210
      fir_client/src/components/FirDownload.vue
  48. 14
      fir_client/src/components/FirFooter.vue
  49. 6
      fir_client/src/components/FirHeader.vue
  50. 138
      fir_client/src/components/FirLogin.vue
  51. 27
      fir_client/src/components/FirRegist.vue
  52. 23
      fir_client/src/components/FirResetPwd.vue
  53. 210
      fir_client/src/components/ShortDownload.vue
  54. 55
      fir_client/src/components/apps/FirAppInfosBase.vue
  55. 5
      fir_client/src/components/apps/FirAppInfosbaseinfo.vue
  56. 489
      fir_client/src/components/apps/FirAppInfossecurity.vue
  57. 50
      fir_client/src/components/apps/FirAppInfossupersign.vue
  58. 43
      fir_client/src/components/apps/FirAppInfostimeline.vue
  59. 72
      fir_client/src/components/apps/FirApps.vue
  60. 26
      fir_client/src/components/base/AppleDeveloperBindApp.vue
  61. 47
      fir_client/src/components/base/BindDomain.vue
  62. 15
      fir_client/src/components/index/FirContact.vue
  63. 4
      fir_client/src/components/index/FirIndexBody.vue
  64. 9
      fir_client/src/components/index/FirIndexTem.vue
  65. 16
      fir_client/src/components/index/FirNews.vue
  66. 1003
      fir_client/src/components/user/FirSuperSignBase.vue
  67. 13
      fir_client/src/components/user/FirUserAdvert.vue
  68. 101
      fir_client/src/components/user/FirUserDomain.vue
  69. 470
      fir_client/src/components/user/FirUserDownload.vue
  70. 733
      fir_client/src/components/user/FirUserNotify.vue
  71. 50
      fir_client/src/components/user/FirUserOrders.vue
  72. 32
      fir_client/src/components/user/FirUserProfileCertification.vue
  73. 192
      fir_client/src/components/user/FirUserProfileInfo.vue
  74. 11
      fir_client/src/components/user/FirUserQrcode.vue
  75. 1214
      fir_client/src/components/user/FirUserStorage.vue
  76. 4
      fir_client/src/main.js
  77. 138
      fir_client/src/restful/download.js
  78. 702
      fir_client/src/restful/index.js
  79. 16
      fir_client/src/router/index.js
  80. 73
      fir_client/src/utils/base/utils.js
  81. 22
      fir_client/src/utils/format.js
  82. 76
      fir_client/src/utils/index.js
  83. 84
      fir_client/vue.config.js
  84. 37
      fir_ser/Dockerfile
  85. 11
      fir_ser/Dockerfile.cli
  86. 2
      fir_ser/admin/urls.py
  87. 66
      fir_ser/admin/utils/serializer.py
  88. 21
      fir_ser/admin/utils/utils.py
  89. 8
      fir_ser/admin/views/app.py
  90. 2
      fir_ser/admin/views/celery_flower.py
  91. 3
      fir_ser/admin/views/domain.py
  92. 34
      fir_ser/admin/views/login.py
  93. 6
      fir_ser/admin/views/order.py
  94. 14
      fir_ser/admin/views/storage.py
  95. 13
      fir_ser/admin/views/supersign.py
  96. 14
      fir_ser/admin/views/user.py
  97. 6
      fir_ser/api/admin.py
  98. 56
      fir_ser/api/base_views.py
  99. 14
      fir_ser/api/management/commands/add_user_download_times.py
  100. 13
      fir_ser/api/management/commands/create_weixin_menu.py
  101. Some files were not shown because too many files have changed in this diff Show More

5
.gitignore vendored

@ -9,8 +9,3 @@ fir_client/yarn.lock
fir_download/package-lock.json
fir_client/src/assets/down_left.png
fir_client/src/assets/down_right.png
mailhtml/build/
mailhtml/node_modules/
mailhtml/.idea/
fir_client/dist_index/
fir_client/dist_short/

@ -1,63 +1,144 @@
### 用与应用分发,苹果超级签名
#### 部署前准备
- 备案域名【至少需要一个域名,以下可通过子域名部署】
- API域名
- 前端web域名
- 下载页域名
- 下载页域名可配置多个
- 存储域名(使用阿里云oss存储)
- ssl证书
- API域名证书
- 存储域名证书(使用阿里云oss存储)
- 前端web域名证书(可选)
- Centos8Stream 服务器
- 如果使用oss存储,则带宽为1M,若使用本地存储,则带宽越大越好
- 如果使用超级签,最低配置为2cpu 4G内存,若干不使用签名,则1cpu2G就行
- 阿里云短信或极光短信服务【可选一个,主要用与注册,重置密码】
- 阿里云短信
- 极光短信
- 邮箱服务【可选,用与注册,重置密码,通知信息】
- 阿里云OSS存储【可选】
- [sts授权配置](https://help.aliyun.com/document_detail/100624.html)
- 阿里云CDN【可选,用与加速访问】
- 极验验证【可选,滑动验证服务】
- 微信公众号【可选,用与微信扫描登录】
- 阿里云支付【可选,用与购买下载次数】
- 微信支付【可选,用与购买下载次数】
#### 自用搭建建议
- 阿里云服务器需要1cpu 2G内存,无需系统盘,如果使用超级签,可以适当增加配置
- 需要阿里云OSS存储和阿里云CDN,并且OSS存储和阿里云服务器部署同一个地区
- 可以申请一个极验进行滑动验证,或者开启验证码验证
- 阿里云备案域名:api和前端可以使用一个域名,下载页单独域名
#### 部署必备资料
- 域名证书
- web域名和证书
- api域名和证书
- 下载页域名(可配置证书)
- 存储域名和证书
- 本地存储,则该域名和证书可以和api域名证书一致
- 阿里云oss存储
- 开启cdn,需要新域名和证书
- 不开启,无需域名和证书
- Centos8Stream 服务器
## 部署方式
### 1.[Docker 部署](./doc/docker.md) 【推荐】
### 2.[本地部署](./doc/local.md)
### 功能预览
![img_1.png](./doc/images/img_1.png)
![img_2.png](./doc/images/img_2.png)
![img_3.png](./doc/images/img_3.png)
![img_4.png](./doc/images/img_4.png)
![img_5.png](./doc/images/img_5.png)
![img_6.png](./doc/images/img_6.png)
![img_7.png](./doc/images/img_7.png)
![img_8.png](./doc/images/img_8.png)
![img_9.png](./doc/images/img_9.png)
![img_10.png](./doc/images/img_10.png)
![img_11.png](./doc/images/img_11.png)
##### 从git上面下载源码
```
cd /data/
git clone https://github.com/nineaiyu/FlyApps
```
#### 超级签名依赖部署
##### fastlane [相关文档](https://github.com/fastlane/fastlane/blob/master/spaceship/docs/DeveloperPortal.md)
```
# fastlane
#1 gem ruby安装
yum install gem -y
yum install ruby-devel -y
yum install gcc gcc-c++ -y
#gem修改为国内镜像
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
gem sources -l
#https://gems.ruby-china.com
#确保只有 gems.ruby-china.com
#2 更新rubby
gpg2 --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
#如果脚本执行失败,可以这样
# echo 199.232.68.133 raw.githubusercontent.com >> /etc/hosts
curl -L get.rvm.io | bash -s stable
source /etc/profile.d/rvm.sh
rvm list known
rvm install 2.7
#3 安装fastlane
gem install fastlane
gem install pry
# space 好像有点问题,需要把这个注释掉
vim /usr/local/rvm/gems/ruby-2.7.0/gems/fastlane-2.146.1/spaceship/lib/spaceship/base.rb +298
#v.gsub!("\n", "\n\t") # to align nested elements #注释掉,要不然会报错
```
##### isign
```
# python2.7
yum update python -y
pip install -U setuptools pip
wget https://github.com/nineaiyu/isign/archive/v1.8.0.tar.gz
tar xvf v1.8.0.tar.gz
cd isign-1.8.0/
python setup.py build
python setup.py install
```
### FlyApp 服务器搭建 [python3 环境]
##### 搭建python env 环境
```
yum install python36 python36-devel redis mariadb-server mariadb-devel -y
python3 -m venv py3
source py3/bin/activate
```
###### 安装pip包
```
cd FlyApps/fir_ser/
pip install -r requirements.txt
```
###### 配置数据库
```
在 settings.py 配置redis 和 mysql 数据库
```
###### 迁移数据库
```
#如果是mysql,需要做一下配置,如果是sqlite,需要升级sqlite
#记得根据配置创建数据库
#create database flyapp default character set utf8 COLLATE utf8_general_ci;
#grant all on flyapp.* to flyuser@'127.0.0.1' identified by 'flypwd00oo.1';
python manage.py makemigrations
python manage.py migrate
```
### FlyApp web端搭建
###### npm编译环境
```
yum install npm
npm install -g n
n latest
npm install -g yarn
```
###### 编译web端
```
cd FlyApps/fir_client/
vim src/restful/index.js #修改api接口地址
yarn install
yarn build
```
###### 编译下载页面
```
cd FlyApps/fir_download/
vim src/restful/download.js #修改api接口地址
yarn install
yarn build
```
###### web目录操作
```
# web目录: /www/wwwroot/fly.dvcloud.xin/
cd /www/wwwroot/fly.dvcloud.xin/
cp -a /data/FlyApps/fir_client/dist/* .
cp -a /data/FlyApps/fir_download/dist/* .
```
##### 更新sqllite
```
tar xvf sqlite-autoconf-3310100.tar.gz
cd sqlite-autoconf-3310100
./configure --prefix=/usr/local/sqlite
make -j4
make install
rm -rf /usr/bin/sqlite3
ln -s /usr/local/sqlite/bin/sqlite3 /usr/bin/sqlite3
ll /usr/bin/sqlite3
echo "/usr/local/sqlite/lib" > /etc/ld.so.conf.d/sqlite3.conf
ldconfig
```

@ -0,0 +1,12 @@
#!/bin/bash
#
#
# shellcheck disable=SC2006
s_path=`pwd`
#first build fir_client
docker pull node:14.17.3
\cp -a build_client.sh fir_client/
docker run --rm -v "${s_path}"/fir_client:/fir_client -it node:14.17.3 sh /fir_client/build_client.sh
cd "${s_path}"/fir_ser/ && docker build . -t flyapps

@ -0,0 +1,7 @@
#!/bin/bash
#
#
cd /fir_client/ && yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build

@ -1,102 +0,0 @@
### Docker最小化部署前准备,如果使用超级签和云存储,需要使用阿里云的服务器
系统|Centos 8 Stream 纯净系统 [2cpu 4G内存]
----|----
域名|```app.hehelucky.cn```
域名SSL证书| nginx 格式证书 [阿里云和腾讯云都可以申请免费ssl证书]
数据路径| ```/data```
### 开始部署
##### 1.从git上面下载源码
```
cd /data/
dnf install git -y
git clone https://github.com/nineaiyu/flyapps
```
## 注意,下面展示的相对路径,都是以```/data/flyapps```为相对目录
#### 2.docker环境安装
```
cd /data/flyapps/docker/init
sh init.sh
```
#### 3.配置域名和证书,如果有cdn或者oss,也要进行配置
a.将域名证书通过```sftp软件```或者```rz命令```复制到```/data/flyapps/nginx.conf.d```目录中
b.将域名证书分别重命名为 ```域名.pem``` 和 ```域名.key``` .本次使用的域名是 ```app.hehelucky.cn``` ,因此,证书名称类似如下
```shell script
nginx.conf.d/app.hehelucky.cn.key
nginx.conf.d/app.hehelucky.cn.pem
```
c.修改NGINX配置文件```nginx.conf.d/flyapps-vhost.conf```,将```server_name```字段修改为域名,将```ssl_certificate```和
```ssl_certificate_key```修改为对应证书路径,修改之后,对应字段如下
```shell
server_name app.hehelucky.cn;
ssl_certificate /etc/nginx/conf.d/app.hehelucky.cn.pem;
ssl_certificate_key /etc/nginx/conf.d/app.hehelucky.cn.key;
```
#### 4.配置api服务需要修改api和web域名,如果有需求,还可以配置 短信,邮箱,geetest,存储等信息
##### fir_ser配置文件 ```fir_ser/config.py```
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
```
##### fir_client配置文件 ```fir_client/vue.confjg.js```
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
##### 构建静态资源
```
cd /data/flyapps/docker/scripts
sh build.sh
```
##### 启动所有服务
```
cd /data/flyapps/docker/scripts
sh start_all.sh
```
##### 关闭所有服务
```
cd /data/flyapps/docker/scripts
sh stop_all.sh
```
##### 根据提示创建默认管理用户,用与访问管理后台
```shell
docker exec -it flyapps python manage.py createsuperuser
```
##### 测试访问
- 在浏览器输入自己配置的域名 https://app.hehelucky.cn/ 进行访问
- 管理后台访问 https://app.hehelucky.cn:3448/ 进行访问
##### 如果要使用本地的数据库和redis,api服务使用容器,需要修改文件```/data/flyapps/docker/flyapps/docker-compose.yml```
```shell
external_links:
- mariadb:mariadb
- redis:redis
```
修改为
```shell
extra_hosts:
- "mariadb:172.31.31.1"
- "redis:172.31.31.1"
```
并将```nginx:```下面的配置注释掉
同时还需要修改mariadb和redis授权

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

@ -1,166 +0,0 @@
### 本地部署前准备
系统 | Centos 8 Stream [2cpu 4G内存]
----|----
域名|```app.hehelucky.cn```
域名SSL证书| nginx 格式证书 [阿里云和腾讯云都可以申请免费ssl证书]
数据路径| ```/data```
##### 从git上面下载源码
```shell
cd /data/
dnf install git gcc zip unzip -y
git clone https://github.com/nineaiyu/flyapps
```
### flyapps 本地部署 [python3 环境]
##### 搭建python env 环境
```shell
dnf install python39 python39-devel redis mariadb-server mariadb-devel -y
python3.9 -m venv py39
source py39/bin/activate
```
###### 安装pip包
```shell
cd /data/flyapps/fir_ser/
pip install -U setuptools pip -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
```
#### 配置数据库
##### 启动mariadb数据库
```shell
systemctl restart mariadb
systemctl enable mariadb
```
##### 创建mysql数据库
```shell
mysql
```
执行下面mysql命令
```mariadb
create database flyapps default character set utf8 COLLATE utf8_general_ci;
grant all on flyapps.* to flyuser@'127.0.0.1' identified by 'KGzKjZpWBp4R4RSa';
```
##### 启动redis数据库
```shell
echo 'requirepass nineven' >> /etc/redis.conf # 配置授权密码
systemctl restart redis
systemctl enable redis
```
### flyapps zsign ipa重签名工具安装
```shell
dnf install openssl-devel -y
dnf install gcc-c++ gcc -y
cd /data/flyapps/fir_ser/
tar xvf zsign-1.1.2.tar.gz
cd zsign-1.1.2/
g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o /usr/bin/zsign
```
#### 配置api服务需要修改api和web域名,如果有需求,还可以配置 短信,邮箱,geetest,存储等信息
##### fir_ser配置文件 ```fir_ser/config.py```
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
```
##### fir_client配置文件 ```fir_client/vue.confjg.js```
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
### flyapps web端搭建
###### npm编译环境
```shell
dnf install npm -y
npm install -g n
n 12.13 # 安装12版本的node或者14版本 ,最新版本会有问题
npm install -g yarn
```
###### 编译web端和下载页
```shell
cd /data/flyapps/fir_client/
vim vue.config.js #修改api接口地址
# pro_base_env 正式环境信息
# dev_base_env 开发环境信息
yarn install
yarn build index # 前端打包,输出目录 dist_index
yarn build short # 下载页打包,输出目录 dist_short
```
###### 编译web管理后台
```shell
cd /data/flyapps/fir_admin/
yarn install
yarn build:prod # 下载页打包,输出目录 dist
```
### nginx配置
##### 安装nginx
```shell
dnf install nginx -y
```
##### 将域名ssl证书解压到```/etc/nginx/conf.d/``` 目录中,并重命名为```域名.pem```,```域名.key```格式
##### 拷贝虚拟主机配置文件
```shell
cp /data/flyapps/nginx.conf.d/flyapps-vhost.conf /etc/nginx/conf.d/
```
1.创建web目录,并将打包好的web复制该目录
```shell
mkdir -pv /data/{fir_client/short,fir_admin}
cp -a /data/flyapps/fir_client/dist_index/* /data/fir_client/
cp -a /data/flyapps/fir_client/dist_short/* /data/fir_client/short/
cp -a /data/flyapps/fir_admin/dist/* /data/fir_admin/
chown nginx.nginx -R /data/{fir_client,fir_admin}
```
2.启动nginx服务
```shell
nginx -t
systemctl restart nginx
systemctl enable nginx
```
##### 添加redis和mariadb uwsgi本地解析
```shell
echo '127.0.0.1 mariadb redis flyapps' >> /etc/hosts
```
###### 迁移数据库
```shell
cd /data/flyapps/fir_ser/
python manage.py makemigrations
python manage.py migrate
```
#### 启动api服务
```shell
echo 'net.core.somaxconn=1024' >> /etc/sysctl.conf
sysctl -p
cd /data/flyapps/fir_ser/
python manage.py start all -u nginx -usm 1 -d
```
##### 根据提示创建默认管理用户,用与访问管理后台
```shell
python manage.py createsuperuser
```
- 需要输入用户名,邮箱和密码
- 用户名和密码用与登录管理后台
- 邮箱和密码用与登录前端web

@ -0,0 +1,51 @@
##### 从git上面下载源码
```
cd /data/
git clone https://github.com/nineaiyu/FlyApps
```
#### docker环境安装 [centos7]
```
yum install epel-release -y
yum clean all && yum makecache
yum install docker -y
```
#### 配置域名和证书,如果有cdn或者oss,也要进行配置
```shell script
nginx.conf.d/app.hehelucky.cn.key
nginx.conf.d/app.hehelucky.cn.pem
nginx.conf.d/flyapps-vhost.conf
```
### api服务需要修改api和web域名,短信,邮箱,geetest,存储等信息
```shell script
vim fir_ser/config.py
API_DOMAIN = "https://app.hehelucky.cn"
WEB_DOMAIN = "https://app.hehelucky.cn"
MOBILEPROVISION = "https://ali-static.jappstore.com/embedded.mobileprovision"
```
### web页面需要修改指定api域名
```vuejs
vim fir_client/vue.config.js
const pro_base_env = {
baseUrl: 'https://flyapps.cn',
index_static: 'https://static.flyapps.cn/index/',
baseShortUrl: 'https://flyapps.top',
short_static: 'https://static.flyapps.top/short/',
};
```
##### 构建静态资源和api服务
```
sh build.sh
```
##### 启动所有服务
```
sh start.sh
```

@ -1,67 +0,0 @@
version: '3.2'
services:
buildflyapps:
image: 'flyapps'
build:
context: ../../fir_ser
dockerfile: Dockerfile
command:
- echo
- success
buildclient:
container_name: buildclient
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- bash
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build index && yarn build short && ([ ! -e /web/fir_client ] && \cp -a dist_index /web/fir_client || \cp -a dist_index/* /web/fir_client/) && ([ ! -e /web/fir_client/short ] && \cp -a dist_short /web/fir_client/short || \cp -a dist_short/* /web/fir_client/short/) && echo build success
buildindex:
container_name: buildindex
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- bash
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build index && ([ ! -e /web/fir_index ] && \cp -a dist_index /web/fir_index || \cp -a dist_index/* /web/fir_index/) && echo build success
buildshort:
container_name: buildshort
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- sh
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build short && ([ ! -e /web/fir_short ] && \cp -a dist_short /web/fir_short || \cp -a dist_short/* /web/fir_short/) && echo build success
buildadmin:
container_name: buildadmin
image: 'node:14.17.3'
working_dir: /fir_admin
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_admin:/fir_admin
- ../../data/web/:/web
command:
- sh
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build:prod && ([ ! -e /web/fir_admin ] && \cp -a dist /web/fir_admin || \cp -a dist/* /web/fir_admin/) && echo build success

@ -1,31 +0,0 @@
version: '3'
networks:
flyapps:
external: true
name: flyapps
services:
flyapps:
container_name: flyapps
restart: always
image: 'nineven/flyapps'
working_dir: /data/fir_ser
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_ser:/data/fir_ser
- ../../data/flyapps/files:/data/fir_ser/files
- ../../data/logs/flyapps/:/data/fir_ser/logs
- ../../data/flyapps/supersign/:/data/fir_ser/supersign
- ../../nginx.conf.d:/data/cert
networks:
flyapps:
ipv4_address: 172.31.31.100
external_links:
- mariadb:mariadb
- redis:redis
sysctls:
- net.core.somaxconn=4096
extra_hosts:
- "mariadb:172.31.31.1"
- "redis:172.31.31.1"

@ -1,48 +0,0 @@
version: '3'
networks:
flyapps:
external: true
name: flyapps
services:
flyapps:
container_name: flyapps
restart: always
image: 'nineven/flyapps'
working_dir: /data/fir_ser
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_ser:/data/fir_ser
- ../../data/flyapps/files:/data/fir_ser/files
- ../../data/logs/flyapps/:/data/fir_ser/logs
- ../../data/flyapps/supersign/:/data/fir_ser/supersign
- ../../nginx.conf.d:/data/cert
networks:
flyapps:
ipv4_address: 172.31.31.100
external_links:
- mariadb:mariadb
- redis:redis
sysctls:
- net.core.somaxconn=4096
nginx:
container_name: nginx
restart: always
image: 'nginx:1.21.3'
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../data/web:/data
- ../../nginx.conf.d:/etc/nginx/conf.d
- ../../data/logs/nginx:/var/log/nginx
networks:
flyapps:
ipv4_address: 172.31.31.200
ports:
- 80:80
- 443:443
- 3448:3448
external_links:
- flyapps:flyapps
depends_on:
- flyapps

@ -1,7 +0,0 @@
#!/bin/bash
#
#
for i in nginx flyapps mariadb redis buildclient buildshort buildadmin ;do echo $i;docker rm -f $i;done
docker network rm flyapps

@ -1,10 +0,0 @@
#!/bin/bash
#
#
docker pull 'bitnami/mariadb:10.7.3'
docker pull 'bitnami/redis:6.2.7'
docker pull 'nginx:1.21.3'
docker pull 'nineven/flyapps:latest'
docker pull 'node:14.17.3'

@ -1,24 +0,0 @@
#!/bin/bash
#
#
which dockerd
if [ $? -ne 0 ];then
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
dnf install docker-ce -y
fi
#which docker-compose
#if [ $? -ne 0 ];then
# curl -L https://get.daocloud.io/docker/compose/releases/download/v2.5.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
#fi
data_path="$(dirname $(dirname `pwd`))/data"
mkdir -pv ${data_path}/{flyapps/files,web,mariadb,redis,logs/{mariadb,nginx,flyapps}}
\cp $(dirname $(dirname `pwd`))/fir_ser/files/head_img.jpeg ${data_path}/flyapps/files/
chown 1001.1001 -R ${data_path}/{flyapps,web,mariadb,redis,logs/{mariadb,nginx,flyapps}}
chown 101.101 -R ${data_path}/{flyapps,logs/flyapps}
systemctl start docker && docker network create flyapps --driver bridge --subnet=172.31.31.0/24 --gateway=172.31.31.1
systemctl enable docker
systemctl status docker

@ -1 +0,0 @@
Asia/Shanghai

@ -1,43 +0,0 @@
version: '3.2'
networks:
flyapps:
external: true
name: flyapps
services:
mariadb:
image: bitnami/mariadb:10.7.3
container_name: mariadb
restart: always
environment:
- MARIADB_ROOT_PASSWORD=rootIPD.xx2.19
- MARIADB_DATABASE=flyapps
- MARIADB_USER=flyuser
- MARIADB_PASSWORD=KGzKjZpWBp4R4RSa
#- ALLOW_EMPTY_PASSWORD=yes
- MARIADB_ENABLE_SLOW_QUERY=1
- MARIADB_LONG_QUERY_TIME=3
- MARIADB_SKIP_TEST_DB=yes
- MARIADB_EXTRA_FLAGS=--max-connect-errors=3000 --max_connections=30000
ports:
- 3306:3306
networks:
flyapps:
ipv4_address: 172.31.31.90
volumes:
- ../init/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ../../data/mariadb:/bitnami/mariadb/data
- ../../data/logs/mariadb:/data/logs/mariadb
- ./server.cnf:/opt/bitnami/mariadb/conf/my_custom.cnf:ro
# adminer:
# image: adminer
# ports:
# - 8080:8080
# networks:
# - fungame

@ -1,108 +0,0 @@
#
# These groups are read by MariaDB server.
# Use it for options that only the server (but not clients) should see
#
# See the examples of server my.cnf files in /usr/share/mysql/
#
# this is only for the mysqld standalone daemon
[mysqld]
binlog_cache_size = 192K
thread_stack = 384K
join_buffer_size = 4096K
query_cache_type = 1
max_heap_table_size = 1024M
default_storage_engine = InnoDB
performance_schema_max_table_instances = 400
table_definition_cache = 400
skip-external-locking
key_buffer_size = 512M
max_allowed_packet = 1G
table_open_cache = 1024
sort_buffer_size = 2048K
net_buffer_length = 4K
read_buffer_size = 2048K
read_rnd_buffer_size = 1024K
myisam_sort_buffer_size = 16M
thread_cache_size = 192
query_cache_size = 256M
tmp_table_size = 1024M
sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
max_connections = 30000
max_connect_errors = 1000
open_files_limit = 65535
expire_logs_days = 10
#log_queries_not_using_indexes=on
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
init_connect='SET NAMES utf8mb4'
#character-set-server=utf8
skip_name_resolve
event_scheduler=1
#skip-grant-tables
#innodb_data_home_dir = /var/lib/mysql/
#innodb_data_file_path = ibdata1:10M:autoextend
#innodb_log_group_home_dir = /var/lib/mysql/
#innodb_buffer_pool_size = 1024M
#innodb_log_file_size = 128M
#innodb_log_buffer_size = 32M
#innodb_flush_log_at_trx_commit = 1
#innodb_lock_wait_timeout = 50
#innodb_max_dirty_pages_pct = 90
#innodb_read_io_threads = 4
#innodb_write_io_threads = 4
#
# Allow server to accept connections on all interfaces.
#
#bind-address=0.0.0.0
#
# this is only for embedded server
# This group is only read by MariaDB servers, not by MySQL.
# If you use the same .cnf file for MySQL and MariaDB,
# you can put MariaDB-only options here
[mariadb]
#autoset_open_files_limit
#enable_slow_query_log
#audit
plugin_load_add=server_audit
server_audit_logging=on
server_audit_events=connect,query
server_audit=force_plus_permanent
server_audit_events=QUERY_DDL,QUERY_DML,CONNECT
server_audit_output_type=file
server_audit_file_rotate_now=on
server_audit_file_rotations=9
server_audit_file_rotate_size=1G
server_audit_file_path=/data/logs/mariadb
log_error=/data/logs/mariadb/mariadb.err.log
log_output=FILE
slow_query_log
long_query_time=3
slow_query_log_file=/data/logs/mariadb/mariadb-slow.log
log_queries_not_using_indexes=ON #Logging Queries That Don't Use Indexes
#server_id=2
log-bin=/data/logs/mariadb/mysql-bin
# This group is only read by MariaDB-10.5 servers.
# If you use the same .cnf file for MariaDB of different versions,
# use this group for options that older servers don't understand

@ -1,25 +0,0 @@
version: '3.2'
networks:
flyapps:
external: true
name: flyapps
services:
redis:
image: 'bitnami/redis:6.2.7'
container_name: redis
restart: always
volumes:
- ../init/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ../../data/redis:/bitnami/redis/data
environment:
#- REDIS_REPLICATION_MODE=master
#- ALLOW_EMPTY_PASSWORD=yes
- REDIS_PASSWORD=nineven
networks:
- flyapps
#ports:
# - 6379:6379

@ -1,6 +0,0 @@
#!/bin/bash
#
#
cd ../build/
docker compose up buildclient buildadmin

@ -1,9 +0,0 @@
#!/bin/bash
#
#
cd ../mariadb/ && docker compose up -d
cd ../redis/ && docker compose up -d
cd ../flyapps/ && docker compose up -d
docker logs -f flyapps

@ -1,10 +0,0 @@
#!/bin/bash
#
#
cd ../flyapps/ && docker compose down
cd ../redis/ && docker compose down
cd ../mariadb/ && docker compose down

@ -2,5 +2,5 @@
ENV = 'production'
# base api
VUE_APP_BASE_API = '/api/v3/fir/server'
VUE_APP_BASE_API = '/prod-api'

@ -53,11 +53,3 @@ export function updateCertificationInfo(data) {
data
})
}
export function resetPassword(data) {
return request({
url: '/user/info',
method: 'post',
data
})
}

@ -4,7 +4,7 @@
<el-input v-model="listQuery.bundle_id" placeholder="Bundle_Id" style="width: 250px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.name" placeholder="应用名称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.short" placeholder="短连接" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<!-- <el-input v-model="listQuery.domain_name" placeholder="应用专属访问域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />-->
<!-- <el-input v-model="listQuery.domain_name" placeholder="应用专属访问域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />-->
<el-select v-if="type_choices" v-model="listQuery.type" placeholder="应用类型" clearable class="filter-item" style="width: 120px" @change="handleFilter">
<el-option v-for="item in type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
@ -40,11 +40,7 @@
</el-table-column>
<el-table-column label="应用名称">
<template slot-scope="scope">
<el-tooltip content="点击访问下载页">
<el-link :underline="false" @click="go_download_url(scope.row)">
{{ scope.row.name }}
</el-link>
</el-tooltip>
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="Bundle_Id" align="center">
@ -75,16 +71,11 @@
{{ scope.row.short }}
</template>
</el-table-column>
<el-table-column label="下载次数" width="80" align="center">
<el-table-column label="下载次数" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.count_hits }}
</template>
</el-table-column>
<el-table-column label="当天下载次数" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.today_hits_count }}
</template>
</el-table-column>
<el-table-column label="应用大小" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.master_release.binary_size }}
@ -211,9 +202,6 @@ export default {
this.fetchData()
},
methods: {
go_download_url(app_info) {
window.open(app_info.preview_url, '_blank', '')
},
deleteApp(app_id) {
this.$confirm('此操作将永久删除该应用, 是否继续?', '提示', {
confirmButtonText: '确定',
@ -221,7 +209,7 @@ export default {
type: 'warning'
}).then(() => {
this.listLoading = true
deleteApp(app_id).then(() => {
deleteApp(app_id).then(response => {
this.$message.success('删除成功')
this.fetchData()
this.listLoading = false

@ -66,7 +66,7 @@ export default {
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 3) {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()

@ -6,21 +6,21 @@
<el-form-item label="UID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.uid" disabled/>
<el-input :value="postForm.uid" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="账号" prop="username">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.username"/>
<el-input v-model="postForm.username" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="昵称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.first_name"/>
<el-input v-model="postForm.first_name" />
</el-col>
</el-row>
</el-form-item>
@ -28,9 +28,7 @@
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.gender" class="filter-item" placeholder="Please select">
<el-option v-for="item in postForm.gender_choices" :key="item.id" :label="item.name"
:value="item.id"
/>
<el-option v-for="item in postForm.gender_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
@ -39,7 +37,7 @@
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.role" class="filter-item" placeholder="Please select">
<el-option v-for="item in postForm.role_choices" :key="item.id" :label="item.name" :value="item.id"/>
<el-option v-for="item in postForm.role_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
@ -48,7 +46,7 @@
<el-form-item label="邮箱" prop="email">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.email"/>
<el-input v-model="postForm.email" />
</el-col>
</el-row>
</el-form-item>
@ -56,37 +54,35 @@
<el-form-item label="手机" prop="mobile">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.mobile"/>
<el-input v-model="postForm.mobile" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="Q Q" prop="number">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.qq"/>
<el-input v-model="postForm.qq" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="职位">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.job"/>
<el-input v-model="postForm.job" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="公司">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.company"/>
<el-input v-model="postForm.company" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="备注">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.memo" :autosize="{ minRows: 4, maxRows: 6}" type="textarea"
placeholder="Please input"
/>
<el-input v-model="postForm.memo" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" placeholder="Please input" />
</el-col>
</el-row>
</el-form-item>
@ -95,9 +91,7 @@
<el-form-item label="用户头像">
<el-row :gutter="12">
<el-col :span="16">
<el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain"
style="width: 100px; height: 100px"
/>
<el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain" style="width: 100px; height: 100px" />
</el-col>
</el-row>
</el-form-item>
@ -183,16 +177,10 @@
<el-form-item label="实名认证">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.certification" class="filter-item" placeholder="Please select"
:disabled="postForm.certification === -1"
>
<el-option v-for="item in postForm.certification_status_choices" :key="item.id" :label="item.name"
:value="item.id"
/>
<el-select v-model="postForm.certification" class="filter-item" placeholder="Please select" :disabled="postForm.certification === -1">
<el-option v-for="item in postForm.certification_status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<router-link v-if="postForm.certification_id"
:to="{name: 'user_authentication_info_edit',params:{id:postForm.certification_id.id}}"
>
<router-link v-if="postForm.certification_id" :to="{name: 'user_authentication_info_edit',params:{id:postForm.certification_id.id}}">
<el-button type="primary">
审核认证信息
</el-button>
@ -215,7 +203,7 @@
<el-form-item label="下载次数" prop="download_times">
<el-row :gutter="12">
<el-col :span="8">
<el-input :value="postForm.download_times" disabled/>
<el-input :value="postForm.download_times" disabled />
</el-col>
<el-col :span="8">
<router-link v-if="postForm.id" :to="{name: 'order_info_list',params:{user_id:postForm.id}}">
@ -227,14 +215,14 @@
<el-form-item label="注册时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.date_joined" type="datetime" disabled/>
<el-date-picker :value="postForm.date_joined" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="应用版本历史记录">
<el-row :gutter="20">
<el-col :span="8">
<el-input v-model="postForm.history_release_limit"/>
<el-input v-model="postForm.history_release_limit" />
</el-col>
</el-row>
</el-form-item>
@ -246,9 +234,6 @@
<el-col :span="8">
<el-button @click="add_amount_download_times(postForm.id)">下载次数充值</el-button>
</el-col>
<el-col :span="8">
<el-button @click="reset_password(postForm.id)">重置密码</el-button>
</el-col>
<el-button v-if="!is_edit" type="primary" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
@ -260,7 +245,7 @@
<script>
import { validURL } from '@/utils/validate'
import { getUserInfos, resetPassword, updateUserInfo } from '@/api/user'
import { getUserInfos, updateUserInfo } from '@/api/user'
import { createOrderInfo } from '@/api/order'
import { changeStorageInfo, getStorageList } from '@/api/storage'
@ -292,7 +277,7 @@ const defaultForm = {
export default {
name: 'UserDetail',
components: {}, filters: {
components: { }, filters: {
userStatusFilter(status) {
const statusMap = {
true: '激活,允许登录',
@ -356,7 +341,8 @@ export default {
storage_selection: []
}
},
computed: {},
computed: {
},
created() {
if (this.isEdit) {
const id = this.$route.params && this.$route.params.id
@ -383,33 +369,9 @@ export default {
message: '取消充值'
})
})
}, reset_password(user_id) {
this.$prompt('请输入新密码,密码至少6位', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(({ value }) => {
resetPassword({ user_id: user_id, password: value }).then(res => {
if (res.code === 1000) {
this.$message.success('密码重置成功')
this.fetchData(user_id)
} else {
this.$message.error(res.msg)
}
})
}).catch((e) => {
console.log(e)
this.$message({
type: 'info',
message: '取消充值'
})
})
},
changeStorageData(force) {
changeStorageInfo({
id: this.postForm.id,
use_storage_id: this.postForm.storage,
force: force
}).then(response => {
changeStorageInfo({ id: this.postForm.id, use_storage_id: this.postForm.storage, force: force }).then(response => {
this.$message.success('存储数据迁移并设置刷新成功')
}).catch(err => {
console.log(err)

@ -60,11 +60,6 @@
{{ scope.row.download_times }}
</template>
</el-table-column>
<el-table-column label="当天苹果|安卓下载次数" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.today_hits_count.ios_today_hits_count }} | {{ scope.row.today_hits_count.android_today_hits_count }}
</template>
</el-table-column>
<el-table-column label="应用数量" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'app_info_list',params:{user_id:scope.row.id}}">

@ -25,54 +25,45 @@
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" disabled type="datetime" />
<el-form-item label="地址">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.address" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="订阅时间" prop="timestamp">
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.subscribe_time*1000" disabled type="datetime" />
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户头像" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-image
:preview-src-list="[postForm.head_img_url]"
:src="postForm.head_img_url"
fit="contain"
style="width: 100px; height: 100px"
/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="扫码登录" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.enable_login" disabled />
<el-image :src="postForm.head_img_url" :preview-src-list="[postForm.head_img_url]" fit="contain" style="width: 100px; height: 100px" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="消息通知" label-width="160px">
<el-form-item label="订阅状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.enable_notify" disabled />
<el-select v-model="postForm.subscribe" class="filter-item">
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="订阅状态" label-width="160px">
<el-form-item label="性别" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.subscribe" class="filter-item" disabled>
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
<el-select v-model="postForm.sex" class="filter-item">
<el-option v-for="item in wxbind_sex_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
@ -93,12 +84,17 @@
<script>
import { getWxBindInfos, updateWxBindInfo } from '@/api/wxbind'
const wxbind_state_choices = [
{ id: false, name: '未订阅' },
{ id: true, name: '已订阅' }
]
const wxbind_sex_choices = [
{ id: 0, name: '未知' },
{ id: 1, name: '男' },
{ id: 2, name: '女' }
]
const defaultForm = {
user_id: undefined,
address: undefined,
@ -107,10 +103,9 @@ const defaultForm = {
id: undefined,
nickname: undefined,
openid: undefined,
sex: undefined,
subscribe: undefined,
subscribe_time: undefined,
enable_login: undefined,
enable_notify: undefined
subscribe_time: undefined
}
export default {
@ -122,6 +117,7 @@ export default {
loading: false,
is_edit: false,
wxbind_state_choices,
wxbind_sex_choices,
id: ''
}
},

@ -50,14 +50,14 @@
{{ scope.row.openid }}
</template>
</el-table-column>
<el-table-column label="允许扫码登录" min-width="100px" align="center">
<el-table-column label="地址" min-width="200px" align="center">
<template slot-scope="scope">
{{ scope.row.enable_login }}
{{ scope.row.address }}
</template>
</el-table-column>
<el-table-column label="允许消息通知" min-width="100px" align="center">
<el-table-column label="性别" min-width="60px" align="center">
<template slot-scope="scope">
{{ scope.row.enable_notify }}
{{ scope.row |sexLableFilter }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="订阅状态" width="95" align="center">
@ -107,6 +107,12 @@ const wxbind_state_choices = [
{ id: true, name: '已订阅' }
]
const wxbind_sex_choices = [
{ id: 0, name: '未知' },
{ id: 1, name: '男' },
{ id: 2, name: '女' }
]
export default {
name: 'WxBindInfo',
components: { Pagination },
@ -133,6 +139,9 @@ export default {
statusLableFilter(row) {
return baseFilter(row.subscribe, wxbind_state_choices)
},
sexLableFilter(row) {
return baseFilter(row.sex, wxbind_sex_choices)
},
statusFilter(status) {
const statusMap = {
true: 'success',

@ -8580,8 +8580,8 @@ querystring@0.2.0:
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.npm.taobao.org/querystringify/download/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha1-M0WUG0FTy50ILY7uTNogFqmu9/Y=
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
randomatic@^3.0.0:
version "3.1.1"
@ -8894,7 +8894,7 @@ require-main-filename@^2.0.0:
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/requires-port/download/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resize-observer-polyfill@^1.5.0:
@ -10373,9 +10373,9 @@ url-loader@^2.2.0:
schema-utils "^2.5.0"
url-parse@^1.4.3, url-parse@^1.5.1:
version "1.5.1"
resolved "https://registry.npm.taobao.org/url-parse/download/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
integrity sha1-1fqYkK+KXh8nSiyYN2UQ9kJfbjs=
version "1.5.10"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"

@ -39,48 +39,5 @@ yarn lint
```
## deploy nginx
#### short deploy nginx
```shell
location / {
try_files $uri $uri/ /short.html;
}
```
#### index.html
```html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="3;URL=https://flyapps.cn">
<title>FLY分发平台</title>
<style>
.container {
width: 60%;
margin: 10% auto 0;
background-color: #f0f0f0;
padding: 2% 5%;
border-radius: 10px
}
ul {
padding-left: 20px;
}
ul li {
line-height: 2.3
}
a {
color: #20a53a
}
</style>
</head>
<body>
<div class="container">
<h3>这是分发平台下载默认页,正在跳转 <a href="https://flyapps.cn">首页</a></h3>
</div>
</body>
</html>
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

@ -11,7 +11,7 @@
"dependencies": {
"ali-oss": "^6.5.1",
"app-info-parser": "^0.3.9",
"axios": "^0.22.0",
"axios": "^0.19.0",
"core-js": "^3.4.4",
"element-ui": "^2.13.0",
"js-base64": "^2.5.2",
@ -36,6 +36,7 @@
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"html-webpack-plugin": "^5.3.1",
"image-webpack-loader": "^7.0.1",
"script-ext-html-webpack-plugin": "^2.1.5",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-template-compiler": "^2.6.10",

@ -2,9 +2,9 @@
<html lang="zh">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
<link href="<%= BASE_URL %>favicon.ico" rel="icon">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<script src="<%= BASE_URL %>gt.js"></script>
<title>FLY分发平台</title>
</head>

@ -2,9 +2,9 @@
<html lang="zh">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
<link href="<%= BASE_URL %>favicon.ico" rel="icon">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<script src="<%= BASE_URL %>gt.js"></script>
<title>应用下载</title>
</head>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@ -39,7 +39,7 @@
<el-row v-if="captcha.captcha_image" style="margin-top: 10px;margin-bottom: -10px">
<el-col :span="16">
<el-input v-model="report_info.verify_code" clearable maxlength="6" placeholder="请输入图片验证码"/>
<el-input v-model="report_info.authcode" clearable maxlength="6" placeholder="请输入图片验证码"/>
</el-col>
<el-col :span="8">
<el-image
@ -352,21 +352,28 @@
<script>
import QRCode from 'qrcodejs2'
import {appReport, geetest, getAuthTokenFun, getdownloadurl, getShortAppinfo, gettask} from '@/restful'
import {checkEmail, checkphone, format_time, getRandomStr} from "@/utils/base/utils";
import {
appReport,
checkEmail,
checkphone,
geetest,
getAuthTokenFun,
getdownloadurl,
getShortAppinfo,
gettask
} from '@/restful/download'
export default {
name: "FirDownload",
data() {
return {
captcha: {"captcha_image": '', "captcha_key": '', "length": 8},
auth_rules: {},
report_info: {
'username': '',
'report_type': '',
'report_reason': '',
'seicode': '',
'verify_code': '',
'authcode': '',
'email': '',
'auth_token': '',
'act': 'email',
@ -415,10 +422,7 @@ export default {
timer: '',
gomobile: true,
mobileprovision: '',
ad_info: {ad_pic: '', ad_uri: ''},
task_spend_time: 0,
task_msg: '执行中',
err_password: false
ad_info: {ad_pic: '', ad_uri: ''}
}
},
beforeDestroy() {
@ -478,12 +482,12 @@ export default {
}
let picode = {
"verify_code": this.report_info.verify_code,
"authcode": this.report_info.authcode,
"captcha_key": this.captcha.captcha_key,
"report": this.currentappinfo.app_id
};
let params = {'act': this.report_info.act, 'target': this.report_info.email, 'ext': picode};
if (this.auth_rules.geetest) {
if (this.captcha.geetest) {
geetest(this, this.report_info.email, params, (n_params) => {
this.do_get_auth_token(n_params);
})
@ -513,8 +517,7 @@ export default {
if (data.code === 1000) {
let jdata = data.data;
if (jdata.enable) {
this.auth_rules = data.data.auth_rules;
this.captcha = this.auth_rules.captcha;
this.captcha = data.data;
if (data.data.s_list) {
this.report_info_list = data.data.s_list;
}
@ -545,52 +548,37 @@ export default {
duration: 0
});
},
loop_check_task(c_count = 1, loop_t, unique_key = getRandomStr()) {
if (c_count === 1) {
// eslint-disable-next-line no-unused-vars
loop_t = window.setInterval(res => {
this.task_spend_time += 1;
this.msg = this.task_msg + ' ○ ' + this.task_spend_time;
}, 3000)
}
gettask(data => {
c_count += 1;
if (c_count > 30) {
window.clearInterval(loop_t)
return;
}
if (data.code === 1000) {
window.clearInterval(loop_t);
if (data.data) {
if (data.data.code === 1000) {
loop_check_task() {
let c_count = 1;
// eslint-disable-next-line no-unused-vars
const loop_t = window.setInterval(res => {
gettask(data => {
c_count += 1;
if (c_count > 120) {
window.clearInterval(loop_t);
}
if (data.code === 1000) {
window.clearInterval(loop_t);
if (data.msg) {
this.show_err_msg(data.msg);
} else {
this.$message.success('签名完成,请点击下载安装');
this.wrong = false;
this.binary_download();
} else {
if (data.data.redirect) {
window.location.href = data.data.redirect
} else if (data.data.msg) {
this.show_err_msg(data.data.msg);
}
}
} else {
if (data.code === 1002) {
window.clearInterval(loop_t);
data.msg = '应用不存在'
}
if (data.msg) {
this.msg = data.msg + ' ○ ' + c_count;
}
}
} else {
if (data.code === 1002) {
window.clearInterval(loop_t);
data.msg = '应用不存在'
}
if (data.msg) {
this.task_msg = data.msg;
}
if (data.code === 1001) {
this.loop_check_task(c_count, loop_t, unique_key)
}
}
}, {
methods: 'GET',
short: this.$route.params.short,
data: {"task_id": this.$route.query.task_id, "unique_key": unique_key}
})
}, {
"short": this.$route.params.short,
data: {"task_id": this.$route.query.task_id}
})
}, 3000)
},
jiaocheng(act) {
let signhelp = this.$refs.signhelp;
@ -608,83 +596,18 @@ export default {
window.location.href = this.mobileprovision;
},
check_msg() {
if (this.agent === 'wxandroid' || this.agent === 'wxapple') {
return
}
if (this.$route.query.msg) {
this.wrong = true;
this.show_err_msg(this.$route.query.msg);
this.currentappinfo.need_password = false;
return;
}
if (this.currentappinfo.need_password) {
if (this.currentappinfo.issupersign && !this.$route.query.udid) {
this.currentappinfo.need_password = false
}
if (this.$route.query.password && !this.err_password) {
this.currentappinfo.need_password = false
this.password = this.$route.query.password;
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
this.download();
}, 1000);
}
} else {
if (this.$route.query.udid && this.$route.query.task_token) {
this.download()
if (this.$route.query.udid) {
if (this.$route.query.task_id) {
this.wrong = true;
this.msg = '签名处理中,请耐心等待';
this.loop_check_task()
} else if (this.$route.query.msg) {
this.wrong = true;
this.show_err_msg(this.$route.query.msg);
}
}
},
download() {
if (this.$route.query.udid && this.$route.query.task_token) {
this.wrong = true;
this.msg = '签名处理中,请耐心等待';
gettask(data => {
if (data.code === 1000) {
if (data.data) {
if (data.data.code === 1000) {
this.wrong = false;
this.currentappinfo.need_password = false
this.binary_download()
} else {
if (data.data.redirect) {
window.location.href = data.data.redirect
} else if (data.data.msg) {
this.show_err_msg(data.data.msg);
}
}
} else if (data.task_id) {
this.$route.query.task_id = data.task_id;
this.loop_check_task()
}
} else if (data.code === 1006) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = false;
this.err_password = true
this.currentappinfo.need_password = true
this.password = ''
} else if (data.code === 1005) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = true;
} else {
this.show_err_msg(data.msg);
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
window.location.href = location.href.replace(location.search, '');
}, 3000);
}
}, {
methods: 'POST',
short: this.$route.params.short,
data: {"task_token": this.$route.query.task_token, 'password': this.password}
})
} else {
this.binary_download()
}
},
binary_download() {
if (this.currentappinfo.app_id) {
this.isdownload = true;
getdownloadurl(res => {
@ -707,11 +630,7 @@ export default {
} else {
if (this.agent !== '') {
this.downloadurl = res.data.download_url;
let pwd = ''
if (res.data.password && res.data.password.length > 4) {
pwd = '&password=' + encodeURIComponent(res.data.password)
}
window.location.href = this.downloadurl + pwd;
window.location.href = this.downloadurl;
if (res.data.extra_url !== "") {
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
@ -758,8 +677,6 @@ export default {
window.location.reload();
}
this.password = '';
this.err_password = true;
this.currentappinfo.need_password = true;
}
}, {
'data': {
@ -805,17 +722,7 @@ export default {
getShortAppinfo(data => {
if (data.code === 1000) {
if (data.redirect && data.data) {
let href = data.data;
if (this.$route.query.password) {
if (href.indexOf('?') !== -1) {
if (href.indexOf('password=') === -1) {
href = href + '&password=' + this.$route.query.password
}
} else {
href = href + '?password=' + this.$route.query.password
}
}
window.location.href = href;
window.location.href = data.data;
return
}
if (data.ad && data.ad.ad_uri) {
@ -926,9 +833,6 @@ export default {
if (this.mcurrentappinfo.release_type === 2 && !this.currentappinfo.issupersign) {
this.signhelplist = this.inhousehelplist;
}
if (this.agent !== '') {
this.check_msg();
}
} else if (data.code === 1002) {
window.location.href = location.href.replace(location.search, '');
} else if (data.code === 302 && data.data) {
@ -1002,6 +906,9 @@ export default {
this.getDownloadTokenFun();
this.full_url = location.href;
this.qrcode();
if (this.agent !== '') {
this.check_msg();
}
}, filters: {
getiOStype: function (type) {
let ftype = '';
@ -1013,7 +920,10 @@ export default {
return ftype
},
formatTime: function (stime) {
return format_time(stime)
if (stime) {
stime = stime.split(".")[0].split("T");
return stime[0] + " " + stime[1]
}
}
}
};

@ -28,12 +28,22 @@
</template>
<script>
const footer = process.env.base_env.footer
export default {
name: "FirFooter",
data() {
return {
footer
footer: {
copyright: 'Copyright © 2020-2022 东城飞阳 版权所有.',
ipcBeiAn: {
url: 'https://beian.miit.gov.cn',
text: '粤ICP备2021097895号',
},
gongAnBeiAn: {
url: 'http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=41142202000062',
text: '豫公网安备41142202000062号',
},
}
}
}
}

@ -51,8 +51,6 @@
<el-dropdown-item command="userinfo">个人资料</el-dropdown-item>
<el-dropdown-item command="apitoken">API token</el-dropdown-item>
<el-dropdown-item command="setdomian">设置域名</el-dropdown-item>
<el-dropdown-item command="download">下载配置</el-dropdown-item>
<el-dropdown-item command="setnotify">消息中心</el-dropdown-item>
<el-dropdown-item v-if="$store.state.userinfo.role>1" command="setadvert">宣传广告
</el-dropdown-item>
<el-dropdown-item v-if="$store.state.userinfo.storage_active" command="storage">存储管理
@ -136,14 +134,10 @@ export default {
} else if (command === 'setdomian') {
this.$router.push({"name": 'FirUserDomain'})
} else if (command === 'download') {
this.$router.push({"name": 'FirUserDownload'})
} else if (command === 'setadvert') {
this.$router.push({"name": 'FirUserAdvert'})
} else if (command === 'myorder') {
this.$router.push({"name": 'FirUserOrders'})
} else if (command === 'setnotify') {
this.$router.push({"name": 'FirUserNotify'})
} else if (command === 'qrcode') {
this.$router.push({"name": 'FirUserQrcode'})
} else if (command === 'contact') {

@ -44,7 +44,7 @@
<el-form-item v-if="captcha.captcha_image" style="height: 40px">
<el-row style="height: 40px">
<el-col :span="16">
<el-input v-model="form.verify_code" clearable maxlength="6"
<el-input v-model="form.authcode" clearable maxlength="6"
placeholder="请输入验证码" @keyup.enter.native="onSubmit"/>
</el-col>
<el-col :span="8">
@ -74,29 +74,7 @@
<div v-if="allow_ways.third && JSON.stringify(allow_ways.third).indexOf('true')!==-1" class="other-way">
<hr>
<el-popover
v-model="showuserlist"
placement="top"
title="该微信绑定了多个用户,请选择账户,并点击登录"
trigger="manual"
width="400">
<el-row style="margin-top: 20px">
<el-col v-for="userinfo in userinfo_list" :key="userinfo.uid" :span="8" style="margin-left: 40px">
<div @click="weixinlogin(userinfo)">
<el-card :body-style="{textAlign:'center',padding:'8px',width:'120px'}" shadow="hover">
<!-- <el-image :src="userinfo.head_img"></el-image>-->
<el-avatar :size="100" :src="userinfo.head_img"></el-avatar>
<div style="margin: 5px 0 5px">
<span>{{ userinfo.first_name }}</span>
</div>
</el-card>
</div>
</el-col>
</el-row>
<span slot="reference" class="info">或使用以下账户登录</span>
</el-popover>
<span class="info">或使用以下账户登录</span>
<el-popover
v-if="allow_ways.third && allow_ways.third.wxp"
@ -125,9 +103,8 @@
</template>
<script>
import {loginFun, wxLoginFun} from "@/restful";
import {geetest} from "@/utils";
import {checkEmail, checkphone, getRandomStr} from "@/utils/base/utils";
import {loginFun, set_auth_token, wxLoginFun} from "@/restful";
import {checkEmail, checkphone, geetest} from "@/utils";
export default {
name: "FirLogin",
@ -136,10 +113,9 @@ export default {
form: {
email: '',
password: '',
verify_code: ''
authcode: ''
},
captcha: {"captcha_image": '', "captcha_key": '', "length": 8},
auth_rules: {},
activeName: 'username',
allow_ways: {'third': {}},
rutitle: '',
@ -149,75 +125,53 @@ export default {
wx_login_qr_url: '',
wx_visible: false,
loop_flag: false,
unique_key: '',
userinfo_list: [],
unique_wid: '',
showuserlist: false
}
},
methods: {
weixinlogin(userinfo) {
wxLoginFun(data => {
if (data.code === 1000) {
this.set_cookie_and_token(data);
} else if (data.code === 1005) {
this.$message.error(data.msg)
} else {
this.showuserlist = false
this.$message.error(data.msg)
}
}, {
'methods': 'PUT',
'data': {uid: userinfo.uid, token: userinfo.token, 'wid': this.unique_wid}
})
},
set_cookie_and_token(data) {
this.$cookies.remove("auth_token");
this.$cookies.set("token", data['token'], 3600 * 24 * 30);
this.$cookies.set("username", data.userinfo.username, 3600 * 24 * 30);
this.$cookies.set("first_name", data.userinfo.first_name, 3600 * 24 * 30);
this.$store.dispatch("doUserinfo", data.userinfo);
set_auth_token();
this.$router.push({name: 'FirApps'})
},
loop_get_wx_info(wx_login_ticket, c_count = 1, unique_key = getRandomStr()) {
loop_get_wx_info(wx_login_ticket) {
if (wx_login_ticket && wx_login_ticket.length < 3) {
this.$message.error("获取登陆码失败,请稍后再试");
return
}
if (!this.wx_visible) {
return;
}
wxLoginFun(data => {
c_count += 1;
if (c_count > 30) {
return;
let c_count = 1;
// eslint-disable-next-line no-unused-vars
const loop_t = window.setInterval(res => {
if (!this.loop_flag) {
window.clearInterval(loop_t);
}
if (data.code === 1000) {
this.set_cookie_and_token(data);
} else if (data.code === 1005) {
this.wx_visible = false;
this.loop_flag = false;
this.$message({
message: data.msg,
type: 'error',
duration: 30000
});
} else if (data.code === 1004) {
this.loop_flag = false;
} else if (data.code === 1006) {
return this.loop_get_wx_info(wx_login_ticket, c_count, unique_key)
} else if (data.code === 2000) {
this.wx_visible = false;
this.loop_flag = false;
this.userinfo_list = data.data;
this.unique_wid = data.wid;
this.showuserlist = true
}
}, {
"methods": "POST",
data: {"ticket": wx_login_ticket, "unique_key": unique_key}
})
// }, 3000)
wxLoginFun(data => {
c_count += 1;
if (c_count > 120) {
window.clearInterval(loop_t);
}
if (data.code === 1000) {
window.clearInterval(loop_t);
this.set_cookie_and_token(data);
} else if (data.code === 1005) {
window.clearInterval(loop_t);
this.wx_visible = false;
this.loop_flag = false;
this.$message({
message: data.msg,
type: 'error',
duration: 30000
});
}
}, {
"methods": "POST",
data: {"ticket": wx_login_ticket}
})
}, 3000)
},
wxLogin() {
@ -235,14 +189,14 @@ export default {
this.loop_flag = false;
}
}, {
"methods": "GET", "data": {"unique_key": this.unique_key}
"methods": "GET",
})
} else {
this.loop_flag = false;
}
},
is_captcha() {
let captcha_flag = this.form.verify_code.length === this.captcha.length;
let captcha_flag = this.form.authcode.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
@ -251,9 +205,9 @@ export default {
onSubmit() {
let email = this.form.email;
let password = this.form.password;
let verify_code = this.form.verify_code;
let authcode = this.form.authcode;
let login_type = 'up';
let captcha_flag = this.form.verify_code.length === this.captcha.length;
let captcha_flag = this.form.authcode.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
@ -290,16 +244,16 @@ export default {
});
return
}
if (password.length >= 6) {
if (password.length > 6) {
let params = {
"username": email,
"password": password,
"verify_code": verify_code,
"authcode": authcode,
"captcha_key": this.captcha.captcha_key,
"login_type": login_type,
};
this.login_disable = true;
if (this.auth_rules.geetest) {
if (this.captcha.geetest) {
geetest(this, this.form.email, params, (n_params) => {
this.do_login(n_params);
})
@ -364,11 +318,10 @@ export default {
get_auth_code() {
loginFun(data => {
if (data.code === 1000) {
this.auth_rules = data.data.auth_rules
this.captcha = this.auth_rules.captcha;
this.captcha = data.data;
this.allow_ways = data.data.login_type;
this.register_enable = data.data.register_enable;
this.form.verify_code = '';
this.form.authcode = '';
this.set_rtitle();
this.set_activename();
} else {
@ -385,7 +338,6 @@ export default {
},
mounted() {
this.get_auth_code();
this.unique_key = getRandomStr();
}, created() {
}
}

@ -29,7 +29,7 @@
<el-form-item v-if="captcha.captcha_image" style="height: 40px">
<el-row style="height: 40px">
<el-col :span="16">
<el-input v-model="form.verify_code" clearable maxlength="6" placeholder="请输入图片验证码"/>
<el-input v-model="form.authcode" clearable maxlength="6" placeholder="请输入图片验证码"/>
</el-col>
<el-col :span="8">
<el-image
@ -88,8 +88,7 @@
<script>
import {getAuthTokenFun, registerFun} from "@/restful";
import {geetest} from "@/utils";
import {checkEmail, checkphone} from "@/utils/base/utils";
import {checkEmail, checkphone, geetest} from "@/utils";
export default {
name: "FirRegist",
@ -99,7 +98,7 @@ export default {
email: '',
password: '',
password2: '',
verify_code: '',
authcode: '',
srccode: '',
seicode: '',
authtoken: '',
@ -108,7 +107,6 @@ export default {
},
captcha: {"captcha_image": '', "captcha_key": '', "length": 8},
auth_rules: {},
allow_r: false,
allow_ways: {},
rutitle: '',
@ -134,10 +132,9 @@ export default {
if (jdata.enable) {
this.allow_r = true;
this.allow_ways = jdata.register_type;
this.form.verify_code = '';
this.form.authcode = '';
this.set_rtitle();
this.auth_rules = data.data.auth_rules;
this.captcha = this.auth_rules.captcha;
this.captcha = data.data;
} else {
this.allow_r = false;
this.$message({
@ -184,19 +181,19 @@ export default {
if (checkEmail(this.form.email)) {
act = 'email'
}
let verify_code = this.form.verify_code;
let captcha_flag = verify_code.length === this.captcha.length;
let authcode = this.form.authcode;
let captcha_flag = authcode.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
if (captcha_flag) {
let picode = {
"verify_code": verify_code,
"authcode": authcode,
"captcha_key": this.captcha.captcha_key,
"icode": this.form.icode,
};
let params = {'act': act, 'target': this.form.email, 'ext': picode};
if (this.auth_rules.geetest) {
if (this.captcha.geetest) {
geetest(this, this.form.email, params, (n_params) => {
this.do_get_auth_token(n_params);
})
@ -280,8 +277,8 @@ export default {
if (!this.docheck()) {
return
}
let verify_code = this.form.verify_code;
let captcha_flag = verify_code.length === this.captcha.length;
let authcode = this.form.authcode;
let captcha_flag = authcode.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
@ -295,7 +292,7 @@ export default {
"auth_key": this.form.seicode
};
this.register_disable = true;
if (this.auth_rules.geetest) {
if (this.captcha.geetest) {
geetest(this, this.form.email, params, (n_params) => {
this.do_register(n_params);
})

@ -20,7 +20,7 @@
<el-form-item v-if="captcha.captcha_image" style="height: 40px">
<el-row style="height: 40px">
<el-col :span="16">
<el-input v-model="form.verify_code" clearable maxlength="6"
<el-input v-model="form.authcode" clearable maxlength="6"
placeholder="请输入验证码" @keyup.enter.native="onSubmit"/>
</el-col>
<el-col :span="8">
@ -72,8 +72,7 @@
<script>
import {loginFun} from "@/restful";
import {geetest} from "@/utils";
import {checkEmail, checkphone} from "@/utils/base/utils";
import {checkEmail, checkphone, geetest} from "@/utils";
export default {
name: "FirResetPwd",
@ -82,12 +81,11 @@ export default {
form: {
email: '',
password: '',
verify_code: '',
authcode: '',
seicode: '',
auth_token: ''
},
captcha: {"captcha_image": '', "captcha_key": '', "length": 8},
auth_rules: {},
activeName: 'username',
allow_ways: {},
rutitle: '',
@ -110,7 +108,7 @@ export default {
}
},
is_captcha() {
let captcha_flag = this.form.verify_code.length === this.captcha.length;
let captcha_flag = this.form.authcode.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
@ -118,8 +116,8 @@ export default {
},
onSubmit() {
let email = this.form.email;
let verify_code = this.form.verify_code;
let captcha_flag = this.form.verify_code.length === this.captcha.length;
let authcode = this.form.authcode;
let captcha_flag = this.form.authcode.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
@ -129,7 +127,7 @@ export default {
if (checke || checkp) {
let params = {
"username": email,
"verify_code": verify_code,
"authcode": authcode,
"captcha_key": this.captcha.captcha_key,
"login_type": 'reset',
};
@ -142,7 +140,7 @@ export default {
params['auth_token'] = auth_token
}
this.login_disable = true;
if (this.auth_rules.geetest) {
if (this.captcha.geetest) {
geetest(this, this.form.email, params, (n_params) => {
this.do_login(n_params);
})
@ -198,10 +196,9 @@ export default {
get_auth_code() {
loginFun(data => {
if (data.code === 1000) {
this.auth_rules = data.data.auth_rules;
this.captcha = this.auth_rules.captcha;
this.captcha = data.data;
this.register_enable = data.data.register_enable;
this.form.verify_code = '';
this.form.authcode = '';
} else {
this.$message({
message: data.msg,

@ -43,7 +43,7 @@
</div>
<div class="content-row">
<div v-if="captcha.captcha_image" class="report_captcha">
<input v-model="report_info.verify_code" maxlength="6" placeholder="请输入图片验证码" type="text"/>
<input v-model="report_info.authcode" maxlength="6" placeholder="请输入图片验证码" type="text"/>
<img :src="captcha.captcha_image" alt="验证码" @click="get_auth_code"/>
</div>
@ -339,21 +339,28 @@
<script>
import QRCode from 'qrcodejs2'
import {appReport, geetest, getAuthTokenFun, getdownloadurl, getShortAppinfo, gettask} from '@/restful/download'
import {checkEmail, checkphone, format_time, getRandomStr} from "@/utils/base/utils";
import {
appReport,
checkEmail,
checkphone,
geetest,
getAuthTokenFun,
getdownloadurl,
getShortAppinfo,
gettask
} from '@/restful/download'
export default {
name: "ShortDownload",
data() {
return {
captcha: {"captcha_image": '', "captcha_key": '', "length": 8},
auth_rules: {},
report_info: {
'username': '',
'report_type': '',
'report_reason': '',
'seicode': '',
'verify_code': '',
'authcode': '',
'email': '',
'auth_token': '',
'act': 'email',
@ -404,10 +411,7 @@ export default {
timer: '',
gomobile: true,
mobileprovision: '',
ad_info: {ad_pic: '', ad_uri: ''},
task_spend_time: 0,
task_msg: '执行中',
err_password: false
ad_info: {ad_pic: '', ad_uri: ''}
}
},
beforeDestroy() {
@ -455,12 +459,12 @@ export default {
}
let picode = {
"verify_code": this.report_info.verify_code,
"authcode": this.report_info.authcode,
"captcha_key": this.captcha.captcha_key,
"report": this.currentappinfo.app_id
};
let params = {'act': this.report_info.act, 'target': this.report_info.email, 'ext': picode};
if (this.auth_rules.geetest) {
if (this.captcha.geetest) {
geetest(this, this.report_info.email, params, (n_params) => {
this.do_get_auth_token(n_params);
})
@ -483,8 +487,7 @@ export default {
if (data.code === 1000) {
let jdata = data.data;
if (jdata.enable) {
this.auth_rules = data.data.auth_rules;
this.captcha = this.auth_rules.captcha;
this.captcha = data.data;
if (data.data.s_list) {
this.report_info_list = data.data.s_list;
}
@ -511,51 +514,36 @@ export default {
this.msg = msg;
alert(this.msg)
},
loop_check_task(c_count = 1, loop_t, unique_key = getRandomStr()) {
if (c_count === 1) {
// eslint-disable-next-line no-unused-vars
loop_t = window.setInterval(res => {
this.task_spend_time += 1;
this.msg = this.task_msg + ' ○ ' + this.task_spend_time;
}, 3000)
}
gettask(data => {
c_count += 1;
if (c_count > 30) {
window.clearInterval(loop_t);
return;
}
if (data.code === 1000) {
window.clearInterval(loop_t);
if (data.data) {
if (data.data.code === 1000) {
this.wrong = false;
this.binary_download();
} else {
if (data.data.redirect) {
window.location.href = data.data.redirect
} else if (data.data.msg) {
this.show_err_msg(data.data.msg);
}
}
}
} else {
if (data.code === 1002) {
loop_check_task() {
let c_count = 1;
// eslint-disable-next-line no-unused-vars
const loop_t = window.setInterval(res => {
gettask(data => {
c_count += 1;
if (c_count > 120) {
window.clearInterval(loop_t);
data.msg = '应用不存在'
}
if (data.msg) {
this.task_msg = data.msg;
}
if (data.code === 1001) {
this.loop_check_task(c_count, loop_t, unique_key)
if (data.code === 1000) {
window.clearInterval(loop_t);
if (data.msg) {
this.show_err_msg(data.msg);
} else {
this.wrong = false;
}
} else {
if (data.code === 1002) {
window.clearInterval(loop_t);
data.msg = '应用不存在'
}
if (data.msg) {
this.msg = data.msg + ' ○ ' + c_count;
}
}
}
}, {
methods: 'GET',
short: this.$route.params.short,
data: {"task_id": this.$route.query.task_id, "unique_key": unique_key}
})
}, {
"short": this.$route.params.short,
data: {"task_id": this.$route.query.task_id}
})
}, 3000)
},
jiaocheng(act) {
let signhelp = this.$refs.signhelp;
@ -573,83 +561,18 @@ export default {
window.location.href = this.mobileprovision;
},
check_msg() {
if (this.agent === 'wxandroid' || this.agent === 'wxapple') {
return
}
if (this.$route.query.msg) {
this.wrong = true;
this.show_err_msg(this.$route.query.msg);
this.currentappinfo.need_password = false;
return;
}
if (this.currentappinfo.need_password) {
if (this.currentappinfo.issupersign && !this.$route.query.udid) {
this.currentappinfo.need_password = false
}
if (this.$route.query.password && !this.err_password) {
this.currentappinfo.need_password = false
this.password = this.$route.query.password;
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
this.download();
}, 1000);
}
} else {
if (this.$route.query.udid && this.$route.query.task_token) {
this.download()
if (this.$route.query.udid) {
if (this.$route.query.task_id) {
this.wrong = true;
this.msg = '签名处理中,请耐心等待';
this.loop_check_task()
} else if (this.$route.query.msg) {
this.wrong = true;
this.show_err_msg(this.$route.query.msg);
}
}
},
download() {
if (this.$route.query.udid && this.$route.query.task_token) {
this.wrong = true;
this.msg = '签名处理中,请耐心等待';
gettask(data => {
if (data.code === 1000) {
if (data.data) {
if (data.data.code === 1000) {
this.wrong = false;
this.currentappinfo.need_password = false
this.binary_download()
} else {
if (data.data.redirect) {
window.location.href = data.data.redirect
} else if (data.data.msg) {
this.show_err_msg(data.data.msg);
}
}
} else if (data.task_id) {
this.$route.query.task_id = data.task_id;
this.loop_check_task()
}
} else if (data.code === 1006) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = false;
this.err_password = true;
this.currentappinfo.need_password = true;
this.password = ''
} else if (data.code === 1005) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = true;
} else {
this.show_err_msg(data.msg);
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
window.location.href = location.href.replace(location.search, '');
}, 3000);
}
}, {
methods: 'POST',
short: this.$route.params.short,
data: {"task_token": this.$route.query.task_token, 'password': this.password}
})
} else {
this.binary_download()
}
},
binary_download() {
if (this.currentappinfo.app_id) {
this.isdownload = true;
getdownloadurl(res => {
@ -672,11 +595,7 @@ export default {
} else {
if (this.agent !== '') {
this.downloadurl = res.data.download_url;
let pwd = ''
if (res.data.password && res.data.password.length > 4) {
pwd = '&password=' + encodeURIComponent(res.data.password)
}
window.location.href = this.downloadurl + pwd;
window.location.href = this.downloadurl;
if (res.data.extra_url !== "") {
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
@ -717,8 +636,6 @@ export default {
window.location.reload();
}
this.password = '';
this.err_password = true;
this.currentappinfo.need_password = true;
}
}, {
'data': {
@ -764,17 +681,7 @@ export default {
getShortAppinfo(data => {
if (data.code === 1000) {
if (data.redirect && data.data) {
let href = data.data;
if (this.$route.query.password) {
if (href.indexOf('?') !== -1) {
if (href.indexOf('password=') === -1) {
href = href + '&password=' + this.$route.query.password
}
} else {
href = href + '?password=' + this.$route.query.password
}
}
window.location.href = href;
window.location.href = data.data;
return
}
if (data.ad && data.ad.ad_uri) {
@ -881,9 +788,6 @@ export default {
if (this.mcurrentappinfo.release_type === 2 && !this.currentappinfo.issupersign) {
this.signhelplist = this.inhousehelplist;
}
if (this.agent !== '') {
this.check_msg();
}
} else if (data.code === 1002) {
window.location.href = location.href.replace(location.search, '');
} else if (data.code === 302 && data.data) {
@ -960,6 +864,9 @@ export default {
this.getDownloadTokenFun();
this.full_url = location.href;
this.qrcode();
if (this.agent !== '') {
this.check_msg();
}
}, filters: {
getiOStype: function (type) {
let ftype = '';
@ -971,7 +878,10 @@ export default {
return ftype
},
formatTime: function (stime) {
return format_time(stime)
if (stime) {
stime = stime.split(".")[0].split("T");
return stime[0] + " " + stime[1]
}
}
}
};

@ -8,37 +8,33 @@
</div>
<div class="badges">
<el-tooltip content="复制到剪切板" placement="top">
<span v-clipboard:copy="appinfos.preview_url"
<span v-clipboard:copy="short_full_url"
v-clipboard:success="copy_success"
class="bundleid short"
>&nbsp;{{ appinfos.preview_url }}</span>
</el-tooltip>
<el-tooltip content="点击查看下载码">
<el-popover
placement="right"
width="288">
<div style="text-align: center; margin: 0">
<vue-qr ref="qr"
:correctLevel="qrinfo.correctLevel" :logoCornerRadius="qrinfo.logoCornerRadius"
:logoScale="qrinfo.logoScale"
:logoSrc="icon_url"
:margin="qrinfo.margin" :size="266"
:text="appinfos.preview_url">
</vue-qr>
<el-button size="small" type="primary" @click="save_qr()">保存本地</el-button>
</div>
<span slot="reference" class="short ng-scope">下载码</span>
</el-popover>
>&nbsp;{{ short_full_url }}</span>
</el-tooltip>
<el-popover
placement="right"
width="288">
<div style="text-align: center; margin: 0">
<vue-qr ref="qr"
:correctLevel="qrinfo.correctLevel" :logoCornerRadius="qrinfo.logoCornerRadius"
:logoScale="qrinfo.logoScale"
:logoSrc="icon_url"
:margin="qrinfo.margin" :size="266"
:text="short_url(appinfos)">
</vue-qr>
<el-button size="small" type="primary" @click="save_qr()">保存本地</el-button>
</div>
<span slot="reference" class="short ng-scope">下载码</span>
</el-popover>
<span>{{ master_release.release_type |getapptype }}</span>
<el-tooltip placement="top">
<div slot="content">总下载次数{{ appinfos.count_hits }}<br/>当天下载次数{{ appinfos.today_hits_count }}</div>
<span><i class="el-icon-cloudy"/><b class="short">{{
appinfos.count_hits
}} &nbsp;|&nbsp; {{ appinfos.today_hits_count }} </b></span>
<el-tooltip content="下载量" placement="top">
<span><i class="el-icon-cloudy"/><b class="short">{{ appinfos.count_hits }}</b></span>
</el-tooltip>
<span class="bundleid ng-binding">BundleID<b class="ng-binding">
<el-tooltip content="复制到剪切板" placement="top">
@ -127,12 +123,13 @@ export default {
margin: 20
},
icon_url: "",
appinfos: {status: 1, preview_url: ''},
appinfos: {status: 1},
master_release: {},
allapp: [],
activity: {
editing: false
},
short_full_url: '',
}
},
methods: {
@ -152,6 +149,9 @@ export default {
//
a.dispatchEvent(new MouseEvent('click'))
},
short_url(appinfo) {
return appinfo.preview_url + '/' + appinfo.short;
},
setfunactive(item, index) {
for (let key in this.$refs) {
if (key === "qr") continue;
@ -168,7 +168,7 @@ export default {
}
},
appDownload(appinfo) {
window.open(appinfo.preview_url, '_blank', '');
window.open(this.short_url(appinfo), '_blank', '');
},
defaulttimeline() {
this.setfunactive('timeline', 5);
@ -219,6 +219,7 @@ export default {
this.master_release = data.data.master_release;
this.appinfos["icon_url"] = this.master_release.icon_url;
this.$store.dispatch('doucurrentapp', this.appinfos);
this.short_full_url = this.appinfos.preview_url + "/" + this.appinfos.short;
} else if (data.code === 1003) {
this.$router.push({name: 'FirApps'});
} else {

@ -12,10 +12,9 @@
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="使用空间" style="width: 56%">
<el-input v-model="currentapp.storage_used" :disabled="true" :value="currentapp.storage_used"/>
</el-form-item>
<el-form-item label="应用名称" style="width: 56%">
<el-input v-model="currentapp.name"/>
</el-form-item>

@ -1,6 +1,5 @@
<template>
<div style="margin-top: 20px;width: 66%;margin-left: 8%">
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
@ -10,249 +9,14 @@
<bind-domain v-if="bind_domain_sure" :app_id="this.currentapp.app_id" :c_domain_name="this.currentapp.domain_name"
:domain_type="2" transitionName="bind-app-domain"/>
</el-dialog>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="download_password_sure"
center
title="应用下载授权码设置"
width="900px">
<el-dialog
:visible.sync="makeTokenVisible"
append-to-body
center
title="下载授权码生成"
width="500px">
<el-dialog
:visible.sync="addUdidVisible"
append-to-body
center
title="添加设备udid授权"
width="500px">
<el-tag style="margin: 10px 0">可以输入多个udid,以空格或者回车分割</el-tag>
<el-input
v-model="makeTokenInfo.udids"
:rows="17"
placeholder="可以输入多个udid,以空格或者回车分割"
type="textarea">
</el-input>
<span slot="footer">
<el-button @click="addUdidVisible=false,makeTokenInfo.udids=''">取消</el-button>
<el-button @click="addUididFunc">确定</el-button>
</span>
</el-dialog>
<el-form ref="form" :model="makeTokenInfo" label-width="110px">
<el-form-item label="指定授权码" style="width: 400px">
<el-tag>当存在指定授权码授权码长度和生成数量被禁用</el-tag>
<el-input v-model="makeTokenInfo.token" clearable placeholder="输入指定下载授权码"></el-input>
</el-form-item>
<el-form-item label="授权码长度">
<el-input-number v-model="makeTokenInfo.token_length" :disabled="tokendisable" :max="32"
:min="4"></el-input-number>
<el-tag style="margin-left: 5px">授权码长度为4-32之间</el-tag>
</el-form-item>
<el-form-item label="生成数量">
<el-input-number v-model="makeTokenInfo.token_number" :disabled="tokendisable" :max="1024"
:min="1" :step="20"></el-input-number>
<el-tag style="margin-left: 5px">单次最多可生成1024个</el-tag>
</el-form-item>
<el-form-item label="最大使用次数">
<el-input-number v-model="makeTokenInfo.token_max_used_number" :disabled="makeTokenInfo.bind_status"
:max="1024"
:min="0"></el-input-number>
<el-tag style="margin-left: 5px">0表示不限使用次数</el-tag>
</el-form-item>
<el-form-item v-if="currentapp.type===1 && currentapp.issupersign" label="是否绑定设备">
<el-tooltip placement="top">
<div slot="content">
<span v-if="makeTokenInfo.bind_status"> 开启绑定设备</span>
<span v-else> 关闭绑定设备</span>
</div>
<el-switch
v-model="makeTokenInfo.bind_status"
:active-value="true"
:inactive-value="false"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</el-tooltip>
<el-tag style="margin-left: 5px">绑定设备之后二次自动进行设备授权下载应用</el-tag>
</el-form-item>
<el-form-item v-if="currentapp.type===1 && currentapp.issupersign && makeTokenInfo.bind_status===true"
label="添加授权设备">
<el-button size="mini" @click="addudid">
添加
</el-button>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelDownloadToken">取消</el-button>
<el-button @click="makeDownloadToken">生成</el-button>
</span>
</el-dialog>
<el-input
v-model="dpwdsearch"
:placeholder="dpwdtitle"
clearable
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-button icon="el-icon-search" type="primary" @click="handleSearch(1)">
搜索
</el-button>
<div style="float: right;width: 400px;text-align: right">
<el-button style="margin:0 10px 5px" @click="makeTokenVisible=true">
生成下载授权码
</el-button>
<el-button plain style="margin: 0 10px 5px" type="warning" @click="cleanToken('all')">清空所有授权码</el-button>
<el-button plain style="margin: 5px 10px 5px" type="warning" @click="cleanToken('invalid')">清理无效授权码</el-button>
<el-button plain style="margin: 5px 10px 5px" type="warning" @click="cleanToken('some')">清理选中授权码</el-button>
</div>
<el-table
v-loading="loading"
:data="app_download_token_list"
border
stripe
style="width: 100%"
@selection-change="tokenHandleSelectionChange">
<el-table-column
align="center"
type="selection"
width="55">
</el-table-column>
<el-table-column
align="center"
label="授权码"
prop="token"
>
<template slot-scope="scope">
<el-popover
placement="top-start"
trigger="hover"
width="200">
<p>
<el-tag v-clipboard:copy="scope.row.token" v-clipboard:success="copy_success" type="success">
<el-link :underline="false">点击复制授权码</el-link>
</el-tag>
</p>
<p>
<el-tag v-clipboard:copy="format_copy_text(scope.row.token)" v-clipboard:success="copy_success">
<el-link :underline="false">点击复制授权码下载链接</el-link>
</el-tag>
</p>
<el-link slot="reference" :underline="false">{{ scope.row.token }}</el-link>
</el-popover>
</template>
</el-table-column>
<el-table-column
:formatter="tokenformatter"
align="center"
label="生成日期"
prop="create_time"
width="160">
</el-table-column>
<el-table-column
align="center"
label="使用次数"
prop="used_count"
width="100"
>
<template slot-scope="scope">
<span v-if="scope.row.max_limit_count === 0">不限次数</span>
<span v-else>{{ scope.row.used_count }}</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="最大可用次数"
prop="max_limit_count"
width="110">
<template slot-scope="scope">
<span v-if="scope.row.max_limit_count === 0">不限次数</span>
<span v-else>{{ scope.row.max_limit_count }}</span>
</template>
</el-table-column>
<div v-if="currentapp.type===1 && currentapp.issupersign">
<el-table-column
align="center"
label="绑定设备"
prop="bind_status"
width="60">
<template slot-scope="scope">
<el-tag v-if="scope.row.bind_status"></el-tag>
<el-tag v-else type="info"></el-tag>
</template>
</el-table-column>
<el-table-column
align="center"
label="设备ID"
prop="bind_udid">
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-clipboard:copy="scope.row.bind_udid"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.bind_udid }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
</div>
<el-table-column
align="center"
fixed="right"
label="操作"
width="90">
<template slot-scope="scope">
<div>
<el-tooltip :content="get_reset_title()" placement="top">
<el-button
size="mini"
@click="resetDownloadUsed(scope.row)">重置
</el-button>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 20px">
<el-pagination
:current-page.sync="pagination.currentPage"
:page-size="pagination.pagesize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total,sizes, prev, pager, next"
@size-change="tokenHandleSizeChange"
@current-change="tokenHandleCurrentChange">
</el-pagination>
</div>
<span slot="footer">
<el-button @click="closeDownloadTokenInfo">关闭</el-button>
</span>
</el-dialog>
<el-form label-width="80px">
<el-form-item label="下载授权码" label-width="200px">
<el-form-item label="访问密码" label-width="200px">
<el-tooltip placement="top">
<div slot="content">
{{ passwordtip.msg }}<br>
<div v-if="passwordtip.val === 'on'">
<el-link :underline="false" icon="el-icon-edit" @click="handleSearch(1)">查看配置下载授权码</el-link>
<el-link :underline="false" icon="el-icon-edit" @click="setaccesspassword">修改</el-link>
</div>
</div>
<el-switch
@ -265,12 +29,7 @@
</el-switch>
</el-tooltip>
<el-popover
placement="top"
trigger="hover">
<el-link :underline="false" icon="el-icon-edit" @click="handleSearch(1)">查看配置下载授权码</el-link>
<el-link slot="reference" :underline="false" style="margin-left: 20px">设置授权码之后用户需要输入授权码才可以下载该应用</el-link>
</el-popover>
<el-link :underline="false" style="margin-left: 20px">设置密码之后用户需要输入密码才可以下载该应用</el-link>
</el-form-item>
@ -334,10 +93,9 @@
</template>
<script>
import {appDownloadToken, apputils,} from "@/restful"
import {apputils,} from "@/restful"
import {deepCopy} from "@/utils";
import BindDomain from "@/components/base/BindDomain";
import {format_time} from "@/utils/base/utils";
export default {
name: "FirAppInfossecurity",
@ -357,159 +115,9 @@ export default {
wxeasy_disable: false,
defualt_dtitle: '专属下载页域名',
bind_domain_sure: false,
download_password_sure: false,
app_download_token_list: [],
loading: false,
makeTokenVisible: false,
addUdidVisible: false,
tokendisable: false,
pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
dpwdsearch: '',
dpwdtitle: '输入下载授权码',
makeTokenInfo: {
token: '',
token_length: 6,
token_number: 20,
token_max_used_number: 0,
bind_status: false,
udids: ''
},
multipleSelection: [],
}
},
methods: {
addUididFunc() {
let udids = this.makeTokenInfo.udids
if (udids && udids.length > 10) {
this.makeDownloadToken()
} else {
this.$message.error('设备udid不存在,请检查')
}
},
get_reset_title() {
let title = '重置使用次数'
if (this.currentapp.type === 1 && this.currentapp.issupersign) {
title = '重置使用次数和授权设备ID'
}
return title
},
addudid() {
this.makeTokenInfo.udids = ''
this.addUdidVisible = true
},
format_copy_text(token) {
return this.currentapp.preview_url + "?password=" + token
},
copy_success() {
this.$message.success('复制剪切板成功');
},
cleanToken(act) {
appDownloadToken(data => {
this.loading = false
if (data.code === 1000) {
this.$message.success("操作成功")
this.handleSearch(1)
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT',
app_id: this.currentapp.app_id,
data: {'act': act, 'tokens': this.format_token(this.multipleSelection)}
})
},
format_token(token_info_list) {
let format_token_list = []
for (let token_info of token_info_list) {
format_token_list.push(token_info.token)
}
return format_token_list
},
tokenHandleSelectionChange(val) {
this.multipleSelection = val;
},
cancelDownloadToken() {
this.makeTokenInfo = {
token: '',
token_length: 6,
token_number: 20,
token_max_used_number: 0,
bind_status: false,
udids: ''
}
this.makeTokenVisible = false
this.addUdidVisible = false
},
resetDownloadUsed(info) {
appDownloadToken(data => {
this.loading = false
if (data.code === 1000) {
this.$message.success("重置成功")
this.showDownloadBase()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT', app_id: this.currentapp.app_id, data: {'token': info.token}
})
},
makeDownloadToken() {
appDownloadToken(data => {
this.loading = false
if (data.code === 1000) {
this.cancelDownloadToken()
this.$message.success("下载授权码生成成功")
this.showDownloadBase()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'POST', app_id: this.currentapp.app_id, data: this.makeTokenInfo
})
},
handleSearch(val) {
this.pagination.currentPage = val;
this.showDownloadBase()
},
showDownloadBase() {
this.showDownloadToken({
"size": this.pagination.pagesize,
"page": this.pagination.currentPage,
"dpwdsearch": this.dpwdsearch.replace(/^\s+|\s+$/g, "")
})
},
showDownloadToken(data) {
this.loading = true
appDownloadToken(data => {
this.loading = false
if (data.code === 1000) {
this.download_password_sure = true;
this.app_download_token_list = data.data
this.pagination.total = data.count
} else {
this.$message.error("获取失败了 " + data.msg)
}
}, {
methods: 'GET', app_id: this.currentapp.app_id, data: data
})
},
closeDownloadTokenInfo() {
this.download_password_sure = false;
},
tokenHandleSizeChange(val) {
this.pagination.pagesize = val;
this.pagination.currentPage = 1;
this.showDownloadBase();
},
tokenHandleCurrentChange(val) {
this.pagination.currentPage = val;
this.showDownloadBase();
},
// eslint-disable-next-line no-unused-vars
tokenformatter(row, column) {
return format_time(row.create_time)
},
set_default_flag() {
this.passwordflag = false;
this.showdownloadflag = false;
@ -536,7 +144,7 @@ export default {
});
},
setbuttondefaltpass(currentapp) {
if (currentapp.need_password === false) {
if (currentapp.password === '') {
this.passwordtip.val = 'off';
this.showpasswordevent("off");
} else {
@ -583,6 +191,57 @@ export default {
this.setxeasytypeshow(currentapp);
this.setwxredirectshow(currentapp);
},
passwordswitch(state) {
this.passwordflag = false;
this.showpasswordevent(state);
this.passwordtip.val = state;
this.passwordflag = true;
},
setaccesspassword() {
this.$prompt('', '请设置访问密码', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
inputValue: `${this.currentapp.password}`,
}).then(({value}) => {
value = value.replace(/\s+/g, "");
if (this.currentapp.password === value) {
if (value === '') {
this.$message({
type: 'success',
message: '访问密码未变'
});
this.passwordflag = false;
this.setbuttondefaltpass(this.currentapp);
return
} else {
return
}
}
this.saveappinfo({
"password": value,
});
if (value === '') {
this.passwordswitch("off");
this.$message({
type: 'success',
message: '设置成功,取消密码访问'
});
} else {
this.passwordtip.msg = '访问密码:' + value;
this.$message({
type: 'success',
message: '设置成功,访问密码是: ' + value
});
}
this.currentapp.password = value;
this.$store.dispatch('doucurrentapp', this.currentapp)
}).catch(() => {
if (this.currentapp.password === '') {
this.passwordswitch("off")
}
});
},
showdownloadevent(newval) {
if (newval === "on") {
if (this.showdownloadflag) {
@ -605,21 +264,19 @@ export default {
showpasswordevent(newval) {
if (newval === "on") {
if (this.passwordflag) {
this.saveappinfo({
"need_password": 1,
});
this.currentapp.need_password = 1;
this.setaccesspassword()
} else {
this.passwordtip.msg = '访问密码:' + this.currentapp.password;
}
this.passwordtip.msg = '已经开启授权码下载功能'
} else {
if (this.passwordflag) {
this.saveappinfo({
"need_password": 0,
"password": '',
});
this.currentapp.need_password = 0;
}
this.passwordtip.msg = '未开启授权码下载功能'
this.currentapp.password = '';
this.$store.dispatch('doucurrentapp', this.currentapp);
this.passwordtip.msg = '无访问密码'
}
},
@ -667,12 +324,12 @@ export default {
this.currentapp = this.$store.state.currentapp;
this.set_default_flag();
this.orgcurrentapp = deepCopy(this.currentapp);
// if (!this.currentapp.domain_name || this.currentapp.domain_name.length < 3) {
// if (this.$store.state.userinfo.domain_name && this.$store.state.userinfo.domain_name.length > 3) {
// this.defualt_dtitle = this.$store.state.userinfo.domain_name;
// }
// }
this.setbuttondefault(this.currentapp);
if (this.currentapp.type === 1 && this.currentapp.issupersign) {
this.dpwdtitle = '输入下载授权码或者设备udid'
} else {
this.dpwdtitle = '输入下载授权码'
}
}
},
mounted() {
@ -685,14 +342,6 @@ export default {
'$store.state.currentapp': function () {
this.appinit();
},
'makeTokenInfo.token': function () {
this.tokendisable = !!(this.makeTokenInfo.token && this.makeTokenInfo.token.length > 0);
},
'makeTokenInfo.bind_status': function () {
if (this.makeTokenInfo.bind_status) {
this.makeTokenInfo.token_max_used_number = 1;
}
}
}, computed: {}
}
</script>

@ -114,34 +114,6 @@
>保存
</el-button>
</el-form-item>
<el-divider/>
<el-form-item label="设备异常等待跳转"
label-width="200px">
<el-switch
v-model="currentapp.abnormal_redirect"
:active-value="true"
:disabled="supersign_disable"
:inactive-value="false"
active-color="#13ce66"
inactive-color="#ff4949"
@change="saveappinfo({abnormal_redirect:currentapp.abnormal_redirect})">
</el-switch>
<el-link :underline="false" style="margin-left: 20px">该配置仅当开启异常设备注册等待有效若启用该配置根据规则会自动跳转链接</el-link>
</el-form-item>
<el-divider/>
<el-form-item label="签名不足跳转链接" label-width="200px">
<el-input v-model="currentapp.supersign_redirect_url" :disabled="supersign_disable"
clearable prefix-icon="el-icon-s-promotion"
style="width: 60%;margin-right: 10px"/>
<el-button :disabled="supersign_disable"
@click="saveappinfo({supersign_redirect_url:currentapp.supersign_redirect_url})"
>保存
</el-button>
<el-link :underline="false">当设备余额不足无法进行签名可配置该链接跳转到第三方下载平台</el-link>
</el-form-item>
<el-divider/>
</el-form>
<el-link v-else :underline="false" type="warning"> 该用户暂未开通超级签权限,请联系管理员申请开通</el-link>
@ -151,7 +123,7 @@
</template>
<script>
import {appSignInfo,} from "@/restful"
import {apputils,} from "@/restful"
import {deepCopy} from "@/utils";
import AppleDeveloperBindApp from "@/components/base/AppleDeveloperBindApp";
@ -192,14 +164,13 @@ export default {
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
appSignInfo(data => {
apputils(data => {
if (data.code === 1000) {
this.$message.success('数据更新成功');
} else {
this.$message.error('操作失败,' + data.msg);
this.$store.dispatch('doucurrentapp', this.orgcurrentapp);
}
this.getappinfo();
loading.close();
}, {
"methods": "PUT",
@ -207,20 +178,6 @@ export default {
"data": data
});
},
getappinfo() {
appSignInfo(data => {
if (data.code === 1000) {
for (let name of Object.keys(data.data)) {
this.currentapp[name] = data.data[name]
}
} else {
this.$message.error('数据获取失败,' + data.msg);
}
}, {
"methods": "GET",
"app_id": this.currentapp.app_id,
});
},
setbuttonsignshow(currentapp) {
if (currentapp.issupersign === true) {
this.supersignevent("on");
@ -272,9 +229,6 @@ export default {
},
appinit() {
this.currentapp = this.$store.state.currentapp;
if (this.currentapp.app_id) {
this.getappinfo();
}
this.set_default_flag();
this.orgcurrentapp = deepCopy(this.currentapp);
this.set_default_ms(this.currentapp);

@ -4,7 +4,7 @@
:color="app.master_color"
:timestamp="app.created_time|formatdatatimeline"
placement="top">
<el-card :body-style="{border: app.is_master ? '#2abb9d 1px solid' : ''}">
<el-card>
<div class="directive-view-release">
<i v-if="! app.is_master" class="el-icon-cloudy"/>
@ -80,7 +80,7 @@
<el-button v-if="!currentapp.issupersign" class="tooltip-top" @click="previewRelease(app)"><i
class="el-icon-view"/> <span
class="ng-binding">预览该版本</span>
class="ng-binding">预览</span>
</el-button>
<el-button v-if="! app.is_master" class="tooltip-top" @click="make_master_release(app)">
@ -103,7 +103,7 @@
</template>
<script>
import {releaseapputils} from "@/restful"
import {getdownloadurl, releaseapputils} from "@/restful"
export default {
name: "FirAppInfostimeline",
@ -126,23 +126,30 @@ export default {
}
},
downloadPackage(app) {
releaseapputils(res => {
if (res.code === 1000) {
window.location.href = res.data.download_url;
} else {
this.$message.error(res.msg);
}
}, {
methods: 'POST',
app_id: this.currentapp.app_id,
release_id: app.release_id,
data: {'token': app.download_token, 'short': this.currentapp.short}
}
);
getdownloadurl(res => {
if (res.code === 1000) {
window.location.href = res.data.download_url;
} else {
this.$message.error(res.msg);
}
}, {
'data': {
'token': app.download_token,
'short': this.currentapp.short,
'release_id': app.release_id,
"password": this.currentapp.password,
"isdownload": true,
},
'app_id': this.currentapp.app_id
})
},
previewRelease(app) {
window.open(this.currentapp.preview_url + '?release_id=' + app.release_id, '_blank', '');
let routeData = this.$router.resolve({
name: 'FirDownload',
params: {short: this.currentapp.short},
query: {release_id: app.release_id}
});
window.open(this.currentapp.preview_url + routeData.href, 'target', '');
},
getAppTimelineFun(act = '') {
const loading = this.$loading({

@ -222,7 +222,7 @@
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="18">
<el-input v-model="short">
<template slot="prepend">{{ analyseappinfo.short_domain_name }}</template>
<template slot="prepend">{{ analyseappinfo.short_domain_name }}/</template>
</el-input>
</el-col>
</el-row>
@ -445,8 +445,8 @@
<div>
<el-tooltip placement="top">
<div slot="content">
1.下载应用 {{ times_info.every_size|diskSize }} 消耗 {{ times_info.base_times / 100 }} 下载次数<br>
2.私有存储模式下载应用 {{ times_info.every_size|diskSize }} 消耗 {{ times_info.private_times / 100 }} 下载次数<br>
1.下载应用100M消耗一次下载次数<br>
2.超级签下载下载消耗次数翻倍<br>
</div>
<span class="name">购买次数</span>
</el-tooltip>
@ -491,16 +491,12 @@
<div class=" app-animator">
<div class="card app card-ios">
<el-tooltip>
<div slot="content">总下载次数{{ r.count_hits }}<br/>当天下载次数{{ r.today_hits_count }}</div>
<i class="down-icon el-icon-cloudy"> {{ r.count_hits }} &nbsp;|&nbsp; {{ r.today_hits_count }}</i>
</el-tooltip>
<i v-if="r.type === 1" class="type-icon iconfont icon-ios"/>
<i v-if="r.type === 1" class=" type-icon iconfont icon-ios"/>
<i v-if="r.type === 0" class="type-icon iconfont icon-android2"/>
<div v-if="r.type === 1 && r.issupersign" class="type-mark" style="border-top: 48px solid #a2b0f5"></div>
<div v-if="r.type === 1 && !r.issupersign" class="type-mark"></div>
<div v-if="r.type === 0" class="type-mark" style="border-top: 48px solid #a4c639"></div>
<div v-if="r.type === 1" class="type-mark"></div>
<div v-if="r.type === 0" class="type-mark" style="border-top: 48px solid #A4C639"></div>
<a class="appicon" @click="appInfos(r)">
<img :src="r.master_release.icon_url|make_icon_url" alt="" class="icon ng-isolate-scope"
height="100" width="100"></a>
@ -591,7 +587,7 @@
</el-container>
</template>
<script>
import {analyseApps, apputils, checkCanSign, get_package_prices, getapps, getuploadurl, my_order} from "@/restful";
import {analyseApps, apputils, get_package_prices, getapps, getuploadurl, my_order} from "@/restful";
import {
dataURLtoFile,
deepCopy,
@ -719,41 +715,22 @@ export default {
analyseappinfo[name] = data.data[name]
}
if (this.currentfile) {
checkCanSign(res => {
if (res.code === 1000) {
analyseappinfo.sign = res.data.sign
}
this.willuploadApp = true;
resolve()
}, {methods: 'GET'})
this.willuploadApp = true;
resolve()
} else {
this.uploadcloud(analyseappinfo, file, true, resolve)
}
} else {
let err_msg = "应用 " + analyseappinfo.appname
if (data.code === 1003) {
err_msg += " 上传token获取失败,请刷新重试"
} else {
err_msg += data.msg
}
this.$message.error(err_msg)
if (this.currentfile) {
this.closeUpload()
} else {
this.trymultiFileList.push(file);
}
this.$message.error("应用 " + analyseappinfo.appname + " 上传token获取失败,请刷新重试")
resolve()
this.trymultiFileList.push(file);
// this.multiFileList.splice(this.multiFileList.indexOf(file), 1)
}
loading.close();
}, {
'methods': 'POST',
'data': {
"bundleid": analyseappinfo.bundleid,
"type": analyseappinfo.type,
"filesize": analyseappinfo.filesize
}
'data': {"bundleid": analyseappinfo.bundleid, "type": analyseappinfo.type}
});
} else {
@ -1011,11 +988,10 @@ export default {
this.willuploadApp = false;
this.uploadflag = false;
this.analyseappinfo = {};
this.currentfile = null;
this.multiupload = false;
this.multiuploaddisable = false;
this.multiFileList = [];
this.trymultiFileList = [];
this.$refs.upload.clearFiles();
this.$refs.upload.abort();
this.uploadprocess = deepCopy(fiveProcess);
@ -1043,7 +1019,7 @@ export default {
if (this.searchfromtype) {
this.getappsFun({"type": this.searchfromtype, 'page': 1, size: 999});
} else {
this.getappsFun({'page': 1, size: 999, 'search': keysearch});
this.getappsFun({'page': 1, size: 999});
}
}
},
@ -1070,7 +1046,7 @@ export default {
let keysearch = this.keysearch.replace(/^\s+|\s+$/g, "");
let newapplists = [];
for (let i = 0; i < this.orgapplists.length; i++) {
if (this.orgapplists[i].name.search(keysearch) >= 0 || this.orgapplists[i].short.search(keysearch) >= 0) {
if (this.orgapplists[i].name.search(keysearch) >= 0) {
newapplists.push(this.orgapplists[i]);
}
}
@ -1164,16 +1140,12 @@ export default {
this.$router.push({name: 'FirAppInfostimeline', params: {id: app.app_id}})
},
appDownload(app) {
window.open(app.preview_url, '_blank', '');
window.open(app.preview_url + '/' + app.short, '_blank', '');
}
}, computed: {
getDelappTitle() {
return `删除应用 ${this.delapp.name}`
},
times_info() {
let times_info = this.$store.state.userinfo.times_info
return times_info ? times_info : {}
}
},
filters: {
diskSize,
@ -1377,16 +1349,6 @@ export default {
}
.page-apps .card.app .down-icon {
position: absolute;
top: 9px;
left: 7px;
z-index: 2;
/*color: #1f72ee;*/
color: #B6BDC1;
}
.page-apps .card.app .type-mark {
position: absolute;
top: 0;

@ -3,8 +3,8 @@
<div>
<div style="text-align: center">
<el-transfer
id="transfer"
v-model="choices_data"
v-loading="loadings"
:button-texts="['', '']"
:data="app_developer_lists"
:format="{
@ -93,7 +93,6 @@
<script>
import {developerBindAppFun} from "@/restful";
import {sort_compare} from "@/utils";
import {Loading} from "element-ui";
export default {
name: 'AppleDeveloperBindApp',
@ -121,7 +120,8 @@ export default {
s_props: {},
s_titles: [],
app_private_used_number: 100,
app_limit_number: 0,
loadings: false,
app_limit_number: 0
};
},
mounted() {
@ -147,12 +147,14 @@ export default {
},
handleChange(value, direction, movedKeys) {
if (this.issuer_id && direction === 'right') {
//
// for (let i = 0; i < movedKeys.length; i++) {
// let app_info = this.get_choice_data_from_key([movedKeys[i]])[0][0]
// if (!(app_info && app_info.app_used_number > 0)) {
// this.choices_data.splice(this.choices_data.indexOf(movedKeys[i]), 1)
// }
// if(value.length > this.app_limit_number){
// this.$message.warning(','+this.app_limit_number+'')
for (let i = 0; i < movedKeys.length; i++) {
let app_info = this.get_choice_data_from_key([movedKeys[i]])[0][0]
if (!(app_info && app_info.app_used_number > 0)) {
this.choices_data.splice(this.choices_data.indexOf(movedKeys[i]), 1)
}
}
// }
}
if (this.app_id && direction === 'right') {
@ -219,7 +221,7 @@ export default {
})
},
getBindInfo(params) {
let loadingInstance = Loading.service({target: '#transfer'})
this.loadings = true
if (this.issuer_id) {
params.issuer_id = this.issuer_id
}
@ -248,9 +250,7 @@ export default {
} else {
this.$message.error("数据获取失败" + data.msg)
}
this.$nextTick(() => { // Loading
loadingInstance.close()
});
this.loadings = false
}, {methods: 'GET', data: params})
},
format_data(data) {

@ -21,7 +21,6 @@
</div>
请联系域名管理员前往 <strong>{{ domain_name }}</strong> 域名 DNS 管理后台添加如下 CNAME 记录
<el-table
v-loading="is_loading"
:data="domain_tData"
border
stripe
@ -52,11 +51,6 @@
prop="dns"
width="300">
<template slot-scope="scope">
<el-tooltip content="该记录值为私有下载服务器">
<el-tag v-if="scope.row.is_private" style="margin-right: 5px" type="small">Private</el-tag>
</el-tooltip>
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.dns" v-clipboard:copy="scope.row.dns"
v-clipboard:success="copy_success"
@ -86,7 +80,7 @@
<el-col :span="6">
<el-button plain size="small" style="margin-top: 8px" type="danger"
@click="remove_domain">
解除绑定并删除记录
解除绑定
</el-button>
</el-col>
</el-row>
@ -104,14 +98,13 @@
<el-col :span="6">
<el-button plain size="small" style="margin-top: 8px" type="danger"
@click="remove_domain">
解除绑定并删除记录
解除绑定
</el-button>
</el-col>
</el-row>
</div>
<el-table
v-loading="is_loading"
:data="domain_tData"
border
stripe
@ -133,23 +126,10 @@
label="记录值"
prop="dns"
width="300">
<template slot-scope="scope">
<el-tooltip content="该记录值为私有下载服务器">
<el-tag v-if="scope.row.is_private" style="margin-right: 5px" type="small">Private</el-tag>
</el-tooltip>
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.dns" v-clipboard:copy="scope.row.dns"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.dns }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div v-if="!bind_status" style="text-align: center;margin: 30px 0">
<el-button :disabled="is_loading" plain type="success" @click="check_cname">已经修改配置再次检查绑定</el-button>
<el-button plain type="success" @click="check_cname">已经修改配置再次检查绑定</el-button>
</div>
</div>
@ -159,8 +139,8 @@
<el-button :disabled="bind_status|| active===1 "
@click="last">上一步
</el-button>
<el-button v-if="force_bind" :disabled="is_loading" @click="next(1)">强制绑定</el-button>
<el-button v-else :disabled="is_loading" @click="next(0)">下一步</el-button>
<el-button v-if="force_bind" @click="next(1)">强制绑定</el-button>
<el-button v-else @click="next(0)">下一步</el-button>
</div>
@ -193,18 +173,17 @@ export default {
c_domain_name: {
type: String,
default: ''
}
},
},
data() {
return {
active: 1,
bind_status: false,
bind_domain_sure: true,
domain_tData: [{'type': 'CNAME', 'host': 'xxx', 'dns': 'demo.xxx.cn', 'is_private': false}],
domain_tData: [{'type': 'CNAME', 'host': 'xxx', 'dns': 'demo.xxx.cn'}],
domain_name: '',
force_bind: false,
b_t_msg: '您的账户',
is_loading: false
}
},
mounted() {
@ -224,13 +203,10 @@ export default {
this.$message.success('复制剪切板成功');
},
check_cname() {
this.is_loading = true
domainFun(data => {
this.is_loading = false
if (data.code === 1000) {
if (this.active++ > 2) this.active = 3;
this.bind_status = true;
this.$message.success("绑定成功")
if (!this.app_id) {
if (this.domain_type === 1) {
this.$store.dispatch("dodomainshow", false);
@ -282,7 +258,7 @@ export default {
this.domain_name = data.data.domain_name;
}
if (data.data.domain_record) {
this.format_domain_tData(data.data);
this.format_domain_tData(data.data.domain_record);
if (this.active++ > 2) this.active = 3;
}
if (data.data.is_enable) {
@ -305,15 +281,14 @@ export default {
domain_name_list.splice(domain_name_list.length - 2, 2);
this.domain_tData[0].host = domain_name_list.join(".")
}
this.domain_tData[0].dns = cname_domain.domain_record;
this.domain_tData[0].is_private = cname_domain.is_private;
this.domain_tData[0].dns = cname_domain;
},
next(force_bind) {
if (this.active === 1) {
domainFun(data => {
if (data.code === 1000) {
if (data.data && data.data.domain_record) {
this.format_domain_tData(data.data);
if (data.data && data.data.cname_domain) {
this.format_domain_tData(data.data.cname_domain);
if (this.active++ > 2) this.active = 3;
}
} else {

@ -15,13 +15,9 @@
<el-divider direction="vertical"/>
<div class="item"><p style="margin-bottom: 16px">应用举报</p>
</div>
<div>
<el-link :href="github" :underline="false"><p><b>FlyApps</b></p></el-link>
</div>
<el-divider/>
<el-tooltip content="拿出手机扫一扫" style="margin-top: 33px">
<el-image :alt="github" :lazy="true" :src="require('@/assets/github.png')"
style="width: 160px">
<p><b>flyapps@126.com</b></p>
<el-tooltip content="拿出手机扫描添加微信" style="margin-top: 33px">
<el-image :lazy="true" :src="require('@/assets/wxchat.jpg')" alt="扫描添加微信" style="width: 160px">
</el-image>
</el-tooltip>
@ -34,8 +30,7 @@ export default {
name: "FirContact",
data() {
return {
bg_img: require("@/assets/imgs/banner_1.jpg"),
github: process.env.base_env.github
bg_img: require("@/assets/imgs/banner_1.jpg")
}
},
}
@ -49,7 +44,7 @@ export default {
.content {
width: 942px;
margin: 10px auto 160px;
margin: 80px auto 160px;
text-align: center;
}

@ -114,12 +114,12 @@ export default {
{
logo: require("@/assets/imgs/qq.png"),
title: "核心优势3",
content: "<p>QQ微信等提供在线解答</p>帮助用户快速准确解决问题"
content: "<p>利用QQ微信等提供在线解答</p>帮助用户快速准确解决问题和故障"
},
{
logo: require("@/assets/imgs/skill.png"),
title: "核心优势4",
content: "<p>自定义配置,容器化部署</p>升级维护超级便捷"
content: "<p>由技术支持工程师,负责问题解答</p>需求受理及故障受理"
}
]
}

@ -21,10 +21,10 @@
<el-link @click="$router.push({name:'FirApps'})">登录</el-link>
</el-col>
<el-col :span="8">
<el-link @click="$router.push({name:'FirContact'})">联系我们</el-link>
<el-link @click="$router.push({name:'FirRegist'})">注册</el-link>
</el-col>
<el-col :span="8">
<el-link @click="goGithub">Github</el-link>
<el-link @click="$router.push({name:'FirContact'})">联系我们</el-link>
</el-col>
</el-row>
</el-col>
@ -64,11 +64,6 @@ export default {
'line-height': '45px',
background: '#e7eaf1'
},
github: process.env.base_env.github
}
}, methods: {
goGithub() {
window.open(this.github, '_blank', '');
}
}
}

@ -6,8 +6,8 @@
<p style="color:#b2b2b2">Company News</p>
</div>
<div class="nav text-center">
<a class="active" href="#">开发历程</a>
<a href="#">发展足迹</a>
<a class="active" href="#">公司新闻</a>
<a href="#">行业动态</a>
</div>
<ul class="news-container">
<li v-for="(item,index) in newsList" :key="index">
@ -40,18 +40,6 @@ export default {
show: false,
newsList: [
{
id: '008',
title: 'ios16系统',
introduce: '超级签名字支持ios16系统签名,只需要手机开启调试模式,即可安装打开',
date: '09-22',
year: '2022'
}, {
id: '007',
title: '私有下载页部署',
introduce: '支持私有下载页部署,方便快捷,稳定访问',
date: '05-18',
year: '2022'
}, {
id: '001',
title: '现有版本',
introduce: '经过好长时间的编写,平台已经支持超级签名,权限管理,自定义域名,自定义存储',

File diff suppressed because it is too large Load Diff

@ -204,7 +204,6 @@
import {advertinfo} from "@/restful";
import {VueCropper} from 'vue-cropper'
import {AvatarUploadUtils, dataURLtoFile, getUserInfoFun} from '@/utils'
import {format_time} from "@/utils/base/utils";
export default {
name: "FirUserAdvert",
@ -299,12 +298,6 @@ export default {
}
},
beforeAvatarUpload(file, id, act) {
if (!file) {
this.$message.warning("展示图片不存在")
this.bind_advert_sure = false;
this.handleCurrentChange(1)
return
}
return AvatarUploadUtils(this, file, {
'app_id': this.$store.state.userinfo.uid,
'upload_key': file.name,
@ -396,7 +389,11 @@ export default {
},
format_time(stime) {
return format_time(stime)
if (stime) {
stime = stime.split(".")[0].split("T");
return stime[0] + " " + stime[1]
} else
return '';
},
}, mounted() {
getUserInfoFun(this);

@ -48,9 +48,6 @@
label="绑定域名"
prop="domain_name">
<template slot-scope="scope">
<el-tooltip content="该域名解析到了私有下载服务器上面">
<el-tag v-if="scope.row.is_private" style="margin-right: 5px" type="small">Private</el-tag>
</el-tooltip>
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.domain_name" v-clipboard:copy="scope.row.domain_name"
v-clipboard:success="copy_success"
@ -80,28 +77,6 @@
</template>
</el-table-column>
<el-table-column
align="center"
label="开启https"
prop="is_https"
width="110">
<template slot-scope="scope">
<el-tooltip content="点击关闭" placement="top">
<div slot="content">
<span v-if="scope.row.is_https">
点击关闭 HTTPS 访问
</span>
<span v-else>
点击开启支持 HTTPS 访问
</span>
</div>
<el-button v-if="scope.row.is_https" size="small" type="success"
@click="changeHttpsFun(scope.row, false)">已开启
</el-button>
<el-button v-else size="small" type="info" @click="changeHttpsFun(scope.row, true)">未开启</el-button>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
@ -126,6 +101,7 @@
</template>
</el-table-column>
<el-table-column
align="center"
label="跳转权重"
@ -178,10 +154,9 @@
<script>
import {domaininfo, personalConfigInfo} from "@/restful";
import {domaininfo} from "@/restful";
import BindDomain from "@/components/base/BindDomain";
import {format_choices, getUserInfoFun} from '@/utils'
import {format_time} from "@/utils/base/utils";
export default {
name: "FirUserDomain",
@ -196,71 +171,9 @@ export default {
domain_type_choices: [],
current_domain_info: {'domain_type': 1, 'app_id': null},
loading: false,
configVisible: false,
config_lists: [],
short_download_uri: ''
}
},
methods: {
changeHttpsFun(domain_info, is_https) {
domain_info['is_https'] = is_https
domaininfo(data => {
if (data.code === 1000) {
this.$message.success("https支持修改成功")
} else {
this.$message.error("修改失败 " + data.msg)
}
}, {methods: 'PUT', data: domain_info})
},
configFun() {
personalConfigInfo(data => {
if (data.code === 1000) {
if (data.data.length === 1) {
this.short_download_uri = data.data[0].value
}
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'short_download_uri')
personalConfigInfo(data => {
if (data.code === 1000) {
this.config_lists = data.data
this.configVisible = true
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'preview_route')
},
changeConfig(info) {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value}
}, 'preview_route')
},
updateConfig() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE'
}, 'preview_route')
},
saveWeight(domain_info) {
domaininfo(data => {
if (data.code === 1000) {
@ -332,10 +245,16 @@ export default {
},
format_create_time(row) {
return format_time(row.created_time)
return this.format_time(row.created_time)
},
format_time(stime) {
if (stime) {
stime = stime.split(".")[0].split("T");
return stime[0] + " " + stime[1]
} else
return '';
},
}, mounted() {
getUserInfoFun(this);
this.get_data_from_tabname();

@ -1,470 +0,0 @@
<template>
<el-main>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="configVisible"
center
title="下载页部署配置"
width="666px">
<el-card v-for="info in config_lists" :key="info.key" class="box-card" shadow="hover"
style="margin-bottom: 20px">
<div slot="header" class="clearfix">
<span><el-tag size="medium" type="info">配置KEY</el-tag> <el-tag size="medium">{{ info.key }}</el-tag></span>
<div style="float: right">
<el-switch
v-model="info.value"
active-color="#13ce66"
active-text="启用"
active-value="true"
inactive-color="#ff4949"
inactive-text="关闭"
inactive-value="false"
@change="changeConfig(info)">
</el-switch>
</div>
</div>
<el-tag size="medium" type="info">描述信息</el-tag>
{{ info.title }}
</el-card>
<el-divider></el-divider>
<el-card style="margin-top: 20px">
<div slot="header" class="clearfix">
<el-tag size="medium" type="success">下载页源码及操作文档</el-tag>
</div>
<div v-if="short_download_list.length>0">
<div style="margin: 10px" v-for="short_download_uri in short_download_list" :key="short_download_uri.key">
<el-tag size="medium" type="info">{{short_download_uri.title}}</el-tag>
<el-link style="margin-left: 10px" :href="short_download_uri.value" target="_blank">点击下载</el-link>
</div>
</div>
</el-card>
<div slot="footer" class="dialog-footer">
<el-button @click="updateConfig">恢复默认值</el-button>
<el-button @click="configVisible=false">取消</el-button>
</div>
</el-dialog>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="addCnameVisible"
center
title="添加下载页服务器域名"
width="800px">
<el-steps :active="p_active" finish-status="success" :align-center="true">
<el-step title="添加服务器域名"></el-step>
<el-step title="验证服务器域名所有权"></el-step>
<el-step title="添加成功"></el-step>
</el-steps>
<el-container>
<div v-if="p_active===0" style="margin-top: 20px">
<el-tag style="margin: 10px 5px">1.如果是cdn等含有CNAME直接添加cdn的 CNAME </el-tag>
<el-tag style="margin: 10px 5px">2.如果是服务器需要将域名解析到服务器然后添加该解析的域名</el-tag>
<div style="width: 600px;text-align: center;margin: auto">
<el-form ref="form" :model="addSerInfo" label-width="120px">
<el-form-item label="下载服务器域名">
<el-input v-model="addSerInfo.domain_record" placeholder="cdn的cname或者服务器的域名,必填"></el-input>
</el-form-item>
<el-form-item label="下载服务器地址">
<el-input v-model="addSerInfo.ip_address" placeholder="如果有服务器地址,添加服务器ip,否则填写服务器域名"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="addSerInfo.description"></el-input>
</el-form-item>
</el-form>
<div style="margin-top: 20px;">
<el-button type="primary" @click="addCnameSer">下一步</el-button>
</div>
</div>
</div>
<div v-else-if="p_active===1" style="margin-top: 30px;text-align: center">
请联系域名管理员前往 <strong>{{ cnameInfo.domain_record }}</strong> 域名 DNS 管理后台添加如下 {{ cnameInfo.r_type }} 记录
<el-table
:data="domain_tData"
border
stripe
v-loading="loading"
style="width: 100%;margin-top: 20px">
<el-table-column
align="center"
label="记录类型"
prop="r_type"
width="100">
</el-table-column>
<el-table-column
align="center"
label="主机记录"
prop="host_r"
>
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.host_r" v-clipboard:copy="scope.row.host_r"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.host_r }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
align="center"
label="记录值"
prop="cname_r"
width="300">
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.cname_r" v-clipboard:copy="scope.row.cname_r"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.cname_r }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-alert :closable="false"
show-icon
style="margin-top: 30px"
title="请在域名DNS配置成功后,点击“下一步”按钮"
type="warning"/>
<div style="margin-top: 20px;text-align: center;">
<el-button type="primary" @click="p_active=0">上一步</el-button>
<el-button type="primary" @click="checkCnameSer" :disabled="loading">下一步</el-button>
</div>
</div>
</el-container>
</el-dialog>
<div>
<el-input
v-model="search_key"
clearable
placeholder="输入下载服务器域名"
style="width: 30%;margin-right: 30px;margin-bottom: 5px"/>
<el-button icon="el-icon-search" type="primary" @click="handleCurrentChange(1)">
搜索
</el-button>
<div style="float: right">
<el-tooltip content="下载页配置,可以定制部署私有下载页">
<el-button plain type="primary" @click="configFun">
下载页配置
</el-button>
</el-tooltip>
<el-button plain type="primary" @click="addDownloadSer">
添加下载页服务器
</el-button>
</div>
<el-table
v-loading="loading"
:data="cname_info_list"
border
stripe
style="width: 100%">
<el-table-column
align="center"
fixed
label="下载服务器域名"
prop="domain_name">
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.domain_record" v-clipboard:copy="scope.row.domain_record"
v-clipboard:success="copy_success"
:underline="false">{{ scope.row.domain_record }}
</el-link>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
align="center"
label="下载服务器地址"
prop="ip_address">
</el-table-column>
<el-table-column
align="center"
label="状态"
prop="is_enable"
width="110">
<template slot-scope="scope">
<el-tooltip v-if="scope.row.is_enable === true" content="已经绑定成功" placement="left-start">
<el-button size="small" type="success">成功
</el-button>
</el-tooltip>
<el-tooltip v-else content="点击激活服务器域名绑定信息" placement="left-start">
<el-button size="small" type="warning" @click="continueBind(scope.row)">继续绑定
</el-button>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
:formatter="format_create_time"
align="center"
label="域名绑定时间"
prop="created_time"
width="170"
>
</el-table-column>
<el-table-column
align="center"
label="备注"
prop="description">
</el-table-column>
<el-table-column
align="center"
label="操作"
prop="is_enable"
width="100">
<template slot-scope="scope">
<el-tooltip content="删除该下载页服务器" placement="left-start">
<el-button size="small" type="danger" @click="delCnameSer(scope.row)">删除
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
<div style="margin-top: 20px;margin-bottom: 20px">
<el-pagination
:current-page.sync="pagination.currentPage"
:page-size="pagination.pagesize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total,sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</el-main>
</template>
<script>
import {dCnameInfoFun, personalConfigInfo} from "@/restful";
import {getUserInfoFun} from '@/utils'
import {format_time} from "@/utils/base/utils";
export default {
name: "FirUserDomain",
data() {
return {
addSerInfo: {},
cnameInfo: {},
domain_tData: [],
p_active: 0,
cname_info_list: [],
search_key: "",
pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
loading: false,
configVisible: false,
addCnameVisible: false,
config_lists: [],
short_download_list: []
}
},
methods: {
continueBind(cname_info) {
this.addSerInfo = cname_info
this.addCnameVisible = true
this.addCnameSer()
},
delCnameSer(cname_info) {
this.$confirm('若下载域名绑定到该解析,同时也会删除绑定的下载域名,是否继续删除?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
dCnameInfoFun(data => {
if (data.code === 1000) {
this.$message.success("删除成功")
this.get_data_from_tabname();
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE',
data: {'domain_record': cname_info.domain_record}
})
})
},
checkCnameSer() {
this.loading=true
dCnameInfoFun(data => {
this.loading=false
if (data.code === 1000) {
this.$message.success(this.addSerInfo.domain_record + " 添加成功")
this.get_data_from_tabname();
this.addDownloadSer()
this.addCnameVisible = false
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT',
data: {'act': 'check', 'domain_record': this.addSerInfo.domain_record}
})
},
addCnameSer() {
let domain_record = this.addSerInfo.domain_record
let ip_address = this.addSerInfo.ip_address
if (domain_record && domain_record.length > 6 && ip_address && ip_address.length > 6) {
dCnameInfoFun(data => {
if (data.code === 1000) {
this.p_active = 1
this.cnameInfo = data.data
this.domain_tData = [this.cnameInfo]
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'POST',
data: this.addSerInfo
})
} else {
this.$message.error("参数有误")
}
},
addDownloadSer() {
this.addSerInfo = {}
this.cnameInfo = {}
this.p_active = 0
this.addCnameVisible = true
},
configFun() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.short_download_list = data.data
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'short_download_uri')
personalConfigInfo(data => {
if (data.code === 1000) {
this.config_lists = data.data
this.configVisible = true
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'preview_route')
},
changeConfig(info) {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value}
}, 'preview_route')
},
updateConfig() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE'
}, 'preview_route')
},
copy_success() {
this.$message.success('复制剪切板成功');
},
handleSizeChange(val) {
this.pagination.pagesize = val;
this.get_data_from_tabname({
"size": this.pagination.pagesize,
"page": 1
})
},
handleCurrentChange(val) {
this.pagination.currentPage = val;
this.get_data_from_tabname({
"size": this.pagination.pagesize,
"page": this.pagination.currentPage
})
},
get_data_from_tabname(data = {}) {
data.search_key = this.search_key.replace(/^\s+|\s+$/g, "");
this.UserCnameInfoFun(data)
},
UserCnameInfoFun(params) {
this.loading = true;
dCnameInfoFun(data => {
if (data.code === 1000) {
this.cname_info_list = data.data;
this.pagination.total = data.count;
} else {
this.$message.error("域名绑定信息获取失败")
}
this.loading = false;
}, {methods: 'GET', data: params})
},
format_create_time(row) {
return format_time(row.created_time)
},
}, mounted() {
getUserInfoFun(this);
this.get_data_from_tabname();
}
}
</script>
<style scoped>
.el-main {
margin: 20px auto 100px;
width: 1166px;
position: relative;
padding-bottom: 1px;
color: #9b9b9b;
-webkit-font-smoothing: antialiased;
border-radius: 1%;
}
</style>

@ -1,733 +0,0 @@
<template>
<el-main>
<el-dialog :close-on-click-modal="false" :destroy-on-close="true" :title="title"
:visible.sync="dialogReceiverVisible" width="780px">
<el-form ref="recevierform" :model="addreceiverinfo"
label-width="80px" style="margin:0 auto;">
<el-form-item label="姓名">
<el-input v-model="addreceiverinfo.receiver_name"/>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="addreceiverinfo.email"/>
</el-form-item>
<el-form-item v-if="captcha.captcha_image" style="height: 40px">
<el-row style="height: 40px">
<el-col :span="16">
<el-input v-model="addreceiverinfo.verify_code" clearable maxlength="6"
placeholder="请输入验证码" @keyup.enter.native="onSubmit"/>
</el-col>
<el-col :span="8">
<el-image
:src="captcha.captcha_image"
fit="contain"
style="margin:0 4px;border-radius:4px;cursor:pointer;height: 40px" @click="get_auth_code">
</el-image>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-row>
<el-col :span="16">
<el-input v-model="addreceiverinfo.seicode" clearable
placeholder="邮箱验证码" prefix-icon="el-icon-mobile"/>
</el-col>
<el-col :span="8">
<el-button plain style="margin:0 4px;border-radius:4px;cursor:pointer;height: 40px" type="info"
@click="onGetCode">获取验证码
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<div id="captcha" ref="captcha"></div>
</el-form-item>
<el-form-item label="微信">
<el-tag v-if="addreceiverinfo.wxopenid && addreceiverinfo.wxopenid.length > 6">
{{ addreceiverinfo.wxopenid }}
</el-tag>
<el-popover v-else
v-model="wx_visible"
placement="top"
title="打开微信扫一扫进行绑定"
trigger="manual">
<div>
<el-image :src="wx_login_qr_url" style="width: 176px;height: 166px"/>
</div>
<el-button slot="reference" size="small" @click="wxLogin">绑定微信</el-button>
</el-popover>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="addreceiverinfo.description"/>
</el-form-item>
<el-button @click="saveReceiver">保存</el-button>
<el-button @click="cancelReceiver">取消</el-button>
</el-form>
</el-dialog>
<el-dialog :close-on-click-modal="false" :destroy-on-close="true" :visible.sync="dialogChangeReceiverVisible"
title="修改消息接收人">
<div style="text-align: left;margin-bottom: 10px">
<div style="margin-bottom: 10px">
<el-tag type="warning">提醒如果以下消息接收人的信息有变更请到消息接收人管理中进行修改</el-tag>
</div>
消息类型{{ notifyConfigMsg.message_type }} - {{ notifyConfigMsg.config_name }}
</div>
<el-table
ref="multipleTable"
:data="receiver_info_lists"
border
stripe
style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
align="center"
fixed
label="姓名"
prop="receiver_name"
width="100">
</el-table-column>
<el-table-column
align="center"
label="邮箱"
prop="email"
width="200">
</el-table-column>
<el-table-column
align="center"
label="微信"
prop="domain_name"
width="260">
<template slot-scope="scope">
<el-popover v-if="scope.row.weixin.openid" placement="top" trigger="hover">
<div style="float: right;margin-top: 10px">
<el-image :src="scope.row.weixin.head_img_url" style="width: 80px;height: 80px"/>
</div>
<p>昵称: {{ scope.row.weixin.nickname }}</p>
<p>性别: {{ scope.row.weixin.sex|sex_filter }}</p>
<p>住址: {{ scope.row.weixin.address }}</p>
<p>微信ID: {{ scope.row.weixin.openid }}</p>
<div slot="reference" class="name-wrapper">
<el-tag>{{ scope.row.weixin.openid }}</el-tag>
</div>
</el-popover>
<el-tag v-else> /</el-tag>
</template>
</el-table-column>
<el-table-column
align="center"
label="备注"
prop="description">
</el-table-column>
</el-table>
<div style="text-align: left;margin-bottom: 10px;margin-top: 10px">
<el-button plain size="small" type="primary" @click="addRecevier">
新增消息接收人
</el-button>
</div>
<span slot="footer">
<el-button @click="updateConfig(undefined)">保存</el-button>
<el-button @click="dialogChangeReceiverVisible=false">取消</el-button>
</span>
</el-dialog>
<el-tabs v-model="activeName" tab-position="top" type="border-card" @tab-click="handleClick">
<el-tab-pane label="基本接收管理" name="config">
<el-table
:data="[{name:'消息类型',email:'邮件通知',weixin:'微信通知',user:'消息接收人'}]"
:show-header="false"
border
stripe
style="width: 100%">
<el-table-column
align="center"
prop="name"
width="200">
</el-table-column>
<el-table-column
align="center"
prop="email"
width="200">
</el-table-column>
<el-table-column
align="center"
prop="weixin"
width="200">
</el-table-column>
<el-table-column
align="center"
prop="user">
</el-table-column>
</el-table>
<el-collapse v-model="activeConfig">
<el-collapse-item v-for="info in message_type_choices" :key="info.id" :disabled="info.disabled"
:name="info.id">
<template slot="title">
<div style="width: 200px">
<h2>{{ info.name }}</h2>
</div>
</template>
<el-table
:data="info.data"
:show-header="false"
border
stripe
style="width: 100%">
<el-table-column
align="center"
fixed
prop="config_name"
width="200">
</el-table-column>
<el-table-column
align="center"
fixed
prop="enable_email"
width="200">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.enable_email" @change="updateConfig(scope.row)">邮件</el-checkbox>
</template>
</el-table-column>
<el-table-column
align="center"
fixed
prop="enable_weixin"
width="200">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.enable_weixin" @change="updateConfig(scope.row)">微信</el-checkbox>
</template>
</el-table-column>
<el-table-column
align="center"
fixed
prop="senders">
<template slot-scope="scope">
<el-tag v-for="user in scope.row.senders" :key="user.id" style="margin-right: 3px" type="info">
{{ user.receiver_name }}
</el-tag>
<el-tag type="success">
<el-link :underline="false" type="success" @click="changeNotifyConfig(scope.row)">修改</el-link>
</el-tag>
</template>
</el-table-column>
</el-table>
</el-collapse-item>
</el-collapse>
</el-tab-pane>
<el-tab-pane label="消息接收人管理" name="receiver">
<div style="float: right;margin-bottom: 10px">
<el-button plain type="primary" @click="addRecevier">
新增消息接收人
</el-button>
</div>
<el-table
v-loading="loading"
:data="receiver_info_lists"
border
stripe
style="width: 100%">
<el-table-column
align="center"
fixed
label="姓名"
prop="receiver_name"
width="100">
</el-table-column>
<el-table-column
align="center"
label="邮箱"
prop="email"
width="200">
</el-table-column>
<el-table-column
align="center"
label="微信"
prop="domain_name">
<template slot-scope="scope">
<el-popover v-if="scope.row.weixin.openid" placement="top" trigger="hover">
<div style="float: right;margin-top: 10px">
<el-image :src="scope.row.weixin.head_img_url" style="width: 80px;height: 80px"/>
</div>
<p>昵称: {{ scope.row.weixin.nickname }}</p>
<p>性别: {{ scope.row.weixin.sex|sex_filter }}</p>
<p>住址: {{ scope.row.weixin.address }}</p>
<p>微信ID: {{ scope.row.weixin.openid }}</p>
<div slot="reference" class="name-wrapper">
<el-tag>{{ scope.row.weixin.openid }}</el-tag>
</div>
</el-popover>
<el-tag v-else> /</el-tag>
</template>
</el-table-column>
<el-table-column
:formatter="formatter"
align="center"
label="添加时间"
prop="create_time"
width="170">
</el-table-column>
<el-table-column
align="center"
label="备注"
prop="description">
</el-table-column>
<el-table-column
align="center"
label="操作"
width="120">
<template slot-scope="scope">
<!-- <el-button size="small" type="text" @click="editReceiver(scope.row)">编辑</el-button>-->
<el-button size="small" type="text" @click="deleteReceiver(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="阈值设置" name="threshold" style="text-align: center">
<el-tag style="margin: 20px 0"> 低于阈值设置将会出发报警若您配置了消息接收将会将报警信息推送至关联的接收人</el-tag>
<div style="margin: auto;width: 700px;height: 100%">
<el-form ref="form" :model="threshold" label-width="180px">
<el-form-item label="下载次数不足阈值设置">
<el-input-number v-model="threshold.notify_available_downloads" :min="0"
style="width: 300px;margin: 0 20px"></el-input-number>
<el-button @click="notifyThreshold('update')">保存修改</el-button>
</el-form-item>
<el-form-item label="签名次数不足阈值设置">
<el-input-number v-model="threshold.notify_available_signs" :min="0"
style="width: 300px;margin: 0 20px"></el-input-number>
<el-button @click="notifyThreshold('update')">保存修改</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
</el-main>
</template>
<script>
import {notifyConfigInfo, NotifyInfoFun, notifyReceiverInfo, wxBindFun, wxLoginFun} from "@/restful";
import {geetest, getUserInfoFun} from "@/utils";
import {checkEmail, checkphone, format_time, getRandomStr} from "@/utils/base/utils";
export default {
name: "FirUserNotify",
data() {
return {
dialogReceiverVisible: false,
captcha: {"captcha_image": '', "captcha_key": '', "length": 8},
change_type: {email: false, sms: false},
wx_visible: false,
wx_login_qr_url: '',
unique_key: '',
userinfo: {},
addreceiverinfo: {description: '', wxopenid: '', email: '', receiver_name: '', verify_code: '', seicode: ''},
message_type_choices: [],
activeConfig: [],
dialogChangeReceiverVisible: false,
notifyConfigSelection: [],
notifyConfigMsg: {},
title: '',
isedit: false,
activeName: 'config',
receiver_info_lists: [],
loading: false,
threshold: {}
}
}, methods: {
onGetCode() {
this.addreceiverinfo.auth_token = '';
this.addreceiverinfo.seicode = '';
this.onSubmit();
},
onSubmit() {
let email = this.addreceiverinfo.email;
let verify_code = this.addreceiverinfo.verify_code;
let captcha_flag = this.addreceiverinfo.verify_code.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
if (captcha_flag) {
let checke = checkEmail(this.addreceiverinfo.email);
let checkp = checkphone(this.addreceiverinfo.email);
if (checke || checkp) {
let params = {
"target": email,
"verify_code": verify_code,
"captcha_key": this.captcha.captcha_key,
"n_type": 'notify',
};
let seicode = this.addreceiverinfo.seicode;
let auth_token = this.addreceiverinfo.auth_token;
if (seicode && seicode.length > 3) {
params['seicode'] = seicode
}
if (auth_token && auth_token.length > 3) {
params['auth_token'] = auth_token
}
if (this.auth_rules.geetest) {
geetest(this, this.addreceiverinfo.email, params, (n_params) => {
this.do_notify_sender(n_params)
}, NotifyInfoFun)
} else {
this.do_notify_sender(params)
}
} else {
this.$message({
message: '邮箱或手机号输入有误',
type: 'error'
});
}
} else {
this.$message({
message: '验证码有误',
type: 'warning'
});
}
},
do_notify_sender(params) {
NotifyInfoFun(data => {
if (data.code === 1000) {
this.$message.success("验证码发送成功")
this.addreceiverinfo.auth_token = data.data.auth_token;
} else {
this.$message.error(data.msg)
}
}, {
'methods': 'POST',
'data': params
})
},
get_auth_code() {
NotifyInfoFun(data => {
if (data.code === 1000) {
this.auth_rules = data.data.auth_rules;
this.captcha = this.auth_rules.captcha;
this.change_type = data.data.change_type;
this.addreceiverinfo.captcha_key = this.captcha.captcha_key;
if (this.addreceiverinfo.verify_code) {
this.addreceiverinfo.verify_code = '';
}
} else {
this.$message({
message: data.msg,
type: 'error'
});
}
}, {
"methods": "GET",
"data": {}
});
},
addRecevier() {
this.addreceiverinfo = {description: '', wxopenid: '', email: '', receiver_name: '', verify_code: '', seicode: ''}
this.dialogReceiverVisible = true
this.title = '新增消息接收人'
this.get_auth_code()
},
editReceiver(receiver) {
this.title = '编辑 ' + receiver.receiver_name + ' 消息接收人'
this.isedit = true
this.addreceiverinfo = receiver
this.dialogReceiverVisible = true
},
updateConfig(config = undefined) {
let data = {}
if (config) {
data = {
'config': config.id,
'enable_email': config.enable_email,
'enable_weixin': config.enable_weixin,
'm_type': config.message_type
}
} else {
let receiver_ids = []
for (let receiver of this.notifyConfigSelection) {
receiver_ids.push(receiver.id)
}
data = {'config': this.notifyConfigMsg.id, 'receiver': receiver_ids, 'm_type': this.notifyConfigMsg.m_type}
}
notifyConfigInfo(data => {
if (data.code === 1000) {
this.$message.success('保存成功')
if (!config) {
this.dialogChangeReceiverVisible = false;
}
this.notifyConfigInfoFun();
} else {
this.$message.error('保存失败,' + data.msg);
}
}, {"methods": 'PUT', "data": data});
},
changeNotifyConfig(info) {
let params = {"config": info.id, "message_type": info.m_type}
this.dialogChangeReceiverVisible = true;
notifyReceiverInfo(data => {
if (data.code === 1000) {
this.receiver_info_lists = data.data;
this.notifyConfigMsg = data.config
// eslint-disable-next-line no-unused-vars
this.$nextTick(res => {
data.data.forEach(row => {
for (let sender of data.senders) {
if (sender.sender === row.id) {
this.$refs.multipleTable.toggleRowSelection(row);
}
}
});
})
} else {
this.$message.error('获取失败,' + data);
}
this.loading = false;
}, {"methods": 'GET', "data": params});
},
handleSelectionChange(val) {
this.notifyConfigSelection = val;
},
// eslint-disable-next-line no-unused-vars
handleClick(tab, event) {
this.get_data_from_tabname(tab.name);
},
cancelReceiver() {
this.dialogReceiverVisible = false;
this.addreceiverinfo = {description: '', wxopenid: '', email: '', receiver_name: '', verify_code: '', seicode: ''}
},
saveReceiver() {
notifyReceiverInfo(data => {
if (data.code === 1000) {
this.$message.success('操作成功');
this.dialogReceiverVisible = false;
if (this.dialogChangeReceiverVisible) {
this.changeNotifyConfig(this.notifyConfigMsg)
} else {
this.get_data_from_tabname(this.activeName);
}
} else {
this.$message.error('操作失败,' + data.msg);
}
}, {"methods": 'POST', 'data': this.addreceiverinfo});
},
deleteReceiver(sinfo) {
this.$confirm(`将要删除接收人 ${sinfo.receiver_name}, 是否继续?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
notifyReceiverInfo(data => {
if (data.code === 1000) {
this.$message.success('删除成功');
this.notifyReceiverInfoFun();
}
}, {"methods": 'DELETE', 'data': {'id': sinfo.id}})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
notifyReceiverInfoFun() {
this.loading = true;
notifyReceiverInfo(data => {
if (data.code === 1000) {
this.receiver_info_lists = data.data;
} else {
this.$message.error('获取失败,' + data);
}
this.loading = false;
}, {"methods": 'GET'});
},
setActiveConfig(message_type_choices) {
let activeConfig = []
for (let i of message_type_choices) {
activeConfig.push(i.id)
}
this.activeConfig = activeConfig;
},
notifyConfigInfoFun() {
this.loading = true;
notifyConfigInfo(data => {
if (data.code === 1000) {
this.receiver_info_lists = data.data;
this.message_type_choices = data.message_type_choices;
this.setActiveConfig(this.message_type_choices);
} else {
this.$message.error('获取失败,' + data);
}
this.loading = false;
}, {"methods": 'GET'});
},
notifyThreshold(act) {
notifyConfigInfo(data => {
if (data.code === 1000) {
this.threshold = data.data
if (act !== 'get') {
this.$message.success('保存成功')
}
} else {
this.$message.error('获取失败,' + data);
}
this.loading = false;
}, {"methods": 'POST', 'data': {'act': act, 'threshold': this.threshold}});
},
loop_get_wx_info(wx_login_ticket, c_count = 1, unique_key = getRandomStr()) {
if (wx_login_ticket && wx_login_ticket.length < 3) {
this.$message.error("获取登陆码失败,请稍后再试");
return
}
if (!this.wx_visible) {
return;
}
wxLoginFun(data => {
c_count += 1;
if (c_count > 30) {
return;
}
if (data.code === 1000) {
if (this.userinfo.uid === data.data.uid) {
if (data.data.to_user) {
this.addreceiverinfo.wxopenid = data.data.to_user
}
this.$message.success("绑定成功");
this.wx_visible = false;
}
} else if (data.code === 1005) {
this.$message({
message: data.msg,
type: 'error',
duration: 30000
});
} else if (data.code === 1004) {
this.loop_flag = false;
} else if (data.code === 1006) {
return this.loop_get_wx_info(wx_login_ticket, c_count, unique_key)
}
}, {
"methods": "POST",
data: {"ticket": wx_login_ticket, "unique_key": unique_key}
})
},
wxLogin() {
this.wx_visible = !this.wx_visible;
this.wx_login_qr_url = '';
if (this.wx_visible) {
wxBindFun(data => {
if (data.code === 1000) {
this.wx_login_qr_url = data.data.qr;
this.loop_get_wx_info(data.data.ticket);
} else {
this.$message.error(data.msg);
this.wx_visible = false;
}
}, {
"methods": "POST", "data": {"unique_key": this.unique_key, "w_type": 'notify'}
})
}
},
// eslint-disable-next-line no-unused-vars
formatter(row, column) {
return format_time(row.create_time)
},
// eslint-disable-next-line no-unused-vars
get_data_from_tabname(tabname, data = {}) {
this.$router.push({"name": 'FirUserNotify', params: {act: tabname}});
if (tabname === "config") {
this.notifyConfigInfoFun();
} else if (tabname === "receiver") {
this.notifyReceiverInfoFun();
} else if (tabname === "threshold") {
this.notifyThreshold('get');
}
},
}, mounted() {
getUserInfoFun(this);
this.userinfo = this.$store.state.userinfo;
this.unique_key = getRandomStr();
if (this.$route.params.act) {
let activeName = this.$route.params.act;
let activeName_list = ["config", "receiver", "threshold"];
for (let index in activeName_list) {
if (activeName_list[index] === activeName) {
this.activeName = activeName;
this.get_data_from_tabname(activeName);
return
}
}
}
this.get_data_from_tabname(this.activeName);
}, filters: {
sex_filter: function (x) {
let ret = '未知';
if (x === 1) {
ret = '男'
} else if (x === 2) {
ret = '女'
}
return ret;
},
}, watch: {
'$store.state.userinfo': function () {
this.userinfo = this.$store.state.userinfo;
}
}
}
</script>
<style scoped>
.el-main {
text-align: center;
margin: 20px auto 100px;
width: 1166px;
position: relative;
padding-bottom: 1px;
color: #9b9b9b;
-webkit-font-smoothing: antialiased;
border-radius: 1%;
}
</style>

@ -247,10 +247,9 @@
<script>
import {my_order, order_sync} from "@/restful";
import {my_order} from "@/restful";
import {format_choices, getUserInfoFun} from '@/utils'
import VueQr from 'vue-qr';
import {format_time, getRandomStr} from "@/utils/base/utils";
export default {
name: "FirUserOrders",
@ -280,7 +279,6 @@ export default {
show_order_info: false,
current_order_info: {},
loading: false,
unique_key: ''
}
},
methods: {
@ -299,46 +297,17 @@ export default {
methods: 'PUT', data: {order_number: order.order_number, act: 'cancel'},
})
},
loop_get_order_info(order_number, c_count = 1, unique_key = getRandomStr()) {
if (!this.wx_pay) {
return
}
this.unique_key = unique_key;
order_sync(data => {
c_count += 1;
if (c_count > 30) {
return;
}
if (data.code === 1000) {
this.$message.success(data.msg);
this.get_data_from_tabname();
this.wx_pay = false;
} else if (data.code === 1004) {
this.loop_flag = false;
if (this.unique_key === unique_key) {
this.wx_pay = false;
}
} else if (data.code === 1001) {
return this.loop_get_order_info(order_number, c_count, unique_key)
}
}, {
"methods": "POST",
data: {"order_number": order_number, 'unique_key': unique_key}
})
},
goto_pay(order) {
this.current_order_info = order;
my_order(res => {
if (res.code === 1000) {
let data = res.data;
if (data && data.url) {
if (data && data.type === 'WX') {
this.pay_code_url = data.url;
this.wx_pay = true;
this.$message.success("请用微信扫描支付");
this.loop_get_order_info(order.order_number)
} else if (data && data.type === 'ALI') {
let pay_url = data.url;
if (pay_url && pay_url.length > 10) {
@ -412,15 +381,23 @@ export default {
},
format_create_time(row) {
return format_time(row.created_time)
return this.format_time(row.created_time)
},
format_pay_time(row) {
return format_time(row.pay_time)
return this.format_time(row.pay_time)
},
format_cancel_time(row) {
return format_time(row.cancel_time)
return this.format_time(row.cancel_time)
},
format_time(stime) {
if (stime) {
stime = stime.split(".")[0].split("T");
return stime[0] + " " + stime[1]
} else
return '';
},
MyOrderFun(params) {
this.loading = true;
@ -454,7 +431,6 @@ export default {
this.order_id_seach = out_trade_no;
}
this.get_data_from_tabname();
this.unique_key = getRandomStr();
}, filters: {}
}
</script>

@ -100,7 +100,7 @@
</el-form-item>
<el-form-item v-if="change_type.sms" label="手机号码">
<el-form-item v-if="captcha.change_type.sms" label="手机号码">
<el-row :gutter="36">
<el-col :span="18">
<el-input ref="phone" v-model="form.mobile" clearable
@ -111,7 +111,7 @@
<el-form-item v-if="captcha.captcha_image" label="图片验证码">
<el-row :gutter="11">
<el-col :span="12">
<el-input v-model="form.verify_code" clearable maxlength="6" placeholder="请输入图片验证码"/>
<el-input v-model="form.authcode" clearable maxlength="6" placeholder="请输入图片验证码"/>
</el-col>
<el-col :span="6">
<el-image
@ -123,7 +123,7 @@
</el-row>
</el-form-item>
<el-form-item v-if="change_type.sms" label="手机验证码">
<el-form-item v-if="captcha.change_type.sms" label="手机验证码">
<el-row :gutter="11">
<el-col :span="12">
<el-input v-model="form.auth_key" clearable maxlength="6"
@ -222,8 +222,7 @@
<script>
import {changeInfoFun, getAuthcTokenFun, user_certification} from "@/restful";
import {AvatarUploadUtils, geetest, getUserInfoFun} from "@/utils";
import {checkphone} from "@/utils/base/utils";
import {AvatarUploadUtils, checkphone, geetest, getUserInfoFun} from "@/utils";
export default {
name: "FirUserProfileCertification",
@ -233,11 +232,10 @@ export default {
captcha: {
captcha_image: '',
captcha_key: '',
verify_code: '',
authcode: '',
length: 8,
change_type: {email: false, sms: false}
},
change_type: {email: false, sms: false},
auth_rules: {},
user_certification: {'one': '', 'two': '', 'three': ''},
certification: {},
certification_status: 0,
@ -266,7 +264,7 @@ export default {
this.$message.error("身份证输入不合法");
return false;
}
if (this.change_type.sms) {
if (this.captcha.change_type.sms) {
let checkp = checkphone(this.form.mobile);
if (!checkp) {
this.$message.error("手机号输入不合法");
@ -308,15 +306,15 @@ export default {
getsmsemailcode(act, target) {
let picode = {
"verify_code": this.form.verify_code,
"authcode": this.form.authcode,
"captcha_key": this.captcha.captcha_key,
};
if (this.captcha.captcha_image) {
if (!this.form.verify_code) {
if (!this.form.authcode) {
this.$message.error("图片验证码输入有误");
return;
}
let captcha_flag = this.form.verify_code.length === this.captcha.length;
let captcha_flag = this.form.authcode.length === this.captcha.length;
if (this.captcha.captcha_key === '' || !this.captcha.captcha_key) {
captcha_flag = true
}
@ -333,7 +331,7 @@ export default {
}
let params = {'act': act, 'target': target, 'ext': picode, 'user_id': target, 'ftype': 'certification'};
if (this.auth_rules.geetest) {
if (this.captcha.geetest) {
geetest(this, this.form.mobile, params, (n_params) => {
this.get_phone_code(n_params);
})
@ -361,14 +359,12 @@ export default {
},
get_auth_code() {
if (this.form.verify_code) {
this.form.verify_code = '';
if (this.form.authcode) {
this.form.authcode = '';
}
changeInfoFun(data => {
if (data.code === 1000) {
this.change_type = data.data.change_type;
this.auth_rules = data.data.auth_rules;
this.captcha = this.auth_rules.captcha
this.captcha = data.data;
if (this.captcha.captcha_key) {
this.form.captcha_key = this.captcha.captcha_key;
}

@ -11,22 +11,6 @@
微信授权登录用户信息
</span>
<el-tag style="margin-bottom: 10px">授权用户将可以使用微信扫码直接登录</el-tag>
<el-button plain style="float: right;margin-top: 10px" @click="wx_web_login_fun"> 点击扫码同步微信用户信息</el-button>
<el-dialog
:visible.sync="show_web_visible"
append-to-body
center
title="手机微信扫码更新个人信息"
width="360px">
<vue-qr ref="qr"
:correctLevel="qrinfo.correctLevel"
:logoCornerRadius="qrinfo.logoCornerRadius"
:logoScale="qrinfo.logoScale"
:margin="qrinfo.margin"
:size="300" :text="wx_web_login_url"
class="code-wrap">
</vue-qr>
</el-dialog>
<el-table
:data="wx_user_list"
border
@ -130,7 +114,7 @@
<el-form-item label="手机号码">
<el-row :gutter="36">
<el-col :span="16">
<el-input ref="phone" v-model="userinfo.mobile" :disabled="!change_type.sms"
<el-input ref="phone" v-model="userinfo.mobile" :disabled="!captcha.change_type.sms"
:readonly="editphone !== true" clearable maxlength="11" placeholder="手机"
prefix-icon="el-icon-mobile"/>
</el-col>
@ -138,7 +122,7 @@
<el-button icon="el-icon-edit" @click="changePhoneValue">
</el-button>
</el-col>
<el-col v-if="editphone === true && change_type.sms" :span="5">
<el-col v-if="editphone === true && captcha.change_type.sms" :span="5">
<el-button class="save-button" plain type="success"
@click="savePhone">
保存
@ -147,11 +131,11 @@
</el-row>
</el-form-item>
<el-form-item v-if="editphone === true && captcha.captcha_image && change_type.sms" label="图片验证码"
<el-form-item v-if="editphone === true && captcha.captcha_image && captcha.change_type.sms" label="图片验证码"
style="height: 40px">
<el-row :gutter="36" style="height: 40px">
<el-col :span="14">
<el-input v-model="userinfo.verify_code" clearable maxlength="6" placeholder="请输入图片验证码"/>
<el-input v-model="userinfo.authcode" clearable maxlength="6" placeholder="请输入图片验证码"/>
</el-col>
<el-col :span="8">
<el-image
@ -163,7 +147,7 @@
</el-row>
</el-form-item>
<el-form-item v-if="editphone === true && change_type.sms" label="手机验证码">
<el-form-item v-if="editphone === true && captcha.change_type.sms" label="手机验证码">
<el-row :gutter="36">
<el-col :span="16">
<el-input v-model="userinfo.auth_key" clearable maxlength="6"
@ -183,7 +167,7 @@
<el-form-item label="邮箱地址">
<el-row :gutter="36">
<el-col :span="16">
<el-input ref="email" v-model="userinfo.email" :disabled="!change_type.email"
<el-input ref="email" v-model="userinfo.email" :disabled="!captcha.change_type.email"
:readonly="editemail !== true" clearable maxlength="20" placeholder="邮箱"
prefix-icon="el-icon-bank-card"/>
</el-col>
@ -191,7 +175,7 @@
<el-button icon="el-icon-edit" @click="changeemailValue">
</el-button>
</el-col>
<el-col v-if="editemail === true && change_type.email" :span="5">
<el-col v-if="editemail === true && captcha.change_type.email" :span="5">
<el-button class="save-button" plain type="success"
@click="saveemail">
保存
@ -201,11 +185,11 @@
</el-form-item>
<el-form-item v-if="editemail === true && captcha.captcha_image && change_type.email" label="图片验证码"
<el-form-item v-if="editemail === true && captcha.captcha_image && captcha.change_type.email" label="图片验证码"
style="height: 40px">
<el-row :gutter="36" style="height: 40px">
<el-col :span="14">
<el-input v-model="userinfo.verify_code" clearable maxlength="6" placeholder="请输入图片验证码"/>
<el-input v-model="userinfo.authcode" clearable maxlength="6" placeholder="请输入图片验证码"/>
</el-col>
<el-col :span="8">
<el-image
@ -217,7 +201,7 @@
</el-row>
</el-form-item>
<el-form-item v-if="editemail === true && change_type.email" label="邮箱验证码">
<el-form-item v-if="editemail === true && captcha.change_type.email" label="邮箱验证码">
<el-row :gutter="36">
<el-col :span="16">
<el-input v-model="userinfo.auth_key" clearable maxlength="6"
@ -302,21 +286,16 @@
</template>
<script>
import {changeInfoFun, getAuthcTokenFun, userinfos, wxBindFun, wxLoginFun, wxutils, wxWebScanFun} from '@/restful'
import {changeInfoFun, getAuthcTokenFun, userinfos, wxLoginFun, wxutils} from '@/restful'
import {deepCopy, geetest} from "@/utils";
import VueQr from 'vue-qr';
import {getRandomStr} from "@/utils/base/utils";
export default {
name: "FirUserProfileInfo",
components: {
VueQr
},
data() {
return {
userinfo: {
srccode: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
verify_code: ''
authcode: ''
},
orguserinfo: {},
editphone: false,
@ -324,75 +303,16 @@ export default {
editdomain_name: false,
edituser_name: false,
editposition: false,
captcha: {"captcha_image": '', "captcha_key": '', "length": 8},
change_type: {email: false, sms: false},
auth_rules: {},
captcha: {"captcha_image": '', "captcha_key": '', "length": 8, change_type: {email: false, sms: false}},
form: {},
wx_login_qr_url: '',
wx_visible: false,
loop_flag: false,
show_wx_visible: false,
show_web_visible: false,
qrinfo: {
logoScale: 0.3,
logoCornerRadius: 12,
correctLevel: 3,
margin: 20
},
wx_web_login_url: '',
wx_user_list: [],
pagination: {"currentPage": 1, "total": 0, "pagesize": 999},
unique_key: ''
}
}, methods: {
loop_get_wx_web_info(wx_login_ticket, c_count = 1, unique_key = getRandomStr()) {
if (wx_login_ticket && wx_login_ticket.length < 3) {
this.$message.error("获取登陆码失败,请稍后再试");
return
}
if (!this.show_web_visible) {
return;
}
wxLoginFun(data => {
c_count += 1;
if (c_count > 30) {
return;
}
if (data.code === 1000) {
if (this.userinfo.uid === data.data.uid) {
this.$message.success("更新成功");
this.show_web_visible = false;
this.get_wx_user_list();
}
} else if (data.code === 1006) {
return this.loop_get_wx_web_info(wx_login_ticket, c_count, unique_key)
} else {
this.$message({
message: data.msg,
type: 'error',
duration: 30000
});
this.show_web_visible = false;
}
}, {
"methods": "POST",
data: {"ticket": wx_login_ticket, "unique_key": unique_key}
})
},
wx_web_login_fun() {
wxWebScanFun(data => {
if (data.code === 1000) {
this.show_web_visible = true;
this.wx_web_login_url = data.data;
this.loop_get_wx_web_info(data.ticket)
} else {
this.$message.error("信息获取失败")
}
}, {methods: 'GET'})
},
delete_wx_u(wx_user_info) {
this.$prompt(`此操作将导致微信用户 “${wx_user_info.nickname}” 无法通过扫码登录, 是否继续删除?`, '警告', {
confirmButtonText: '确定',
@ -437,66 +357,72 @@ export default {
}
})
},
loop_get_wx_info(wx_login_ticket, c_count = 1, unique_key = getRandomStr()) {
loop_get_wx_info(wx_login_ticket) {
if (wx_login_ticket && wx_login_ticket.length < 3) {
this.$message.error("获取登陆码失败,请稍后再试");
return
}
if (!this.wx_visible) {
return;
}
wxLoginFun(data => {
c_count += 1;
if (c_count > 30) {
return;
let c_count = 1;
// eslint-disable-next-line no-unused-vars
const loop_t = window.setInterval(res => {
if (!this.loop_flag) {
window.clearInterval(loop_t);
}
if (data.code === 1000) {
if (this.userinfo.uid === data.data.uid) {
this.$message.success("绑定成功");
this.wx_visible = false;
wxLoginFun(data => {
c_count += 1;
if (c_count > 120) {
window.clearInterval(loop_t);
}
} else if (data.code === 1005) {
this.$message({
message: data.msg,
type: 'error',
duration: 30000
});
} else if (data.code === 1004) {
this.loop_flag = false;
} else if (data.code === 1006) {
return this.loop_get_wx_info(wx_login_ticket, c_count, unique_key)
}
}, {
"methods": "POST",
data: {"ticket": wx_login_ticket, "unique_key": unique_key}
})
if (data.code === 1000) {
window.clearInterval(loop_t);
if (this.userinfo.uid === data.userinfo.uid) {
this.$message.success("绑定成功");
this.wx_visible = false;
this.loop_flag = false;
}
} else if (data.code === 1005) {
window.clearInterval(loop_t);
this.$message({
message: data.msg,
type: 'error',
duration: 30000
});
}
}, {
"methods": "POST",
data: {"ticket": wx_login_ticket}
})
}, 3000)
},
wxLogin() {
this.wx_visible = !this.wx_visible;
this.wx_login_qr_url = '';
if (this.wx_visible) {
wxBindFun(data => {
userinfos(data => {
if (data.code === 1000) {
this.wx_login_qr_url = data.data.qr;
this.loop_flag = true;
this.loop_get_wx_info(data.data.ticket);
} else {
this.$message.error(data.msg);
this.wx_visible = false;
this.loop_flag = false;
}
}, {
"methods": "POST", "data": {"unique_key": this.unique_key, "w_type": 'login'}
"methods": "POST",
})
} else {
this.loop_flag = false;
}
},
get_auth_code() {
changeInfoFun(data => {
if (data.code === 1000) {
this.auth_rules = data.data.auth_rules;
this.captcha = this.auth_rules.captcha;
this.change_type = data.data.change_type;
this.captcha = data.data;
this.userinfo.captcha_key = this.captcha.captcha_key;
if (this.userinfo.verify_code) {
this.userinfo.verify_code = '';
if (this.userinfo.authcode) {
this.userinfo.authcode = '';
}
} else {
this.$message({
@ -531,7 +457,7 @@ export default {
auth_token: this.userinfo.auth_token,
auth_key: this.userinfo.auth_key,
captcha_key: this.userinfo.captcha_key,
verify_code: this.userinfo.verify_code,
authcode: this.userinfo.authcode,
};
},
saveemail() {
@ -638,14 +564,14 @@ export default {
},
getsmsemailcode(act, target) {
let picode = {
"verify_code": this.userinfo.verify_code,
"authcode": this.userinfo.authcode,
"captcha_key": this.captcha.captcha_key,
};
let params = {
'act': act, 'target': target, 'ext': picode
};
if (this.auth_rules.geetest) {
geetest(this, target, params, (n_params) => {
if (this.captcha.geetest) {
geetest(this, this.form.email, target, params, (n_params) => {
this.do_get_auth_token(n_params);
})
} else {
@ -657,7 +583,7 @@ export default {
// this.updateUserInfo({"methods":false});
this.userinfo = this.$store.state.userinfo;
this.orguserinfo = deepCopy(this.$store.state.userinfo);
this.unique_key = getRandomStr();
}, watch: {
'$store.state.userinfo': function () {

@ -56,7 +56,7 @@
:logoSrc="appinfo.master_release.icon_url"
:margin="qrinfo.margin" :qid="appinfo.app_id"
:size="200"
:text="appinfo.preview_url">
:text="short_url(appinfo)">
</vue-qr>
<div style="margin: 5px 0 5px">
<i v-if="appinfo.type === 1" class=" type-icon iconfont icon-ios"/>
@ -65,9 +65,9 @@
<span>{{ appinfo.name }}</span>
<div class="bottom clearfix">
<el-popover placement="top" trigger="hover">
<span v-clipboard:copy="appinfo.preview_url"
<span v-clipboard:copy="short_url(appinfo)"
v-clipboard:success="copy_success"
>{{ appinfo.preview_url }}</span>
>{{ short_url(appinfo) }}</span>
<div slot="reference" class="name-wrapper">
<el-button plain size="small" type="primary" @click="go_download(appinfo)">预览</el-button>
<el-button plain size="small" type="primary" @click="save_qr(appinfo)">保存本地</el-button>
@ -213,12 +213,15 @@ export default {
this.isIndeterminate = checkedCount > 0 && checkedCount < this.allQrcodeAppid.length;
},
go_download(appinfo) {
window.open(appinfo.preview_url, '_blank', '');
window.open(this.short_url(appinfo), '_blank', '');
},
qrback(dataUrl, id) {
this.qrcode_img_info[id] = dataUrl;
this.allQrcodeAppid.push(id);
},
short_url(appinfo) {
return appinfo.preview_url + '/' + appinfo.short;
},
save_qr(appinfo) {
let dtype = "I";
if (appinfo.master_release.release_type === 0) {

File diff suppressed because it is too large Load Diff

@ -25,8 +25,6 @@ import {
Checkbox,
CheckboxGroup,
Col,
Collapse,
CollapseItem,
Container,
DatePicker,
Dialog,
@ -160,8 +158,6 @@ Vue.use(InputNumber);
Vue.use(Radio);
Vue.use(Backtop);
Vue.use(Transfer);
Vue.use(Collapse);
Vue.use(CollapseItem);
Vue.prototype.$message = Message;
Vue.prototype.$notify = Notification;
Vue.prototype.$loading = Loading.service;

@ -1,20 +1,12 @@
import Axios from 'axios'
import {geetestbase} from "@/utils/base/utils";
const DOMAIN = process.env.base_env.baseShortUrl;
const USERSEVER = 'api/v1/fir/server';
// eslint-disable-next-line no-console
console.log("js build version:" + process.env.base_env.version);
// create an axios instance
const service = Axios.create({
baseURL: DOMAIN, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests
timeout: 120000 // request timeout
})
Axios.defaults.withCredentials = true;
const DOMAIN = process.env.base_env.baseShortUrl;
const APIPATH = '/api/v1/fir/server';
let USERSEVER = DOMAIN + APIPATH;
function ErrorMsg(error, callBack) {
function ErrorMsg(error) {
if (error && error.response) {
switch (error.response.status) {
case 400:
@ -35,45 +27,63 @@ function ErrorMsg(error, callBack) {
} else {
error.message = '连接服务器失败!';
}
callBack({code: -1, msg: error.message})
}
function responseMiddleware(data, callBack) {
if (data.code === 999) {
window.alert(data.detail)
} else {
callBack(data);
if (error.message === 'Network Error') {
alert('网络连接失败')
}
}
function getData(methods = 'GET', url, params = {}, callBack) {
var uri = '';
var keys = Object.keys(params);
var values = Object.values(params);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = values[i];
uri = uri + key + "=" + value;
if (i < keys.length - 1) {
uri = uri + "&"
}
}
if (uri !== "") {
uri = "?" + uri
}
url = url + uri;
if (methods === "PUT") {
service
Axios
.put(url, params)
.then(function (response) {
responseMiddleware(response.data, callBack);
callBack(response.data);
})
.catch(function (error) {
ErrorMsg(error, callBack);
ErrorMsg(error);
callBack({"code": -1});
});
} else if (methods === 'POST') {
service
Axios
.post(url, params)
.then(function (response) {
responseMiddleware(response.data, callBack);
callBack(response.data);
})
.catch(function (error) {
ErrorMsg(error, callBack);
ErrorMsg(error);
callBack({"code": -1});
});
} else
service
.get(url, {params: params})
Axios
.get(url, params)
.then(function (response) {
responseMiddleware(response.data, callBack);
callBack(response.data);
let x = '';
if (x !== '') {
alert(x)
}
})
.catch(function (error) {
ErrorMsg(error, callBack);
ErrorMsg(error);
callBack({"code": -1});
});
}
@ -103,6 +113,17 @@ export function getdownloadurl(callBack, params) {
);
}
/**获取签名任务状态 */
export function gettask(callBack, params) {
getData(
'GET',
USERSEVER + '/task/' + params.short,
params.data,
data => {
callBack(data);
},
);
}
/**应用举报 */
export function appReport(callBack, params) {
@ -140,25 +161,44 @@ export function getAuthTokenFun(callBack, params) {
);
}
export function geetest(self, uid, params, callback) {
return geetestbase(loginFun, self, uid, params, callback, res => {
window.alert(res.msg);
})
export function checkEmail(email) {
let re = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
return re.test(email);
}
/** 超级签名************************************************相关api */
/**获取签名任务状态 */
let SIGNSEVER = DOMAIN + '/api/v1/fir/xsign';
export function checkphone(email) {
let re = /^1\d{10}$/;
return re.test(email);
}
export function gettask(callBack, params) {
getData(
params.methods,
SIGNSEVER + '/task/' + params.short,
params.data,
data => {
callBack(data);
},
);
export function geetest(self, uid, params, callback) {
loginFun(res => {
if (res.code === 1000) {
let data = res.data;
// eslint-disable-next-line no-undef
initGeetest({
gt: data.gt,
challenge: data.challenge,
new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: "float", // 产品形式,包括:float,popup
width: "100%"
}, (captchaObj) => {
self.$refs.captcha.innerHTML = '';
captchaObj.appendTo("#captcha");
captchaObj.onReady(() => {
}).onSuccess(() => {
params.geetest = captchaObj.getValidate();
callback(params);
}).onError(() => {
captchaObj.destroy();
});
});
} else {
alert(res.msg);
}
}, {
"methods": "PUT",
"data": {user_id: uid}
});
}

File diff suppressed because it is too large Load Diff

@ -146,17 +146,6 @@ const router = new VueRouter({
}
]
},
{
path: 'notify',
component: () => import("@/components/user/FirUserNotify"),
children: [
{
path: ':act',
name: 'FirUserNotify',
meta: {label: '消息中心'},
}
]
},
{
path: 'orders',
name: 'FirUserOrders',
@ -168,11 +157,6 @@ const router = new VueRouter({
name: 'FirUserDomain',
meta: {label: '绑定域名详情'},
component: () => import("@/components/user/FirUserDomain"),
}, {
path: 'download',
name: 'FirUserDownload',
meta: {label: '下载页设置'},
component: () => import("@/components/user/FirUserDownload"),
},
{
path: 'advert',

@ -1,73 +0,0 @@
export function GetRandomNum(Min, Max) {
const Range = Max - Min;
const Rand = Math.random();
return (Min + Math.round(Rand * Range));
}
export function getRandomStr(str_length = 32) {
const SIGNING_v1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'//36
// const SIGNING_v2 ='ABCDEFGHJKMNPQRSTUVWXYZ23456789'//31
// const SIGNING_v3 ='1234567890'//10
// const SIGNING_v4 ='ABCDEFGHIJKLMNOPQRSTUVWXYZ'//26
const SIGNING_v5 = 'abcdefghigklmnopqrstuvwxyz'//26
let random_str = SIGNING_v1 + SIGNING_v5
let random_code = ''
str_length -= 1
for (let i = 0; i < str_length; i++) {
random_code = random_code + random_str[GetRandomNum(0, str_length)]
}
return random_code
}
export function checkEmail(email) {
let re = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
return re.test(email);
}
export function checkphone(email) {
let re = /^1\d{10}$/;
return re.test(email);
}
export function geetestbase(func, self, uid, params, callback, errback, readyback) {
func(res => {
if (res.code === 1000) {
let data = res.data;
// eslint-disable-next-line no-undef
initGeetest({
gt: data.gt,
challenge: data.challenge,
new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: "bind", // 产品形式,包括:float,popup
width: "100%"
}, (captchaObj) => {
self.$refs.captcha.innerHTML = '';
// captchaObj.appendTo("#captcha");
captchaObj.onReady(() => {
readyback(captchaObj)
}).onSuccess(() => {
params.geetest = captchaObj.getValidate();
callback(params);
}).onError(() => {
captchaObj.destroy();
});
});
} else {
errback(res)
}
}, {
"methods": "PUT",
"data": {user_id: uid}
});
}
export function format_time(stime) {
if (stime) {
stime = stime.split(".")[0].split("T");
return stime[0] + " " + stime[1]
} else
return '';
}

@ -1,22 +0,0 @@
module.exports =function dateFormat(format, date=null) {
if(!date){
date = new Date()
}
const args = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
"S": date.getMilliseconds()
};
if (/(y+)/.test(format))
format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
for (let i in args) {
let n = args[i];
if (new RegExp("(" + i + ")").test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? n : ("00" + n).substr(("" + n).length));
}
return format;
}

@ -1,8 +1,5 @@
//滚动条在Y轴上的滚动距离
import {geetestbase} from "@/utils/base/utils";
import {getuploadurl, loginFun, uploadimgs, uploadstorage, userinfos} from '@/restful'
export function getScrollTop() {
let scrollTop = 0, bodyScrollTop = 0, documentScrollTop = 0;
if (document.body) {
@ -237,14 +234,16 @@ export function uploadaliyunoss(file, certinfo, app, successcallback, processcal
}
import {getuploadurl, loginFun, uploadimgs, uploadstorage, userinfos} from '@/restful'
export function uploadlocalstorage(file, certinfo, app, successcallback, processcallback) {
uploadstorage(certinfo, file, successcallback, processcallback)
}
export function removeArray(_arr, _obj) {
let length = _arr.length;
for (let i = 0; i < length; i++) {
export function removeAaary(_arr, _obj) {
var length = _arr.length;
for (var i = 0; i < length; i++) {
if (_arr[i] == _obj) {
if (i == 0) {
_arr.shift(); //删除并返回数组的第一个元素
@ -327,6 +326,15 @@ export function deepCopy(data) {
return obj;
}
export function checkEmail(email) {
let re = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
return re.test(email);
}
export function checkphone(email) {
let re = /^1\d{10}$/;
return re.test(email);
}
export function ImgToBase64(url, callback) {
let Img = new Image(),
@ -345,21 +353,47 @@ export function ImgToBase64(url, callback) {
};
}
export function geetest(self, uid, params, callback, http_func = loginFun) {
export function geetest(self, uid, params, callback) {
const loading = self.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
});
return geetestbase(http_func, self, uid, params, callback, res => {
self.$message({
message: res.msg,
type: 'error'
});
}, captchaObj => {
captchaObj.verify()
loading.close()
})
loginFun(res => {
if (res.code === 1000) {
let data = res.data;
// eslint-disable-next-line no-undef
initGeetest({
gt: data.gt,
challenge: data.challenge,
new_captcha: data.new_captcha, // 用于宕机时表示是新验证码的宕机
offline: !data.success, // 表示用户后台检测极验服务器是否宕机,一般不需要关注
product: "float", // 产品形式,包括:float,popup
width: "100%"
}, (captchaObj) => {
self.$refs.captcha.innerHTML = '';
captchaObj.appendTo("#captcha");
captchaObj.onReady(() => {
loading.close()
}).onSuccess(() => {
params.geetest = captchaObj.getValidate();
callback(params);
}).onError(() => {
captchaObj.destroy();
});
});
} else {
self.$message({
message: res.msg,
type: 'error'
});
}
}, {
"methods": "PUT",
"data": {user_id: uid} //self.form.email
});
}
export function format_money(s, n = 2) {
@ -626,7 +660,7 @@ export function diskSize(num) {
let k = 1024; //设定基础容量大小
let sizeStr = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; //容量单位
let i = 0; //单位下标和次幂
for (let l = 0; l < 9; l++) { //因为只有8个单位所以循环八次
for (let l = 0; l < 8; l++) { //因为只有8个单位所以循环八次
if (num / Math.pow(k, l) < 1) { //判断传入数值 除以 基础大小的次幂 是否小于1,这里小于1 就代表已经当前下标的单位已经不合适了所以跳出循环
break; //小于1跳出循环
}
@ -662,11 +696,3 @@ export function format_choices(key, obj) {
}
return "未知"
}
export function getFormatDate(timestamp) {
let now = new Date(timestamp * 1000)
let y = now.getFullYear()
let m = now.getMonth() + 1
let d = now.getDate()
return y + "-" + (m < 10 ? "0" + m : m) + "-" + (d < 10 ? "0" + d : d) + " " + now.toTimeString().substr(0, 8);
}

@ -1,14 +1,15 @@
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV);
const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const dateFormat = require("./src/utils/format.js");
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const argv = process.argv;
function resolve(dir) {
return path.join(__dirname, dir);
}
const argv = process.argv;
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const compress = new CompressionWebpackPlugin(
{
filename: info => {
@ -18,7 +19,7 @@ const compress = new CompressionWebpackPlugin(
threshold: 10240,
test: new RegExp(
'\\.(' +
['js', 'css'].join('|') +
['js','css'].join('|') +
')$'
),
minRatio: 0.8,
@ -27,7 +28,6 @@ const compress = new CompressionWebpackPlugin(
);
const index = {
outputDir: 'dist_index',
// page 的入口
entry: 'src/main.js',
// 模板来源
@ -39,11 +39,10 @@ const index = {
title: 'Fly分发平台',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index', 'elementui', 'runtime', 'alioss', 'qiniujs', 'qrcodejs2']
chunks: ['chunk-vendors', 'chunk-common', 'index', 'elementui','runtime', 'alioss', 'qiniujs', 'qrcodejs2']
};
// eslint-disable-next-line no-unused-vars
const mshort = {
outputDir: 'dist_mshort',
// page 的入口
entry: 'src/main.short.js',
// 模板来源
@ -58,7 +57,6 @@ const mshort = {
chunks: ['chunk-vendors', 'chunk-common', 'mshort', 'chunk-elementUI', 'runtime']
};
const short = {
outputDir: 'dist_short',
// page 的入口
entry: 'src/short.js',
// 模板来源
@ -73,57 +71,41 @@ const short = {
chunks: ['chunk-vendors', 'chunk-common', 'short', 'chunk-commons', 'runtime']
};
let pages = {index, short, mshort};
let outputDir = 'dist'
let page = argv[3];
if (!page) {
page = 'index'
}
for (const key of Object.keys(pages)) {
if (key === page) {
const tmp = pages[key];
pages = {};
pages[key] = tmp;
outputDir = tmp.outputDir
const page = argv[3];
if (page) {
for (const key of Object.keys(pages)) {
if (key === page) {
const tmp = pages[key];
pages = {};
pages[key] = tmp;
}
}
}
const version = '2.3.9';
const github = 'https://github.com/nineaiyu/FlyApps';
const version='2.0.0';
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
baseUrl: 'https://flyapps.cn',
index_static: 'https://static.flyapps.cn/index/',
baseShortUrl: 'https://flyapps.top',
short_static: 'https://static.flyapps.top/short/',
version: version,
};
const dev_base_env = {
baseUrl: 'https://app.hehelucky.cn/',
baseUrl: 'https://app.hehelucky.cn',
baseShortUrl: 'https://app.hehelucky.cn',
short_static: '/',
index_static: '/',
baseShortUrl: 'https://app.hehelucky.cn/',
short_static: 'https://app.hehelucky.cn/short/',
version: version,
};
let base_evn = dev_base_env;
const footer_info = {
copyright: 'Copyright © 2022-2099 isummer 版权所有.',
ipcBeiAn: {
url: 'https://beian.miit.gov.cn',
text: '京ICP备681262896号',
},
gongAnBeiAn: {
url: 'https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=681262896',
text: '京公网安备681262896号',
},
}
if (IS_PROD) {
base_evn = pro_base_env
} else {
base_evn = dev_base_env
}
base_evn.footer_info = footer_info
function get_public_path(pages) {
if (!IS_PROD) {
@ -136,9 +118,9 @@ function get_public_path(pages) {
}
let publicPath = get_public_path(pages);
module.exports = {
pages: pages,
outputDir: outputDir,
productionSourceMap: false, //去除生产环境的productionSourceMap
assetsDir: "static", //静态文件存储位置
lintOnSave: true,
@ -159,13 +141,7 @@ module.exports = {
config
.plugin('define')
.tap(args => {
args[0]['process.env']['base_env'] = JSON.stringify({
baseUrl: base_evn.baseUrl,
baseShortUrl: base_evn.baseShortUrl,
version: dateFormat("yyyyMMdd.hhmmss") + '.' + base_evn.version,
footer: base_evn.footer_info,
github
});
args[0]['process.env']['base_env'] = JSON.stringify({baseUrl: base_evn.baseUrl,baseShortUrl:base_evn.baseShortUrl,version:base_evn.version});
return args
});
@ -254,11 +230,7 @@ module.exports = {
priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?ali-oss(.*)/ // in order to adapt to cnpm
},
appinfo: {
name: 'appinfo', // split elementUI into a single package
priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?app-info-parser(.*)/ // in order to adapt to cnpm
},
qrcodejs2: {
name: 'qrcodejs2', // split elementUI into a single package
priority: 23, // the weight needs to be larger than libs and app or it will be packaged into libs or app
@ -271,7 +243,7 @@ module.exports = {
},
common: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
test: resolve(resolve('src/components')), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true

@ -1,32 +1,41 @@
FROM python:3.9.14-slim
FROM python:3.6.14-slim
# Fixes some weird terminal issues such as broken clear / CTRL+L
ARG PIP_MIRROR=https://mirrors.aliyun.com/pypi/simple
#RUN sed -i 's/deb.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
# && sed -i 's/security.debian.org/mirrors.163.com/g' /etc/apt/sources.list \
RUN apt update \
&& apt-get install g++ wget zip unzip -y \
&& apt-get install libssl-dev openssl libmariadb-dev -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
&& sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
&& apt update
RUN apt-get install g++ wget -y
RUN apt-get install libssl-dev openssl libmariadb-dev -y
RUN rm -rf /var/lib/apt/lists/*
#COPY fir_client/dist /data/flyapps/fir_client
#RUN cd /opt/ && wget https://github.com/nineaiyu/zsign/archive/refs/tags/v1.1.2.tar.gz
ADD zsign-1.1.2.tar.gz /opt/
RUN cd /opt/zsign-1.1.2/ && g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o zsign && cp zsign /usr/bin/ && rm -rf /opt/zsign-1.1.2/
COPY zsign-1.1.2.tar.gz /opt/zsign-1.1.2.tar.gz
RUN cd /opt/ && tar xvf zsign-1.1.2.tar.gz && cd zsign-1.1.2/ && g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o zsign && cp zsign /usr/bin/
# install pip
COPY requirements.txt /opt/requirements.txt
RUN cd /opt/ && pip install -U setuptools pip --ignore-installed && pip install --no-cache-dir -r requirements.txt && pip install --no-cache-dir uwsgi
RUN cd /opt/ && pip install -U setuptools pip -i ${PIP_MIRROR} --ignore-installed && pip install --no-cache-dir -r requirements.txt -i ${PIP_MIRROR} && pip install --no-cache-dir uwsgi -i ${PIP_MIRROR}
RUN rm -rf /var/cache/yum/
#COPY Docker/flyapps.conf /etc/supervisor/conf.d/flyapps.conf
#COPY Docker/uwsgi.conf /data/flyapps/fir_ser/uwsgi.conf
#COPY Docker/flyapps-vhost.conf /etc/nginx/conf.d/flyapps-vhost.conf
#COPY Docker/app.hehelucky.cn.pem /data/flyapps/app.hehelucky.cn.pem
#COPY Docker/app.hehelucky.cn.key /data/flyapps/app.hehelucky.cn.key
WORKDIR /data/fir_ser/
COPY entrypoint.sh entrypoint.sh
RUN addgroup --system --gid 101 nginx \
&& adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx
#EXPOSE 443
#ENTRYPOINT ["./entrypoint.sh"]
#ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf", "-n"]
#CMD ["python", "manage.py", "start", "all","-u","nginx","-usm","1"]
ENTRYPOINT ["/bin/bash", "entrypoint.sh"]
CMD ["python", "manage.py", "start", "all","-u","nginx","-usm","1"]

@ -1,11 +0,0 @@
FROM python:3.9.14-slim
RUN pip install --upgrade pip --no-cache-dir && \
pip install setuptools-rust oss2 requests-toolbelt androguard requests --no-cache-dir
WORKDIR /opt/
COPY cli.py /opt/cli.py
CMD ["python", "cli.py"]

@ -22,8 +22,8 @@ from admin.views.login import LoginView, LoginUserView
from admin.views.order import OrderInfoView
from admin.views.report import AdminReportView
from admin.views.storage import StorageInfoView, StorageChangeView
from admin.views.supersign import DeveloperInfoView, DevicesInfoView, SuperSignBillView, SuperSignBillUserInfoView
from admin.views.user import UserInfoView, UserCertificationInfoView, ThirdWxAccountView
from xsign.views.admin.supersign import DeveloperInfoView, DevicesInfoView, SuperSignBillView, SuperSignBillUserInfoView
router = SimpleRouter(False)
router.register('app/info', AppInfoView)

@ -4,11 +4,10 @@ import os
from rest_framework import serializers
from api import models
from api.models import Apps
from api.utils.serializer import AppReleaseSerializer, UserInfoSerializer, AppsSerializer, StorageSerializer, \
ThirdWxSerializer, DomainNameSerializer, AppReportSerializer, OrdersSerializer
DeveloperSerializer, SuperSignUsedSerializer, ThirdWxSerializer, DomainNameSerializer, BillInfoSerializer, \
AppReportSerializer
from common.base.baseutils import get_choices_dict, get_choices_name_from_key
from common.utils.caches import get_app_today_download_times
from common.utils.storage import Storage
logger = logging.getLogger(__name__)
@ -94,18 +93,6 @@ class AdminUserInfoSerializer(UserInfoSerializer):
def get_app_count(self, obj):
return models.Apps.objects.filter(user_id=obj).count()
today_hits_count = serializers.SerializerMethodField()
def get_today_hits_count(self, obj):
android_app_ids = Apps.objects.filter(**{"user_id": obj, "type": 0}).values('app_id')
android_app_ids = [app_dict.get('app_id') for app_dict in android_app_ids]
ios_app_ids = Apps.objects.filter(**{"user_id": obj, "type": 1}).values('app_id')
ios_app_ids = [app_dict.get('app_id') for app_dict in ios_app_ids]
return {
"android_today_hits_count": get_app_today_download_times(android_app_ids),
"ios_today_hits_count": get_app_today_download_times(ios_app_ids)
}
def update(self, instance, validated_data):
return super(AdminUserInfoSerializer, self).update(instance, validated_data)
@ -176,7 +163,41 @@ class AdminStorageSerializer(StorageSerializer):
used_id = serializers.IntegerField(source="user_id.pk")
class AdminOrdersSerializer(OrdersSerializer):
class AdminDeveloperSerializer(DeveloperSerializer):
class Meta:
model = models.AppIOSDeveloperInfo
# depth = 1
exclude = ["p8key", ]
auth_type_choices = serializers.SerializerMethodField()
def get_auth_type_choices(self, obj):
return get_choices_dict(obj.auth_type_choices)
status_choices = serializers.SerializerMethodField()
def get_status_choices(self, obj):
return get_choices_dict(obj.status_choices)
class AdminSuperSignUsedSerializer(SuperSignUsedSerializer):
class Meta:
model = models.APPSuperSignUsedInfo
fields = ["created_time", "device_udid", "device_name", "developer_id", "bundle_id", "bundle_name", "app_id",
"id", "user_id", "short", "developer_pk"]
app_id = serializers.IntegerField(source="app_id.pk")
user_id = serializers.IntegerField(source="user_id.pk")
short = serializers.CharField(source="app_id.short")
developer_status = serializers.CharField(source="developerid.get_status_display")
developer_id = serializers.CharField(source="developerid.issuer_id")
developer_description = serializers.CharField(source="developerid.description")
developer_pk = serializers.IntegerField(source="developerid.pk")
class AdminOrdersSerializer(serializers.ModelSerializer):
class Meta:
model = models.Order
fields = "__all__"
@ -244,6 +265,19 @@ class AdminDomainNameSerializer(DomainNameSerializer):
return get_choices_dict(obj.domain_type_choices)
class AdminBillInfoSerializer(BillInfoSerializer):
class Meta:
model = models.IosDeveloperPublicPoolBill
fields = '__all__'
read_only_fields = ["id", "user_id", "to_user_id", "action", "number", "app_info", "udid",
"udid_sync_info", "app_id", "remote_addr"]
action_choices = serializers.SerializerMethodField()
def get_action_choices(self, obj):
return get_choices_dict(obj.action_choices)
class AdminAppReportSerializer(AppReportSerializer):
class Meta:
model = models.AppReportInfo

@ -4,9 +4,9 @@
# author: NinEveN
# date: 2022/1/6
from rest_framework.pagination import PageNumberPagination
from rest_framework.viewsets import ModelViewSet
from common.core.response import ApiResponse
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
class AppsPageNumber(PageNumberPagination):
@ -33,3 +33,20 @@ class BaseModelSet(ModelViewSet):
def update(self, request, *args, **kwargs):
data = super().update(request, *args, **kwargs).data
return ApiResponse(data=data)
class ApiResponse(Response):
def __init__(self, code=1000, msg='success', data=None, status=None, headers=None, content_type=None, **kwargs):
dic = {
'code': code,
'msg': msg
}
if data is not None:
dic['data'] = data
dic.update(kwargs)
self._data = data
# 对象来调用对象的绑定方法,会自动传值
super().__init__(data=dic, status=status, headers=headers, content_type=content_type)
# 类来调用对象的绑定方法,这个方法就是一个普通函数,有几个参数就要传几个参数
# Response.__init__(data=dic,status=status,headers=headers,content_type=content_type)

@ -11,14 +11,12 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from admin.utils.serializer import AdminAppsSerializer, AdminAppReleaseSerializer
from admin.utils.utils import AppsPageNumber, BaseModelSet
from admin.utils.utils import AppsPageNumber, ApiResponse, BaseModelSet
from api.base_views import app_delete
from api.models import AppReleaseInfo, Apps
from api.utils.TokenManager import verify_token
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
from common.utils.caches import del_cache_response_by_short
from common.utils.download import get_download_url_by_cache
from common.utils.token import verify_token
from common.utils.caches import del_cache_response_by_short, get_download_url_by_cache
logger = logging.getLogger(__name__)

@ -2,7 +2,7 @@
#
from django.conf import settings
from django.http import HttpResponse
from django.utils.translation import gettext as _
from django.utils.translation import ugettext as _
from proxy.views import proxy_view
from rest_framework.views import APIView

@ -11,10 +11,9 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from admin.utils.serializer import AdminDomainNameSerializer
from admin.utils.utils import BaseModelSet, AppsPageNumber
from admin.utils.utils import BaseModelSet, AppsPageNumber, ApiResponse
from api.models import UserDomainInfo
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
from common.utils.caches import reset_app_wx_easy_type
logger = logging.getLogger(__name__)

@ -9,12 +9,10 @@ import logging
from django.contrib import auth
from rest_framework.views import APIView
from api.models import UserInfo
from api.utils.auth.util import AuthInfo
from admin.utils.utils import ApiResponse
from api.utils.serializer import UserInfoSerializer
from api.utils.utils import set_user_token
from api.utils.utils import get_captcha, valid_captcha, set_user_token
from common.core.auth import ExpiringTokenAuthentication
from common.core.response import ApiResponse
from common.core.sysconfig import Config
from common.core.throttle import VisitRegister1Throttle, VisitRegister2Throttle
from common.utils.caches import login_auth_failed
@ -31,11 +29,11 @@ class LoginView(APIView):
code = 1000
msg = 'success'
data = None
# if Config.LOGIN.get("captcha"):
# is_valid = valid_captcha(receive.get("captcha_key", None), receive.get("verify_code", None), username)
# else:
# is_valid = True
if True:
if Config.LOGIN.get("captcha"):
is_valid = valid_captcha(receive.get("captcha_key", None), receive.get("authcode", None), username)
else:
is_valid = True
if is_valid:
if login_auth_failed("get", username):
password = receive.get("password")
user = auth.authenticate(username=username, password=password)
@ -70,8 +68,9 @@ class LoginView(APIView):
return ApiResponse(code=code, msg=msg, data=data)
def get(self, request):
auth_obj = AuthInfo(Config.LOGIN.get("captcha"), False)
data = auth_obj.make_rules_info()
data = {}
if Config.LOGIN.get("captcha"):
data = get_captcha()
return ApiResponse(data=data)
@ -82,16 +81,3 @@ class LoginUserView(APIView):
def get(self, request):
serializer = UserInfoSerializer(request.user, )
return ApiResponse(data=serializer.data)
def post(self, request):
data = request.data
user_id = data.get('user_id')
password = data.get('password')
if user_id and password and len(password) >= 6:
user_obj = UserInfo.objects.filter(pk=user_id).first()
if user_obj:
user_obj.set_password(password)
user_obj.save(update_fields=['password'])
serializer = UserInfoSerializer(user_obj, )
return ApiResponse(data=serializer.data)
return ApiResponse(code=1001, msg='重置密码失败,用户不存在')

@ -11,11 +11,9 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from admin.utils.serializer import AdminOrdersSerializer
from admin.utils.utils import BaseModelSet, AppsPageNumber
from admin.utils.utils import BaseModelSet, AppsPageNumber, ApiResponse
from api.models import UserInfo, Order
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
from common.core.sysconfig import Config
from common.utils.caches import update_order_info, admin_change_user_download_times
logger = logging.getLogger(__name__)
@ -57,7 +55,7 @@ class OrderInfoView(BaseModelSet):
obj = UserInfo.objects.filter(pk=pk).first()
if obj:
if amount > 0:
if admin_change_user_download_times(obj, amount * Config.APP_USE_BASE_DOWNLOAD_TIMES):
if admin_change_user_download_times(obj, amount):
return ApiResponse()
else:
return ApiResponse(code=1005, msg="订单创建失败")

@ -13,12 +13,11 @@ from rest_framework.filters import OrderingFilter
from rest_framework.views import APIView
from admin.utils.serializer import AdminStorageSerializer
from admin.utils.utils import AppsPageNumber, BaseModelSet
from admin.utils.utils import AppsPageNumber, BaseModelSet, ApiResponse
from api.base_views import storage_change
from api.models import UserInfo, AppStorage
from api.tasks import migrate_storage_job
from common.base.baseutils import format_storage_selection
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
logger = logging.getLogger(__name__)
@ -64,13 +63,8 @@ class StorageChangeView(APIView):
use_storage_id = data.get("use_storage_id", None)
force = data.get("force", None)
if use_storage_id:
c_task = migrate_storage_job.apply_async((use_storage_id, obj.pk, force))
msg = c_task.get(propagate=False)
logger.info(f"run migrate storage task {obj} msg:{msg}")
if c_task.successful():
c_task.forget()
else:
ApiResponse(code=1006, msg=msg)
if not storage_change(use_storage_id, obj, force):
ApiResponse(code=1006, msg="修改失败")
return ApiResponse()
return ApiResponse(code=1004, msg="数据校验失败")

@ -11,14 +11,13 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from rest_framework.views import APIView
from admin.utils.utils import AppsPageNumber, BaseModelSet
from admin.utils.serializer import AdminDeveloperSerializer, AdminSuperSignUsedSerializer, AdminBillInfoSerializer
from admin.utils.utils import AppsPageNumber, BaseModelSet, ApiResponse
from api.models import APPSuperSignUsedInfo, AppIOSDeveloperInfo, IosDeveloperPublicPoolBill, IosDeveloperBill
from api.utils.modelutils import get_user_public_used_sign_num, get_user_public_sign_num, get_user_obj_from_epu
from api.utils.utils import get_developer_devices
from common.base.baseutils import get_real_ip_address
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
from xsign.models import APPSuperSignUsedInfo, AppIOSDeveloperInfo, IosDeveloperPublicPoolBill, IosDeveloperBill
from xsign.utils.modelutils import get_user_public_used_sign_num, get_user_public_sign_num, get_developer_devices, \
get_user_obj_from_epu
from xsign.utils.serializer import AdminDeveloperSerializer, AdminSuperSignUsedSerializer, AdminBillInfoSerializer
logger = logging.getLogger(__name__)
@ -122,7 +121,7 @@ class SuperSignBillUserInfoView(APIView):
'used_balance': get_user_public_used_sign_num(user_obj),
'all_balance': get_user_public_sign_num(user_obj)
}
use_num = get_developer_devices(AppIOSDeveloperInfo.objects.filter(user_id=user_obj), user_obj)
use_num = get_developer_devices(AppIOSDeveloperInfo.objects.filter(user_id=user_obj))
private_balance_info = {
'used_balance': use_num.get('flyapp_used_sum', 0) + use_num.get('other_used_sum', 0),
'all_balance': use_num.get('max_total', 0)

@ -12,11 +12,10 @@ from rest_framework.filters import OrderingFilter
from rest_framework.views import APIView
from admin.utils.serializer import AdminUserInfoSerializer, AdminUserCertificationSerializer, AdminThirdWxSerializer
from admin.utils.utils import AppsPageNumber, BaseModelSet
from admin.utils.utils import AppsPageNumber, BaseModelSet, ApiResponse
from api.models import UserInfo, UserCertificationInfo, ThirdWeChatUserInfo
from common.base.baseutils import get_dict_from_filter_fields
from common.core.auth import AdminTokenAuthentication
from common.core.response import ApiResponse
from common.core.sysconfig import Config
from common.utils.caches import auth_user_download_times_gift
@ -47,12 +46,12 @@ class UserInfoView(APIView):
def put(self, request):
data = request.data
pk = data.get("id", None)
if not pk:
id = data.get("id", None)
if not id:
return ApiResponse(code=1003, msg="参数错误")
user_obj = UserInfo.objects.filter(id=pk).first()
user_obj = UserInfo.objects.filter(id=id).first()
if user_obj:
data['pk'] = pk
data['pk'] = id
users_serializer = AdminUserInfoSerializer(user_obj, data=data, partial=True)
if users_serializer.is_valid():
users_serializer.save()
@ -106,9 +105,6 @@ class UserCertificationInfoView(APIView):
class ThirdWxAccountFilter(filters.FilterSet):
user_id = filters.NumberFilter(field_name="user_id__id")
openid = filters.CharFilter(field_name="weixin__openid")
nickname = filters.CharFilter(field_name="weixin__nickname")
subscribe = filters.BooleanFilter(field_name="weixin__subscribe")
class Meta:
model = ThirdWeChatUserInfo

@ -9,10 +9,16 @@ admin.site.register(AppReleaseInfo)
admin.site.register(Token)
admin.site.register(VerifyName)
admin.site.register(AppStorage)
admin.site.register(AppUDID)
admin.site.register(AppIOSDeveloperInfo)
admin.site.register(APPSuperSignUsedInfo)
admin.site.register(APPToDeveloper)
admin.site.register(UDIDsyncDeveloper)
admin.site.register(Price)
admin.site.register(Order)
admin.site.register(DomainCnameInfo)
admin.site.register(UserDomainInfo)
admin.site.register(UserAdDisplayInfo)
admin.site.register(IosDeveloperPublicPoolBill)
admin.site.register(AppReportInfo)
admin.site.register(RemoteClientInfo)

@ -6,12 +6,11 @@
import logging
from api.models import AppReleaseInfo, UserInfo, AppScreenShot, AppStorage
from api.models import AppReleaseInfo, APPToDeveloper, UserInfo, AppScreenShot, AppStorage
from api.utils.app.supersignutils import IosUtils
from api.utils.response import BaseResponse
from api.utils.signalutils import run_delete_app_signal
from api.utils.utils import delete_local_files, delete_app_screenshots_files, change_storage_and_change_head_img, \
migrating_storage_data, clean_storage_data, check_storage_is_new_storage, migrating_storage_file_data
from common.base.magic import MagicCacheData
migrating_storage_data, clean_storage_data, check_storage_is_new_storage
from common.cache.state import MigrateStorageState
from common.utils.caches import del_cache_response_by_short, del_cache_by_delete_app, \
del_cache_storage
@ -32,7 +31,10 @@ def app_delete(app_obj):
res.msg = "数据迁移中"
return res
run_delete_app_signal(app_obj)
count = APPToDeveloper.objects.filter(app_id=app_obj).count()
if app_obj.issupersign or count > 0:
logger.info(f"app_id:{app_obj.app_id} is supersign ,delete this app need clean IOS developer")
IosUtils.clean_app_by_user_obj(app_obj)
storage = Storage(user_obj)
has_combo = app_obj.has_combo
@ -51,7 +53,6 @@ def app_delete(app_obj):
has_combo.has_combo = None
has_combo.save(update_fields=['has_combo'])
MagicCacheData.invalid_cache(app_obj.app_id)
app_obj.delete()
return res
@ -65,9 +66,42 @@ def app_screen_delete(screen_id, apps_obj, storage):
del_cache_response_by_short(apps_obj.app_id)
def check_storage_ok(user_obj, new_storage_obj):
if migrating_storage_file_data(user_obj, 'head_img.jpeg', new_storage_obj, False):
return True
def app_release_delete(app_obj, release_id, storage):
res = BaseResponse()
user_obj = app_obj.user_id
if app_obj:
apprelease_count = AppReleaseInfo.objects.filter(app_id=app_obj).values("release_id").count()
appreleaseobj = AppReleaseInfo.objects.filter(app_id=app_obj, release_id=release_id).first()
if not appreleaseobj.is_master:
logger.info(f"delete app release {appreleaseobj}")
storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type)
delete_local_files(appreleaseobj.release_id, appreleaseobj.release_type)
storage.delete_file(appreleaseobj.icon_url)
appreleaseobj.delete()
elif appreleaseobj.is_master and apprelease_count < 2:
logger.info(f"delete app master release {appreleaseobj} and clean app {app_obj} ")
count = APPToDeveloper.objects.filter(app_id=app_obj).count()
if app_obj.issupersign or count > 0:
logger.info(f"app_id:{app_obj.app_id} is supersign ,delete this app need clean IOS developer")
IosUtils.clean_app_by_user_obj(app_obj)
storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type)
delete_local_files(appreleaseobj.release_id, appreleaseobj.release_type)
storage.delete_file(appreleaseobj.icon_url)
del_cache_by_delete_app(app_obj.app_id)
appreleaseobj.delete()
delete_app_screenshots_files(storage, app_obj)
has_combo = app_obj.has_combo
if has_combo:
app_obj.has_combo.has_combo = None
app_obj.delete()
else:
pass
del_cache_response_by_short(app_obj.app_id)
return res
def storage_change(use_storage_id, user_obj, force):
@ -76,8 +110,6 @@ def storage_change(use_storage_id, user_obj, force):
return True
try:
if use_storage_id == -1:
if not check_storage_ok(user_obj, None):
return False
change_storage_and_change_head_img(user_obj, None)
if migrating_storage_data(user_obj, None, False):
if check_storage_is_new_storage(user_obj, None):
@ -85,8 +117,6 @@ def storage_change(use_storage_id, user_obj, force):
UserInfo.objects.filter(pk=user_obj.pk).update(storage=None)
else:
new_storage_obj = AppStorage.objects.filter(pk=use_storage_id).first()
if not check_storage_ok(user_obj, new_storage_obj):
return False
change_storage_and_change_head_img(user_obj, new_storage_obj)
if migrating_storage_data(user_obj, new_storage_obj, False):
if check_storage_is_new_storage(user_obj, new_storage_obj):

@ -1,14 +0,0 @@
from django.core.management.base import BaseCommand
from common.utils.caches import add_user_ds
class Command(BaseCommand):
help = 'add user download times'
def add_arguments(self, parser):
parser.add_argument('uid', type=str, default='')
parser.add_argument('download_times', type=int, default=100)
def handle(self, *args, **options):
add_user_ds(options.get('uid', None), options.get('download_times', 100))

@ -1,13 +0,0 @@
from django.core.management.base import BaseCommand
from common.libs.mp.wechat import create_menu
class Command(BaseCommand):
help = 'create weixin menu'
def add_arguments(self, parser):
parser.add_argument('menu_json', nargs='?', type=str, default=None)
def handle(self, *args, **options):
create_menu(options.get('menu_json', None))

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

Loading…
Cancel
Save