浏览代码

Merge branch 'v2' of https://github.com/jdf2e/nutui into v2

yangkaixuan 5 年之前
父节点
当前提交
741f1962fe

+ 2 - 2
package.json

@@ -137,7 +137,7 @@
         "vue": "^2.6.10",
         "vue-i18n": "8.1.0",
         "vue-jest": "2.6.0",
-        "vue-lazyload": "1.3.3",
+        "vue-lazyload": "^1.3.3",
         "vue-loader": "15.4.0",
         "vue-router": "^3.0.2",
         "vue-style-loader": "^4.1.2",
@@ -162,4 +162,4 @@
         "instrument": false,
         "sourceMap": false
     }
-}
+}

+ 96 - 33
scripts/mdToVue.js

@@ -6,6 +6,7 @@ let { version } = require("../package.json");
 let marked = require('marked');
 let contrast = require('./contrast');
 let rimraf = require("rimraf");
+let Chokidar = require('chokidar');
 if (!marked) {
     console.log('you need npm i marked -D!');
 }
@@ -33,6 +34,7 @@ class mdVue{
                 _that.needHandleFiles = res;
                 _that.ishasOutFile(_that.options.output).then(res=>{
                     _that.star();
+                      _that.filelisten()
                 });  
             })  
         })
@@ -59,6 +61,37 @@ class mdVue{
         
     }
     apply(){} 
