shenwu пре 7 месеци
комит
bf55dff111
100 измењених фајлова са 2017 додато и 0 уклоњено
  1. 12 0
      .editorconfig
  2. 7 0
      .eslintrc.js
  3. 7 0
      .gitignore
  4. 10 0
      babel.config.js
  5. 7 0
      config/dev.ts
  6. 93 0
      config/index.ts
  7. 36 0
      config/prod.ts
  8. 82 0
      package.json
  9. 15 0
      project.config.json
  10. 13 0
      project.tt.json
  11. 12 0
      src/Hook/useApi.ts
  12. 59 0
      src/app.config.ts
  13. 34 0
      src/app.less
  14. 75 0
      src/app.tsx
  15. 20 0
      src/components/Empty/index.module.less
  16. 16 0
      src/components/Empty/index.tsx
  17. 20 0
      src/components/KeepReadPop/index.module.less
  18. 29 0
      src/components/KeepReadPop/index.tsx
  19. 15 0
      src/components/PupPetry/BannerBox/index.less
  20. 38 0
      src/components/PupPetry/BannerBox/index.tsx
  21. 29 0
      src/components/PupPetry/BookBox/BookboxColumnBig.tsx
  22. 28 0
      src/components/PupPetry/BookBox/BookboxColumnSmall.tsx
  23. 43 0
      src/components/PupPetry/BookBox/BookboxRowBig.tsx
  24. 38 0
      src/components/PupPetry/BookBox/BookboxRowMiddle.tsx
  25. 45 0
      src/components/PupPetry/BookBox/BookboxRowSmall.tsx
  26. 310 0
      src/components/PupPetry/BookBox/index.less
  27. 28 0
      src/components/PupPetry/BookHead/index.less
  28. 36 0
      src/components/PupPetry/BookHead/index.tsx
  29. 51 0
      src/components/PupPetry/BookTypeTabs/index.less
  30. 63 0
      src/components/PupPetry/BookTypeTabs/index.tsx
  31. 32 0
      src/components/PupPetry/Divider/index.module.less
  32. 26 0
      src/components/PupPetry/Divider/index.tsx
  33. 109 0
      src/components/PupPetry/Loading/index.module.less
  34. 23 0
      src/components/PupPetry/Loading/index.tsx
  35. 95 0
      src/components/ScrollView/index.tsx
  36. 188 0
      src/components/TopNavBar/index.less
  37. 158 0
      src/components/TopNavBar/index.tsx
  38. 45 0
      src/config.ts
  39. 70 0
      src/globaStyle.less
  40. BIN
      src/icon/VIP.png
  41. BIN
      src/icon/allow.jpg
  42. BIN
      src/icon/appleId.png
  43. BIN
      src/icon/avatar.jpg
  44. BIN
      src/icon/backBlock.png
  45. BIN
      src/icon/backWhile.png
  46. BIN
      src/icon/bean.png
  47. BIN
      src/icon/beanLight.png
  48. BIN
      src/icon/beanNo.png
  49. BIN
      src/icon/beanRead.png
  50. BIN
      src/icon/beanSmall.png
  51. BIN
      src/icon/bookShelfNull.png
  52. BIN
      src/icon/booksNull.png
  53. BIN
      src/icon/bottom.png
  54. BIN
      src/icon/bt.png
  55. BIN
      src/icon/btn.png
  56. BIN
      src/icon/caiyunjing.jpg
  57. BIN
      src/icon/caiyunremen.jpg
  58. BIN
      src/icon/clear.png
  59. BIN
      src/icon/clickDesktopTip.png
  60. BIN
      src/icon/clickFcTip.png
  61. BIN
      src/icon/coreBack.png
  62. BIN
      src/icon/cyrt.png
  63. BIN
      src/icon/dailyReading.png
  64. BIN
      src/icon/delete.png
  65. BIN
      src/icon/desktop.png
  66. BIN
      src/icon/fl_1.png
  67. BIN
      src/icon/fl_2.png
  68. BIN
      src/icon/floatingWin.png
  69. BIN
      src/icon/giftBox.png
  70. BIN
      src/icon/grade.png
  71. BIN
      src/icon/hdLogo.png
  72. BIN
      src/icon/hh.png
  73. BIN
      src/icon/homeBlock.png
  74. BIN
      src/icon/homeWhile.png
  75. BIN
      src/icon/in.png
  76. BIN
      src/icon/indicate.png
  77. BIN
      src/icon/inviteFriends.png
  78. BIN
      src/icon/jtRight.png
  79. BIN
      src/icon/left.png
  80. BIN
      src/icon/loaginBt.png
  81. BIN
      src/icon/lvBack.png
  82. BIN
      src/icon/messageSub.png
  83. BIN
      src/icon/ml_b.png
  84. BIN
      src/icon/ml_h.png
  85. BIN
      src/icon/more.png
  86. BIN
      src/icon/myBack.png
  87. BIN
      src/icon/noIn.png
  88. BIN
      src/icon/noVideo.png
  89. BIN
      src/icon/noinSucc.png
  90. BIN
      src/icon/order.png
  91. BIN
      src/icon/people.png
  92. BIN
      src/icon/peopleNo.png
  93. BIN
      src/icon/phone.png
  94. BIN
      src/icon/re_order.png
  95. BIN
      src/icon/refresh.png
  96. BIN
      src/icon/right.png
  97. BIN
      src/icon/sc_1.png
  98. BIN
      src/icon/sc_2.png
  99. BIN
      src/icon/sc_b.png
  100. BIN
      src/icon/sc_h.png

+ 12 - 0
.editorconfig

@@ -0,0 +1,12 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false

+ 7 - 0
.eslintrc.js

@@ -0,0 +1,7 @@
+module.exports = {
+  "extends": ["taro/react"],
+  "rules": {
+    "react/jsx-uses-react": "off",
+    "react/react-in-jsx-scope": "off"
+  }
+}

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+dist/
+deploy_versions/
+.temp/
+.rn_temp/
+node_modules/
+.DS_Store
+.swc

+ 10 - 0
babel.config.js

@@ -0,0 +1,10 @@
+// babel-preset-taro 更多选项和默认值:
+// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
+module.exports = {
+  presets: [
+    ['taro', {
+      framework: 'react',
+      ts: true
+    }]
+  ]
+}

+ 7 - 0
config/dev.ts

@@ -0,0 +1,7 @@
+module.exports = {
+  env: {
+    NODE_ENV: '"development"'
+  },
+  defineConstants: {
+  },
+}

+ 93 - 0
config/index.ts

@@ -0,0 +1,93 @@
+import path from "path"
+
+const config = {
+  projectName: 'new-taro-mini-book',
+  date: '2024-8-26',
+  designWidth: 750,
+  deviceRatio: {
+    640: 2.34 / 2,
+    750: 1,
+    828: 1.81 / 2
+  },
+  sourceRoot: 'src',
+  outputRoot: 'dist',
+  plugins: [],
+  defineConstants: {
+  },
+  copy: {
+    patterns: [
+    ],
+    options: {
+    }
+  },
+  framework: 'react',
+  compiler: 'webpack5',
+  cache: {
+    enable: false // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
+  },
+  alias:{
+    '@src': path.resolve(__dirname, '..', 'src'),
+  },
+  weapp: {
+    module: {
+      postcss: {
+        // css modules 功能开关与相关配置
+        cssModules: {
+          enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
+          config: {
+            namingPattern: 'module', // 转换模式,取值为 global/module,下文详细说明
+            generateScopedName: '[name]__[local]___[hash:base64:5]',
+          },
+        }
+      }
+    },
+  },
+  mini: {
+    postcss: {
+      pxtransform: {
+        enable: true,
+        config: {
+
+        }
+      },
+      url: {
+        enable: true,
+        config: {
+          limit: 1024 // 设定转换尺寸上限
+        }
+      },
+      cssModules: {
+        enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
+        config: {
+          namingPattern: 'module', // 转换模式,取值为 global/module
+          generateScopedName: '[name]__[local]___[hash:base64:5]'
+        }
+      }
+    }
+  },
+  h5: {
+    publicPath: '/',
+    staticDirectory: 'static',
+    postcss: {
+      autoprefixer: {
+        enable: true,
+        config: {
+        }
+      },
+      cssModules: {
+        enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
+        config: {
+          namingPattern: 'module', // 转换模式,取值为 global/module
+          generateScopedName: '[name]__[local]___[hash:base64:5]'
+        }
+      }
+    }
+  }
+}
+
+module.exports = function (merge) {
+  if (process.env.NODE_ENV === 'development') {
+    return merge({}, config, require('./dev'))
+  }
+  return merge({}, config, require('./prod'))
+}

+ 36 - 0
config/prod.ts

