VUE学习随笔

8 篇文章 0 订阅

 

 

vue.js是一套构建用户界面的渐进式框架。vue采用自底向上增量开发的设计。vue的核心库只关心视图层,非常容易学习,非常容易与其它库和已有项目整合。vue完全有能力驱动采用单文件组件和vue生态系统支持的库开发的复杂单页应用。

vue.js的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件。

 

总体框架

一个vue-cli的项目结构如下,其中src文件夹是需要掌握的,所以本文也重点讲解其中的文件,至于其他相关文件,了解一下即可。

文件结构细分

 

1.build——[webpack配置]

build文件主要是webpack的配置,主要启动文件是dev-server.js,当我们输入npm run dev首先启动的就是dev-server.js,它会去检查node及npm版本,加载配置文件,启动服务。

 

2.config——[vue项目配置]

config文件主要是项目相关配置,我们常用的就是当端口冲突时配置监听端口,打包输出路径及命名等

 

3.node_modules——[依赖包]

node_modules里面是项目依赖包,其中包括很多基础依赖,自己也可以根据需要安装其他依赖。安装方法为打开cmd,进入项目目录,输入npm install [依赖包名称],回车。

在两种情况下我们会自己去安装依赖:

(1)项目运行缺少该依赖包:例如项目加载外部css会用到的css-loader,路由跳转vue-loader等(安装方法示例:npm install css-loader)

