diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4cc1c234fcc611bb32278f77f3c8adcaf43da2a7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/unpackage
+/common/config.js
diff --git a/App.vue b/App.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d6599f751d0031c0fd2c1766c01f394a743e5f2c
--- /dev/null
+++ b/App.vue
@@ -0,0 +1,18 @@
+
+
+
diff --git a/README.en.md b/README.en.md
deleted file mode 100644
index f147a2d6d9b6f9624ab214df8f3c818f0b7ab188..0000000000000000000000000000000000000000
--- a/README.en.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# course-tencent-cloud-app
-
-#### Description
-酷瓜云课堂(腾讯云版)APP
-
-#### Software Architecture
-Software architecture description
-
-#### Installation
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Instructions
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Contribution
-
-1. Fork the repository
-2. Create Feat_xxx branch
-3. Commit your code
-4. Create Pull Request
-
-
-#### Gitee Feature
-
-1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
-2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
-3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
-4. The most valuable open source project [GVP](https://gitee.com/gvp)
-5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
-6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/README.md b/README.md
index ff471f3ec0edad24d10707e50011747dc76c1065..506da8183594c8416f63fb572ce7ebc148b64928 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,126 @@
-# course-tencent-cloud-app
+## 酷瓜云课堂客户端
-#### 介绍
-酷瓜云课堂(腾讯云版)APP
+酷瓜云课堂客户端采用 [uni-app](https://uniapp.dcloud.io) 为基础开发框架,前端界面使用 [uview-ui](http://uviewui.com)
-#### 软件架构
-软件架构说明
+### 相关项目
+- PC端以及API [course-tencent-cloud](https://gitee.com/koogua/course-tencent-cloud)
+- docker运行环境 [course-tencent-cloud-docker](https://gitee.com/koogua/course-tencent-cloud-docker)
-#### 安装教程
+### 安装使用
-1. xxxx
-2. xxxx
-3. xxxx
+为避免不必要的困扰,请先熟悉 [uni-app](https://uniapp.dcloud.io) 相关文档,以及 [HBuilderX](https://www.dcloud.io/hbuilderx.html) 开发工具
-#### 使用说明
+#### 下载源码
-1. xxxx
-2. xxxx
-3. xxxx
+```
+git clone https://gitee.com/koogua/course-tencent-cloud-app.git
+```
-#### 参与贡献
+#### 导入项目
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
+使用HBuilderX导入项目(文件->导入->从本地目录导入)
+#### 修改配置文件
+
+把 `common/config.sample.js` 另存为 `common/config.js`, 修改 `apiBaseUrl` 为实际的地址(尾部不要加“/”)
+
+```
+export const apiBaseUrl = '{{ your-domain }}/api'
+```
+
+#### 运行
+
+遇到跨域的问题,请修改 `course-tencent-cloud` 项目中的配置文件 `app/config/config.php` CORS 相关部分。
+
+**注意:开发环境跨域域名可使用通配符, 非开发环境请使用实际的域名**
+
+```
+/**
+ * 允许跨域
+ */
+$config['cors']['enabled'] = true;
+
+/**
+ * 允许跨域域名(字符|数组)
+ */
+$config['cors']['allow_origin'] = '*';
+
+/**
+ * 允许跨域字段(string|array)
+ */
+$config['cors']['allow_headers'] = '*';
+
+/**
+ * 允许跨域方法
+ */
+$config['cors']['allow_methods'] = ['GET', 'POST', 'OPTIONS'];
+```
+
+### 发行
+
+#### H5发行
+
+(a) 发布的文件上传到服务器本地
+
+把生成的目录 `upackage/dist/build/h5` 上传到 `course-tencent-cloud` 项目的 `public`目录下
+
+对应的 `manifest.json` h5部分配置如下:
+
+```
+"h5" : {
+ "title" : "酷瓜云课堂",
+ "optimization" : {
+ "treeShaking" : {
+ "enable" : true
+ }
+ },
+ "router" : {
+ "base" : "/h5/",
+ "mode" : "hash"
+ },
+ "sdkConfigs" : {
+ "maps" : {}
+ },
+ "publicPath" : "",
+ "template" : "template.h5.html",
+ "domain" : "your-domain.com"
+ }
+```
+
+(b) 发布的文件上传到CDN
+
+把生成的目录 `upackage/dist/build/h5` 上传到 腾讯云存储相关 bucket 的相关目录下
+
+把 `upackage/dist/build/h5/index.html` 上传到 `course-tencent-cloud` 项目的 `public/h5`目录下
+
+对应的 `manifest.json` h5部分配置如下:
+
+```
+"h5" : {
+ "title" : "酷瓜云课堂",
+ "optimization" : {
+ "treeShaking" : {
+ "enable" : true
+ }
+ },
+ "router" : {
+ "base" : "/h5/",
+ "mode" : "hash"
+ },
+ "sdkConfigs" : {
+ "maps" : {}
+ },
+ "publicPath" : "https://course-1255691183.file.myqcloud.com/static/h5",
+ "template" : "template.h5.html",
+ "domain" : "your-domain.com"
+ }
+```
+
+如果访问页面遇到403错误,修改 `public/h5/index.html` 的文件权限
+
+```
+chmod 644 public/h5/index.html
+```
-#### 特技
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/common/about.js b/common/about.js
new file mode 100644
index 0000000000000000000000000000000000000000..942db705aa916db0fc393470e5431038a3c39fce
--- /dev/null
+++ b/common/about.js
@@ -0,0 +1,12 @@
+/**
+ * NOTE:尾部不要加"/"
+ */
+//export const apiBaseUrl = 'https://ctc.koogua.com/api'
+export const apiBaseUrl = 'http://10.86.41.156/api'
+
+export const appInfo = {
+ name: '酷瓜云课堂',
+ alias: 'ctc-app',
+ link: 'https://gitee.com/koogua/course-tencent-cloud-app',
+ version: '1.0.0'
+}
diff --git a/common/api.js b/common/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..70f435241e835a9e18dbc5e11d39ec19ada0cc2e
--- /dev/null
+++ b/common/api.js
@@ -0,0 +1,421 @@
+import * as About from '@/common/about.js'
+import * as Config from '@/common/config.js'
+import * as Utils from '@/common/utils.js'
+import * as Storage from '@/common/storage.js'
+
+export const search = (params) => {
+ return httpGet('/search', params)
+}
+
+export const getSocketInfo = () => {
+ return httpGet('/socket/info')
+}
+
+export const getSiteInfo = () => {
+ return httpGet('/site/info')
+}
+
+export const getCaptchaInfo = () => {
+ return httpGet('/captcha/info')
+}
+
+export const getRewardOptions = () => {
+ return httpGet('/reward/options')
+}
+
+export const getVipOptions = () => {
+ return httpGet('/vip/options')
+}
+
+export const getHelpList = () => {
+ return httpGet('/help/list')
+}
+
+export const getHelpInfo = (id) => {
+ return httpGet(`/help/${id}/info`)
+}
+
+export const getPageInfo = (id) => {
+ return httpGet(`/page/${id}/info`)
+}
+
+export const getIndexSlides = () => {
+ return httpGet('/index/slides')
+}
+
+export const getIndexNewCourses = () => {
+ return httpGet('/index/courses/new')
+}
+
+export const getIndexFreeCourses = () => {
+ return httpGet('/index/courses/free')
+}
+
+export const getIndexVipCourses = () => {
+ return httpGet('/index/courses/vip')
+}
+
+export const getCourseCategories = () => {
+ return httpGet('/course/categories')
+}
+
+export const getCourseList = (params = {}) => {
+ return httpGet('/course/list', params)
+}
+
+export const getCourseInfo = (id) => {
+ return httpGet(`/course/${id}/info`)
+}
+
+export const getCourseChapters = (id) => {
+ return httpGet(`/course/${id}/chapters`)
+}
+
+export const getCoursePackages = (id) => {
+ return httpGet(`/course/${id}/packages`)
+}
+
+export const getCourseReviews = (id, params = {}) => {
+ return httpGet(`/course/${id}/reviews`, params)
+}
+
+export const getCourseConsults = (id, params = {}) => {
+ return httpGet(`/course/${id}/consults`, params)
+}
+
+export const favoriteCourse = (id) => {
+ return httpPost(`/course/${id}/favorite`)
+}
+
+export const getChapterInfo = (id) => {
+ return httpGet(`/chapter/${id}/info`)
+}
+
+export const getChapterConsults = (id, params = {}) => {
+ return httpGet(`/chapter/${id}/consults`, params)
+}
+
+export const getChapterResources = (id) => {
+ return httpGet(`/chapter/${id}/resources`)
+}
+
+export const likeChapter = (id) => {
+ return httpPost(`/chapter/${id}/like`)
+}
+
+export const learningChapter = (id, params) => {
+ return httpPost(`/chapter/${id}/learning`, params)
+}
+
+export const getLiveList = () => {
+ return httpGet('/live/list')
+}
+
+export const getLiveChats = (id) => {
+ return httpGet(`/live/${id}/chats`)
+}
+
+export const bindLiveUser = (id, params) => {
+ return httpPost(`/live/${id}/user/bind`, params)
+}
+
+export const sendLiveMessage = (id, params) => {
+ return httpPost(`/live/${id}/msg/send`, params)
+}
+
+export const createConsult = (params) => {
+ return httpPost('/consult/create', params)
+}
+
+export const getConsultInfo = (id) => {
+ return httpGet(`/consult/${id}/info`)
+}
+
+export const updateConsult = (id, params) => {
+ return httpPost(`/consult/${id}/update`, params)
+}
+
+export const deleteConsult = (id) => {
+ return httpPost(`/consult/${id}/delete`)
+}
+
+export const likeConsult = (id) => {
+ return httpPost(`/consult/${id}/like`)
+}
+
+export const createReview = (params) => {
+ return httpPost('/review/create', params)
+}
+
+export const getReviewInfo = (id) => {
+ return httpGet(`/review/${id}/info`)
+}
+
+export const updateReview = (id, params) => {
+ return httpPost(`/review/${id}/update`, params)
+}
+
+export const deleteReview = (id) => {
+ return httpPost(`/review/${id}/delete`)
+}
+
+export const likeReview = (id) => {
+ return httpPost(`/review/${id}/like`)
+}
+
+export const unlikeReview = (id) => {
+ return httpPost(`/review/${id}/unlike`)
+}
+
+export const getOrderInfo = (sn) => {
+ return httpGet('/order/info', {
+ sn: sn
+ })
+}
+
+export const getOrderConfirm = (itemId, itemType) => {
+ return httpGet('/order/confirm', {
+ item_id: itemId,
+ item_type: itemType
+ })
+}
+
+export const createOrder = (params) => {
+ return httpPost('/order/create', params)
+}
+
+export const cancelOrder = (sn) => {
+ return httpPost('/order/cancel', {
+ sn: sn
+ })
+}
+
+export const getRefundConfirm = (sn) => {
+ return httpGet('/refund/confirm', {
+ sn: sn
+ })
+}
+
+export const getRefundInfo = (sn) => {
+ return httpGet('/refund/info', {
+ sn: sn
+ })
+}
+
+export const createRefund = (params) => {
+ return httpPost('/refund/create', params)
+}
+
+export const cancelRefund = (sn) => {
+ return httpPost('/refund/cancel', {
+ sn: sn
+ })
+}
+
+export const getTradeInfo = (sn) => {
+ return httpGet('/trade/info', {
+ sn: sn
+ })
+}
+
+export const createH5Trade = (params) => {
+ return httpPost('/trade/h5/create', params)
+}
+
+export const createMpTrade = (params) => {
+ return httpPost('/trade/mp/create', params)
+}
+
+export const createAppTrade = (params) => {
+ return httpPost('/trade/app/create', params)
+}
+
+export const getImGroupList = (params = {}) => {
+ return httpGet('/im/group/list', params)
+}
+
+export const getImGroupInfo = (id) => {
+ return httpGet(`/im/group/${id}/info`)
+}
+
+export const getImGroupUsers = (id, params = {}) => {
+ return httpGet(`/im/group/${id}/users`, params)
+}
+
+export const getTeacherList = (params = {}) => {
+ return httpGet('/teacher/list', params)
+}
+
+export const getTeacherInfo = (id) => {
+ return httpGet(`/teacher/${id}/info`)
+}
+
+export const getTeacherCourses = (id, params = {}) => {
+ return httpGet(`/teacher/${id}/courses`, params)
+}
+
+export const getUserInfo = (id) => {
+ return httpGet(`/user/${id}/info`)
+}
+
+export const getUserCourses = (id, params = {}) => {
+ return httpGet(`/user/${id}/courses`, params)
+}
+
+export const getUserFavorites = (id, params = {}) => {
+ return httpGet(`/user/${id}/favorites`, params)
+}
+
+export const getUserFriends = (id, params = {}) => {
+ return httpGet(`/user/${id}/friends`, params)
+}
+
+export const getUserGroups = (id, params = {}) => {
+ return httpGet(`/user/${id}/groups`, params)
+}
+
+export const getMyProfile = () => {
+ return httpGet('/uc/profile')
+}
+
+export const getMyAccount = () => {
+ return httpGet('/uc/account')
+}
+
+export const getMyOrders = (params = {}) => {
+ return httpGet('/uc/orders', params)
+}
+
+export const getMyRefunds = (params = {}) => {
+ return httpGet('/uc/refunds', params)
+}
+
+export const getMyCourses = (params = {}) => {
+ return httpGet('/uc/courses', params)
+}
+
+export const getMyConsults = (params = {}) => {
+ return httpGet('/uc/consults', params)
+}
+
+export const getMyReviews = (params = {}) => {
+ return httpGet('/uc/reviews', params)
+}
+
+export const getMyFavorites = (params = {}) => {
+ return httpGet('/uc/favorites', params)
+}
+
+export const getMyFriends = (params = {}) => {
+ return httpGet('/uc/friends', params)
+}
+
+export const getMyGroups = (params = {}) => {
+ return httpGet('/uc/groups', params)
+}
+
+export const updateMyProfile = (params = {}) => {
+ return httpPost('/uc/profile/update', params)
+}
+
+export const registerAccount = (params = {}) => {
+ return httpPost('/account/register', params)
+}
+
+export const loginByPassword = (params = {}) => {
+ return httpPost('/account/password/login', params)
+}
+
+export const loginByVerify = (params = {}) => {
+ return httpPost('/account/verify/login', params)
+}
+
+export const logoutAccount = (params = {}) => {
+ return httpPost('/account/logout', params)
+}
+
+export const resetPassword = (params = {}) => {
+ return httpPost('/account/password/reset', params)
+}
+
+export const updatePhone = (params = {}) => {
+ return httpPost('/account/phone/update', params)
+}
+
+export const updateEmail = (params = {}) => {
+ return httpPost('/account/email/update', params)
+}
+
+export const updatePassword = (params = {}) => {
+ return httpPost('/account/password/update', params)
+}
+
+export const sendSmsVerifyCode = (params = {}) => {
+ return httpPost('/verify/sms/code', params)
+}
+
+export const sendEmailVerifyCode = (params = {}) => {
+ return httpPost('/verify/email/code', params)
+}
+
+export const uploadAvatar = (filePath) => {
+ return new Promise((resolve, reject) => {
+ uni.uploadFile({
+ url: Config.apiBaseUrl + '/upload/avatar/img',
+ name: 'file',
+ filePath: filePath,
+ success: (res) => {
+ resolve(res)
+ },
+ fail: (e) => {
+ reject(e)
+ }
+ })
+ })
+}
+
+const httpGet = (path, params = {}, header = {}) => {
+ return httpRequest(path, params, 'GET', header)
+}
+
+const httpPost = (path, params = {}, header = {}) => {
+ return httpRequest(path, params, 'POST', header)
+}
+
+const httpRequest = (path, params = {}, method = 'GET', header = {}) => {
+
+ let url = Config.apiBaseUrl + path
+
+ header['X-Version'] = About.appInfo.version
+ header['X-Platform'] = Utils.getPlatform()
+ header['X-Timestamp'] = Utils.getNowTime()
+ header['X-Token'] = Storage.get(Storage.cacheKey.token)
+
+ return new Promise(function(resolve, reject) {
+ uni.request({
+ url: url,
+ data: params,
+ method: method,
+ header: header,
+ success: function(res) {
+ if (res.statusCode == 200) {
+ if (res.data.code == 0) {
+ resolve(res.data)
+ } else {
+ reject(res.data)
+ }
+ } else if (res.statusCode == 401) {
+ Utils.clearToken()
+ uni.navigateTo({
+ url: '/pages/account/login'
+ })
+ } else {
+ reject(res.data)
+ }
+ },
+ fail: function(err) {
+ reject(err)
+ }
+ })
+ })
+}
diff --git a/common/captcha.js b/common/captcha.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc7f46d2cadc8cad8ea0411afd0ef9fc5e28b048
--- /dev/null
+++ b/common/captcha.js
@@ -0,0 +1,49 @@
+"use strict"
+
+import * as Api from '@/common/api.js'
+
+/**
+ * 加载验证码JS文件
+ */
+function loadScript(url) {
+ return new Promise((resolve, reject) => {
+ if (window.TencentCaptcha == undefined) {
+ let script = document.createElement("script")
+ let head = document.getElementsByTagName("head")[0]
+ script.type = "text/javascript"
+ script.charset = "UTF-8"
+ script.src = url
+ head.appendChild(script)
+ script.onload = function() {
+ resolve()
+ };
+ script.onerror = function() {
+ reject(new Error("验证码脚本加载失败,请重试"))
+ };
+ } else {
+ resolve()
+ }
+ });
+}
+
+/**
+ * 验证码creater
+ * @param {function} callback 回调函数, 回调结果请参见 https://cloud.tencent.com/document/product/1110/36841
+ * @param {object} options 更多配置参数, 请参见 https://cloud.tencent.com/document/product/1110/36841#pzcs
+ * @returns {Promise