@@ -0,0 +1,36 @@
+module.exports = {
+  env: {
+    NODE_ENV: '"production"'
+  },
+  defineConstants: {
+  },
+  h5: {
+    /**
+     * WebpackChain 插件配置
+     * @docs https://github.com/neutrinojs/webpack-chain
+     */
+    // webpackChain (chain) {
+    //   /**
+    //    * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。
+    //    * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer
+    //    */
+    //   chain.plugin('analyzer')
+    //     .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
+
+    //   /**
+    //    * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。
+    //    * @docs https://github.com/chrisvfritz/prerender-spa-plugin
+    //    */
+    //   const path = require('path')
+    //   const Prerender = require('prerender-spa-plugin')
+    //   const staticDir = path.join(__dirname, '..', 'dist')
+    //   chain
+    //     .plugin('prerender')
+    //     .use(new Prerender({
+    //       staticDir,
+    //       routes: [ '/pages/index/index' ],
+    //       postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') })
+    //     }))
+    // }
+  }
+}

+ 82 - 0
package.json

@@ -0,0 +1,82 @@
+{
+  "name": "new-taro-mini-book",
+  "version": "1.0.0",
+  "private": true,
+  "description": "mini-book",
+  "templateInfo": {
+    "name": "mobx",
+    "typescript": true,
+    "css": "Less",
+    "framework": "React"
+  },
+  "scripts": {
+    "build:weapp": "taro build --type weapp",
+    "build:swan": "taro build --type swan",
+    "build:alipay": "taro build --type alipay",
+    "build:tt": "taro build --type tt",
+    "build:h5": "taro build --type h5",
+    "build:rn": "taro build --type rn",
+    "build:qq": "taro build --type qq",
+    "build:quickapp": "taro build --type quickapp",
+    "dev:weapp": "npm run build:weapp -- --watch",
+    "dev:swan": "npm run build:swan -- --watch",
+    "dev:alipay": "npm run build:alipay -- --watch",
+    "dev:tt": "npm run build:tt -- --watch",
+    "dev:h5": "npm run build:h5 -- --watch",
+    "dev:rn": "npm run build:rn -- --watch",
+    "dev:qq": "npm run build:qq -- --watch",
+    "dev:quickapp": "npm run build:quickapp -- --watch"
+  },
+  "browserslist": [
+    "last 3 versions",
+    "Android >= 4.1",
+    "ios >= 8"
+  ],
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "@babel/runtime": "^7.7.7",
+    "@tarojs/components": "3.6.26",
+    "@tarojs/helper": "3.6.26",
+    "@tarojs/plugin-platform-weapp": "3.6.26",
+    "@tarojs/plugin-platform-alipay": "3.6.26",
+    "@tarojs/plugin-platform-tt": "3.6.26",
+    "@tarojs/plugin-platform-swan": "3.6.26",
+    "@tarojs/plugin-platform-jd": "3.6.26",
+    "@tarojs/plugin-platform-qq": "3.6.26",
+    "@tarojs/plugin-platform-h5": "3.6.26",
+    "@tarojs/runtime": "3.6.26",
+    "@tarojs/shared": "3.6.26",
+    "@tarojs/taro": "3.6.26",
+    "@tarojs/plugin-framework-react": "3.6.26",
+    "mobx": "^4.8.0",
+    "mobx-react": "^6.1.4",
+    "react-dom": "^18.0.0",
+    "@tarojs/react": "3.6.26",
+    "react": "^18.0.0"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.8.0",
+    "@tarojs/cli": "3.6.26",
+    "@types/webpack-env": "^1.13.6",
+    "@types/react": "^18.0.0",
+    "postcss": "^8.4.18",
+    "webpack": "^5.78.0",
+    "@tarojs/taro-loader": "3.6.26",
+    "@tarojs/webpack5-runner": "3.6.26",
+    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
+    "react-refresh": "^0.11.0",
+    "babel-preset-taro": "3.6.26",
+    "eslint-config-taro": "3.6.26",
+    "eslint": "^8.12.0",
+    "eslint-plugin-react": "^7.8.2",
+    "eslint-plugin-import": "^2.12.0",
+    "eslint-plugin-react-hooks": "^4.2.0",
+    "stylelint": "9.3.0",
+    "@typescript-eslint/parser": "^5.20.0",
+    "@typescript-eslint/eslint-plugin": "^5.20.0",
+    "typescript": "^4.1.0",
+    "ts-node": "^10.9.1",
+    "@types/node": "^18.15.11"
+  }
+}

+ 15 - 0
project.config.json

@@ -0,0 +1,15 @@
+{
+  "miniprogramRoot": "./dist",
+  "projectname": "new-taro-mini-book",
+  "description": "mini-book",
+  "appid": "touristappid",
+  "setting": {
+    "urlCheck": true,
+    "es6": false,
+    "enhance": false,
+    "compileHotReLoad": false,
+    "postcss": false,
+    "minified": false
+  },
+  "compileType": "miniprogram"
+}

+ 13 - 0
project.tt.json

@@ -0,0 +1,13 @@
+{
+  "miniprogramRoot": "./",
+  "projectname": "new-taro-mini-book",
+  "description": "mini-book",
+  "appid": "touristappid",
+  "setting": {
+    "urlCheck": true,
+    "es6": false,
+    "postcss": false,
+    "minified": false
+  },
+  "compileType": "miniprogram"
+}

+ 12 - 0
src/Hook/useApi.ts

@@ -0,0 +1,12 @@
+import { getShortBookInfoAppListOfPage } from '@src/server/book/short'
+/**用于长短篇小说区别,使用不同的接口*/
+function useApi(store) {
+    //判断是长篇
+    let isLong = store.appInfo.appCategory === 1
+    return {
+        /**获取小说列表*/
+        getBookPageList: isLong ? getShortBookInfoAppListOfPage : getShortBookInfoAppListOfPage,
+    }
+
+}
+export default useApi

+ 59 - 0
src/app.config.ts

@@ -0,0 +1,59 @@
+/**全局配置https://taro-docs.jd.com/taro/docs/tutorial#%E5%85%A5%E5%8F%A3%E6%96%87%E4%BB%B6 */
+export default {
+  pages: [
+    'pages/index/index',//书城
+    'pages/bookrack/index',//书架
+    'pages/classify/index',//分类
+    'pages/my/index',//我的
+    'pages/book/bookDetails/index',//书籍详情
+    // 'pages/task/index',//福利中心
+    'pages/vipCore/index',//会员中心
+    // 'pages/contactus/index',//联系客服
+    // 'pages/bookCatalog/index',//书籍目录
+    // 'pages/aboutWe/index',//关于我们
+    // 'pages/book/bookArticle/index',//书籍内容 阅读页
+    // 'pages/search/index',//搜索
+    // 'pages/indexMore/index', // 更多
+    // 'pages/book/bookDetails1/index',//书籍详情
+  ],
+
+  window: {
+    backgroundTextStyle: 'dark',
+    navigationBarBackgroundColor: '#fff',
+    navigationBarTitleText: 'WeChat',
+    navigationBarTextStyle: 'black',
+    // enablePullDownRefresh:true,
+  },
+  tabBar: {//底部导航
+    color: '#000',//字体颜色
+    selectedColor: '#ff0000',//字体选中颜色
+    position: 'bottom',//定位
+    borderStyle: 'white',//上边框颜色
+    list: [
+      {
+        text: '书架',//名称
+        pagePath: 'pages/bookrack/index',//路径
+        iconPath: './icon/sj_1.png',//图片
+        selectedIconPath: './icon/sj_2.png',//选中的图片变化
+      },
+      {
+        text: '书城',//名称
+        pagePath: 'pages/index/index',//路径
+        iconPath: './icon/sc_1.png',//图片
+        selectedIconPath: './icon/sc_2.png',//选中的图片变化
+      },
+      {
+        text: '分类',//名称
+        pagePath: 'pages/classify/index',//路径
+        iconPath: './icon/fl_1.png',//图片
+        selectedIconPath: './icon/fl_2.png',//选中的图片变化
+      },
+      {
+        text:'我的',//名称
+        pagePath:'pages/my/index',//路径
+        iconPath:'./icon/wd_1.png',//图片
+        selectedIconPath:'./icon/wd_2.png',//选中的图片变化
+      },
+    ]
+  }
+}

+ 34 - 0
src/app.less

@@ -0,0 +1,34 @@
+// 横向
+.row {
+    display: flex;
+    flex-flow: row wrap;
+    justify-content: space-between;
+    width: 690px;
+    margin: 0 auto;
+  }
+  // 循环BookboxRowBig组件用到
+  .for_top {
+    > view {
+      margin-top: 40px;
+      &:nth-child(1) {
+        margin-top: 30px;
+      }
+    }
+  }
+  //循环BookboxRowBig组件用到
+  .for_top1 {
+    > view {
+      margin-top: 40px;
+      &:nth-child(1) {
+        margin-top: 11px;
+      }
+    }
+  }
+  //底部padding留空
+  .pd_btm {
+    padding-bottom: 50px;
+  }
+  .scrollview {
+    height: 100%;
+  }
+  image{height:auto}