(2)安装插件:如vux(基于WEUI的移动端组件库),vue-swiper(轮播插件

注:有时会安装指定依赖版本,需在依赖包名称后加上版本号信息,如安装11.1.4版本的vue-loader,输入npm install vue-loader@11.1.4

 

4.src——[项目核心文件] 

项目核心文件前面已经进行了简单的说明,接下来重点讲解main.js,App.vue,及router的index.js

 

4.1 index.html——[主页]

index.html如其他html一样,但一般只定义一个空的根节点,在main.js里面定义的实例将挂载在根节点下,内容都通过vue组件来填充

 

4.2 App.vue——[根组件]

一个vue页面通常由三部分组成:模板(template)、js(script)、样式(style)

【template】

其中模板只能包含一个父节点,也就是说顶层的div只能有一个(例如上图,父节点为#app的div,其没有兄弟节点)

<router-view></router-view>是子路由视图,后面的路由页面都显示在此处

打一个比喻吧,<router-view>类似于一个插槽,跳转某个路由时,该路由下的页面就插在这个插槽中渲染显示

 

【script】

vue通常用es6来写,用export default导出,其下面可以包含数据data,生命周期(mounted等),方法(methods)等,具体语法请看vue.js文档,在后面我也会通过例子来说明。

 

【style】

样式通过style标签<style></style>包裹,默认是影响全局的,如需定义作用域只在该组件下起作用,需在标签上加scoped,<style scoped></style>

如要引入外部css文件,首先需给项目安装css-loader依赖包,打开cmd,进入项目目录,输入npm install css-loader,回车。安装完成后,就可以在style标签下import所需的css文件,例如:

    <style> 

        import './assets/css/public.css'  

    </style>  

这样,我们就可以把style下的样式封装起来,写到css文件夹,再引入到页面使用,整个vue页面也看上去更简洁。

 

4.3 main.js——[入口文件]

main.js主要是引入vue框架,根组件及路由设置,并且定义vue实例,下图中的

components:{App}就是引入的根组件App.vue

后期还可以引入插件,当然首先得安装插件。

 

4.4 router——[路由配置]

router文件夹下,有一个index.js,即为路由配置文件

这里定义了路径为'/'的路由,该路由对应的页面是Hello组件,所以当我们在浏览器url访问http://localhost:8080/#/时就渲染的Hello组件

类似的,我们可以设置多个路由,‘/index’,'/list'之类的,当然首先得引入该组件,再为该组件设置路由。

 

项目加载分析

1、项目启动分析

①入口文件main.js——②入口文件中配置了相关的模板注入信息——③扫描路由。先找到公共路由,再根据注入的模板信息扫描模块中的路由。

2、发送请求获取数据执行分析

①发送请求——②根据请求路由到相关页面——③页面创建后调用钩子函数调用需要用的js方法——④js方法访问API方法——⑤根据API访问后台接口——⑥后台接口返回相关数据——⑦修改数据模型——⑧数据模型双向绑定。

 

3、路由页面分析

①公共路由与模块路由启动后被扫描。

②根据访问路径查找相关路径对应信息:

a、先找到父路径:path: '/saas-clients',//父路径

b、找到子路径:path: 'index'

c、根据子路径跳转视图地址:component: _import('saas-clients/pages/index'), //跳转的vue视图

d、找到module-saas-clients模块目录下的pages目录下的index.vue文件

e、跳转地址的主目录为saas-clients,而并非module-saas-clients是如何对应的?

根据公共路由中import_development.js、import_production.js2个文件的配置,根据vue项目规则自动加的前缀。

 

elementui自定义主题(任意颜色)换肤功能

我的项目中使用了scss,elementui官网提供了解决方案如下:

在项目中改变 SCSS 变量
 
Element 的 theme-chalk 使用 SCSS 编写,如果你的项目也使用了 SCSS,那么可以直接在项目中改变 Element 的样式变量。
1. 新建一个样式文件,例如 element-variables.scss,写入以下内容:
 
/* 改变主题色变量 */
$--color-primary: teal;
 
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
 
@import "~element-ui/packages/theme-chalk/src/index";
 
2. 之后,在项目的入口文件中,直接引入以上样式文件即可(无需引入 Element 编译好的 CSS 文件):
 
import Vue from 'vue'
import Element from 'element-ui'
import './element-variables.scss'
 
Vue.use(Element)
需要注意的是,覆盖字体路径变量是必需的,将其赋值为 Element 中 icon 图标所在的相对路径即可。

以上方法,我们修改主题色变量 $--color-primary 的值,然后在项目入口文件 main.js 中引入该样式文件,覆盖elementui的css文件,即可实现换肤。 

那么怎么做到自定义任意颜色的主题呢?大致思路如下:

  • 1.使用 el-color-picker 组件,供用户选择颜色
  • 2.监听颜色的变化,创建 style 标签,生成样式内容
  • 3.将创建好的样式内容插入 head 中

实际步骤如下:

  • 1.在项目中新建一个scss文件,比如就叫 elementui-variables.scss 

内容如下:

/* theme color */
$--color-primary: #1890ff;
$--color-success: #13ce66;
$--color-warning: #FFBA00;
$--color-danger: #ff4949;
// $--color-info: #1E1E1E;
 
$--button-font-weight: 400;
 
// $--color-text-regular: #1f2d3d;
 
$--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5;
 
$--table-border:1px solid #dfe6ec;
 
/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
 
// @import "~element-ui/packages/theme-chalk/src/index";
 
:export {
  theme: $--color-primary;
}

2.考虑到项目全局都有可能使用到颜色这个变量,所以决定用vuex,如图:新建文件 settings.js

settings.js 内容如下,theme 的初始值为 elementui-variables.scss 中定义的theme。

import variables from '@/styles/element-variables.scss'
 
const settings = {
  state: {
    theme: variables.theme,
  },
  mutations: {
    CHANGE_SETTING: (state, { key, value }) => {
      if (state.hasOwnProperty(key)) {
        state[key] = value
      }
    }
  },
  actions: {
    changeSetting({ commit }, data) {
      commit('CHANGE_SETTING', data)
    }
  }
 
}
 
export default settings
  • 3.新建一个组件(一般在components目录下,我将它命名为ThemePicker),用于监听颜色变量 theme 的改变,然后 getHandler 函数生成样式文件,插入head中。在这里我遇到了一个问题,如果使用 document.head.appendChild(styleTag) ,页面就会变成一片空白,然后我就在想有可能是样式的顺序导致一些有用的样式被覆盖了,所以我就讲它改为 document.getElementsByTagName('style')[0].insertBefore(styleTag, null) ; 完美解决问题。

ThemePicker文件内容如下,可以看到默认的颜色是 this.$store.state.settings.theme ,它是在 elementui-variables.scss 中定义的。

<template>
  <el-color-picker
    v-model="colors.primary"
    :predefine="['#409EFF', '#67C23A', '#E6A23C', '#f5222d', '#11a983', '#13c2c2', '#6959CD', '#434f5d', ]"
    class="theme-picker"
    popper-class="theme-picker-dropdown" title="换肤"
    @change="colorChange"
  />
</template>
 
<script>
import generateColors from "../../utils/color"
import objectAssign from "object-assign"
export default {
  name: "App",
  data() {
    return {
      originalStylesheetCount: -1, //记录当前已引入style数量
      originalStyle: "", //获取拿到的.css的字符串
      colors: {
        //颜色选择器默认颜色值,这个值要和element-variables一样
        primary: "#001529",
      },
      primaryColor: "", //提交成功后设置默认颜色
      cssUrl: [
        "//unpkg.com/element-ui/lib/theme-chalk/index.css",
        "./static/css/index.css",
      ],
    }
  },
  methods: {
    colorChange(e) {
      if (!e) return
      localStorage.setItem("color", e)
      this.primaryColor = this.colors.primary
      this.colors = objectAssign(
        {},
        this.colors,
        generateColors(this.colors.primary)
      )
      this.writeNewStyle()
    },
    writeNewStyle() {
      let cssText = this.originalStyle
      Object.keys(this.colors).forEach((key) => {
        cssText = cssText.replace(
          new RegExp("(:|\\s+)" + key, "g"),
          "$1" + this.colors[key]
        )
      })
      if (this.originalStylesheetCount === document.styleSheets.length) {
        // 如果之前没有插入就插入
        const style = document.createElement("style")
        style.innerText =
          ".primaryColor{background-color:" +
          this.colors.primary +
          "}" +
          cssText
        document.head.appendChild(style)
      } else {
        // 如果之前没有插入就修改
        document.head.lastChild.innerText =
          ".primaryColor{background-color:" +
          this.colors.primary +
          "} " +
          cssText
      }
    },
    getIndexStyle(url) {
      let that = this
      var request = new XMLHttpRequest()
      request.open("GET", url)
      request.onreadystatechange = function () {
        if (
          request.readyState === 4 &&
          (request.status == 200 || request.status == 304)
        ) {
          // 调用本地的如果拿不到会得到html,html是不行的
          if (request.response && !/DOCTYPE/gi.test(request.response)) {
            that.originalStyle = that.getStyleTemplate(request.response)
            that.writeNewStyle()
          } else {
            that.originalStyle = ""
          }
        } else {
          that.originalStyle = ""
        }
      }
      request.send(null)
    },
    getStyleTemplate(data) {
      const colorMap = {
        "#3a8ee6": "shade-1",
        "#409eff": "primary",
        "#53a8ff": "light-1",
        "#66b1ff": "light-2",
        "#79bbff": "light-3",
        "#8cc5ff": "light-4",
        "#a0cfff": "light-5",
        "#b3d8ff": "light-6",
        "#c6e2ff": "light-7",
        "#d9ecff": "light-8",
        "#ecf5ff": "light-9",
      }
      Object.keys(colorMap).forEach((key) => {
        const value = colorMap[key]
        data = data.replace(new RegExp(key, "ig"), value)
      })
      return data
    },
  },
  mounted() {
    // 默认从线上官方拉取最新css,2秒钟后做一个检查没有拉到就从本地在拉下
    let that = this
    // 如果是记住用户的状态就需要,在主题切换的时候记录颜色值,在下次打开的时候从新赋值
    this.colors.primary = localStorage.getItem("color") || this.colors.primary //例如
    this.getIndexStyle(this.cssUrl[0])
    setTimeout(function () {
      if (that.originalStyle) {
        return
      } else {
        that.getIndexStyle(that.cssUrl[1])
      }
    }, 2000)
    this.$nextTick(() => {
      // 获取页面一共引入了多少个style 文件
      this.originalStylesheetCount = document.styleSheets.length
    })
  },
}
</script>

<style>
.theme-picker {
  float: left;
  margin-top: 10px;
}
 
.theme-message,
.theme-picker-dropdown {
  z-index: 99999 !important;
}
 
.theme-picker .el-color-picker__trigger {
  height: 26px !important;
  width: 26px !important;
  padding: 2px;
}
 
 
 
.theme-picker-dropdown .el-color-dropdown__link-btn {
  display: none;
}
</style>

辅助文件:utils/color.js

import color from 'css-color-function'
import formula from './formula.json'

const generateColors = primary => {
  let colors = {}
  console.log("当前颜色:" , primary)
  Object.keys(formula).forEach(key => {
    const value = formula[key].replace(/primary/g, primary)
    colors[key] = color.convert(value)
  })
  return colors
}

export default generateColors

页面上有些组件的样式可能是自定义的,不会随 theme 一起变化,可这么实现:

     <router-link v-for="tag in Array.from(visitedViews)" ref="tag" :style="isActive(tag)?{
        'background-color': theme,
        'color': '#ffffff',
        'border-color': theme
        }:{}"
        :to="tag" :key="tag.path" class="tags-view-item">
        {{ tag.name }}
        <span class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)"/>
     </router-link>