+    filelisten(){
+        let _that = this;
+        let watcher = Chokidar.watch(_that.options.entry,{
+            persistent: true,
+            usePolling: true,
+        });
+        let log = console.dir.bind(console);
+        let watchAction = function({event, eventPath}){      
+            // 这里进行文件更改后的操作
+            // fileReadStar(eventPath,(res)=>{           
+            //     createdFile(param.output + res.mdName + '.vue', res.html, param.needCode)
+            // })
+            console.log(path.join(__dirname,JSON.stringify(eventPath)))
+            if(/\.md$/.test(eventPath)){
+                _that.vueDesWrite(eventPath)
+            }            
+            // "d:\workplace\nutui\docs\international.md"
+            // _that.read(eventPath).then(res=>{
+            //     _that.headHandle();
+            //     _that.markHandle();
+            //     let html = _that.marked(res);                                
+            //     _that.write({
+            //         outsrc:_that.options.output,
+            //         name:fileName + '.vue',
+            //         html:html
+            //     });
+            // }) 
+        }
+        watcher.on('change', path => watchAction({event: 'change', eventPath: path}))
+        .on('unlink', path => watchAction({event: 'remove', eventPath: path}));
+    }
     star(){  
         let _that = this;        
         hashElement(_that.options.entry, {
@@ -68,43 +101,73 @@ class mdVue{
         }).then(hash => {        
 
             nodeFilelist.read([_that.options.entry],{"ext":'md'}, res => {                  
-                res.map((item,index) =>{
-                    if(index == 30){
-                        debugger
-                    }
-                    let fileSplits = item.path.split(path.sep);
-                    let fileName = fileSplits.pop();
-                    if(_that.isDoc(fileName)){
-                            fileName = fileSplits.pop();
-                    }else{
-                        fileName = fileName.replace(/\.md/,'');
-                    }
-                    if(_that.needHandleFiles[fileName]){                   
-                        // _that.read(item.path).then(res=>{
-                        //     _that.headHandle();
-                        //     _that.markHandle();
-                        //     let html = _that.marked(res);                                
-                        //     _that.write({
-                        //         outsrc:_that.options.output,
-                        //         name:fileName + '.vue',
-                        //         html:html
-                        //     });
-                        // })
-                    }
-                    _that.read(item.path).then(res=>{
-                        _that.headHandle();
-                        _that.markHandle();
-                        let html = _that.marked(res);                                
-                        _that.write({
-                            outsrc:_that.options.output,
-                            name:fileName + '.vue',
-                            html:html
-                        });
-                    })               
+                res.map((item,index) =>{   
+                    _that.vueDesWrite(item.path)             
+                    // let fileSplits = item.path.split(path.sep);
+                    // let fileName = fileSplits.pop();
+                    // if(_that.isDoc(fileName)){
+                    //         fileName = fileSplits.pop();
+                    // }else{
+                    //     fileName = fileName.replace(/\.md/,'');
+                    // }
+                    // if(_that.needHandleFiles[fileName]){                   
+                    //     // _that.read(item.path).then(res=>{
+                    //     //     _that.headHandle();
+                    //     //     _that.markHandle();
+                    //     //     let html = _that.marked(res);                                
+                    //     //     _that.write({
+                    //     //         outsrc:_that.options.output,
+                    //     //         name:fileName + '.vue',
+                    //     //         html:html
+                    //     //     });
+                    //     // })
+                    // }
+                    // _that.read(item.path).then(res=>{
+                    //     _that.headHandle();
+                    //     _that.markHandle();
+                    //     let html = _that.marked(res);                                
+                    //     _that.write({
+                    //         outsrc:_that.options.output,
+                    //         name:fileName + '.vue',
+                    //         html:html
+                    //     });
+                    // })               
                 });
             });
         })
     }
+    vueDesWrite(getpath){
+        let _that = this;
+        let fileSplits = getpath.split(path.sep);
+        let fileName = fileSplits.pop();
+        if(_that.isDoc(fileName)){
+                fileName = fileSplits.pop();
+        }else{
+            fileName = fileName.replace(/\.md/,'');
+        }
+        if(_that.needHandleFiles[fileName]){                   
+            // _that.read(item.path).then(res=>{
+            //     _that.headHandle();
+            //     _that.markHandle();
+            //     let html = _that.marked(res);                                
+            //     _that.write({
+            //         outsrc:_that.options.output,
+            //         name:fileName + '.vue',
+            //         html:html
+            //     });
+            // })
+        }
+        _that.read(getpath).then(res=>{
+            _that.headHandle();
+            _that.markHandle();
+            let html = _that.marked(res);                                
+            _that.write({
+                outsrc:_that.options.output,
+                name:fileName + '.vue',
+                html:html
+            });
+        })
+    }
     isDoc(name){
         return (name == "doc.md") ? true : false;
     }

+ 1 - 1
sites/demo/view/index.vue

@@ -3,7 +3,7 @@
     <a href="https://github.com/jdf2e/nutui/" target="_blank" class="github-btn">Github</a>
 
     <h1 class="logo"></h1>
-    <div class="version">NutUI 2.0</div>
+    <div class="version">NutUI {{version}}</div>
     <p>一套京东风格的移动端Vue组件库</p>
     <div :class="['demo-list-box',{'unfold':foldStatus[item]}]" v-for="item in sorts" :key="item">
       <h4 @click="toggleFold(item)">

文件差异内容过多而无法显示
+ 535 - 518
src/config.json


+ 110 - 107
src/nutui.js

@@ -1,6 +1,6 @@
-import {version} from '../package.json';
-import {packages as pkgList} from './config.json';
-import {locale} from './locales';
+import { version } from '../package.json';
+import { packages as pkgList } from './config.json';
+import { locale } from './locales';
 import Cell from './packages/cell/index.js';
 import './packages/cell/cell.scss';
 import Dialog from './packages/dialog/index.js';
@@ -99,58 +99,61 @@ import LeftSlip from './packages/leftslip/index.js';
 import './packages/leftslip/leftslip.scss';
 import TabSelect from "./packages/tabselect/index.js";
 import "./packages/tabselect/tabselect.scss";
-
 import './packages/popup/popup.scss';
+import LuckDraw from "./packages/luckdraw/index.js";
+import "./packages/luckdraw/luckdraw.scss";
+
 const packages = {
-    Cell,
-    Dialog,
-    Icon,
-    Toast,
-    ActionSheet,
-    Tab,
-    TabPanel,
-    TabBar,
-    Calendar,
-    DatePicker,
-    NavBar,
-    NoticeBar,
-    Switch,
-    Slider,
-    Range,
-    Picker,
-    Progress,
-    Price,
-    Flex,
-    Col,
-    Row,
-    Steps,
-    Button,
-    Badge,
-    Rate,
-    Swiper,
-    Menu,
-    Stepper,
-    ButtonGroup,
-    SearchBar,
-    ImagePicker,
-    Radio,
-    RadioGroup,
-    CheckBox,
-    CheckBoxGroup,
-    ShortPassword,
-    Skeleton,
-    Scroller,
-    BackTop,
-    CountDown,
-    InfiniteLoading,
-    Uploader,
-    TextInput,
-    TextBox,
-    Avatar,
-    Elevator,
-    Popup,
-    LeftSlip,
-    TabSelect: TabSelect
+  Cell,
+  Dialog,
+  Icon,
+  Toast,
+  ActionSheet,
+  Tab,
+  TabPanel,
+  TabBar,
+  Calendar,
+  DatePicker,
+  NavBar,
+  NoticeBar,
+  Switch,
+  Slider,
+  Range,
+  Picker,
+  Progress,
+  Price,
+  Flex,
+  Col,
+  Row,
+  Steps,
+  Button,
+  Badge,
+  Rate,
+  Swiper,
+  Menu,
+  Stepper,
+  ButtonGroup,
+  SearchBar,
+  ImagePicker,
+  Radio,
+  RadioGroup,
+  CheckBox,
+  CheckBoxGroup,
+  ShortPassword,
+  Skeleton,
+  Scroller,
+  BackTop,
+  CountDown,
+  InfiniteLoading,
+  Uploader,
+  TextInput,
+  TextBox,
+  Avatar,
+  Elevator,
+  Popup,
+  LeftSlip,
+  TabSelect: TabSelect,
+  LuckDraw: LuckDraw
 };
 
 const components = {};
@@ -158,79 +161,79 @@ const methods = {};
 const filters = {};
 const directives = {};
 pkgList.map(item => {
-    const pkg = packages[item.name];
-    if (!pkg) return;
+  const pkg = packages[item.name];
+  if (!pkg) return;
 
-    if (item.type == 'component') {
-        if (pkg.name) {
-            components[pkg.name] = pkg;
-        } else {
-            for (let n in pkg) {
-                components[n] = pkg[n];
-            }
-        }
-    } else if (item.type == 'method') {
-        methods[item.name] = pkg;
-    } else if (item.type == 'filter') {
-        filters[item.name] = pkg;
-    } else if (item.type == 'directive') {
-        directives[item.name] = pkg;
+  if (item.type == 'component') {
+    if (pkg.name) {
+      components[pkg.name] = pkg;
+    } else {
+      for (let n in pkg) {
+        components[n] = pkg[n];
+      }
     }
+  } else if (item.type == 'method') {
+    methods[item.name] = pkg;
+  } else if (item.type == 'filter') {
+    filters[item.name] = pkg;
+  } else if (item.type == 'directive') {
+    directives[item.name] = pkg;
+  }
 });
 
-const install = function(Vue, opts = {}) {
-    if (install.installed) return;
+const install = function (Vue, opts = {}) {
+  if (install.installed) return;
 
-    if (opts.locale) {
-        Vue.config.lang = opts.locale;
-    }
+  if (opts.locale) {
+    Vue.config.lang = opts.locale;
+  }
 
-    if (opts.lang) locale(Vue.config.lang, opts.lang);
+  if (opts.lang) locale(Vue.config.lang, opts.lang);
 
-    for (let cptName in methods) {
-        if (Array.isArray(methods[cptName])) {
-            Vue.prototype['$' + cptName.toLowerCase()] = methods[cptName][0];
-            Vue.component(methods[cptName][1].name, methods[cptName][1]);
-        } else {
-            Vue.prototype['$' + cptName.toLowerCase()] = methods[cptName];
-        }
+  for (let cptName in methods) {
+    if (Array.isArray(methods[cptName])) {
+      Vue.prototype['$' + cptName.toLowerCase()] = methods[cptName][0];
+      Vue.component(methods[cptName][1].name, methods[cptName][1]);
+    } else {
+      Vue.prototype['$' + cptName.toLowerCase()] = methods[cptName];
     }
+  }
 
-    for (let cptName in components) {
-        if (components[cptName] && components[cptName].name) {
-            Vue.component(components[cptName].name, components[cptName]);
-        }
+  for (let cptName in components) {
+    if (components[cptName] && components[cptName].name) {
+      Vue.component(components[cptName].name, components[cptName]);
     }
+  }
 
-    for (let cptName in filters) {
-        if (filters[cptName] && filters[cptName].name) {
-            Vue.filter(cptName, filters[cptName]);
-        }
+  for (let cptName in filters) {
+    if (filters[cptName] && filters[cptName].name) {
+      Vue.filter(cptName, filters[cptName]);
     }
+  }
 
-    for (let cptName in directives) {
-        if (directives[cptName] && directives[cptName].name) {
-            Vue.directive(directives[cptName].name, directives[cptName]);
-        }
+  for (let cptName in directives) {
+    if (directives[cptName] && directives[cptName].name) {
+      Vue.directive(directives[cptName].name, directives[cptName]);
     }
+  }
 
-    Vue.use(Lazyload, {
-        lazyComponent: true,
-        loading: '//img12.360buyimg.com/imagetools/jfs/t1/73967/28/14561/916/5dc142e4E0666555b/bf33454553c6035e.png'
-    });
+  Vue.use(Lazyload, {
+    lazyComponent: true,
+    loading: '//img12.360buyimg.com/imagetools/jfs/t1/73967/28/14561/916/5dc142e4E0666555b/bf33454553c6035e.png'
+  });
 };
 
 if (typeof window !== 'undefined' && window.Vue) {
-    install(window.Vue);
+  install(window.Vue);
 }
 
 export default {
-    version,
-    locale,
-    install,
-    Lazyload,
-    ...components,
-    ...filters,
-    ...directives,
-    ...methods
+  version,
+  locale,
+  install,
+  Lazyload,
+  ...components,
+  ...filters,
+  ...directives,
+  ...methods
 };

+ 1 - 1
src/packages/cell/cell.scss

@@ -2,7 +2,7 @@
     display: block;
     padding: 0 10px;
     text-decoration: none;
-    -webkit-tap-highlight-color: transparent;
+    -webkit-tap-highlight-color: rgba(0,0,0,0);
     outline: none;
     &.nut-cell-link:active {
         background-color: $light-color !important;

+ 39 - 8
src/packages/cell/cell.vue

@@ -1,18 +1,29 @@
 <template>
-    <a :class="['nut-cell',{'nut-cell-link':isLink}]" :href="linkUrl||'javascript:;'" :style="{'background-color':bgColor}">
-        <div class="nut-cell-box">
+    <a :class="['nut-cell',{'nut-cell-link':isLink}]" 
+    :href="linkUrl" 
+    :style="{'background-color':bgColor}"
+    :target="target"
+    @click="jumpPage">
+        <div class="nut-cell-box" @click="clickCell">
+            <slot name='avatar'></slot>
             <div class="nut-cell-left">
                 <span class="nut-cell-title"><slot name="title">{{title}}</slot></span>
                 <span class="nut-cell-sub-title"><slot name="sub-title">{{subTitle}}</slot></span>
             </div>
             <div class="nut-cell-right">
                 <span class="nut-cell-desc"><slot name="desc">{{desc}}</slot></span>
-                <span class="nut-cell-icon"><slot name="icon" v-if="showIcon"><img src="data:image/svg+xml,%3Csvg viewBox='0 0 5 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.215 9.757l3.577-4.17a.931.931 0 0 0 0-1.173L1.215.244a.642.642 0 0 0-1.007 0 .929.929 0 0 0 0 1.172L3.283 5 .208 8.584a.93.93 0 0 0 0 1.173.643.643 0 0 0 1.007 0z' fill='rgb(200,200,205)'/%3E%3C/svg%3E" alt=""></slot></span>
+                <span class="nut-cell-icon">
+                <slot name="icon" v-if="showIcon">
+                  <nut-icon type="right" size="15px" color="#848484"></nut-icon>
+                </slot>
+                </span>
             </div>
         </div>
     </a>
 </template>
 <script>
+import Icon from '../icon/icon.vue';
+
 export default {
   name: "nut-cell",
   props: {
@@ -34,20 +45,40 @@ export default {
     },
     linkUrl: {
       type: String,
-      default: null
+      default: "javascript:void(0)"
     },
     showIcon: {
       type: Boolean,
       default: false
     },
-    bgColor: {
-      type: String,
-      default: "#FFFFFF"
+    bgColor:{
+      type:String,
+      default:"#fff"
+    },
+    to:{
+      type:String,
+      default:""
+    },
+    target:{
+      type:String,
+      default:"_self"
     }
   },
+  components:{
+    'nut-icon':Icon
+  },
   data() {
     return {};
   },
-  methods: {}
+  methods:{
+    clickCell(){
+      this.$emit('click-cell')
+    },
+    jumpPage(){
+      if(!this.to) return false;
+      this.$router.push(this.to)
+    }
+  }
+
 };
 </script>

+ 20 - 8
src/packages/cell/demo.vue

@@ -2,23 +2,24 @@
     <div>
         <h4>基本用法</h4>
         <div>
-            <nut-cell title="我是标题" desc="描述文字">
+            <nut-cell title="我是标题" desc="描述文字" @click-cell="clickEvnt" to="/index">
             </nut-cell>
-            <nut-cell :showIcon="true" title="我是标题" subTitle="我是副标题" desc="展示默认ICON">
-            </nut-cell>
-            <nut-cell :isLink="true" linkUrl="//m.jd.com" :showIcon="true" title="带链接">
+            <nut-cell :is-link="true" link-url="//m.jd.com" :show-icon="true" title="带链接" target="_target">
             </nut-cell>
         </div>
         <h4>通过Slot插槽分发内容</h4>
         <div>
-            <nut-cell :isLink="true" :showIcon="true">
+            <nut-cell :is-link="true" :show-icon="true">
                 <span slot="title">我是主标题</span>
                 <span slot="sub-title">我是副标,我们都是通过Slot分发的</span>
                 <span slot="desc">我是描述</span>
             </nut-cell>
-            <nut-cell :showIcon="true">
+            <nut-cell :show-icon="true">
                 <span slot="title">通过Slot自定义右侧ICON</span>
-                <img slot="icon" src="data:image/svg+xml, %3Csvg version='1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M16 3.2c0-.7-.5-1.2-1.2-1.2-.3 0-.7.1-.9.4-2.4 2.5-6.2 6.8-7.7 8.5-.1.1-.2.2-.4 0L2.1 7.2c-.3-.3-.6-.4-.9-.4-.7 0-1.2.5-1.2 1.1 0 .3.1.6.3.8l.1.1 4.9 4.9c.8.8 1.6 0 2-.5 4.1-4.4 7.9-8.6 8.4-9.2.2-.3.3-.5.3-.8z' fill='rgb(115,115,131)'/%3E%3C/svg%3E" alt="">
+                <nut-icon type="tick" slot="icon" size="15px" color="#848484"></nut-icon>
+            </nut-cell>
+            <nut-cell :show-icon="true" title="我是标题" sub-title="我是副标题" desc="展示默认ICON">
+              <div slot="avatar"><nut-avatar></nut-avatar></div>
             </nut-cell>
         </div>
     </div>
@@ -27,13 +28,24 @@
 <script>
 import locale from '../../mixins/locale';
 import {locale as i18n} from '../../locales';
+import Icon from '../icon/icon.vue';
+import Avatar from '../avatar/avatar.vue';
+
 
 export default {
   mixins: [locale],
+  components:{
+    'nut-icon':Icon,
+    'nut-avatar':Avatar
+  },
   data() {
     return {};
   },
-  methods: {},
+  methods: {
+    clickEvnt(){
+      console.log('click cell');
+    }
+  },
   mounted() {
         
   }

+ 39 - 26
src/packages/cell/doc.md

@@ -4,36 +4,40 @@
 
 ## 基本用法
 
+**to**有值的时候,跳转路由,**click-cell**点击cell触发事件
+
 ```html
-<nut-cell 
-  title = "左侧主标题" 
-  subTitle = "左侧副标题"
-  desc = "右侧描述文字"
-  :showIcon = "true"
->
+<nut-cell title="我是标题" desc="描述文字" @click-cell="clickEvnt" to="/index">
 </nut-cell>
 ```
 
-**isLink**值为true时有点击状态。
+设置**link-url**,点击可跳转。设置**target**,可以配置是否打开新的页面
+
+```html
+<nut-cell :is-link="true" link-url="//m.jd.com" :show-icon="true" title="带链接" target="_target">
+</nut-cell>
+```
+可以通过设置slot,设置cell的具体内容
 
 ```html
-<nut-cell 
-  :isLink = "true"
-  title = "左侧主标题"
-  subTitle = "左侧副标题"
-  desc="右侧描述文字">
+<nut-cell :is-link="true" :show-icon="true">
+    <span slot="title">我是主标题</span>
+    <span slot="sub-title">我是副标,我们都是通过Slot分发的</span>
+    <span slot="desc">我是描述</span>
 </nut-cell>
 ```
 
-设置**linkUrl**,点击可跳转。
+```html
+<nut-cell :show-icon="true">
+    <span slot="title">通过Slot自定义右侧ICON</span>
+    <nut-icon type="tick" slot="icon" size="15px" color="#848484"></nut-icon>
+</nut-cell>
+```
+**slot为avatar**可以添加左侧头像,具体组件配置请参考avatar组件
 
 ```html
-<nut-cell 
-  :isLink = "true"
-  linkUrl = http://m.jd.com"
-  title = "左侧主标题" 
-  subTitle = "左侧副标题"
-  desc="右侧描述文字">
+<nut-cell :show-icon="true" title="我是标题" sub-title="我是副标题" desc="展示默认ICON">
+  <div slot="avatar"><nut-avatar></nut-avatar></div>
 </nut-cell>
 ```
 
@@ -41,10 +45,19 @@
 
 | 字段 | 说明 | 类型 | 默认值
 |----- | ----- | ----- | ----- 
-| title | 左侧主slot | String | -
-| subTitle | 左侧副slot | String | -
-| desc | 右侧slot | String | -
-| isLink | 是否是链接 | Boolean | false
-| linkUrl | 链接Url | String | -
-| showIcon | 是否展示右侧箭头Icon | Boolean | false
-| bgColor | 背景颜色 | String | "#FFFFFF"
+| title | 左侧主标题 | String | -
+| sub-title | 左侧副标题 | String | -
+| desc | 右侧部分内容 | String | -
+| is-link | 是否是链接 | Boolean | false
+| link-url | 链接Url | String | -
+| show-icon | 是否展示右侧箭头Icon | Boolean | false
+| bg-color | 背景颜色 | String | "#FFFFFF"
+| to      |路由路径| String |-|
+| target |同`<a>`中target属性|String|_self|
+
+
+
+## Event
+|字段|说明|回调参数|
+|--|--|--|
+|click-cell|点击cell区域触发事件回调函数|无|

+ 157 - 160
src/packages/checkboxgroup/checkboxgroup.vue

@@ -1,184 +1,181 @@
 <template>
-  <div :class="['nut-checkboxgroup', { vertical: vertical }, customClass]">
-    <div
-      class="checkbox-item"
-      v-for="(item, index) in initData"
-      :key="item[keys.id]"
-    >
-      <nut-checkbox
-        :name="name || item[keys.name]"
-        :disabled="disabled || item[keys.disabled]"
-        :label="item[keys.label]"
-        :animation="animation"
-        :size="item.size ? item.size : size"
-        :id="item[keys.id]"
-        :checked.sync="item.checked"
-        v-model="checkboxValues[index]"
-        @change="changeEvt(arguments, item)"
-        >{{ item[keys.label] || item[keys.value] || item }}
-      </nut-checkbox>
+    <div :class="['nut-checkboxgroup',{'vertical':vertical},customClass]" >
+        <div class="checkbox-item" v-for="(item,index) in checkBoxData" :key="item[keys.id]">
+            <nut-checkbox 
+            :name="name||item[keys.name]" 
+            :disabled=" disabled || item[keys.disabled]" 
+            :label="item[keys.label]"
+            :animation="animation"
+            :size="item.size ? item.size : size"
+            
+            :id="item[keys.id]"
+            :checked.sync="item.checked"
+            
+            
+            v-model="checkboxValues[index]"
+            @change="changeEvt(arguments,item)"
+
+            >{{ item[keys.label] || item[keys.value] || item}}
+            </nut-checkbox>
+        </div>
     </div>
-  </div>
 </template>
 <script>
 import nutcheckbox from "../checkbox/checkbox.vue";
-import "../checkbox/checkbox.scss";
+import "../checkbox/checkbox.scss";	
 export default {
-  name: "nut-checkboxgroup",
-  props: {
-    name: String,
-    checkBoxData: {
-      type: Array,
-      required: true
-    },
+    name:'nut-checkboxgroup',
+    props: {
+        name: String,
+    	checkBoxData:{
+    		type:Array,
+            required:true,
+            
+    	},
 
-    value: {
-      type: Array,
-      required: true
+        value: {
+            type: Array,
+            required: true
+        },
+        keys: {
+            type: Object,
+            default() {
+                return {
+                    id: 'id',
+                    name: 'name',
+                    class: 'class',
+                    label: 'label',
+                    value: 'value',
+                    disabled: 'disabled'
+                };
+            }
+        },
+        customClass:{
+            type: String,
+            default: ''
+        },
+        label:{
+            type: [String, Number, Boolean],
+            default: ''
+        },
+        size: {
+            type: [String, Number],
+            default: 'base'
+        },
+        disabled: {
+            type: Boolean,
+            default: false
+        },
+        animation: {
+            type: Boolean,
+            default: true
+        },
+        vertical:{
+            type: Boolean,
+            default: false
+        }
     },
-    keys: {
-      type: Object,
-      default() {
+    data() {
         return {
-          id: "id",
-          name: "name",
-          class: "class",
-          label: "label",
-          value: "value",
-          disabled: "disabled"
+            ignoreChange: false,
+        	checkboxValues: [],
+            initialValue: []
         };
-      }
-    },
-    customClass: {
-      type: String,
-      default: ""
     },
-    label: {
-      type: [String, Number, Boolean],
-      default: ""
+    components: {
+	    [nutcheckbox.name]: nutcheckbox
     },
-    size: {
-      type: [String, Number],
-      default: "base"
-    },
-    disabled: {
-      type: Boolean,
-      default: false
-    },
-    animation: {
-      type: Boolean,
-      default: true
-    },
-    vertical: {
-      type: Boolean,
-      default: false
-    }
-  },
-  data() {
-    return {
-      ignoreChange: false,
-      initData: [],
-      checkboxValues: [],
-      initialValue: []
-    };
-  },
-  components: {
-    [nutcheckbox.name]: nutcheckbox
-  },
-  watch: {
-    value() {
-      this.init();
-    }
-  },
-  mounted() {
-    this.init();
-  },
-  methods: {
-    init() {
-      this.initialValue = this.value;
-      this.checkBoxData.map(item => {
-        if (typeof item === "object") {
-          item.checked = this.isOptionCheckedByDefault(item);
+    watch:{
+        value(){
+            this.init();
         }
-      });
     },
-    isObject(obj) {
-      return obj !== null && typeof obj === "object";
+    mounted(){
+        this.init()
     },
-    looseIndexOf(arr, val) {
-      for (let i = 0; i < arr.length; i++) {
-        if (this.looseEqual(arr[i], val)) {
-          return i;
-        }
-      }
+    methods: {
+        init(){
+            this.initialValue = this.value;
+            this.checkBoxData.map(item=>{
+                if(typeof item ==="object"){
+                    item.checked = this.isOptionCheckedByDefault(item)
+                }
+            })
+        },
+        isObject(obj) {
+            return obj !== null && typeof obj === 'object';
+        },
+        looseIndexOf(arr, val) {
+            for (let i = 0; i < arr.length; i++) {
+                if (this.looseEqual(arr[i], val)) {
+                    return i;
+                }
+            }
 
-      return -1;
-    },
-    isOptionCheckedByDefault(item) {
-      return (
-        this.looseIndexOf(this.initialValue, item[this.keys.value] || item) > -1
-      );
-    },
-    looseEqual(a, b) {
-      return (
-        a == b ||
-        (this.isObject(a) && this.isObject(b)
-          ? JSON.stringify(a) === JSON.stringify(b)
-          : false)
-      );
-    },
-    changeEvt(args, item) {
-      if (this.ignoreChange) {
-        return;
-      }
+            return -1;
+        },
+        isOptionCheckedByDefault(item) {
+            return this.looseIndexOf(this.initialValue, item[this.keys.value] || item) > -1;
+        },
+        looseEqual(a, b) {
+            return a == b || (
+               this.isObject(a) && this.isObject(b) ? JSON.stringify(a) === JSON.stringify(b) : false
+            );
+        },
+        changeEvt(args,item) {
+            if (this.ignoreChange) {
+                return;
+            }
 
-      const checked = args[0];
-      const label = args[1];
-      const e = args[2];
+            const checked = args[0];
+            const label = args[1];
+            const e = args[2];
 
-      let value = [];
-      const itemValue = item[this.keys.value] || item;
-      const i = this.looseIndexOf(this.value, itemValue);
+            let value = [];
+            const itemValue = item[this.keys.value] || item;
+            const i = this.looseIndexOf(this.value, itemValue);
 
-      if (checked && i < 0) {
-        value = this.value.concat(itemValue);
-      }
+            if (checked && i < 0) {
+                value = this.value.concat(itemValue);
+            }
 
-      if (!checked && i > -1) {
-        value = this.value.slice(0, i).concat(this.value.slice(i + 1));
-      }
+            if (!checked && i > -1) {
+                value = this.value.slice(0, i).concat(this.value.slice(i + 1));
+            }
 
-      this.$emit("input", value);
-      this.$emit("change", value, label, e);
-    },
-    toggleAll(checked) {
-      if (checked === false) {
-        this.$emit("input", []);
-        return;
-      }
-      if (checked === true) {
-        this.checkBoxData.map(item => {
-          item.checked = true;
-        });
-      }
-      if (!checked) {
-        this.checkBoxData.map(item => {
-          item.checked = !item.checked;
-        });
-      }
+            this.$emit('input', value);
+            this.$emit('change', value,label, e);
+
+           
+        },
+        toggleAll(checked) {
+            if (checked === false) {
+                this.$emit("input", []);
+                return;
+            }
+            if(checked === true){
+                this.checkBoxData.map(item => {
+                    item.checked = true;
+                });
+            }
+            if (!checked) {
+                this.checkBoxData.map(item => {
+                    item.checked = !item.checked;
+                });
+            }
 
-      let value = [],
-        label = [];
-      let resData = this.checkBoxData.filter(item => {
-        if (item.checked) {
-          value.push(item.value);
-          label.push(item.label);
+            let value = [],
+                label = [];
+            let resData = this.checkBoxData.filter(item => {
+                if (item.checked) {
+                value.push(item.value);
+                label.push(item.label);
+                }
+                return item.checked;
+            });
+            this.$emit("input",value);
+            this.$emit("change", value, label, null);
         }
-        return item.checked;
-      });
-      this.$emit("input", value);
-      this.$emit("change", value, label, null);
     }
-  }
-};
-</script>
+}
+</script>

+ 196 - 0
src/packages/luckdraw/demo.vue

@@ -0,0 +1,196 @@
+<template>
+  <div class="luckDrawBox">
+    <h4>基本用法</h4>
+    <div>
+      <nut-cell>
+        <span slot="title">剩余抽奖次数:{{ num }}</span>
+      </nut-cell>
+    </div>
+    <nut-luckdraw
+      class="drawTable"
+      ref="luckDrawPrize"
+      :luckWidth="luckWidth"
+      :luckheight="luckheight"
+      :prizeList="prizeList"
+      :turnsNumber="turnsNumber"
+      :turnsTime="turnsTime"
+      :prizeIndex="prizeIndex"
+      :styleOpt="styleOpt"
+      @endTurns="endTurns"
+    >
+      <template slot="item" slot-scope="scope">
+        <div class="drawTable-name">{{ scope.item.prizeName }}</div>
+        <div class="drawTable-img">
+          <img :src="scope.item.prizeImg">
+        </div>
+      </template>
+    </nut-luckdraw>
+    <div @click="startTurns" class="pointer" :style="pointerStyle"></div>
+  </div>
+</template>
+
+<script>
+  export default {
+    data() {
+      return {
+        // 转盘大小
+        luckWidth: '300px',
+        luckheight: '300px',
+        // 转盘指针图片样式
+        pointerStyle: {
+          width: '80px',
+          height: '80px',
+          backgroundImage: 'url("https://img11.360buyimg.com/imagetools/jfs/t1/106989/15/11126/137350/5e265414E8ee514bc/3456bd0d3a0454da.png")',
+          backgroundSize: 'contain',
+          backgroundRepeat: 'no-repeat',
+        },
+        // 转盘上要展示的奖品数据
+        prizeList: [
+          {
+            id: 'xiaomi',
+            prizeName: '小米手机',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/96788/40/337/73706/5dabd0e2E1f166028/7120ca2b421cb0a0.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'blue',
+            prizeName: '蓝牙耳机',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/65070/13/4325/183551/5d26e23fE09ab2010/a94eaff8242e6c63.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'apple',
+            prizeName: 'apple watch',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/105083/3/4010/126031/5de4aa51E1c7fefc6/0288f4cf3016e061.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'fruit',
+            prizeName: '迪士尼苹果',
+            prizeImg: '//m.360buyimg.com/mobilecms/s750x750_jfs/t1/47486/35/13399/356858/5da3cde2E9b3ec40f/3b3a56d54d5db565.jpg!q80.dpg.webp',
+          },
+          {
+            id: 'fish',
+            prizeName: '海鲜套餐',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/109529/24/1330/283533/5dfc836fE33d8ce6b/372adb638802710a.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'thanks',
+            prizeName: '谢谢参与',
+            prizeImg: 'https://img11.360buyimg.com/imagetools/jfs/t1/104502/28/10892/5123/5e265414Ec167392c/2831c6155895f33d.png',
+          }
+        ],
+        // 转动圈数
+        turnsNumber: 5,
+        // 转动需要持续的时间(秒)
+        turnsTime: 5,
+        // 转盘样式的选项
+        styleOpt: {
+          // 转盘中每一块扇形的背景色,根据奖品的index来取每一块的对应颜色
+          prizeBgColors: ['rgb(255, 231, 149)','rgb(255, 247, 223)','rgb(255, 231, 149)','rgb(255, 247, 223)','rgb(255, 231, 149)','rgb(255, 247, 223)'],
+          // 每一个扇形的外边框颜色
+          borderColor: '#ff9800',
+        },
+        // 中奖的奖品的index(此数据可根据后台返回的值重新赋值)
+        prizeIndex: -1,
+        // 用来锁定转盘,避免同时多次点击转动
+        lock: false,
+        // 剩余抽奖次数
+        num: 5,
+      }
+    },
+    methods: {
+      startTurns() {
+        // 如果还不可以转动
+        if (!this.canBeRotated()) {
+          return false;
+        }
+        // 开始转动
+        // 先上锁
+        this.lock = true;
+        // 设置在哪里停下,应该与后台交互,这里随机抽取0~5 ,这里应该是后台返回的中奖信息,现在是测试
+        const index = Math.floor(Math.random() * this.prizeList.length);
+        // 成功后次数减少一次
+        this.num--;
+        this.prizeIndex = index;
+        // 告诉子组件,开始转动了
+        this.$refs.luckDrawPrize.rotate(index);
+      },
+      // 已经转动完转盘触发的函数
+      endTurns() {
+        // 提示中奖
+        this.$dialog({
+          content: `恭喜中奖!!!${this.prizeList[this.prizeIndex].prizeName}`,
+          noCloseBtn: false,
+          noOkBtn: true,
+          cancelBtnTxt: "我知道了"
+        });
+        // 解锁
+        this.lock = false;
+      },
+      // 判断是否可以转动
+      canBeRotated() {
+        if (this.num <= 0) {
+          alert('已经没有次数了,继续加油赚积分吧!');
+          this.$dialog({
+            content: `已经没有次数了,继续加油赚积分吧!`,
+            noCloseBtn: false,
+            noOkBtn: true,
+            cancelBtnTxt: "我知道了"
+          });
+          return false;
+        }
+        if (this.lock) {
+          return false;
+        }
+        return true;
+      },
+    }
+  }
+</script>
+
+<style lang="scss">
+.luckDrawBox {
+  h3 {
+    width: 100%;
+    text-align: center;
+    font-size: 24px;
+  }
+  // .drawTable {
+  //   position: absolute;
+  //   left: 50%;
+  //   top: 50%;
+  //   transform: translate(-50%, -50%);
+  // }
+  // .drawTable-name {
+  //   position: absolute;
+  //   left: 10px;
+  //   top: 20px;
+  //   width: calc(100% - 20px);
+  //   font-size: 12px;
+  //   text-align: center;
+  //   color: #ff5722;
+  // }
+  // .drawTable-img {
+  //   position: absolute;
+  //   /*要居中就要50% - 宽度 / 2*/
+  //   left: calc(50% - 30px / 2);
+  //   top: 60px;
+  //   width: 30px;
+  //   height: 30px;
+  //   img {
+  //     display: inline-block;
+  //     width: 100%;
+  //     height: 100%;
+  //   }
+  // }
+  // .pointer {
+  //   position: absolute;
+  //   // left: calc(50% - 35px);
+  //   // top: calc(50% - 40px);
+  //   left: 50%;
+  //   top: 50%;
+  //   transform: translate(-43.75%, -50%);
+  //   // background-image: url("https://img11.360buyimg.com/imagetools/jfs/t1/106989/15/11126/137350/5e265414E8ee514bc/3456bd0d3a0454da.png");
+  //   // background-size: contain;
+  //   // background-repeat: no-repeat;
+  // }
+}
+</style>

+ 150 - 0
src/packages/luckdraw/doc.md

@@ -0,0 +1,150 @@
+# LuckDraw 转盘抽奖
+
+## 基本用法
+
+```html
+<luck-draw
+  class="drawTable"
+  ref="luckDrawPrize"
+  :luckWidth="luckWidth"
+  :luckheight="luckheight"
+  :prizeList="prizeList"
+  :turnsNumber="turnsNumber"
+  :turnsTime="turnsTime"
+  :prizeIndex="prizeIndex"
+  :styleOpt="styleOpt"
+  @endTurns="endTurns"
+>
+  <template slot="item" slot-scope="scope">
+    <div class="drawTable-name">{{ scope.item.prizeName }}</div>
+    <div class="drawTable-img">
+      <img :src="scope.item.prizeImg">
+    </div>
+  </template>
+</luck-draw>
+<div @click="startTurns" class="pointer" :style="pointerStyle"></div>
+```
+
+```javascript
+export default {
+    data() {
+      return {
+        // 转盘大小
+        luckWidth: '300px',
+        luckheight: '300px',
+        // 转盘指针图片样式
+        pointerStyle: {
+          width: '80px',
+          height: '80px',
+          backgroundImage: 'url("https://img11.360buyimg.com/imagetools/jfs/t1/106989/15/11126/137350/5e265414E8ee514bc/3456bd0d3a0454da.png")',
+          backgroundSize: 'contain',
+          backgroundRepeat: 'no-repeat',
+        },
+        // 奖品列表
+        prizeList: [
+          {
+            id: 'xiaomi',
+            prizeName: '小米手机',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/96788/40/337/73706/5dabd0e2E1f166028/7120ca2b421cb0a0.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'blue',
+            prizeName: '蓝牙耳机',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/65070/13/4325/183551/5d26e23fE09ab2010/a94eaff8242e6c63.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'apple',
+            prizeName: 'apple watch',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/105083/3/4010/126031/5de4aa51E1c7fefc6/0288f4cf3016e061.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'fruit',
+            prizeName: '迪士尼苹果',
+            prizeImg: '//m.360buyimg.com/mobilecms/s750x750_jfs/t1/47486/35/13399/356858/5da3cde2E9b3ec40f/3b3a56d54d5db565.jpg!q80.dpg.webp',
+          },
+          {
+            id: 'fish',
+            prizeName: '海鲜套餐',
+            prizeImg: '//m.360buyimg.com/mobilecms/s843x843_jfs/t1/109529/24/1330/283533/5dfc836fE33d8ce6b/372adb638802710a.jpg!q70.dpg.webp',
+          },
+          {
+            id: 'thanks',
+            prizeName: '谢谢参与',
+            prizeImg: 'https://img11.360buyimg.com/imagetools/jfs/t1/104502/28/10892/5123/5e265414Ec167392c/2831c6155895f33d.png',
+          }
+        ],
+        turnsNumber: 5, // 转动圈数
+        turnsTime: 5,// 转动时间:S
+        styleOpt: {
+          prizeBgColors: ['rgb(255, 231, 149)','rgb(255, 247, 223)','rgb(255, 231, 149)','rgb(255, 247, 223)','rgb(255, 231, 149)','rgb(255, 247, 223)'],
+          borderColor: '#ff9800',
+        },
+        prizeIndex: -1, // 中奖奖品的index
+        lock: false,// 防止多次连续点击抽奖
+        num: 5,// 抽奖次数,根据需求定义
+      }
+    },
+    methods: {
+      startTurns() {
+        if (!this.canBeRotated()) {
+          return false;
+        }
+        this.lock = true;
+        // 此为模拟随机数字,这里可以接受后台中奖信息
+        const index = Math.floor(Math.random() * this.prizeList.length);
+        // 成功后抽奖次数减1
+        this.num--;
+        // 中奖奖品的index
+        this.prizeIndex = index;
+        // 调用组件的方法,使转盘转动并停留在中奖奖品的那个扇形区域
+        this.$refs.luckDrawPrize.rotate(index);
+      },
+      endTurns() {
+        this.$dialog({
+          content: `恭喜中奖!!!${this.prizeList[this.prizeIndex].prizeName}`,
+          noCloseBtn: false,
+          noOkBtn: true,
+          cancelBtnTxt: "我知道了"
+        });
+        this.lock = false;
+      },
+      canBeRotated() {
+        if (this.num <= 0) {
+          this.$dialog({
+            content: `已经没有次数了,继续加油赚积分吧!`,
+            noCloseBtn: false,
+            noOkBtn: true,
+            cancelBtnTxt: "我知道了"
+          });
+          return false;
+        }
+        if (this.lock) {
+          return false;
+        }
+        return true;
+      },
+    }
+  }
+```
+
+
+## Prop
+
+| 字段 | 说明 | 类型 | 默认值
+|----- | ----- | ----- | ----- 
+| ref | 当前转盘的类名,转动的时候根据类名执行回调函数 | String | luckDrawPrize
+| luckWidth | 转盘的宽度 | String | 300px
+| luckHeight | 转盘的高度 | String | 300px
+| prizeList | 奖品列表 | Array | -
+| turnsNumber | 转动的圈数 | Number | 5
+| turnsTime | 从开始转动到结束所用时间 | Number | 5(单位是秒)
+| styleOpt | 转盘中的样式,包括每个扇区的背景颜色,扇区的边框颜色 | Object | {prizeBgColors: [],borderColor: ''}
+| pointerStyle | 转盘中指针的样式,包括背景图片、大小等 | Object | {width: '80px',height: '80px'}
+
+
+
+## Event
+
+| 字段 | 说明 | 回调参数
+|----- | ----- | -----
+| endTurns | 转盘中停止转动后的回调函数 | - 

+ 8 - 0
src/packages/luckdraw/index.js

@@ -0,0 +1,8 @@
+import LuckDraw from './luckdraw.vue';
+import './luckdraw.scss';
+
+LuckDraw.install = function(Vue) {
+  Vue.component(LuckDraw.name, LuckDraw);
+};
+
+export default LuckDraw

+ 60 - 0
src/packages/luckdraw/luckdraw.scss

@@ -0,0 +1,60 @@
+.nut-luckdraw{
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    text-align: center;
+    // transform: translateZ(0);
+    .drawTable-name {
+        position: absolute;
+        left: 10px;
+        top: 20px;
+        width: calc(100% - 20px);
+        font-size: 12px;
+        text-align: center;
+        color: #ff5722;
+      }
+      .drawTable-img {
+        position: absolute;
+        /*要居中就要50% - 宽度 / 2*/
+        left: calc(50% - 30px / 2);
+        top: 60px;
+        width: 30px;
+        height: 30px;
+        img {
+          display: inline-block;
+          width: 100%;
+          height: 100%;
+        }
+      }
+    .lucktable {
+        position: absolute;
+        left: 0;
+        top: 0;
+        width: 100%;
+        height: 100%;
+    }
+    .prize {
+        position: absolute;
+        left: 25%;
+        top: 0;
+        width: 50%;
+        height: 50%;
+        .item {
+            position: absolute;
+            left: 0;
+            top: 0;
+            width: 100%;
+            height: 100%;
+            transform-origin: center bottom;
+        }
+    }
+}
+.pointer {
+    position: absolute;
+    // left: calc(50% - 35px);
+    // top: calc(50% - 40px);
+    left: 50%;
+    top: 50%;
+    transform: translate(-43.75%, -50%);
+}

+ 113 - 0
src/packages/luckdraw/luckdraw.vue

@@ -0,0 +1,113 @@
+<template>
+    <div class="nut-luckdraw" ref="luckdraw" :style="{width:luckWidth, height:luckheight}">
+        <div class="lucktable" :style="{transform: rotateAngle, transition: rotateTransition}">
+            <canvas id="canvas" ref="canvas">
+                浏览器版本过低
+            </canvas>
+            <div class="prize">
+                <div v-for="(item, index) in prizeList" class="item" :style="getRotateAngle(index)" :key="index">
+                <slot name="item" :item="item"></slot>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    name:'nut-luckdraw',
+    mounted() {
+      this.init();
+    },
+    props: {
+      luckWidth: {
+        required: true
+      },
+      luckheight: {
+        required: true
+      },
+      prizeList: {
+        required: true
+      },
+      turnsNumber: {
+        default: 5
+      },
+      styleOpt: {
+        default: () => {
+          return {
+            // 每一块扇形的背景色,默认值,可通过父组件来改变
+            prizeBgColors: ['rgb(255, 231, 149)','rgb(255, 247, 223)','rgb(255, 231, 149)','rgb(255, 247, 223)','rgb(255, 231, 149)','rgb(255, 247, 223)'],
+            // 每一块扇形的外边框颜色,默认值,可通过父组件来改变
+            borderColor: '#ff9800',
+          }
+        }
+      },
+      turnsTime: {
+        // 转动需要持续的时间(秒)  
+        default: 5
+      }
+    },
+    data() {
+      return {
+        winningPrize: 0,
+        // 开始转动的角度
+        startRotateDegree: 0,
+        // 设置指针默认指向的位置,现在是默认指向2个扇形之间的边线上
+        rotateAngle: 0,//`rotate(30deg)`
+        rotateTransition: '',
+      }
+    },
+    methods: {
+      // 根据index计算每一格要旋转的角度的样式
+      getRotateAngle(index) {
+        const angle = 360 / this.prizeList.length * index + (180 / this.prizeList.length);
+        return {
+          transform: `rotate(${angle}deg)`
+        };
+      },
+      // 初始化圆形转盘canvas
+      init() {
+        const data = this.styleOpt;
+        const prizeNum = this.prizeList.length;
+        const {prizeBgColors, borderColor} = data;
+        // 开始绘画
+        const canvas = this.$refs.canvas;
+        const ctx = canvas.getContext("2d");
+        const canvasW = this.$refs.canvas.width = this.$refs.luckdraw.clientWidth; // 画板的高度
+        const canvasH = this.$refs.canvas.height = this.$refs.luckdraw.clientHeight; // 画板的宽度
+        // translate方法重新映射画布上的 (0,0) 位置
+        ctx.translate(0, canvasH);
+        // rotate方法旋转当前的绘图,因为文字是和当前扇形中心线垂直的
+        ctx.rotate(-90 * Math.PI / 180);
+        // 圆环的外圆的半径,可用来调整圆盘大小来适应外部盒子的大小
+        const outRadius = canvasW / 2 - 1;
+        // 圆环的内圆的半径
+        const innerRadius = 0;
+        const baseAngle = Math.PI * 2 / prizeNum; // 每个奖项所占角度数
+        ctx.clearRect(0, 0, canvasW, canvasH); //去掉背景默认色
+        ctx.strokeStyle = borderColor; // 设置画图线的颜色
+        for (let index = 0; index < prizeNum; index++) {
+          const angle = index * baseAngle;
+          ctx.fillStyle = prizeBgColors[index]; //设置每个扇形区域的颜色
+          ctx.beginPath(); //开始绘制
+          // 标准圆弧:arc(x,y,radius,startAngle,endAngle,anticlockwise)
+          ctx.arc(canvasW * 0.5, canvasH * 0.5, outRadius, angle, angle + baseAngle, false);
+          ctx.arc(canvasW * 0.5, canvasH * 0.5, innerRadius, angle + baseAngle, angle, true);
+          ctx.stroke();
+          ctx.fill();
+          ctx.save();
+        }
+      },
+      // 转动起来
+      rotate(index) {
+        const turnsTime = this.turnsTime;
+        const rotateAngle = this.startRotateDegree + this.turnsNumber * 360 + 360 - (180 / this.prizeList.length + 360 / this.prizeList.length * index) - this.startRotateDegree % 360;
+        this.startRotateDegree = rotateAngle;
+        this.rotateAngle = `rotate(${rotateAngle}deg)`;
+        this.rotateTransition = `transform ${turnsTime}s cubic-bezier(0.250, 0.460, 0.455, 0.995)`;
+        setTimeout(() => {
+          this.$emit('endTurns');
+        }, turnsTime * 1000 + 500)
+      },
+    },
+}
+</script>

+ 1 - 0
types/nutui.d.ts

@@ -68,3 +68,4 @@ export declare class LeftSlip extends UIComponent {}
 export declare class TabSelect extends UIComponent {}
 export declare class Popup extends UIComponent {}
 
+export declare class LuckDraw extends UIComponent {}