+ 75 - 0
src/app.tsx

@@ -0,0 +1,75 @@
+import { Component } from 'react'
+import { Provider, ProviderProps } from 'mobx-react'
+import Taro from '@tarojs/taro'
+import './app.less'
+// ======store======
+import userInfo from './store/userInfo'
+import indexStore from './store/index'
+import Book, { BookStore } from './store/book'
+import appInfoStore, { AppInfoStore } from './store/appInfo'
+import classifyStore, { ClassifyStore } from './store/classIfy'
+// ======引入全局配置======
+import { initApp, setApp } from './config'
+// ======引入拦截器=========
+import './interceptor'
+import api from './server/index'
+
+
+
+export interface Store {
+  appInfoStore: AppInfoStore
+  userInfo: any,
+  indexStore: any,
+  bookStore: BookStore,
+  classifyStore:ClassifyStore,
+}
+const store: Store = {
+  userInfo,
+  indexStore,
+  appInfoStore,
+  bookStore: Book,
+  classifyStore
+}
+class App extends Component {
+
+  //每次打开小程序触发,后台切入不算
+  async onLaunch(options) {
+    log("初始化只第一次发生", options, app.initToken)
+    initApp()//初始化全局变量
+    let appId = Taro.getAccountInfoSync().miniProgram.appId//获取当前小程序的APPID 
+    setApp({ appId })
+    // 初始化登录,获取登录使用的token存放用于登录使用
+    await api.loginInit(appId)
+  }
+  componentDidMount() {
+    /**获取状态栏高度 */
+    Taro.getSystemInfo({}).then(res => {
+      let { system, statusBarHeight } = res
+      indexStore.SET_SYSTEM(system || '')
+      indexStore.SET_NAVBARTOP(statusBarHeight || 0)
+      setApp({ system })
+    })
+  }
+  async componentDidShow(options) {
+    let { scene } = options//获取当前小程序进入的场景值
+    indexStore.SET_SCENE(scene)
+    if (JSON.stringify(options.query) !== "{}") {
+      let keys = Object.keys(options.query)
+      if (keys?.indexOf('share_sources') !== -1) {
+        indexStore.SET_SHARESOURCES(options.query.share_sources)
+      }
+    }
+  }
+  componentDidHide() { }
+
+  componentDidCatchError() { }
+
+  render() {
+    log("store.appInfoStore.appInfo", store.appInfoStore.appInfo)
+    return <Provider store={store}>
+      {(this.props as ProviderProps).children}
+    </Provider>
+  }
+}
+
+export default App

+ 20 - 0
src/components/Empty/index.module.less

@@ -0,0 +1,20 @@
+.contenNull {
+    display: flex;
+    justify-content: center;
+    padding-top: 206px;
+    flex-direction: column;
+    align-items: center;
+    width: 100%;
+    image{
+      width: 438px;
+      height: 222px;
+    }
+    text {
+      margin-top: 20px;
+      font-size: 32px;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #999999;
+      line-height: 44px;
+    }
+  }

+ 16 - 0
src/components/Empty/index.tsx

@@ -0,0 +1,16 @@
+import { Image, Text } from '@tarojs/components'
+import styles from './index.module.less'
+/**空数据展示 */
+
+import { View } from "@tarojs/components";
+interface Props {
+    imgName?: string,//展示图片名称 booksNull.png 放在icon目录下
+    text?: string,//展示文本
+}
+export default function Empty(props: Props) {
+    let { imgName = 'booksNull.png', text = "暂无书籍哦~" } = props
+    return <View className={styles.contenNull}>
+        <Image src={require('@src/icon/'+imgName)} />
+        <Text>{text}</Text>
+    </View>
+}

+ 20 - 0
src/components/KeepReadPop/index.module.less

@@ -0,0 +1,20 @@
+.contenNull {
+    display: flex;
+    justify-content: center;
+    padding-top: 206px;
+    flex-direction: column;
+    align-items: center;
+    width: 100%;
+    image{
+      width: 438px;
+      height: 222px;
+    }
+    text {
+      margin-top: 20px;
+      font-size: 32px;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #999999;
+      line-height: 44px;
+    }
+  }

+ 29 - 0
src/components/KeepReadPop/index.tsx

@@ -0,0 +1,29 @@
+import { Image, Text,Navigator } from '@tarojs/components'
+import styles from './index.module.less'
+import { View } from "@tarojs/components";
+/**
+ * 继续阅读弹窗
+ */
+interface Props {
+    imgName?: string,//展示图片名称 booksNull.png 放在icon目录下
+    readInfo:{
+        chapter_name:string,//章节名称
+        chapter_id:string,//章节ID
+        book_name:string,//小说名称
+        book_id:string,//小说id
+
+
+    },//阅读信息
+}
+ function KeepReadPop(props: Props) {
+    let { imgName = 'booksNull.png', readInfo } = props
+    let {book_name,book_id,chapter_name,chapter_id} = readInfo
+    return <View className={styles.lastRead}>
+        <View className={styles.left}>
+            <Image src={require('@src/icon/'+imgName)} onClick={() => { this.props.store.indexStore.SET_ISLASTREAD(false) }} />
+            <Text>上次阅读{book_name}{chapter_name}</Text>
+        </View>
+        <Navigator className={styles.right} url={`/pages/book/bookArticle/index?book_id=${book_id}&chapter_id=${chapter_id || ''}`} hoverClass="none">继续</Navigator>
+    </View>
+}
+export default KeepReadPop

+ 15 - 0
src/components/PupPetry/BannerBox/index.less

@@ -0,0 +1,15 @@
+@import "../../../globaStyle.less";
+.banner_box {
+  width: 690px;
+  height: 248px;
+  background: @bg_D8;
+  border-radius: @r_20;
+  margin:26px auto 0px;
+  overflow: hidden;
+  .img{
+    width: 690rpx;
+    height: 248rpx;
+    object-fit:cover;
+    border-radius: @r_20;
+  }
+}

+ 38 - 0
src/components/PupPetry/BannerBox/index.tsx

@@ -0,0 +1,38 @@
+import { Swiper, SwiperItem, Image, Navigator } from "@tarojs/components";
+import React from "react";
+import './index.less'
+/**轮播图 */
+interface Item {
+    img_url: string,
+    action: string
+}
+interface BannerBoxProps {
+    data?: Item[]
+}
+function BannerBox(props: BannerBoxProps) {
+    const { data } = props
+    return <Swiper
+        className='banner_box'
+        indicatorColor='#999'
+        indicatorActiveColor='#333'
+        vertical={false}
+        circular
+        indicatorDots
+        autoplay
+    >
+        {
+            data?.map((item: Item, index: number) => {
+                let {action} = item
+                let arr = action?.split('/')
+                let id = arr[arr.length-1]
+                return <SwiperItem key={index}>
+                    <Navigator url={'/pages/book/bookDetails/index?book_id='+id} hoverClass="none">
+                        <Image src={item?.img_url} className='img' />
+                    </Navigator>
+                </SwiperItem>
+            })
+        }
+    </Swiper>
+}
+
+export default React.memo(BannerBox)

+ 29 - 0
src/components/PupPetry/BookBox/BookboxColumnBig.tsx

@@ -0,0 +1,29 @@
+/**分类书籍的box容器 横大*/
+
+import { Text, View, Image, Navigator } from "@tarojs/components";
+import React from 'react'
+import './index.less'
+export interface BookboxColumnBigProps {
+    book_id?: number,//书籍ID
+    book_name?: string,//名称
+    penname?: string,//作者
+    intro?: string,//描述
+    cover?: string,//图片
+    category?: string,//标签分类
+    sign?: string,//热度,阅读人数 
+    action?: string//地址
+}
+function BookboxColumnBig(props: BookboxColumnBigProps) {
+    const { cover, book_name, penname, book_id } = props
+    return <View className='book_box_column_big'>
+        <Navigator url={'/pages/book/bookDetails/index?book_id=' + book_id} hoverClass="none">
+            <Image src={cover || ''} className='img'></Image>
+            <View className='bottom'>
+                <Text className='title'>{book_name}</Text>
+                <Text className='author'>{penname}</Text>
+            </View>
+        </Navigator>
+    </View>
+}
+
+export default React.memo(BookboxColumnBig)

+ 28 - 0
src/components/PupPetry/BookBox/BookboxColumnSmall.tsx