computed: {
    //...,
    theme() {
      return this.$store.state.settings.theme
    }
  },

4.最后在页面上使用 ThemePicker 组件即可:

实际效果录屏:

 

 

Vue项目引入阿里图标

引入步骤

1、访问网址https://www.iconfont.cn/

2、选择好后加入购物车

如何批量添加阿里巴巴iconfont图标到购物车

在页面上右键--审查元素,切换到console 控制台 输入如下代码,其实就是触发点击事件,可以一次性选择当前页面所有的图标

var span = document.querySelectorAll('.icon-cover');
for (var i = 0, len = span.length; i < len; i++) {
     console.log(span[i].querySelector('span').click());
}

如何批量添加阿里巴巴iconfont图标到购物车-风君子博客

3、进入购物车,添加至项目

4、进入我的项目,下载到本地

5、将解压后的文件放入目标文件夹下

6、在main.js文件内全局引入 阿里云字体图标 css,记得使用 路径要正确,如果此时编译不正确,说明 引用路径存在问题

为避免出现图标方框的情况,请对font进行初始化

在reset.css 或者全局index.css中加入如下代码

// 阿里字体图标设置
.icon, .iconfont {
  font-family:"iconfont" !important;
  font-size:16px;
  font-style:normal;
  -webkit-font-smoothing: antialiased;
  -webkit-text-stroke-width: 0.2px;
  -moz-osx-font-smoothing: grayscale;
}

