提交 1e756523 编写于 作者: N nenyah

fix(*): update style

上级 0c04c6c2
此差异已折叠。
......@@ -3,10 +3,10 @@
* @Author: Steven
* @Date: 2020-09-08 09:22:51
* @LastEditors: Steven
* @LastEditTime: 2020-09-08 14:32:04
* @LastEditTime: 2020-09-11 14:19:35
-->
<template>
<view>
<view class="bg-color pt-4">
<vote-rule></vote-rule>
<view class="text-white flex px-4">
<view class="attr"
......@@ -33,4 +33,9 @@ export default Vue.extend({
})
</script>
<style></style>
<style lang="scss" scoped>
.bg-color {
background-image: $rule-bg-base64-code;
background-size: 100%;
}
</style>
<!--
* @Description:
* @Author: Steven
* @Date: 2020-09-11 12:30:27
* @LastEditors: Steven
* @LastEditTime: 2020-09-11 12:31:50
-->
<template>
<view class="text-lg flex py-auto items-center">
<view class="mr-1 ">{{title}}</view>
<image src="/static/rule-icon.png" class="w-4 h-4">
</view>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
props:{
title:String,
}
})
</script>
<style>
</style>
\ No newline at end of file
<!-- mosowe-canvas-image -->
<template>
<view class='mosowe-canvas-image'>
<view class="slot-view" @click="createCanvas">
<slot></slot>
</view>
<view class="canvas-wrap">
<canvas class="canvas-wrap" canvas-id="canvas" :style="'width: '+ width +'px; height: '+ height +'px;'"></canvas>
</view>
</view>
</template>
<script>
import QR from './wxqrcode.js';
export default {
name: 'mosowe-canvas-image',
components: {},
props: {
showPreview: { // 生成图像后是否预览
type: Boolean,
default: false
},
height: { // canvas高度
type: [String, Number],
default: 200
},
width: { // canvas宽度
type: [String, Number],
default: 200
},
lists: {
type: Array,
default: () => {
return [// 图片,图片有先后,叠加画图
{
type: 'image',
content: 'https://www.zhonglixunqing.cn/images/uniapp/1.jpg', // 图片url
width: 200, // 图片绘制宽度
height: 200, // 图片绘制高度
x: 0, // 图片绘制X轴位置
y: 0, // 图片绘制Y轴位置
arc: false, // 圆形
arcX: 0, // 圆的x坐标
arcY: 0, // 圆的y坐标
arcR: 0 // 圆的半径
},
{
type: 'image',
content: 'https://www.zhonglixunqing.cn/images/uniapp/2.jpg', // 图片url
width: 100, // 图片绘制宽度
height: 100, // 图片绘制高度
x: 0, // 图片绘制X轴位置
y: 0, // 图片绘制Y轴位置
arc: false, // 圆形,如果需要圆形图片绘制,请放在列表最后,否则后续绘制将在此圆形内
arcX: 0, // 圆的x坐标
arcY: 0, // 圆的y坐标
arcR: 0 // 圆的半径
},
{
type: 'text',
content: '你好', // 文字
x: 10, // X轴
y: 50, // Y轴
color: '#ff0000', // 颜色
size: 20, // 字号
maxWidth: 100, // 最大宽度
align: 'left', // 对齐方式
},
{
type: 'image',
content: 'https://www.zhonglixunqing.cn/images/uniapp/3.jpg', // 图片url
width: 100, // 图片绘制宽度
height: 100, // 图片绘制高度
x: 150, // 图片绘制X轴位置
y: 150, // 图片绘制Y轴位置
arc: true, // 圆形,如果需要圆形图片绘制,请放在列表最后,否则后续绘制将在此圆形内
arcX: 200, // 圆的x坐标
arcY: 200, // 圆的y坐标
arcR: 50 // 圆的半径
},
];
}
}
},
data () {
return {
canvas: null,
listsIndex: 0,
listsLength: 0
};
},
watch: {},
// 组件实例化之前
beforeCreate () {},
// 组件创建完成
created () {
this.canvas = uni.createCanvasContext('canvas', this);
},
// 组件挂载之前
beforeMount () {},
// 组件挂载之后
mounted () {},
// 组件数据更新时
beforeUpdate () {},
// 组价更新
updated () {},
// 组件销毁前
beforeDestroy () {},
// 组件销毁后
destroyed () {},
// 页面方法
methods: {
// 开始绘制
createCanvas () {
this.listsIndex = 0;
this.listsLength = this.lists.length - 1;
// #ifndef H5
uni.showLoading();
// #endif
// #ifdef H5
uni.showLoading({
mask: true
});
// #endif
this.dataDrawCanvas();
},
// 数据绘制
dataDrawCanvas () {
let item = this.lists[this.listsIndex];
if (item.type === 'image') { // 图片
// #ifndef H5
// 非H5
this.downloadImageNotH5(item);
// #endif
// #ifdef H5
// H5
this.downloadImageH5(item);
// #endif
} else if (item.type === 'text') { // 文本
this.drawText(item);
} else if (item.type === 'rect') { // 矩形(线条)
this.drawRect(item);
} else if (item.type === 'arc') { // 圆形
this.drawArc(item);
} else if (item.type === 'qr') { // 二维码
this.drawQR(item);
}
},
// #ifndef H5
// 图片下载本地并绘制,非H5
downloadImageNotH5 (item) {
uni.downloadFile({
url: item.content,
header: {
'Access-Control-Allow-Origin': '*',
},
success: (res) => {
if (item.hasOwnProperty('arc') && item.arc) { // 绘制圆形
this.canvas.arc(item.arcX, item.arcY, item.arcR, 0, 2 * Math.PI);
this.canvas.clip();
this.canvas.closePath();
}
this.canvas.drawImage(
res.tempFilePath,
item.x,
item.y,
item.hasOwnProperty('width') ? item.width : this.width,
item.hasOwnProperty('height') ? item.height : this.height
);
this.checkDrawOver();
},
fail: (res) => {
console.log(res);
uni.hideLoading();
}
});
},
// #endif
// #ifdef H5
// 图片下载本地并绘制,H5
downloadImageH5 (item) {
let image = new Image();
image.setAttribute('crossOrigin', 'anonymous');
image.src = item.content;
image.onload = () => {
if (item.arc) { // 绘制圆形
this.canvas.arc(item.arcX, item.arcY, item.arcR, 0, 2 * Math.PI);
this.canvas.clip();
this.canvas.closePath();
}
this.canvas.drawImage(
item.content,
item.x,
item.y,
item.hasOwnProperty('width') ? item.width : this.width,
item.hasOwnProperty('height') ? item.height : this.height
);
this.checkDrawOver();
};
},
// #endif
// 文本绘制
drawText (item) {
this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000');
this.canvas.setFontSize(item.hasOwnProperty('size')? item.size : 20);
this.canvas.setTextAlign(item.hasOwnProperty('align') ? item.align: 'left');
if (item.maxWidth) {
this.canvas.fillText(item.content, item.x, item.y, item.maxWidth);
} else {
this.canvas.fillText(item.content, item.x, item.y);
}
this.checkDrawOver();
},
// 矩形(线条)绘制
drawRect (item) {
this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000');
this.canvas.fillRect(item.x, item.y, item.width, item.height);
this.checkDrawOver();
},
// 圆形绘制
drawArc (item) {
this.canvas.arc(item.arcX, item.arcY, item.arcR, 0, 2 * Math.PI);
this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000');
this.canvas.fill();
this.canvas.closePath();
this.checkDrawOver();
},
// 二维码绘制
drawQR (item) {
item['qr'] = QR.createQrCodeImg(item.content, {
size: parseInt(300)
});
this.canvas.drawImage(
item.qr,
item.x,
item.y,
item.hasOwnProperty('width') ? item.width : this.width,
item.hasOwnProperty('height') ? item.height : this.height
);
this.checkDrawOver();
},
// 判断是否绘制完
checkDrawOver () {
if (this.listsIndex < this.listsLength) { // lists未画完
this.listsIndex++;
this.dataDrawCanvas();
} else {
this.canvasImage();
}
},
// 绘制到画布并生成图片
canvasImage () {
this.canvas.draw(false, setTimeout(() => {
setTimeout(() => {
// #ifndef MP-ALIPAY
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: Number(this.width),
height: Number(this.height),
fileType: 'jpg',
canvasId: 'canvas',
success: (res) => {
this.$emit('canvasImage', res.tempFilePath);
if (this.showPreview) {
this.showPreviewFn(res.tempFilePath);
}
},
fail: (res) => {
console.log(res);
},
complete: () => {
uni.hideLoading();
}
}, this);
// #endif
// #ifdef MP-ALIPAY
// 支付宝的
// #endif
}, 500);
}));
},
// 预览图
showPreviewFn (img) {
uni.previewImage({
current: 0,
urls: [img]
});
},
}
};
</script>
<style lang='scss' scoped>
.mosowe-canvas-image{
overflow: hidden;
.canvas-wrap {
overflow: hidden;
height: 0;
width: 0;
}
}
</style>
\ No newline at end of file
## mosowe-canvas-image:一个可以制作多用途图片的插件(海报,二维码,分享图)
#### 平台支持:
| APP | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节跳动小程序 | QQ小程序 |
| :--: | :--: | :--------: | :----------: | :--------: | :------------: | :------: |
| √ | √ | √ | × | 未测 | 未测 | 未测 |
#### 插件功能
1. 支持多图片绘制,多文本绘制,圆形图片绘制;
2. 支持矩形(线条)绘制;
3. 支持圆形绘制;
4. 支持二维码生成,项目用不上可以去插件内去除,毕竟这个插件携带的比较大,单纯用来生成二维码图片也是阔以的;
5. 支持绘图后预览。
多用于海报图,分享图;
注意H5跨域问题及小程序白名单配置;
图片是网络图片:https://....(require及import引入不了3Kb以上的绝对路径图片,若有大神知道处理方法,望不吝赐教,谢谢!)
#### 属性
| 名称 | 类型 | 默认值 | 说明 |
| :---------- | :--------------- | :----- | :------------------------------------------------------- |
| width | Number \| String | 200 | canvas画布宽度,也是导出图片宽度,单位px,值中不要带单位 |
| height | Number \| String | 200 | canvas画布高度,也是导出图片高度,单位px,值中不要带单位 |
| showPreview | Boolean | false | 绘制完成后是否打开预览 |
| lists | Array | [] | 绘制的元素列表:图片,文字,矩形(线条),圆形,二维码 |
#### lists\<item>属性
注意:图文先后顺序,底层的图片靠前,最上层的在最后,圆形图片放在最后,因为一旦绘制圆形,后续的元素都只在该圆形内显示,而超过圆形范围的将看不见。
| 名称 | 类型 | 必填 | 说明 |
| :------- | :----- | :--- | :----------------------------------------------------------- |
| type | String | 是 | 元素类型:`image`图片,`text`文本,`rect`矩形(线条),`arc`圆形,`qr`二维码 |
| content | String | 否 | image:图片路径(必填),text:文字(必填),qr:转二维码的数据(必填),rect及arc:非必填 |
| x | Number | 是 | X轴坐标,绘制圆形图片时:x = arcX - arcR |
| y | Number | 是 | Y轴坐标,绘制圆形图片时:y = arcY - arcR |
| width | Number | 否 | 图片、矩形(线条)、二维码宽度 |
| height | Number | 否 | 图片、矩形(线条)、二维码高度 |
| arc | Boolen | 否 | type=image时:是否绘制圆形图片 |
| arcX | Number | 否 | type=image、arc时:绘制圆形时中心点X轴坐标 |
| arcY | Number | 否 | type=image、arc时:绘制圆形时中心点Y轴坐标 |
| arcR | Number | 否 | type=image、arc时:绘制圆形的半径 |
| color | String | 否 | 绘制文本、矩形(线条)的颜色,默认:#000000 |
| size | Number | 否 | 绘制文本的字号大小,默认:20 |
| align | String | 否 | 绘制文本的对齐方式,默认:left |
| maxWidth | Number | 否 | 绘制文本的最大宽度,文字长度超过该值会被压缩 |
#### slots
| 名称 | 说明 |
| :------ | :--------------------------------- |
| default | 自定义插槽,点击此区会触发绘图事件 |
#### 事件
| 名称 | 回调参数 | 说明 |
| ----------- | -------- | ------------------------------------ |
| canvasImage | url | 绘制成功后返回的本地地址,H5为base64 |
#### 使用方式
`page.json`中配置了`"easycom": true`,则无需`script`引入就可以使用,没有则需要引入。
1. 无slot:组件标签添加`ref`属性,采用父级调用子组件`createCanvas()`方法使用,见后文示例;
2. 有slot:slot区点击就会执行
#### 示例
```javascript
// js
data () {
return {
canvasUrl: '',
lists: [
{
type: 'image',
content: 'https://www.zhonglixunqing.cn/images/uniapp/1.jpg',
width: 200,
height: 100,
x: 50,
y: 20,
},
{
type: 'image',
content: 'https://www.zhonglixunqing.cn/images/uniapp/2.jpg',
width: 80,
height: 80,
x: 20,
y: 200,
arc: false,
arcX: 0,
arcY: 0,
arcR: 0
},
{
type: 'text',
content: '扫一扫,获取更多信息',
x: 120,
y: 250,
color: '#ff0000',
size: 10,
// maxWidth: 100,
// align: 'left',
},
{
type: 'rect',
width: 1,
height: 100,
x: 0,
y: 10,
color: '#ff0000',
},
{
type: 'image',
content: 'https://www.zhonglixunqing.cn/images/uniapp/3.jpg',
width: 100,
height: 100,
x: 200,
y: 200,
arc: true,
arcX: 250,
arcY: 250,
arcR: 50
},
]
};
},
methods: {
beginCanvas () {
this.$refs.mosoweCanvasComponents.createCanvas();
},
_canvasImage (e) {
this.canvasUrl = e;
}
}
```
插件外独立按钮触发:
```html
<button type="default" @click="beginCanvas">开始绘图</button>
<image :src="canvasUrl" mode="widthFix"></image>
<mosowe-canvas-image
ref="mosoweCanvasComponents"
@canvasImage="_canvasImage"
:lists="lists"
height="300"
width="300"
showPreview />
```
slot插槽触发:
```html
<mosowe-canvas-image
:lists="lists"
height="300"
width="300"
showPreview >
<view class="in_btn">
slot按钮的
</view>
</mosowe-canvas-image>
```
#### 预览地址
此差异已折叠。
......@@ -3,19 +3,19 @@
* @Author: Steven
* @Date: 2020-09-08 15:49:57
* @LastEditors: Steven
* @LastEditTime: 2020-09-10 16:38:55
* @LastEditTime: 2020-09-11 10:20:55
-->
<template>
<view>
<view class="text-center">
<view class="flex my-2 py-2" :class="{ 'bg-purple-light': isActive }">
<view class="flex-1">
<view class="text-center">
<view :class="top3">{{ index + 1 }}</view>
</view>
<view class="flex-1">{{ item.id }}</view>
<view class="text-center">{{ item.id }}</view>
<navigator :url="toUrl">
<view class="flex-1">{{ item.name }}</view>
<view class="text-center">{{ item.name }}</view>
</navigator>
<view class="flex-1">{{ item.vote }}</view>
<view class="text-center">{{ item.vote }}</view>
</view>
</view>
</template>
......
......@@ -3,17 +3,15 @@
* @Author: Steven
* @Date: 2020-09-08 09:22:09
* @LastEditors: Steven
* @LastEditTime: 2020-09-10 17:01:43
* @LastEditTime: 2020-09-11 13:10:52
-->
<template>
<view class="px-4 pt-2">
<view
class="w-4-5 border-2 border-gray-100 mt-2 p-2 border border-b-0 border-gray-200 border-solid"
>
<view class="flex">
<view class="w-4-5 mt-2 p-2 stat-border">
<view class="flex stat-content">
<view class="flex-1" v-for="(item, index) in content" :key="index">
<view class="text-gray-100 text-center text-xl">
{{ item.value }} {{isDetail&&index===2?'':''}}
{{ item.value }} {{ isDetail && index === 2 ? "" : "" }}
</view>
<view class="text-yellow-900 text-center">
{{ item.name }}
......@@ -44,4 +42,26 @@ export default Vue.extend({
})
</script>
<style></style>
<style lang="scss" scoped>
.stat-border {
padding: 3px;
border-radius: 0;
border-top: 1px solid #4e5789;
border-left: 1px solid;
border-right: 1px solid;
border-bottom: none;
border-image: linear-gradient(to top, #0d164a, #4e5789);
border-image-slice: 10;
.stat-content {
margin: 0 auto;
padding: 5px 15px;
border-top: 1px solid #283164;
border-left: 1px solid;
border-right: 1px solid;
border-bottom: none;
border-image: linear-gradient(to top, #0d164a, #283164);
border-image-slice: 10;
background: #0d144d;
}
}
</style>
......@@ -3,22 +3,22 @@
* @Author: Steven
* @Date: 2020-09-08 14:38:03
* @LastEditors: Steven
* @LastEditTime: 2020-09-08 14:41:57
* @LastEditTime: 2020-09-11 12:37:52
-->
<template>
<view class="px-4 text-gray-200">
<view class="text-lg"
>活动详情 <view class="fa fa-ellipsis-v px-1 text-orange-400"></view
><view class="fa fa-ellipsis-v pr-1 text-orange-300"></view
><view class="fa fa-ellipsis-v text-orange-100"></view
></view>
<main-title :title="title"></main-title>
<view class="mt-2">{{ activate.desc }}</view>
</view>
</template>
<script lang="ts">
import Vue from "vue"
import mainTitle from "@/components/main-title/main-title.vue"
export default Vue.extend({
components: {
mainTitle,
},
data() {
return {
activate: {
......@@ -27,6 +27,7 @@ export default Vue.extend({
活动方有权对票数异常的选手做出相应处罚,作弊违规行为包括但不限于:使用辅助软件的作弊行为、网上买僵尸号的投票行为、找投票公司付费投票行为。第一次电话口头警告并减去相关的票数,第二次再出现类似情况,不再另行通知,票数系统将自动清零,严重者取消参评资格和获奖资格。
活动重在参与,意在宣传推广,打赏属自愿行为,我们不提倡给选手打赏,请酌情购买,主办方对本活动保留最终解释权!.`,
},
title: "活动详情",
}
},
onLoad() {},
......
......@@ -3,12 +3,12 @@
* @Author: Steven
* @Date: 2020-09-08 09:24:10
* @LastEditors: Steven
* @LastEditTime: 2020-09-11 09:39:00
* @LastEditTime: 2020-09-11 10:18:36
-->
<template>
<view class="text-gray-100">
<view v-if="!isIndex">
<view class="flex w-4-5 px-2 m-auto text-orange-500">
<view class="flex w-4-5 px-2 m-auto text-orange-500 text-center">
<view class="flex-1">排名</view>
<view class="flex-1">编号</view>
<view class="flex-1">参与选手</view>
......
......@@ -3,16 +3,11 @@
* @Author: Steven
* @Date: 2020-09-08 14:28:34
* @LastEditors: Steven
* @LastEditTime: 2020-09-11 10:16:19
* @LastEditTime: 2020-09-11 14:05:54
-->
<template>
<view class="my-2 px-4 text-gray-200">
<view class="text-lg mb-2">
活动规则
<view class="fa fa-ellipsis-v px-1 text-orange-400"></view>
<view class="fa fa-ellipsis-v pr-1 text-orange-300"></view>
<view class="fa fa-ellipsis-v text-orange-100"></view>
</view>
<view class="mt-5 pt-8 z-20 mb-2 px-4 text-gray-200 -t-10">
<main-title :title="title"></main-title>
<view class="flex">
<view class="w-18">
<view class="fa fa-clock-o text-orange-500 mr-2"></view>
......@@ -47,12 +42,17 @@
<script lang="ts">
import Vue from "vue"
import mainTitle from "@/components/main-title/main-title.vue"
export default Vue.extend({
components: {
mainTitle,
},
data() {
return {
startTime: "2020-10-15 00:00",
endTime: "2020-12-01 00:00",
rule: { day: 3, item: 1 },
title: "活动规则",
}
},
onLoad() {},
......@@ -60,4 +60,8 @@ export default Vue.extend({
})
</script>
<style></style>
<style lang="scss" scoped>
.-t-10 {
top:-20px;
}
</style>
......@@ -28,7 +28,7 @@
"globalStyle": {
"navigationBarTextStyle": "#cfcfcf",
"navigationBarTitleText": "投票小程序",
"navigationBarBackgroundColor": "#000",
"navigationBarBackgroundColor": "#0d134d",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
......
......@@ -3,16 +3,14 @@
* @Author: Steven
* @Date: 2020-08-26 16:08:15
* @LastEditors: Steven
* @LastEditTime: 2020-09-10 11:26:21
* @LastEditTime: 2020-09-11 13:17:42
-->
<template>
<view class="bg-purple">
<banner></banner>
<title></title>
<stats :content="indexstats">
<view
class="mt-2 p-2 text-gray-100 text-center border border-r-0 border-l-0 border-gray-100 border-solid"
>
<view class="mt-2 p-2 text-gray-100 text-center diff-time-box">
活动结束时间还有{{ lastdate }}
</view>
</stats>
......@@ -59,4 +57,13 @@ export default Vue.extend({
})
</script>
<style lang="scss"></style>
<style lang="scss" scoped>
.diff-time-box {
margin: 2px;
padding: 4px 0;
background: url('/static/diff-time.png') no-repeat;
background-size: 100% 100%;
border: none;
border-radius: 0;
}
</style>
......@@ -21,32 +21,32 @@ $uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
$uni-text-color: #333; //基本色
$uni-text-color-inverse: #fff; //反色
$uni-text-color-grey: #999; //辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
$uni-bg-color: #ffffff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; //点击状态颜色
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:24rpx;
$uni-font-size-base:28rpx;
$uni-font-size-lg:32rpx;
$uni-font-size-sm: 24rpx;
$uni-font-size-base: 28rpx;
$uni-font-size-lg: 32rpx;
/* 图片尺寸 */
$uni-img-size-sm:40rpx;
$uni-img-size-base:52rpx;
$uni-img-size-lg:80rpx;
$uni-img-size-sm: 40rpx;
$uni-img-size-base: 52rpx;
$uni-img-size-lg: 80rpx;
/* Border Radius */
$uni-border-radius-sm: 4rpx;
......@@ -68,9 +68,11 @@ $uni-spacing-col-lg: 24rpx;
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:40rpx;
$uni-color-title: #2c405a; // 文章标题颜色
$uni-font-size-title: 40rpx;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:36rpx;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:30rpx;
\ No newline at end of file
$uni-font-size-subtitle: 36rpx;
$uni-color-paragraph: #3f536e; // 文章段落颜色
$uni-font-size-paragraph: 30rpx;
@import "@/common/base64.store.scss";
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册