@@ -0,0 +1,28 @@
+/**分类书籍的box容器 横大*/
+
+import { Text, View, Image, Navigator } from "@tarojs/components";
+import React from 'react'
+import './index.less'
+export interface BookboxColumnSmallProps {
+    book_id?: number,//书籍ID
+    book_name?: string,//名称
+    penname?: string,//作者
+    intro?: string,//描述
+    cover?: string,//图片
+    category?: string,//标签分类
+    sign?: string,//热度,阅读人数 
+    action?: string//地址
+}
+function BookboxColumnSmall(props: BookboxColumnSmallProps) {
+    const { cover, book_name, book_id } = props
+    return <View className='book_box_column_small'>
+        <Navigator url={'/pages/book/bookDetails/index?book_id='+ book_id} hoverClass="none">
+            <Image src={cover || ''} className='img'></Image>
+            <View className='bottom'>
+                <Text className='title'>{book_name}</Text>
+            </View>
+        </Navigator>
+    </View>
+}
+
+export default React.memo(BookboxColumnSmall)

+ 43 - 0
src/components/PupPetry/BookBox/BookboxRowBig.tsx

@@ -0,0 +1,43 @@
+/**分类书籍的box容器 横大*/
+
+import { Text, View, Image, Navigator } from "@tarojs/components";
+import React from 'react'
+import './index.less'
+export interface BookboxRowBigProps {
+    bookId: number,//书ID
+    authorName:string,//作者
+    bookName: string,//书名称
+    bookDesc:string,//描述
+    picUrl: string,//封面
+    bookStatus: number,//完本|连载状态
+    wordCount: number,//字数
+    labelInfoList:{name:string}[],//标签
+}
+function BookboxRowBig(props: BookboxRowBigProps) {
+    const { bookName, picUrl, bookDesc, labelInfoList, authorName, bookId, wordCount, bookStatus } = props
+    let describe = ""
+    if(wordCount && bookStatus) {
+        describe = `${bookName}·${(wordCount/10000).toFixed(0)}万字·${bookStatus === 0 ? '连载' : '完本'}`
+    }
+    return <View className='book_box_row_big'>
+        <Navigator url={'/pages/book/bookDetails/index?bookId=' + bookId} hoverClass="none">
+            <Image src={picUrl || ''} className='img'></Image>
+            <View className='right'>
+                <Text className='title'>{bookName}</Text>
+                <View className='content'>
+                    <View className='left'>
+                        <Text className='details'>{bookDesc.replace(/\s/ig,'')}</Text>
+                        <View className='bottom'>
+                            <Text className='describe'>{describe || authorName}</Text>
+                            {
+                                labelInfoList.length > 0 && <Text className='label'>{labelInfoList[0].name}</Text>
+                            }
+                        </View>
+                    </View>
+                </View>
+            </View>
+        </Navigator>
+    </View>
+}
+
+export default React.memo(BookboxRowBig)

+ 38 - 0
src/components/PupPetry/BookBox/BookboxRowMiddle.tsx

@@ -0,0 +1,38 @@
+/**分类书籍的box容器 横大*/
+
+import { Text, View, Image, Navigator } from "@tarojs/components";
+import React from 'react'
+import './index.less'
+export interface BookboxRowMiddleProps {
+    book_id?: number,//书籍ID
+    book_name?: string,//名称
+    penname?: string,//作者
+    intro?: string,//描述
+    cover?: string,//图片
+    category?: string,//标签分类
+    sign?: string,//热度,阅读人数 
+    action?: string//地址
+}
+function BookboxRowMiddle(props: BookboxRowMiddleProps) {
+    const { book_id, book_name, penname, intro, cover, category, sign, action, } = props
+    let tags = category ? category.split(',') : []
+    return <View className='book_box_row_middle'>
+        <Navigator url={'/pages/book/bookDetails/index?book_id='+book_id} hoverClass="none">
+            <Image src={cover || ''} className='img'></Image>
+            <View className='right'>
+                <Text className='title'>{book_name}</Text>
+                <View className='content'>
+                    <View className='left'>
+                        <Text className='details'>{intro}</Text>
+                        <View className='bottom'>
+                            <Text className='describe'>{penname}</Text>
+                            <Text className='label'>{tags[0]}</Text>
+                        </View>
+                    </View>
+                </View>
+            </View>
+        </Navigator>
+    </View>
+}
+
+export default React.memo(BookboxRowMiddle)

+ 45 - 0
src/components/PupPetry/BookBox/BookboxRowSmall.tsx

@@ -0,0 +1,45 @@
+/**书架中书籍的box容器横小 */
+
+import { Text, View, Image, Button, Navigator } from "@tarojs/components";
+import React from 'react'
+import './index.less'
+
+export interface BookboxRowSmallProps {
+    book_id: number,  // 书ID
+    book_name: string, // 书名
+    category: string, //书类型
+    cover: string, //封面
+    intro: string,
+    penname: string, // 作者
+    sign: string,  //阅读人数
+    section: string,//章节
+    chapter_name?: string,
+    chapter_id?: number
+}
+function BookboxRowSmall(props: BookboxRowSmallProps) {
+    const { book_id, book_name, category, cover, penname, chapter_name, section, chapter_id } = props
+    return <View className='book_box_row_small'>
+        <Navigator url={`/pages/book/bookArticle/index?book_id=${book_id}&chapter_id=${chapter_id || ''}`} hoverClass="none">
+            <Image src={cover} className='img'></Image>
+            <View className='right'>
+                <Text className='title'>{book_name}</Text>
+                <View className='content'>
+                    <View className='left'>
+                        <Text className='author'>{penname}</Text>
+                        <View className='bottom'>
+                            {
+                                chapter_name ? <>
+                                    {/* <Text className='time'>{time}</Text> */}
+                                    <Text className='section'>{chapter_name}</Text>
+                                </>:<Text className='null'>未读</Text>
+                            }
+                        </View>
+                    </View>
+                    <Button className='btn' plain={true}>继续阅读</Button>
+                </View>
+            </View>
+        </Navigator>
+    </View>
+}
+
+export default React.memo(BookboxRowSmall)

+ 310 - 0
src/components/PupPetry/BookBox/index.less

