【sciter】案例 TodoList 带你入门 sciter 组件化编程

本文通过TodoList案例,详细介绍了使用sciter进行组件化编程的方法,涵盖JSX组件化、组件通信、生命周期、事件绑定、this指向处理及模块化CSS等关键点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

案例 TodoList 带你入门 sciter 组件化编程

下诉案例已基本覆盖 sciter 组件化编程所涉及到的内容,及注意细节

1、效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、技术要点

  • JSX 组件化编程(类组件)
  • 组件间如何通信
  • 组件生命周期
  • 组件如何绑定事件
  • 如何处理组件中 this 指向
  • 模块化 CSS

3、如何使用

<div class="container"></div>
<script type="module">
    import { TodoList } from './component/index.js';
    document.$(`.container`).content(<TodoList />);
</script>

4、组件 TodoList 源码

export class TodoList extends Element {
    value;
    list = [];
    constructor(props) {
        super(); // 必写
    }

    // 组件挂载
    componentDidMount() { }
    
    // 组件销毁前
    componentWillUnmount() { }
    
    componentUpdate(newdata) {
        if (typeof newdata == "object") {
            Object.assign(this, newdata);
        }
        this.post(() => this.patch(this.render()));
    }

    render() {
        return (
            <div styleset={__DIR__ + "index.css#todolist"}>
                <div class="header">
                    <div class="info">To-Do List</div>
                </div>
                <div class="title">~Today I need to ~</div>
                <div class="form">
                    <div class="form-input">
                        <input type="text" placeholder="Add new todo..." value={this.value} onchange={(event) => this.value = event.target.value} />
                        <button onclick={this.submit}><span class="submit">Submit</span></button>
                    </div>
                </div>
                {this.list.length == 0 && (
                    <div class="empty-todos">
                        <svg class="icon" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="clipboard-check" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" data-v-132cabf7=""><path class="" fill="currentColor" d="M336 64h-80c0-35.3-28.7-64-64-64s-64 28.7-64 64H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zM192 40c13.3 0 24 10.7 24 24s-10.7 24-24 24-24-10.7-24-24 10.7-24 24-24zm121.2 231.8l-143 141.8c-4.7 4.7-12.3 4.6-17-.1l-82.6-83.3c-4.7-4.7-4.6-12.3.1-17L99.1 285c4.7-4.7 12.3-4.6 17 .1l46 46.4 106-105.2c4.7-4.7 12.3-4.6 17 .1l28.2 28.4c4.7 4.8 4.6 12.3-.1 17z"></path></svg>
                        <span class="todos-text">Congrat, you have no more tasks to do</span>
                    </div>
                )}
                {this.list.length != 0 && (
                    <List data={this.list}
                        finshed={this.finshed}
                        del={this.del}
                    />
                )}
            </div>
        );
    }

    // 提交
    submit = (event) => {
        const value = this.value.trim();
        if (value.length != 0) {

            this.list.unshift({
                id: Math.random(),
                text: value,
                state: false
            });

            this.componentUpdate({
                value: "",
                list: this.list
            });
        }

    }

    // 完成
    finshed = (data) => {
        const i = this.list.findIndex(item => item.id == data.id);
        if (i != -1) this.list[i].state = true;

        this.componentUpdate({ list: this.list });
    }

    // 删除
    del = (data) => {
        data.forEach(item1 => {
            const i = this.list.findIndex(item2 => item2.id == item1.id);
            this.list.splice(i, 1);
        });
        this.componentUpdate({ list: this.list });
    }
}

5、List 组件源码

class List extends Element {
    id; // 组件 id
    Dom; // 组件 DOM

    data = [];
    
    finshed; // 回调 
    del; // 回调

    key = 'All';

    constructor(props) {
        super();
        // 生成唯一 id
        this.id = `list-${Math.ceil(Math.random() * 100)}`;

        this.data = props.data;
        this.finshed = props.finshed;
        this.del = props.del;
    }

    // 挂载前
    componentDidMount() {
        // 保存 list 组件的 Dom 元素
        this.Dom = document.querySelector(`#${this.id}`);
    }

    componentUpdate(newData) {
        if (typeof newdata == "object") {
            this.data = newData.data;
            this.key = newData.key;
        }
        this.post(() => this.patch(this.render()));
    }