7、项目中使用字体图标,就可以使用了

(3)Symbol  SVG图片格式

 

①支持多色图标了,不再受单色限制。

②支持像字体那样通过font-size,color来调整样式。

③支持 ie9+

④可利用CSS实现动画。

⑤减少HTTP请求。

⑥矢量,缩放不失真

⑦可以很精细的控制SVG图标的每一部分

使用方法:
第一步:拷贝下载项目,复制inconfont.js到资源文件目录下,并引入inonfont.js文件

引入 ./iconfont.js

第二步:加入通用css代码(引入一次就行):

<style type="text/css">
    .icon {
       width: 1em; height: 1em;
       vertical-align: -0.15em;
       fill: currentColor;
       overflow: hidden;
    }
</style>

第三步:挑选相应图标并获取类名,应用于页面:

<svg class="icon" aria-hidden="true">
    <use xlink:href="#icon-xxx"></use>
</svg>

 

vue项目中引入阿里图标SVG

引入过程中出现的问题:页面没有出现图标,原因:在main.js中没有引入iconfont.js

1、阿里demo中的引入方式:

 

2、我的引入方式(参考别人的项目)

   上图中的引入方式,在iconfonts.js中注册了标签

  我的方式是在上面的基础上封装了一层,每次使用的时候可以不用写

   我的目录结构:

icons/svg中放所有的图标文件

1)index.js中代码功能,注册全局标签,并获取所有的svg文件,index.js中的代码如下:

import Vue from 'vue'
import SvgIcon from '../../components/SvgIcon'// svg component

// register globally
Vue.component('svg-icon', SvgIcon)

const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

2)SvgIcon组件中的代码,主要共功能将引入图标的代码封装到组件中,并注册为全局标签使用

<<span style="color: #f92672;">template>
<<span style="color: #f92672;">div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<<span style="color: #f92672;">svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<<span style="color: #f92672;">use :href="iconName" />

</<span style="color: #f92672;">svg>
</<span style="color: #f92672;">template>

<<span style="color: #f92672;">script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '../utils/validate'

export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
</<span style="color: #f92672;">script>

<<span style="color: #f92672;">style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}

.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</<span style="color: #f92672;">style>

3)在main.js中引入即可

import './assets/icons'
import './assets/fonts/iconfont.js'

 

store的值刷新就被覆盖解决方案

最近在用vue写pc端项目,用vuex来做全局的状态管理, 发现当刷新网页后,保存在vuex实例store里的数据会丢失。

1. 产生原因
2. 解决思路
3. 解决过程
1. 产生原因
其实很简单,因为store里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,store里面的数据就会被重新赋值。

2. 解决思路
一种是state里的数据全部是通过请求来触发action或mutation来改变

一种是将state里的数据保存一份到本地存储(localStorage、sessionStorage、cookie)中

很显然,第一种方案基本不可行,除非项目很小或者vuex存储的数据很少。而第二种可以保证刷新页面数据不丢失且易于读取。

3. 解决过程
首先得选择合适的客户端存储

localStorage是永久存储在本地,除非你主动去删除;

sessionStorage是存储到当前页面关闭为止;

cookie则根据你设置的有效时间来存储,但缺点是不能储存大数据且不易读取。

我选择的是sessionStorage,选择的原因vue是单页面应用,操作都是在一个页面跳转路由,另一个原因是sessionStorage可以保证打开页面时sessionStorage的数据为空,而如果是localStorage则会读取上一次打开页面的数据。

然后是怎么用sessionStorage来保存state里的数据。

第一种方案

由于state里的数据是响应式,所以sessionStorage存储也要跟随变化。又由于vuex规定所有state里数据必须通过mutation方法来修改,所以第一种方案就是mutation修改state的同时修改sessionStorage对应存储的属性

第二种方案

第一种方案确实可以解决问题,但这种方法很明显让人觉得怪异,都这样了,那不如直接用sessionStorage来做状态管理。

那怎么才能不用每次修改state时同时也要修改sessionStorage呢?这时我们可以换一个思路,因为我们是只有在刷新页面时才会丢失state里的数据,那有没有办法在点击页面刷新时先将state数据保存到sessionStorage,然后才真正刷新页面?

当然有,beforeunload这个事件在页面刷新时先触发的。那这个事件应该在哪里触发呢?我们总不能每个页面都监听这个事件,所以我选择放在app.vue这个入口组件中,这样就可以保证每次刷新页面都可以触发。