@@ -0,0 +1,310 @@
+@import "../../../globaStyle.less";
+.book_box_row_small {
+  margin: 30px;
+  display: block;
+  overflow: hidden;
+  .img {
+    width: 146px;
+    height: 196px;
+    background: @bg_D8;
+    border-radius: @r_10;
+    float: left;
+  }
+  .right {
+    float: left;
+    margin-left: 30px;
+    width: 514px;
+    .title {
+      width: 100%;
+      height: 50px;
+      font-size: @fs_36;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: @fw_400;
+      color: @cl_333;
+      line-height: 50px;
+      .hide_font(1,nowrap);
+    }
+    .content {
+      margin-top: 18px;
+      overflow: hidden;
+      display: flex;
+      justify-content: space-between;
+      .left {
+        flex: 1;
+        .author {
+          width: 200px;
+          height: 40px;
+          font-size: @fs_28;
+          font-family: PingFangSC-Regular, PingFang SC;
+          font-weight: @fw_400;
+          color: @cl_999;
+          line-height: 40px;
+          margin-bottom: 44px;
+          display: block;
+        }
+        .bottom {
+          display: flex;
+          .time {
+            font-size: @fs_28;
+            font-family: DIN-Regular, DIN;
+            font-weight: @fw_400;
+            margin-right: 14px;
+          }
+          .section {
+            width: calc(514px - 150px);
+            font-size: @fs_28;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: @fw_400;
+            .hide_font(1,nowrap);
+          }
+          .null {
+            width: 56px;
+            height: 40px;
+            font-size: @fs_28;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: @fw_400;
+            color: @cl_949;
+            line-height: 40px;
+          }
+        }
+      }
+      .btn {
+        margin-top: 68px;
+        width: 140px;
+        height: 60px;
+        border-radius: @r_30;
+        border: @br_2_6E;
+        font-size: @fs_24;
+        font-family: PingFangSC-Regular, PingFang SC;
+        font-weight: @fw_400;
+        color: @cl_6E9;
+        padding: 0;
+      }
+    }
+  }
+}
+.book_box_row_big {
+  margin: 0 auto;
+  display: block;
+  overflow: hidden;
+  .img {
+    width: 176px;
+    height: 236px;
+    background: @bg_D8;
+    border-radius: @r_10;
+    float: left;
+  }
+  .right {
+    float: left;
+    margin-left: 30px;
+    width: 484px;
+    .title {
+      width: 100%;
+      height: 50px;
+      font-size: @fs_36;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: @fw_500;
+      color: @cl_333;
+      line-height: 50px;
+      .hide_font(1,nowrap);
+    }
+    .content {
+      margin-top: 16px;
+      overflow: hidden;
+      .left {
+        float: left;
+        width: 100%;
+        .details {
+          width: 484px;
+          height: 120px;
+          font-size: @fs_28;
+          font-family: PingFangSC-Regular, PingFang SC;
+          font-weight: @fw_400;
+          color: @cl_999;
+          line-height: 40px;
+          margin-bottom: 16px;
+          .hide_font(3,pre-wrap);
+        }
+        .bottom {
+          display: flex;
+          justify-content: space-between;
+          .describe {
+            font-size: @fs_24;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: @fw_400;
+            height: 34px;
+            color: @cl_949;
+            line-height: 34px;
+          }
+          .label {
+            padding: 0 12px;
+            height: 32px;
+            border-radius: @r_10;
+            border: 2px solid #f4f4f4;
+            font-size: @fs_20;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: @fw_400;
+            color: #949bab;
+            line-height: 32px;
+            text-align: center;
+          }
+        }
+      }
+      .btn {
+        float: right;
+        margin-top: 68px;
+        width: 140px;
+        height: 60px;
+        border-radius: @r_30;
+        border: @br_2_6E;
+        font-size: @fs_24;
+        font-family: PingFangSC-Regular, PingFang SC;
+        font-weight: @fw_400;
+        color: @cl_6E9;
+        padding: 0;
+      }
+    }
+  }
+}
+.book_box_row_middle {
+  margin: 30px auto 0;
+  width: 690px;
+  display: block;
+  overflow: hidden;
+  .img {
+    width: 146px;
+    height: 196px;
+    background: @bg_D8;
+    border-radius: @r_10;
+    float: left;
+  }
+  .right {
+    float: left;
+    margin-left: 30px;
+    width: 514px;
+    .title {
+      width: 100%;
+      height: 50px;
+      font-size: @fs_36;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: @fw_500;
+      color: @cl_333;
+      line-height: 50px;
+      .hide_font(1,nowrap);
+    }
+    .content {
+      margin-top: 16px;
+      overflow: hidden;
+      .left {
+        float: left;
+        width: 100%;
+        .details {
+          width: 100%;
+          height: 80px;
+          font-size: @fs_28;
+          font-family: PingFangSC-Regular, PingFang SC;
+          font-weight: @fw_400;
+          color: @cl_999;
+          line-height: 40px;
+          margin-bottom: 16px;
+          .hide_font(2,pre-wrap);
+        }
+        .bottom {
+          display: flex;
+          justify-content: space-between;
+          .describe {
+            font-size: @fs_24;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: @fw_400;
+            height: 34px;
+            color: @cl_949;
+            line-height: 34px;
+          }
+          .label {
+            width: 64px;
+            height: 32px;
+            border-radius: @r_10;
+            border: 2px solid #f4f4f4;
+            font-size: @fs_20;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: @fw_400;
+            color: #949bab;
+            line-height: 32px;
+            text-align: center;
+          }
+        }
+      }
+      .btn {
+        float: right;
+        margin-top: 68px;
+        width: 140px;
+        height: 60px;
+        border-radius: @r_30;
+        border: @br_2_6E;
+        font-size: @fs_24;
+        font-family: PingFangSC-Regular, PingFang SC;
+        font-weight: @fw_400;
+        color: @cl_6E9;
+        padding: 0;
+      }
+    }
+  }
+}
+.book_box_column_small {
+  display: flex;
+  flex-flow: column;
+  width: 146px;
+  margin-top: 30px;
+  .img {
+    width: 100%;
+    height: 196px;
+    background: @bg_D8;
+    border-radius: @r_10;
+  }
+  .bottom {
+    .title {
+      width: 100%;
+      height: 80px;
+      font-size: @fs_28;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: @fw_400;
+      color: @cl_333;
+      line-height: 40px;
+      .hide_font(2,pre-wrap);
+    }
+  }
+}
+.book_box_column_big {
+  display: flex;
+  flex-flow: column;
+  width: 202px;
+  margin-top: 30px;
+  .img {
+    width: 198px;
+    height: 264px;
+    background: @bg_D8;
+    border-radius: @r_10;
+  }
+  .bottom {
+    .title {
+      width: 100%;
+      height: 40px;
+      font-size: @fs_28;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: @fw_400;
+      color: @cl_333;
+      line-height: 40px;
+      .hide_font(1,nowrap);
+    }
+    .author {
+      width: 100%;
+      height: 34px;
+      font-size: @fs_24;
+      font-family: PingFangSC-Regular, PingFang SC;
+      font-weight: @fw_400;
+      color: @cl_999;
+      line-height: 34px;
+      .hide_font(1,nowrap);
+    }
+  }
+}

+ 28 - 0
src/components/PupPetry/BookHead/index.less

@@ -0,0 +1,28 @@
+@import "../../../globaStyle.less";
+
+.Bookheader{
+    padding: 0px 30px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-top: 50px;
+    .title{
+        font-size: 40px;
+        font-family: PingFangSC-Medium, PingFang SC;
+        font-weight: 500;
+        color: @cl_333;
+    }
+    .fitTitle{
+        font-size: 28px;
+        font-family: PingFangSC-Regular, PingFang SC;
+        font-weight: 400;
+        color: @cl_949;
+        display: flex;
+        align-items: center;
+        image{
+            width: 28px;
+            height: 28px;
+        }
+    }
+    
+}

+ 36 - 0
src/components/PupPetry/BookHead/index.tsx

@@ -0,0 +1,36 @@
+import { View, Text, Image } from '@tarojs/components'
+import './index.less'
+import React from 'react'
+import Taro from '@tarojs/taro'
+
+/**
+ * 分类投
+ */
+
+type props = {
+    title: string,    // 主标题
+    fitTitle?: string, // 副标题
+    fitIsOnBack?: boolean,   // 点击父标题是否需要触发函数
+    fitOnBack?: () => void,  // 点击父标题触发事件
+    imgUrl?: string,         // 副标题的图片url
+    fontSize?: number,       // 标题字体大小
+    imgWidth?: number        // 图标宽度
+}
+
+const BookHead = (props: props) => {
+    let { title, fitTitle, fitOnBack, fitIsOnBack = true, imgUrl, fontSize, imgWidth } = props
+    return <View className="Bookheader">
+        <View className="title" style={{fontSize: fontSize ? Taro.pxTransform(fontSize) : Taro.pxTransform(40)}}>{title}</View>
+        <View className="fitTitle" onClick={fitIsOnBack ? fitOnBack : ()=>{}}>
+            {
+                fitTitle && <Text>{fitTitle}</Text>
+            }
+            {
+                imgUrl && <Image src={imgUrl} mode="widthFix" style={imgWidth ? {width: Taro.pxTransform(imgWidth)} : {}}/>
+            }
+        </View>
+        
+    </View>
+}
+
+export default React.memo(BookHead)

+ 51 - 0
src/components/PupPetry/BookTypeTabs/index.less

@@ -0,0 +1,51 @@
+@import "../../../globaStyle.less";
+.BookTypeTabs{
+    margin: 15px;
+    display: flex;
+    justify-content: flex-start;
+    flex-wrap: wrap;
+    .typeTab{
+        width: 25%;    
+        padding: 15px;
+        box-sizing: border-box;
+        &>view{
+            height: 66px;
+            background: #F4F4F4;
+            border-radius: 10px;
+            text-align: center;
+            line-height: 66px;
+            text {
+                font-size: 32px;
+                font-family: PingFangSC-Regular, PingFang SC;
+                font-weight: 400;
+                color: @cl_666;
+            }
+            &.select{ 
+                background: @bg_FF;
+                text {
+                    color: @cl_F02;
+                }
+            }
+        }
+        
+    }
+
+    .more{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-top: 11px;
+        flex: 1;
+        text {
+            font-size: 28px;
+            font-family: PingFangSC-Regular, PingFang SC;
+            font-weight: 400;
+            color: #949BAB;
+        }
+        image{
+            width: 32px;
+            height: 32px;
+            margin-left: 12px;
+        }
+    }
+}

+ 63 - 0
src/components/PupPetry/BookTypeTabs/index.tsx