    render(props, kids) {
        const activeLen = this.data.filter(item => item.state).length;
        const state = this.data.some(item => item.state);
        const len = this.data.length;
        return (
            <div styleset={__DIR__ + "index.css#list"} id={this.id}>
                <ul class="list">
                    {this.data.map(item => {
                        return (
                            /* key={Math.random()} 使其重新渲染 */
                            <li class={item.state ? 'item active' : 'item'} onclick={this.itemFinshed.bind(this, item)} tag={item.id} key={Math.random()}>
                                <i class='state-icon'></i>
                                <span class='text'>{item.text} </span>
                                <i class='close-icon' onclick={() => this.itemDel(item)}></i>
                            </li>
                        )
                    })}
                </ul>
                <div class="detail">
                    <span>{len} item left</span>
                    <span class={this.key == 'All' ? 'state-active' : ''} onclick={this.itemFilter.bind(this, 'All')}>All</span>
                    <span class={this.key == 'Active' ? 'state-active' : ''} style={(state && activeLen != len) ? '' : 'display: none'} onclick={this.itemFilter.bind(this, 'Active')}>Active</span>
                    <span class={this.key == 'Completed' ? 'state-active' : ''} style={(state && activeLen != len) ? '' : 'display: none'} onclick={this.itemFilter.bind(this, 'Completed')}>Completed</span>
                    <span class={this.key == 'Clear completed' ? 'state-active' : ''} style={state ? '' : 'display: none'} onclick={this.clearCompleted.bind(this)}>Clear completed</span>
                </div>
            </div>
        )
    }

    // 完成
    itemFinshed = (item, event) => {
        if (!event.target.classList.contains('close-icon')) {
            this.finshed && this.finshed(item);
        }
    }

    // 删除
    itemDel = (item) => {
        this.del && this.del([item]);
    }

    // 清除完成
    clearCompleted = () => {
        const completedList = this.data.filter(item => item.state);
        this.del && this.del(completedList);
    } 

    // 过滤
    itemFilter = (key) => {
        const children = this.Dom.querySelector(`.list`).children;
        const stateList = this.Dom.querySelector(`.detail`).children;

        Array.from(stateList).forEach(item => { item.classList.remove('state-active') });

        const activeList = this.data.filter(item => !item.state);
        const completedList = this.data.filter(item => item.state);

        // 手动操作 DOM
        Array.from(children).forEach(item => {
            const id = item.getAttribute('tag');

            if (key == 'All') {
                stateList[1].classList.add('state-active');
                Object.assign(item.style, { display: 'block' });
            }

            if (key == 'Active') {
                stateList[2].classList.add('state-active');
                const i = activeList.findIndex(activeItem => activeItem.id == id);
                const style = i != -1 ? { display: 'block' } : { display: 'none' };
                Object.assign(item.style, style);
            }

            if (key == 'Completed') {
                stateList[3].classList.add('state-active');
                const i = completedList.findIndex(activeItem => activeItem.id == id);
                const style = i != -1 ? { display: 'block' } : { display: 'none' };
                Object.assign(item.style, style);
            }
        });
    }
}

6、模块化 CSS

以下 CSS代码 为 TodoList 的 CSS 样式,可以结合 TodoList 中 render() 渲染方法中的 DOM 结构

/* 模块化 css */