具体的代码如下:在入口App.vue下加入

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
  created() {
    //在页面加载时读取sessionStorage里的状态信息
    if (sessionStorage.getItem("store")) {
      this.$store.replaceState(
        Object.assign(
          {},
          this.$store.state,
          JSON.parse(sessionStorage.getItem("store"))
        )
      )
    }

    //在页面刷新时将vuex里的信息保存到sessionStorage里
    window.addEventListener("beforeunload", () => {
      sessionStorage.setItem("store", JSON.stringify(this.$store.state))
    })
  },
}
</script>

<style>
</style>

问题汇总

1、VUE问题

1.1、jsp中使用vue,加载页面显示{}的问题

描述:在jsp中vue的时候,页面加载的时候总会有{{ }}闪烁,看着太烦人了。

解决方案:

①样式中添加

[v-cloak] {
    display: none;
}

 ②vue根元素添加

<body>
    ... 其它元素 ...
    <div id="app" v-cloak></div>
 </body>

1.2、动态加载代码 点击事件不管用 解决办法

vue 中拼接代码

vue 中处理

1.3、vue使用el-dialog关闭后重置数据的最佳方法

此方法试用所有需要重置数据的场景

el-dialog打开一次之后,再次打开之前的数据不会销毁,依然存在。

我们需要在关闭后重新初始化数据。

重置表单的方法

this.$refs[formRef].resetFields();
复制代码

有些数据不是表单中的数据,也需要重置。

难道一个个的重新手动赋值吗?当然可以,就是比较麻烦。好在vue帮我们保存了一份原始数据,直接把data复制为原始数据即可

this.$data = this.$options.data();

以上就可以正常运行, 但是如果data中有表单验证相关,会导致控制台出现报错信息,如下面代码中的ruleValidate,排除即可。

data () {
  return {
    dialogVisible: false,
    submitLoading: false,
    model: {
      id: 0,
      carCard: "",
      driver: "",
      remark: "",
    },
    ruleValidate: {
      carCard: {required: true, message: "不能为空", trigger: "blur"},
    },
  };
},

重置表单数据,使用的地方特别多,我们封装为全局方法

//重置表单,formRef为表单的ref值,excludeFields为要排除重新初始化值得属性
Vue.prototype.$reset = function (formRef, ...excludeFields) {
  this.$refs[formRef].resetFields();
  let obj1 = this.$data;
  let obj2 = this.$options.data.call(this);
  if (!excludeFields || excludeFields.length === 0) {
    excludeFields = ["ruleValidate"];
  }
  for (let attrName in obj1) {
    if (excludeFields && excludeFields.includes(attrName)) {
      continue;
    }
    obj1[attrName] = obj2[attrName];
  }
};

使用方法

<template>
  <el-dialog
      v-el-drag-dialog
      :close-on-click-modal="false"
      :visible.sync="dialogVisible"
      :title="model.id === 0 ? '新增车辆' : '编辑车辆'"
      class="car-edit"
      width="450px"
      top="5vh"
      @close="$reset('form')">
    <el-form ref="form"
             :model="model"
             :rules="ruleValidate"
             class="formFillWidth"
             label-width="50px">
      <el-form-item label="车牌" prop="carCard">
        <el-input v-model="model.carCard" placeholder="请输入"/>
      </el-form-item>
      <el-form-item label="司机" prop="driver">
        <el-input v-model="model.driver" placeholder="请输入"/>
      </el-form-item>
      <el-form-item label="备注" prop="remark">
        <el-input v-model="model.remark" placeholder="请输入"/>
      </el-form-item>
    </el-form>
    <span slot="footer">
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button :loading="submitLoading" type="primary" @click="handleSubmit">保存</el-button>
    </span>
  </el-dialog>
</template>

2、GIT提交冲突解决

3、element使用问题

3.1、el-dialog相互调用是遮罩问题

A页面中调起B(dialog)页面,B(dialog)页面调起C(dialog)页面,会出现遮罩问题,如下图:

解决办法:在C页面中加入append-to-body,如下图:

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值