@@ -0,0 +1,63 @@
+import { View, Text, Image } from '@tarojs/components'
+import './index.less'
+import React, { useCallback, useState, useEffect } from 'react'
+import bottomImg from '@src/icon/bottom.png'
+import classifyStore from '@src/store/classIfy'
+type props = {
+    typeValue: {
+        name: string,
+        id: number
+    }[],
+    typeTabIndex?: {
+        name: string,
+        id: number
+    },  // tab选中第几个  默认1
+    onCallback?: (value: string | number, isType?: boolean) => void,     // 切换回调函数
+    workDirection: string,//频道
+}
+
+
+
+const BookTypeTabs = (props: props) => {
+    let { typeValue, typeTabIndex, workDirection } = props
+    let [open, setOpen] = useState<boolean>(false)
+
+    let selectHandle = useCallback((value: {id:number,name:string}) => {
+        classifyStore.setData({ categoryId: value })
+    }, [])
+
+    let openHandle = useCallback(() => {
+        setOpen(!open)
+    }, [open])
+
+    useEffect(() => {
+        // onCallback("", true)
+    }, [workDirection])
+    return <View className="BookTypeTabs">
+        {
+            typeValue?.map((item, index) => {
+                if (!open && index < 8) {
+                    return <View className="typeTab">
+                        <View className={`${item.id === typeTabIndex?.id ? 'select' : ''}`} onClick={() => { selectHandle(item) }}>
+                            <Text>{item.name}</Text>
+                        </View>
+                    </View>
+                } else if (open) {
+                    return <View className="typeTab">
+                        <View className={`${item.id === typeTabIndex?.id ? 'select' : ''}`} onClick={() => { selectHandle(item) }}>
+                            <Text>{item.name}</Text>
+                        </View>
+                    </View>
+                }
+
+            })
+        }
+        {
+            typeValue.length > 8 && !open && <View className="more" onClick={openHandle}>
+                <Text>查看更多</Text> <Image src={bottomImg} />
+            </View>
+        }
+    </View>
+}
+
+export default React.memo(BookTypeTabs)

+ 32 - 0
src/components/PupPetry/Divider/index.module.less

@@ -0,0 +1,32 @@
+
+.divider{
+    margin: 86px 30px 48px;
+    height: 2px;
+    position: relative;
+    background: #EDEDED;
+    position: relative;
+    .text{
+        position: absolute;
+        font-size: 28px;
+        font-family: PingFangSC-Regular, PingFang SC;
+        font-weight: 400;
+        color: #949BAB;
+        background: #fff;
+        padding: 0 22px;
+    }
+    .center {
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+    }
+    .left {
+        top: 50%;
+        left: 8%;
+        transform: translateY(-50%);
+    }
+    .right {
+        top: 50%;
+        right: 8%;
+        transform: translateY(-50%);
+    }
+}

+ 26 - 0
src/components/PupPetry/Divider/index.tsx

@@ -0,0 +1,26 @@
+import { View } from '@tarojs/components'
+import styles from './index.module.less'
+import React  from 'react';
+
+/**
+ * 分割线
+ */
+
+type props = {
+    text?: string,       // 分割线上的文字
+    backColor?: string,  // 背景色
+    site?: string,       // 位置
+    lineColor?: string   // 线的颜色
+}
+
+const Divider = (props: props) => {
+    let { text, site = "center", backColor = "#FFF", lineColor = "#EDEDED" } = props
+
+    return <View className={styles.divider} style={{backgroundColor: lineColor}}>
+        {
+            text && <View className={`${styles['text']} ${styles[site]}`} style={{backgroundColor: backColor}}>{text}</View>
+        }
+    </View>
+}
+
+export default React.memo(Divider)

+ 109 - 0
src/components/PupPetry/Loading/index.module.less

@@ -0,0 +1,109 @@
+.spinner {
+    margin: 30px auto;
+    width: 40px;
+    height: 40px;
+    position: relative;
+  }
+   
+  .container1 > view, .container2 > view, .container3 > view {
+    width: 12px;
+    height: 12px;
+    background-color: #DFDFDF;
+    border-radius: 100%;
+    position: absolute;
+    -webkit-animation: bouncedelay 1.2s infinite ease-in-out;
+    animation: bouncedelay 1.2s infinite ease-in-out;
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+  }
+   
+  .spinner .spinnerContainer {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+  }
+   
+  .container2 {
+    -webkit-transform: rotateZ(45deg);
+    transform: rotateZ(45deg);
+  }
+   
+  .container3 {
+    -webkit-transform: rotateZ(90deg);
+    transform: rotateZ(90deg);
+  }
+   
+  .circle1 { top: 0; left: 0; }
+  .circle2 { top: 0; right: 0; }
+  .circle3 { right: 0; bottom: 0; }
+  .circle4 { left: 0; bottom: 0; }
+   
+  .container2 .circle1 {
+    -webkit-animation-delay: -1.1s;
+    animation-delay: -1.1s;
+  }
+   
+  .container3 .circle1 {
+    -webkit-animation-delay: -1.0s;
+    animation-delay: -1.0s;
+  }
+   
+  .container1 .circle2 {
+    -webkit-animation-delay: -0.9s;
+    animation-delay: -0.9s;
+  }
+   
+  .container2 .circle2 {
+    -webkit-animation-delay: -0.8s;
+    animation-delay: -0.8s;
+  }
+   
+  .container3 .circle2 {
+    -webkit-animation-delay: -0.7s;
+    animation-delay: -0.7s;
+  }
+   
+  .container1 .circle3 {
+    -webkit-animation-delay: -0.6s;
+    animation-delay: -0.6s;
+  }
+   
+  .container2 .circle3 {
+    -webkit-animation-delay: -0.5s;
+    animation-delay: -0.5s;
+  }
+   
+  .container3 .circle3 {
+    -webkit-animation-delay: -0.4s;
+    animation-delay: -0.4s;
+  }
+   
+  .container1 .circle4 {
+    -webkit-animation-delay: -0.3s;
+    animation-delay: -0.3s;
+  }
+   
+  .container2 .circle4 {
+    -webkit-animation-delay: -0.2s;
+    animation-delay: -0.2s;
+  }
+   
+  .container3 .circle4 {
+    -webkit-animation-delay: -0.1s;
+    animation-delay: -0.1s;
+  }
+   
+  @-webkit-keyframes bouncedelay {
+    0%, 80%, 100% { -webkit-transform: scale(0.0) }
+    40% { -webkit-transform: scale(1.0) }
+  }
+   
+  @keyframes bouncedelay {
+    0%, 80%, 100% {
+      transform: scale(0.0);
+      -webkit-transform: scale(0.0);
+    } 40% {
+      transform: scale(1.0);
+      -webkit-transform: scale(1.0);
+    }
+  }

+ 23 - 0
src/components/PupPetry/Loading/index.tsx

@@ -0,0 +1,23 @@
+import { View } from "@tarojs/components";
+import styles from './index.module.less'
+/**一个转圈圈的loding动画效果组件 */
+function UpLoading() {
+    return <View className={styles.spinner}>
+    {
+            [1, 2, 3]?.map((item, index) => {
+                return <View className={`${styles.spinnerContainer} ${styles['container'+item]}`} key={index}>
+                    <View className={styles.circle1}></View>
+                    <View className={styles.circle2}></View>
+                    <View className={styles.circle3}></View>
+                    <View className={styles.circle4}></View>
+                </View>
+            })
+        }
+    </View>
+};
+
+function DownLoading() {
+    return <View>2</View>
+};
+
+export { UpLoading, DownLoading }

+ 95 - 0
src/components/ScrollView/index.tsx

@@ -0,0 +1,95 @@
+import { ScrollView } from "@tarojs/components"
+import { Component } from "react"
+import { UpLoading } from "@src/components/PupPetry/Loading"
+interface State {
+    isShow: boolean  //是否开启刷新状态
+    loadNum: number, //加载次数
+    ref: any,
+    isBoShow: boolean, //是否开启下拉刷新状态
+    isNull: boolean,  // 是否还有数据
+}
+interface Props {
+    children: any,
+    refresh?: () => Promise<any>,//刷新
+    load?: () => Promise<any>,//加载
+}
+/**页面上下加载*/
+class ScrollViewHoc extends Component<Props> {
+    constructor(props) {
+        super(props)
+    }
+    state: State = {
+        isShow: false,
+        loadNum: 0,
+        ref: null,
+        isBoShow: false,
+        isNull: false
+    }
+    //**向上刷新 */
+    onScrollToUpper = () => {
+        console.log("向上刷新")
+        let { isShow } = this.state
+        log(isShow)
+        if (!isShow) {
+            log('下拉')
+            this.setState({ isShow: true }, async () => {
+                if (this?.props?.refresh) {
+                    await this?.props?.refresh().then(() => {
+                        setTimeout(() => { this.setState({ isShow: false }) }, 500)
+                    })
+                } else {
+                    log('请在组件中自定义refresh事件')
+                }
+            })
+        }
+    }
+    /**向下加载 */
+    onScrollToLower = () => {
+        let { isBoShow } = this.state
+        if (!isBoShow) {
+            log('上拉')
+            this.setState({ isBoShow: true }, () => {
+                if (this?.props?.load) {
+                    this?.props?.load().then(res => {
+                        this.setState({ isNull: false })
+                    }).catch(e => {
+                        this.setState({ isNull: true, isBoShow: false })
+                    })
+                    setTimeout(() => { this.setState({ isBoShow: false }) }, 500)
+                } else {
+                    log('请在组件中自定义load事件')
+                }
+            })
+        }
+    }
+    componentDidShow() {
+    }
+    ref = (ref) => {//拿到子组件的方法
+        this.setState({ ref })
+    }
+    render() {
+        const { children } = this.props
+        return <ScrollView
+            lowerThreshold={10}
+            refresherEnabled={true}
+            refresherTriggered={this.state.isShow}
+            onScrollToLower={this.onScrollToLower}
+            className='scrollview'
+            scrollY={true}
+            onRefresherRefresh={() => { this.onScrollToUpper() }}
+            refresherDefaultStyle='black'
+            scrollWithAnimation={true}
+        >
+            <>
+                {children}
+                {
+                    this.state.isBoShow && <UpLoading />
+                }
+            </>
+
+        </ScrollView>
+    }
+}
+
+
+export default ScrollViewHoc