@set todolist {
    :root {
        padding: 30px 40px 20px;
        text-align: center;
        width: 440px;
        max-width: 100%;
        margin: 0 auto;
        border-radius: 15px;
        background: #f2f2f2;
    }

    :root .header {
        height: 88px;
        width: 240px;
        margin: auto;
        flow: horizontal;
    }

    :root .header::before {
        content: "";
        dispaly: inline-block;
        width: 88px;
        height: 88px;
        background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCgkgdmlld0JveD0iMCAwIDUxMiA1MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHBhdGggc3R5bGU9ImZpbGw6I0YyOUMxRjsiIGQ9Ik0yODkuNjU1LDQyMC4xOTNsMjE2LjI3Ni0yMTYuMjc2YzcuOTQ1LTcuOTQ1LDcuOTQ1LTIyLjA2OSwwLTMwLjAxNGwtMTYuNzcyLTE2Ljc3MgoJYy05LjcxLTkuNzEtMjUuNi05LjcxLTM1LjMxLDBMMjQwLjIyMSwzNzAuNzU5TDI4OS42NTUsNDIwLjE5M3oiLz4KPHBhdGggc3R5bGU9ImZpbGw6I0NDQjM3NTsiIGQ9Ik0yODkuNjU1LDQyMC4xOTNsLTQ5LjQzNC00OS40MzRsMTQ4LjMwMy0xNDguMzAzVjM1LjMxYzAtOS43MS03Ljk0NS0xNy42NTUtMTcuNjU1LTE3LjY1NUgyNjQuOTM4CglWOC44MjhjMC01LjI5Ny0zLjUzMS04LjgyOC04LjgyOC04LjgyOEgxMzIuNTI0Yy01LjI5NywwLTguODI4LDMuNTMxLTguODI4LDguODI4djguODI4SDE3Ljc2NkM4LjA1NSwxNy42NTUsMC4xMSwyNS42LDAuMTEsMzUuMzEKCXY0NTkuMDM0YzAsOS43MSw3Ljk0NSwxNy42NTUsMTcuNjU1LDE3LjY1NWgzNTMuMTAzYzkuNzEsMCwxNy42NTUtNy45NDUsMTcuNjU1LTE3LjY1NVYzMjIuMjA3TDI4OS42NTUsNDIwLjE5M3oiLz4KPHBhdGggc3R5bGU9ImZpbGw6I0VDRjBGMTsiIGQ9Ik0yODkuNjU1LDQyMC4xOTNsLTQ5LjQzNC00OS40MzRsMTEyLjk5My0xMTIuOTkzVjYxLjc5M2MwLTUuMjk3LTMuNTMxLTguODI4LTguODI4LTguODI4aC03OS40NDgKCXY4LjgyOGMwLDUuMjk3LTMuNTMxLDguODI4LTguODI4LDguODI4SDEzMi41MjRjLTUuMjk3LDAtOC44MjgtMy41MzEtOC44MjgtOC44Mjh2LTguODI4SDQ0LjI0OGMtNS4yOTcsMC04LjgyOCwzLjUzMS04LjgyOCw4LjgyOAoJdjQwNi4wNjljMCw1LjI5NywzLjUzMSw4LjgyOCw4LjgyOCw4LjgyOGgzMDAuMTM4YzUuMjk3LDAsOC44MjgtMy41MzEsOC44MjgtOC44MjhWMzU3LjUxN0wyODkuNjU1LDQyMC4xOTN6Ii8+CjxwYXRoIHN0eWxlPSJmaWxsOiNBRjk3NjI7IiBkPSJNMjU2LjExLDcwLjYyMUgxMzIuNTI0Yy01LjI5NywwLTguODI4LTMuNTMxLTguODI4LTguODI4VjguODI4YzAtNS4yOTcsMy41MzEtOC44MjgsOC44MjgtOC44MjgKCUgyNTYuMTFjNS4yOTcsMCw4LjgyOCwzLjUzMSw4LjgyOCw4LjgyOHY1Mi45NjZDMjY0LjkzOCw2Ny4wOSwyNjEuNDA3LDcwLjYyMSwyNTYuMTEsNzAuNjIxIi8+Cjxwb2x5Z29uIHN0eWxlPSJmaWxsOiNFNkU3RTg7IiBwb2ludHM9IjQ4My44NjIsMjI2Ljg2OSA0NDUuOTAzLDI2NC44MjggMzk2LjQ2OSwyMTQuNTEgNDMzLjU0NSwxNzcuNDM0ICIvPgo8cGF0aCBzdHlsZT0iZmlsbDojRUVEREM4OyIgZD0iTTI0Mi44NjksNDM4LjczMWw0Ni43ODYtMTcuNjU1bC00OS40MzQtNTAuMzE3bC0xNy42NTUsNDYuNzg2CglDMjE3LjI2OSw0MzAuNzg2LDIyOS42MjgsNDQ0LjAyOCwyNDIuODY5LDQzOC43MzEiLz4KPGc+Cgk8cGF0aCBzdHlsZT0iZmlsbDojNzk2ODNCOyIgZD0iTTIzOC40NTUsMTQxLjI0MWgtODguMjc2Yy01LjI5NywwLTguODI4LTMuNTMxLTguODI4LTguODI4YzAtNS4yOTcsMy41MzEtOC44MjgsOC44MjgtOC44MjhoODguMjc2CgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhDMjQ3LjI4MywxMzcuNzEsMjQzLjc1MiwxNDEuMjQxLDIzOC40NTUsMTQxLjI0MXoiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMzAwLjI0OCwxOTQuMjA3aC01Mi45NjVjLTUuMjk3LDAtOC44MjgtMy41MzEtOC44MjgtOC44MjhzMy41MzEtOC44MjgsOC44MjgtOC44MjhoNTIuOTY1CgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhTMzA1LjU0NSwxOTQuMjA3LDMwMC4yNDgsMTk0LjIwN3oiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMjExLjk3MiwxOTQuMjA3SDg4LjM4NmMtNS4yOTcsMC04LjgyOC0zLjUzMS04LjgyOC04LjgyOHMzLjUzMS04LjgyOCw4LjgyOC04LjgyOGgxMjMuNTg2CgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhTMjE3LjI2OSwxOTQuMjA3LDIxMS45NzIsMTk0LjIwN3oiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMzAwLjI0OCwyMjkuNTE3aC0yNi40ODNjLTUuMjk3LDAtOC44MjgtMy41MzEtOC44MjgtOC44MjhzMy41MzEtOC44MjgsOC44MjgtOC44MjhoMjYuNDgzCgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhTMzA1LjU0NSwyMjkuNTE3LDMwMC4yNDgsMjI5LjUxN3oiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMjM4LjQ1NSwyMjkuNTE3aC03MC42MjFjLTUuMjk3LDAtOC44MjgtMy41MzEtOC44MjgtOC44MjhzMy41MzEtOC44MjgsOC44MjgtOC44MjhoNzAuNjIxCgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhTMjQzLjc1MiwyMjkuNTE3LDIzOC40NTUsMjI5LjUxN3oiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMTMyLjUyNCwyMjkuNTE3SDg4LjM4NmMtNS4yOTcsMC04LjgyOC0zLjUzMS04LjgyOC04LjgyOHMzLjUzMS04LjgyOCw4LjgyOC04LjgyOGg0NC4xMzgKCQljNS4yOTcsMCw4LjgyOCwzLjUzMSw4LjgyOCw4LjgyOFMxMzcuODIxLDIyOS41MTcsMTMyLjUyNCwyMjkuNTE3eiIvPgoJPHBhdGggc3R5bGU9ImZpbGw6Izc5NjgzQjsiIGQ9Ik0zMDAuMjQ4LDI2NC44MjhoLTcwLjYyMWMtNS4yOTcsMC04LjgyOC0zLjUzMS04LjgyOC04LjgyOGMwLTUuMjk3LDMuNTMxLTguODI4LDguODI4LTguODI4aDcwLjYyMQoJCWM1LjI5NywwLDguODI4LDMuNTMxLDguODI4LDguODI4QzMwOS4wNzYsMjYxLjI5NywzMDUuNTQ1LDI2NC44MjgsMzAwLjI0OCwyNjQuODI4eiIvPgoJPHBhdGggc3R5bGU9ImZpbGw6Izc5NjgzQjsiIGQ9Ik0xOTQuMzE3LDI2NC44MjhoLTUyLjk2NmMtNS4yOTcsMC04LjgyOC0zLjUzMS04LjgyOC04LjgyOGMwLTUuMjk3LDMuNTMxLTguODI4LDguODI4LTguODI4aDUyLjk2NgoJCWM1LjI5NywwLDguODI4LDMuNTMxLDguODI4LDguODI4QzIwMy4xNDUsMjYxLjI5NywxOTkuNjE0LDI2NC44MjgsMTk0LjMxNywyNjQuODI4eiIvPgoJPHBhdGggc3R5bGU9ImZpbGw6Izc5NjgzQjsiIGQ9Ik0xMDYuMDQxLDI2NC44MjhIODguMzg2Yy01LjI5NywwLTguODI4LTMuNTMxLTguODI4LTguODI4YzAtNS4yOTcsMy41MzEtOC44MjgsOC44MjgtOC44MjhoMTcuNjU1CgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhDMTE0Ljg2OSwyNjEuMjk3LDExMS4zMzgsMjY0LjgyOCwxMDYuMDQxLDI2NC44Mjh6Ii8+Cgk8cGF0aCBzdHlsZT0iZmlsbDojNzk2ODNCOyIgZD0iTTI0Ny4yODMsMzAwLjEzOGgtNTIuOTY2Yy01LjI5NywwLTguODI4LTMuNTMxLTguODI4LTguODI4czMuNTMxLTguODI4LDguODI4LTguODI4aDUyLjk2NgoJCWM1LjI5NywwLDguODI4LDMuNTMxLDguODI4LDguODI4UzI1Mi41NzksMzAwLjEzOCwyNDcuMjgzLDMwMC4xMzh6Ii8+Cgk8cGF0aCBzdHlsZT0iZmlsbDojNzk2ODNCOyIgZD0iTTE1OS4wMDcsMzAwLjEzOEg4OC4zODZjLTUuMjk3LDAtOC44MjgtMy41MzEtOC44MjgtOC44MjhzMy41MzEtOC44MjgsOC44MjgtOC44MjhoNzAuNjIxCgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhTMTY0LjMwMywzMDAuMTM4LDE1OS4wMDcsMzAwLjEzOHoiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMjExLjk3MiwzMzUuNDQ4aC03MC42MjFjLTUuMjk3LDAtOC44MjgtMy41MzEtOC44MjgtOC44MjhzMy41MzEtOC44MjgsOC44MjgtOC44MjhoNzAuNjIxCgkJYzUuMjk3LDAsOC44MjgsMy41MzEsOC44MjgsOC44MjhTMjE3LjI2OSwzMzUuNDQ4LDIxMS45NzIsMzM1LjQ0OHoiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMTA2LjA0MSwzMzUuNDQ4SDg4LjM4NmMtNS4yOTcsMC04LjgyOC0zLjUzMS04LjgyOC04LjgyOHMzLjUzMS04LjgyOCw4LjgyOC04LjgyOGgxNy42NTUKCQljNS4yOTcsMCw4LjgyOCwzLjUzMSw4LjgyOCw4LjgyOFMxMTEuMzM4LDMzNS40NDgsMTA2LjA0MSwzMzUuNDQ4eiIvPgoJPHBhdGggc3R5bGU9ImZpbGw6Izc5NjgzQjsiIGQ9Ik0xODUuNDksMzcwLjc1OWgtMTcuNjU1Yy01LjI5NywwLTguODI4LTMuNTMxLTguODI4LTguODI4czMuNTMxLTguODI4LDguODI4LTguODI4aDE3LjY1NQoJCWM1LjI5NywwLDguODI4LDMuNTMxLDguODI4LDguODI4UzE5MC43ODYsMzcwLjc1OSwxODUuNDksMzcwLjc1OXoiLz4KCTxwYXRoIHN0eWxlPSJmaWxsOiM3OTY4M0I7IiBkPSJNMTMyLjUyNCwzNzAuNzU5SDg4LjM4NmMtNS4yOTcsMC04LjgyOC0zLjUzMS04LjgyOC04LjgyOHMzLjUzMS04LjgyOCw4LjgyOC04LjgyOGg0NC4xMzgKCQljNS4yOTcsMCw4LjgyOCwzLjUzMSw4LjgyOCw4LjgyOFMxMzcuODIxLDM3MC43NTksMTMyLjUyNCwzNzAuNzU5eiIvPgo8L2c+CjxwYXRoIHN0eWxlPSJmaWxsOiNENzVBNEE7IiBkPSJNNTA1LjkzMSwyMDQuOGM3Ljk0NS03Ljk0NSw3Ljk0NS0yMi4wNjksMC0zMC4wMTRsLTE2Ljc3Mi0xNi43NzJjLTkuNzEtOS43MS0yNS42LTkuNzEtMzUuMzEsMAoJbC0xOS40MjEsMTkuNDIxbDUwLjMxNyw1MC4zMTciLz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==);
        background-repeat: no-repeat;
        background-size: contain;
        margin-right: 10px;
    }

    :root .header .info {
        transform: rotate(3deg);
        font-size: 16px;
        padding: 12px 16.8px 12px 16.8px;
        border-top-left-radius: 6px;
        border-top-right-radius: 6px;
        border-bottom-left-radius: 6px;
        border-bottom-right-radius: 16px;
        color: #fff;
        background: #fe7345;
        user-select: none;
        font-weight: bolder;
        margin-top: 20px;
    }

    :root .title {
        font-size: 22px;
        margin-bottom: 18px;
        font-weight: bolder;
        text-align: center;
    }

    :root .form {
        width: 360px;
        height: 30px;
        margin: auto;
    }

    :root .form .form-input {
        width: 250px;
        height: 30px;
        border-bottom: 3px dashed #fe7345;
        flow: horizontal;
    }

    :root .form .form-input input {
        border: none;
        padding: 5px 0 3px;
        font-size: 14px;
        background: transparent;
        width: 100%;
        color: rgba(73, 74, 75, .35);
    }

    :root .form .form-input button {
        position: relative;
        transform: rotate(4deg);
        border-radius: 6px;
        outline: none;
        border: none;
        background: none;
        cursor: pointer;
    }

    :root .form .form-input button .submit {
        position: relative;
        display: block;
        font-size: 16.5px;
        padding: 0.34em 0.84em;
        border: 2px solid #494a4b;
        border-radius: inherit;
        background-color: #fff;
        box-sizing: border-box;
    }

    :root .empty-todos {
        margin-top: 30px;
        text-align: center;
    }

    :root .empty-todos .icon {
        display: inline-block;
        font-size: inherit;
        height: 1em;
        width: 0.75em;
        color: rgba(73, 74, 75, .35);
        margin-right: 12px;
    }

    :root .empty-todos .todos-text {
        font-size: 16px;
        padding-top: 5px;
        color: rgba(73, 74, 75, .45);
    }
}

源码

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

H_四叶草

您的鼓励将是我前进的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值