+ 188 - 0
src/components/TopNavBar/index.less

@@ -0,0 +1,188 @@
+@import "../../globaStyle.less";
+
+.lastRead{
+    position: fixed;
+    width: 690px;
+    height: 100px;
+    left: 30px;
+    right: 30px;
+    bottom: 30px;
+    background-color: rgba(0, 0, 0, .8);
+    z-index: 1000;
+    border-radius: 20px;
+    display: flex;
+    justify-content: space-between;
+    padding: 0 10px;
+    box-sizing: border-box;
+    align-items: center;
+    &>.left{
+        display: flex;
+        justify-content: flex-start;
+        align-items: center;
+        width: 555px;
+        
+        image{
+            width: 60px;
+            height: 60px;
+        }
+        text{
+            color: rgb(212, 212, 212);
+            font-size: 26px;
+            margin-left: 10px;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis; 
+        }
+    }
+    &>.right{
+        width: 120px;
+        height: 55px;
+        text-align: center;
+        line-height: 55px;
+        color: rgb(34, 34, 34);
+        background-color: rgb(212, 212, 212);
+        border-radius: 30px;
+        font-size: 28px;
+    }
+}
+.navbarWrap{
+    display: flex;
+    justify-content: flex-start;
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 99998;
+    .content{
+        flex: 1;
+        // border: 1px solid red;
+        display: flex;
+        align-items: center;
+        padding: 0 15px;
+        .navbar{
+            display: flex;
+            justify-content: space-between;
+            width: 100%;
+            .tabs{
+                display: flex;
+                justify-content: flex-start;
+                flex: 1;
+                align-items: center;
+                .tab{
+                    font-size: 36px;
+                    font-family: PingFangSC-Medium, PingFang SC;
+                    font-weight: 500;   
+                    color: @cl_999;
+                    padding: 15px;
+                }
+                .select{              
+                    font-size: 48px;
+                    font-weight: 500;
+                    color: @cl_333;
+                    font-family: PingFangSC-Medium, PingFang SC;
+                    position: relative;
+                    view {
+                        position: absolute;
+                        bottom: 6px;
+                        left: 50%;
+                        transform: translateX(-50%);
+                        background-color: @cl_F02;
+                        width: 38px;
+                        height: 4px;
+                        border-radius: 4px;
+                    }
+                }
+            }
+            .search{
+                display: flex;
+                width: 28%;
+                align-items: center;
+                .searchImg {
+                    width: 70px;
+                    height: 70px;
+                }
+            }
+        }
+        .title{
+            margin-left: 18px;
+            display: flex;
+            justify-content: flex-start;
+            align-items: center;
+            font-weight: 500;
+            height: 100%;
+            width: 100%;
+            .back{
+                padding-right: 12px;
+                display: flex;
+                align-items: center;
+                height: 100%;
+                position: relative;
+                margin-right: 10px;
+                &>view{
+                    width: 160px;
+                    height: 55px;
+                    border: 1px solid #eeeeee;
+                    border-radius: 35px;
+                    display: flex;
+                    justify-content: space-around;
+                    align-items: center;
+                    &>view{
+                        display: flex;
+                        justify-content: center;
+                        align-items: center;
+                        &>image{
+                            width: 30px;
+                        }
+                        &.backBt{
+                            width: 50%;
+                            height: 55px;
+                            &>image{
+                                width: 35px;
+                                height: 40px;
+                            }
+                        }
+                        &.line{
+                            width: 2px;
+                            height: 28px;
+                            background-color: #000;
+                        }
+                        &.homebt {
+                            width: 50%;
+                            height: 55px;
+                            &>image{
+                                width: 36px;
+                            }
+                        }
+                    }
+
+                    // position: absolute;
+                    // width: 25px;
+                    // height: 3.6px;
+                    // background: #333;
+                    // top: 50%;
+                    // transform: translateY(-50%);
+                    // border-radius: 2px;
+                    // // margin-top: 3px;
+                    // &.top{
+                    //     transform-origin: left;
+                    //     transform: translateY(-0.5px) rotate(45deg);
+                    // }
+                    // &.bottom{
+                    //     transform-origin: left;
+                    //     transform: rotate(-45deg);
+                    // }
+                }
+            }
+            .txt{
+                flex: 1;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+                overflow: hidden;
+                font-size: 36px;
+                font-family: PingFangSC-Medium, PingFang SC;
+                font-weight: 500;
+                color: #333333;
+            }
+        }
+    }
+}

+ 158 - 0
src/components/TopNavBar/index.tsx

@@ -0,0 +1,158 @@
+
+import { View, Text, Image, Navigator } from '@tarojs/components'
+import './index.less'
+import Taro from '@tarojs/taro'
+import { Component } from 'react';
+import searchPng from '../../icon/search.png'
+import { observer, inject } from 'mobx-react'
+import { BookStore } from '@src/store/book';
+/**顶部自定义导航
+ * 
+ */
+type Props = {
+    children?: JSX.Element,             // 
+    backgroundColor?: string,           // navbar 背景色
+    color?: string                      // 头部显示内容 颜色
+    fontSize?: number,                  // 头部显示内容字体大小
+    title?: string,                     // 头部显示内容
+    tab?: boolean,                      // 是否是tab类型
+    tabVal?: {                          // tabs 内容
+        text: number | string,                   // 展示内容
+        value: number | string                   // 选中传回内容
+    }[],
+    tabSelect?: string,                   // tab默认展示第几个
+    search?: boolean,                   // 是否展示搜索按钮
+    searchBack?: boolean,            // 点击搜索触发
+    isToBack?: boolean,                 // 是否可返回上一页
+    zxTitle?: string,                   // 资讯头
+    lastRead?: boolean,                 // 是否展示上次阅读
+}
+
+
+function CustomNavbar(WrappedComponent, props: Props) {
+    let { children, title, color, fontSize, backgroundColor, tab = false, tabVal, tabSelect = "", search = false, searchBack = false, isToBack = false, zxTitle = "", lastRead = false } = props
+    log(zxTitle);
+
+    interface Page {
+        props: {
+            store: {
+                indexStore: {
+                    navBarTop: number,
+                    openType: number,
+                    isLastRead: boolean,
+                    SET_ISLASTREAD: Function,
+                    lastReadLog: any
+                },
+                bookStore: BookStore
+            },
+            extraProp: any,
+            passThroughProps: any
+        }
+    }
+    @inject('store')
+    @observer
+    class Page extends Component {
+        state = {
+            navBar: 0,
+            capsuleWidth: 0,
+            navBarTop: 0,
+
+        }
+        componentDidMount() {
+            log('计算高')
+            const { indexStore: { navBarTop } } = this.props.store
+            let buttonBounding = Taro.getMenuButtonBoundingClientRect()
+            log(buttonBounding);
+            let { height, top, width } = buttonBounding
+            this.setState({ capsuleWidth: width + 10, navBar: (top - navBarTop) * 2 + height + 8, navBarTop })
+        }
+
+        selectHandle = (value: string | number) => {
+            this.props.store.bookStore.setWorkDirection(value as 0 | 1)
+        }
+
+        gotoBack = () => {
+            Taro.navigateBack()
+        }
+        searchBackHandle = () => {
+            Taro.navigateTo({
+                url: '/pages/search/index'
+            })
+        }
+        componentDidUpdate(props) {
+            let oldNavBarTop = this.state.navBarTop
+            let newNavBarTop = this.props.store.indexStore.navBarTop
+            if (oldNavBarTop !== newNavBarTop) {
+                let buttonBounding = Taro.getMenuButtonBoundingClientRect()
+                log(buttonBounding);
+                let { height, top, width } = buttonBounding
+                this.setState({ capsuleWidth: width + 10, navBar: (top - newNavBarTop) * 2 + height + 8, navBarTop: newNavBarTop })
+            }
+            if (props.store.indexStore.openType === 1) {
+                log(123321);
+            }
+
+        }
+
+        render() {
+            const { extraProp, ...passThroughProps } = this.props
+            let { navBar, capsuleWidth } = this.state
+            const { indexStore: { navBarTop, openType, },bookStore } = this.props.store
+            let stateIndex = bookStore.workDirection
+            console.log("stateIndex",stateIndex)
+            return <View style={{ height: navBar + navBarTop + 5 }}>
+                <View className='navbarWrap' style={{ paddingTop: navBarTop, height: navBar, backgroundColor: backgroundColor ? backgroundColor : "#FFFFFF" }}>
+                    <View className='content'>
+                        {
+                            tab ? <View className="navbar">
+                                <View className="tabs">
+                                    {
+                                        tabVal?.map((item) => {
+                                            return <View className={`tab ${item.value === stateIndex ? 'select' : ''}`} onClick={() => { this.selectHandle(item.value) }}>
+                                                <Text>{item.text}</Text>
+                                                {
+                                                    item.value === stateIndex && <View></View>
+                                                }
+                                            </View>
+                                        })
+                                    }
+                                </View>
+                                {
+                                    search && <View className="search">
+                                        <Image src={searchPng} className="searchImg" onClick={searchBack ? this.searchBackHandle : () => { }} />
+                                    </View>
+                                }
+                            </View>
+                                :
+                                children ?
+                                    children :
+                                    <View className="title" style={{ color: color ? color : "#333333", fontSize: fontSize ? Taro.pxTransform(fontSize) : Taro.pxTransform(48) }}>
+                                        {
+                                            isToBack && <View className="back" >
+                                                <View style={{ backgroundColor: color === '#fff' ? '#fff' : '#fff' }}>
+                                                    <View onClick={this.gotoBack} className="backBt">
+                                                        <Image src={require('../../icon/backBlock.png')} className="searchImg" onClick={() => { }} />
+                                                    </View>
+                                                    <View className="line" style={{ backgroundColor: color === '#fff' ? '#e9e9e9' : '#e9e9e9' }}></View>
+                                                    <View onClick={() => { }} className="homebt">
+                                                        <Image src={require('../../icon/homeBlock.png')} className="searchImg" onClick={() => { }} mode='widthFix' />
+                                                    </View>
+                                                </View>
+                                            </View>
+                                        }
+                                        <View className="txt" style={{ color: color ? color : "#333333" }}>{title}</View>
+                                    </View>
+                        }
+                    </View>
+                    <View style={{ width: capsuleWidth + 'px' }}></View>
+                </View>
+                <View style={{ paddingTop: (navBar + navBarTop + 5), height: `100vh`, boxSizing: 'border-box' }}>
+                    <WrappedComponent {...passThroughProps} stateIndex={stateIndex} navBar={navBar + navBarTop + 5} />
+                </View>
+            </View>
+        }
+    }
+    return Page
+}
+
+export default CustomNavbar

+ 45 - 0
src/config.ts

@@ -0,0 +1,45 @@
+import Taro from "@tarojs/taro";
+
+globalThis.app = {
+    appName: "",//小程序名称
+    apiUrl: "https://test-distribution-api.zanxiangnet.com",//接口地址头部
+    appId: "",//当前小程序ID
+    system: "",//当前系统环境
+    showLog: true,//控制台是否打印日志
+    isLoding: false,//是否显示每次请求的加载loding弹窗
+    initToken: "",//初始化toekn用于登录时使用
+    token: "",//登录后的token
+}
+export interface App {
+    appName?: string;//小程序名称
+    apiUrl?: string;//接口地址头部
+    appId?: string;//当前小程序ID
+    system?: string,//当前系统环境
+    showLog?: boolean,//控制台是否打印日志
+    isLoding?: boolean,//是否显示每次请求的加载loding弹窗
+    initToken?: string,//初始化toekn用于登录时使用
+    token?: string,//登录后的token
+}
+
+// // 定义全局日志函数
+globalThis.log = app.showLog ? console.log : () => { };
+/**
+ * 传入参数设置全局变量(进入APP后固定不变的参数,并存入到本地)
+*/
+export function setApp(props?: App) {
+    // log("初始化设置APP关键固定参数===>", props)
+    if (props) {
+        Object.keys(props).forEach((key) => {
+            globalThis.app[key] = props[key]
+        })
+    }
+    // log("app====>",app)
+    Taro.setStorageSync("app", app)
+}
+/**
+ * 初始化app全局变量
+ * */
+export function initApp() {
+    // log("初始化app全局变量")
+    app = Taro.getStorageSync("app") || app
+}

+ 70 - 0
src/globaStyle.less

@@ -0,0 +1,70 @@
+/**字体大小**/
+@fs_20:20px;
+@fs_24:24px;
+@fs_28:28px;
+@fs_30:30px;
+@fs_32:32px;
+@fs_34:34px;
+@fs_36:36px;
+@fs_38:38px;
+@fs_40:40px;
+@fs_42:42px;
+@fs_44:44px;
+@fs_46:46px;
+@fs_48:48px;
+@fs_50:50px;
+@fs_54:54px;
+@fs_58:58px;
+/**字体粗细**/
+@fw_100:100;
+@fw_400:400;
+@fw_500:500;
+@fw_900:900;
+/**字体颜色**/
+@cl_333:#333333;
+@cl_666:#666666;
+@cl_999:#999999;
+@cl_949:#949BAB;
+@cl_6E9:#6E9DE8;
+@cl_F02:#F02C4D;
+@cl_253:#253C6F;
+@cl_C8:#C8C8C8;
+@cl_94:#949bab;
+/**背景色***/
+@bg_EC:#ECECEC;
+@bg_FF:#FFF0F3;
+@bg_E5:#E5EBFA;
+@bg_D8:#D8D8D8;
+@bg_F8:#F8F8F8;
+@bg_E9:#E9E9E9;
+@bg_F4:#F4F4F4;
+@bg_F5:#F5F6F7;
+@bg_F5E:#F5F6EE;
+@bg_D6E:#D6E4CC;
+@bg_36:#363738;
+@bg_11:#111111;
+@bg_F7:#F7F8F9;
+@bg_94:#949bab;
+/**边框色***/
+@br_2_6E: 2px solid #6E9DE8;
+/**圆角**/
+@r_10:10px;
+@r_20:20px;
+@r_30:30px;
+@r_34:34px;
+@r_26:26px;
+/**单行溢出隐藏**/
+.hide_font(@a,@b){
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-box-orient: vertical;
+    white-space:@b;
+    -webkit-line-clamp: @a;
+    display: -webkit-box;
+}
+/**行高**/
+@lh_60:60px;
+@lh_64:64px;
+@lh_72:72px;
+@lh_84:84px;
+@lh_96:96px;


BIN
src/icon/allow.jpg


BIN
src/icon/appleId.png


BIN
src/icon/avatar.jpg


BIN
src/icon/backBlock.png


BIN
src/icon/backWhile.png


BIN
src/icon/bean.png


BIN
src/icon/beanLight.png


BIN
src/icon/beanNo.png


BIN
src/icon/beanRead.png


BIN
src/icon/beanSmall.png


BIN
src/icon/bookShelfNull.png


BIN
src/icon/booksNull.png


BIN
src/icon/bottom.png




BIN
src/icon/caiyunjing.jpg


BIN
src/icon/caiyunremen.jpg


BIN
src/icon/clear.png


BIN
src/icon/clickDesktopTip.png


BIN
src/icon/clickFcTip.png


BIN
src/icon/coreBack.png


BIN
src/icon/cyrt.png


BIN
src/icon/dailyReading.png


BIN
src/icon/delete.png


BIN
src/icon/desktop.png


BIN
src/icon/fl_1.png


BIN
src/icon/fl_2.png


BIN
src/icon/floatingWin.png


BIN
src/icon/giftBox.png


BIN
src/icon/grade.png


BIN
src/icon/hdLogo.png



BIN
src/icon/homeBlock.png


BIN
src/icon/homeWhile.png



BIN
src/icon/indicate.png


BIN
src/icon/inviteFriends.png


BIN
src/icon/jtRight.png


BIN
src/icon/left.png


BIN
src/icon/loaginBt.png


BIN
src/icon/lvBack.png


BIN
src/icon/messageSub.png


BIN
src/icon/ml_b.png


BIN
src/icon/ml_h.png


BIN
src/icon/more.png


BIN
src/icon/myBack.png


BIN
src/icon/noIn.png


BIN
src/icon/noVideo.png


BIN
src/icon/noinSucc.png


BIN
src/icon/order.png


BIN
src/icon/people.png


BIN
src/icon/peopleNo.png


BIN
src/icon/phone.png


BIN
src/icon/re_order.png


BIN
src/icon/refresh.png


BIN
src/icon/right.png


BIN
src/icon/sc_1.png


BIN
src/icon/sc_2.png


BIN
src/icon/sc_b.png


BIN
src/icon/sc_h.png


Неке датотеке нису приказане због велике количине промена