自学Vue开发Dapp去中心化钱包(三)

news2024/10/6 6:48:03

前言

本篇主要记录学习Vue并实际参与完结web3门户项目的经验和走过的弯路。拖了这么久才来还债,说项目忙那是借口,还是因为个人懒!从自学到实战Vue实际中间就1周的学习熟悉时间,学习不够深就会造成基础不稳,多次推翻重来的情况,从架子搭设到实际页面功能都存在这种情况,说来真是惭愧。最终,算是圆满完工吧。


一、项目框架

1.打包方式

vue新建项目打包方式分2种(其他的方式暂未学习):

1.使用webpack工具

学习时参照了bilibili教学老师的打包方式,也就是上篇文章(自学Vue开发Dapp去中心化钱包(二))介绍的,之后按照这个新建项目开始开发web3门户。

命令如下:

vue init webpack 项目名称

项目结构如下:

2.使用vue-cli工具

命令如下:

vue create 项目名称

项目结构如下:

总结:就学习而言,webpack打包方式新手比较适合,多数参数都能接触到,然对项目而言,再经过学习和调查后发现多数快速搭建大家用的是vue-cli工具。最终web3门户这个项目我使用了vue-cli这种打包方式的项目,结构很明朗。

2.vuex组件

store的结构上篇文章(自学Vue开发Dapp去中心化钱包(二))也介绍过,这里对文章中store的模块化重新做了优化,使其更符合“模块化”这个概念。

结构如下:

这里myStore,user,settings相当于3个不同的模块,存储3组不同的信息分别对应web3相关参数、用户相关参数、系统配置项相关参数。

注:Vuex持久化插件vuex-persistedstate这里主要是为了解决刷新后数据消失的问题,持久化缓存一些全局变量。使用时注意createPersistedState里面应该是模块的参数,比如myStore.account,是myStore模块下的参数account。

myStroe.js

import * as ethers from "ethers";
import {getWethAddress} from "@/config/contracts";
import {getEth_chainId} from "@/methods/common";

const state = {
    //provider对象
    provider: {},
    //合约对象
    contracts: {},
    //签名对象
    signer: {},
    //小狐狸钱包的账户address
    account: '',
    //以太坊网络ID:0x5
    net: '',
    //gas费,后续可能要用
    gasPrice: 0,
    //钱包余额
    balance: '0.0',
    //作为是否链接登录到小狐狸钱包的标志
    isConnectWallet: false,
    //绑卡列表数据,用于下拉框
    accountList: [],
    //交易计数,用于发生交易时同步刷新交易记录列表
    tradeCounter: 0,
}
const mutations = {
    saveProviderStore: (state, provider) => {
        state.provider = provider;
    },
    saveContractsStore: (state, contracts) => {
        state.contracts = contracts;
    },

    saveAccountStore: (state, account) => {
        state.account = account;
    },

    saveBalanceStore: (state, balance) => {
        state.balance = balance;
    },

    saveNetStore: (state, net) => {
        state.net = net;
    },

    saveGasPriceStore: (state, gasPrice) =>  {
        state.gasPrice = gasPrice;
    },
    saveIsConnectWallet: (state, isConnectWallet) =>  {
        state.isConnectWallet = isConnectWallet;
    },
    saveSigner: (state, signer) =>  {
        state.signer = signer;
    },
    saveAccountList: (state, accountList) =>  {
        state.accountList = accountList;
    },
    saveTradeCounter: (state, tradeCounter) =>  {
        state.tradeCounter = tradeCounter;
    },

}

const actions = {
    // 触发保存方法

    SET_PROVIDER: ({ commit }, payload) => {
        commit('saveProviderStore', payload);
    },
    SET_CONTRACTS: ({ commit }, payload) => {
        commit('saveContractsStore', payload);
    },
    SET_ACCOUNT: ({ commit }, payload) => {
        commit('saveAccountStore', payload);
    },
    SET_BALANCE: ({ commit }, payload) => {
        commit('saveBalanceStore', payload);
    },
    SET_NET: ({ commit }, payload) => {
        commit('saveNetStore', payload);
    },
    SET_GAS_PRICE: ({ commit }, payload) => {
        commit('saveGasPriceStore', payload);
    },

    SET_IS_CONNECT_WALLET: ({ commit }, payload) => {
        commit('saveIsConnectWallet', payload);
    },

    SET_SIGNER: ({ commit }, payload) => {
        commit('saveSigner', payload);
    },

    SET_ACCOUNT_LIST: ({ commit }, payload) => {
        commit('saveAccountList', payload);
    },
    SET_TRADE_COUNTER: ({ commit }, payload) => {
        commit('saveTradeCounter', payload);
    },

    async connectWallet({ dispatch }) {
        let web3Provider;
        if (window.ethereum) {
            web3Provider = window.ethereum;
            try {

                //通过
                const addressArray = await web3Provider.request({
                    method: "eth_requestAccounts",
                });

                let address = addressArray[0];
                const obj = {
                    status: "👆🏽 Write a message in the text-field above.",
                    address: address,
                };
                let chainId = await getEth_chainId();
                dispatch("setProvider",{address,chainId});
                dispatch("addWalletListener");
                return obj;
            } catch (err) {
                return {
                    address: "",
                    status: "😥 " + err.message,
                };
            }
        } else {
            return {
                address: "",
                status: (
                    <span>
          <p>
            {" "}
              🦊{" "}
              <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
                ),
            };
        }
    },

    async getCurrentWalletConnected ({ dispatch }) {
        let web3Provider;
        if (window.ethereum) {
            web3Provider = window.ethereum;
            try {
                const addressArray = await web3Provider.request({
                    method: "eth_accounts",
                });
                if (addressArray.length > 0) {
                    let address = addressArray[0];
                    //请求chain写在这里,防止beforeEach时参数还未放入store中
                    let chainId = await getEth_chainId();
                    //vuex dispatch多个参数时使用object对象传递
                    dispatch("setProvider",{address,chainId});
                    dispatch("addWalletListener");

                    return {
                        address: addressArray[0],
                        status: "👆🏽 Write a message in the text-field above.",
                    };
                } else {
                    return {
                        address: "",
                        status: "🦊 Connect to Metamask using the top right button.",
                    };
                }
            } catch (err) {
                return {
                    address: "",
                    status: "😥 " + err.message,
                };
            }
        } else {
            return {
                address: "",
                status: (
                    <span>
          <p>
            {" "}
              🦊{" "}
              <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
                ),
            };
        }
    },
    setProvider({commit},data) {
        let web3Provider;
        if (window.ethereum) {
            web3Provider = window.ethereum;
            const provider = new ethers.providers.Web3Provider(web3Provider);
            const signer = provider.getSigner();
            const contractABI = require("@/config/constants/contract-abi.json");
            const wethAddress = getWethAddress();
            const daiContract = new ethers.Contract(wethAddress, contractABI, provider);

            //先改变isConnectWallet值,后改变account值
            commit('saveNetStore', data.chainId);
            commit('saveIsConnectWallet', true);
            commit('saveAccountStore', data.address);
            commit('saveProviderStore', provider);
            commit('saveContractsStore', daiContract);
            commit('saveSigner', signer);
            //监听区块
            /*provider.on("block", (blockNumber) => {
              // Emitted on every block change
              console.log("blockNumber: " + blockNumber);
            })*/
        }
    },
    addWalletListener({commit}) {
        let web3Provider;
        if (window.ethereum) {
            web3Provider = window.ethereum;
            web3Provider.on('accountsChanged', accounts => {
                //断开链接后,初始化一些值
                if(accounts.length===0){
                    commit('saveIsConnectWallet', false);
                    commit('saveProviderStore', {});
                    commit('saveContractsStore', {});
                    commit('saveSigner', {});
                    commit('saveBalanceStore', '0.0');
                    commit('saveAccountList', []);
                }else{
                    //先改变isConnectWallet值,后改变account值
                    commit('saveIsConnectWallet', true);
                }

                commit('saveAccountStore', accounts[0]);
            });

            web3Provider.on('chainChanged', (chainId) => {
                commit('saveNetStore', chainId);
            });
        }
    },


}

export default {
    state,
    mutations,
    actions
}


getter.js

// 获取最终的状态信息
const getters = {
    provider: state => state.myStore.provider,
    contracts: state => state.myStore.contracts,
    signer: state => state.myStore.signer,
    account: state => state.myStore.account,
    net: state => state.myStore.net,
    gasPrice: state => state.myStore.gasPrice,
    isConnectWallet: state => state.myStore.isConnectWallet,
    accountList: state => state.myStore.accountList,
    tradeCounter: state => state.myStore.tradeCounter,
    token: state => state.user.token,
    avatar: state => state.user.avatar,
    name: state => state.user.name,
    mrspFlag: state => state.user.mrspFlag,
    roles: state => state.user.roles,
    permissions: state => state.user.permissions,
    defaultDecimalPalces: state => state.settings.defaultDecimalPalces,
    tokenName: state => state.settings.tokenName,
    legalTender: state => state.settings.legalTender,
    legalDecimalPalces: state => state.settings.legalDecimalPalces,
}
export default getters

index.js

import Vue from 'vue';
import Vuex from 'vuex';
import myStore from '@/store/modules/myStore';
import user from "@/store/modules/user";
import settings from '@/store/modules/settings';
import getters from '@/store/getters';
import createPersistedState from 'vuex-persistedstate';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    myStore,user,settings,
  },
  getters,
  plugins: [
    createPersistedState({
      paths: ['myStore.isConnectWallet', 'myStore.account', 'myStore.net']
    }),
  ],
});

export default store

二、实战经验

1.router

  1. 由于项目做了改版,存在多级子路由,这里路由路径要注意的是子路由带/和不带/是有区别的。

比如:

{
    path:'/home',
    meta: {authRequired: true},
    component: Home,
    children: [
        {path:'/', redirect: 'wallet'},
        {
            path:'wallet',
            component: Wallet,
            children: [
            {path:'/', redirect: 'balances'},
            {
                path:'balances',
                component:Balances

            },{
                path:'transfer',
                component: Transfer
            },{
                path:'swap',
                component: Swap
            },{
                path:'receive',
                component: Receive
            }]
        },
    ]
  }

如果这里的path:'balances'改为path:'/balances',子路由前面加/ ,加上/就不会拼接上父级路由的path路径,地址则为http://localhost:8080/#/balances,这样就造成点击菜单时没法联动,点击父菜单子菜单也不会切换。

完整的index.js

import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import Home from '@/components/Home'
import Wallet from '@/components/pages/Wallet'
import Balances from "@/views/wallet/Balances";
import Transfer from "@/views/wallet/Transfer";
import Receive from "@/views/wallet/Receive";
import Swap from "@/views/wallet/Swap";
import Bridge from '@/components/pages/Bridge'
import Deposit from "@/views/bridge/Deposit";
import Withdraw from "@/views/bridge/Withdraw";
import Card from '@/components/pages/Card'
import Bongloy from "@/views/card/Bongloy";

Vue.use(Router);


const originalPush = Router.prototype.push
Router.prototype.push = function push (location) {
  return originalPush.call(this, location).catch(err => err)
}

let routes =[
  {path: "*", redirect: "/"},
  {
    path:'/',
    name:"login",
    component: Login,
  },
  {
    path:'/home',
    meta: {authRequired: true},
    component: Home,
    children: [
        {path:'/', redirect: 'wallet'},
        {
            path:'wallet',
            component: Wallet,
            children: [
            {path:'/', redirect: 'balances'},
            {
                path:'balances',
                component:Balances

            },{
                path:'transfer',
                component: Transfer
            },{
                path:'swap',
                component: Swap
            },{
                path:'receive',
                component: Receive
            }]
        },{
            path:'bridge',
            component: Bridge,
            children: [
            {path:'/', redirect: 'deposit'},
            {
                path:'deposit',
                component:Deposit

            },{
                path:'withdraw',
                component: Withdraw
            }]
        },{
            path:'card',
            component: Card,
            children: [
            {path:'/', redirect: 'bongloy'},
            {
                path:'bongloy',
                component:Bongloy

            }]
        }
    ]
  },
];

export default new Router({
    mode: 'history', // 去掉url中的#
    routes:routes
})

菜单跳转时path

<router-link to="balances">{{$t(item.navname)}}</router-link>

效果:

  1. router里面的meta: {authRequired: true} 这个authRequired参数是做拦截路由的,当请求的路由时,验证是否需要登录认证。

需要再main.js里增加如下代码:

//拦截路由,当请求的路由时,验证是否需要登录认证,并验证当前是否已连接小狐狸且网络是0x5通道,如果不是则进入登录页面;
//authRequired是router中自定义的参数
router.beforeEach((to, from, next) => {
  if (to.matched.some(res => res.meta.authRequired)) { // 验证是否需要登陆
    if (store.getters.account&&store.getters.net===getChainId()) { // 查询本地存储信息是否已经登陆且通道正确
      next();
    } else {
      //未登录则跳转至login页面
      next({path: '/', });
    }
  } else {
    next();
  }
})

效果如下

2.父子方法调用

  1. 父页面调用子页面方法用this.$refs

父页面

...
<--引入的子页面-->
<my-temp-page ref="myTempPageRef" >
...

methods:{
    initEdit(row){
      this.$refs.myTempPageRef.handleUpdate(row);
    },
},

子页面

myTempPage.vue

methods:{
    handleUpdate(){
      //TODO dosomething
    },
  },

  1. 子页面调用父页面方法用this.$emit()

父页面

...
<Success  @toBack="onNotifyBack"/>
...
methods:{
    onNotifyBack(){
      //dosomething
    },
  },

子页面

success.vue

...
<button @click="toBack" class="reset-button" variant="outlined" data-testid="transaction-receipt-reset">{{ $t('lang.swap41') }}</button>
...
methods:{
    toBack(){
      this.$emit("toBack");
    },
  },

3.store的使用

  1. 页面使用语法糖获取store属性

computed: {
      ...mapState({
        balance: state => state.myStore.balance,
        address: state => state.myStore.account,
      }),
    },

...mapState是语法糖。

取值时注意不能是state.account,因为vuex结构修改成多个模块,取值时要加上定义的模块,比如state.myStore.account、state.user.email等等

  1. 页面对store属性变更

这时这里的SET_TRADE_COUNTER方法名前不加模块名

this.$store.dispatch('SET_TRADE_COUNTER', this.tradeCounter+1);
  1. 页面调用store定义的方法

同样的方法名前不加模块名

this.$store.dispatch('connectWallet').then((res) => {
  //TODO
});
  1. 在user(其他)模块中使用另外一个模块myStore里的方法

使用dispatch,参数中增加{root: true}

user.js
...
methodName({ dispatch }) {
    ...
    commit('SET_EMAIL', res.data.email)//调用自己模块更新属性方法
    dispatch('SET_ACCOUNT', 参数值,{root: true});//调用myStore里的更新account属性的方法
}

4.监听数据变化

vue监听某个值变化使用watch。

如下是监听store某个属性的变化,需是有变化时才会监听到。

computed: {
    storeTradeCounter(){
      return this.$store.getters.tradeCounter;//获取属性
    }
  },

...

watch:{
    //监听有交易发生时,刷新列表
    storeTradeCounter (newValue,oldValue) {

      //交易发送时试试修改store里的绑卡余额及钱包余额
      //dosomething
    },

  },

5.input框监听

监听输入框只能输入2位小数的数字,其他均无法输入

...
<input v-model="formData.amount" type="text" name="amount" placeholder="0.00"
                 @input="handleAmountInput(formData.amount)">
...

methods:{
    handleAmountInput(value) {
        //大于等于0,且只能输入2位小数
        let val=value.replace(/^\D*([0-9]\d*\.?\d{0,6})?.*$/,'$1');
        if(val==null||val==undefined||val==''){
          val=''
        }
        this.formData.amount = val;
      },
}

6.vue生成二维码

  1. 引入vue-qr

npm install vue-qr --save
  1. 使用

import VueQr from 'vue-qr'
...
components:{
    VueQr,
  },
...
<vue-qr
          :text="this.account"
          :size="148"
          logoSrc=""
          :logoScale="0.2">
      </vue-qr>

7.小狐狸3d logo

  1. 下载小狐狸钱包3d logo资源

本人在github上和其他网站均找了许久,最后融合到项目整了几次,总算总结出来具体哪些文件可用,并且好用的。资源如下:

Metamask小狐狸3d Logo

  1. 代码中使用

将metamask-logo放入utils下,
package.json文件中引入这2个

"gl-mat4": "1.1.4",
"gl-vec3": "1.0.3"

然后npm install

使用

...
<div id="logo-container" class="meta-mask-fox mr-2 h-10 w-auto md:h-16" ></div>
...

data(){
    return {
      viewer: null,
    }
  },

mounted () {
    //加載3D小狐狸logo
    const ModelViewer = require('@/utils/metamask-logo');
    this.viewer = ModelViewer({
      // Dictates whether width & height are px or multiplied
      pxNotRatio: true,
      width: 60,
      height: 60,
      // To make the face follow the mouse.
      followMouse: true,
      // head should slowly drift (overrides lookAt)
      slowDrift: false,
    });

    var container = document.getElementById('logo-container');
    container.appendChild(this.viewer.container);

  },
destroyed() {
    if(this.viewer!==null){
      this.viewer.setFollowMouse(true);
      this.viewer.stopAnimation();
    }
  },
  1. 效果

三、记录用到的方

1.金额格式化(千分位)

效果是:9775格式化为9,775.500000

/**
 * @description 格式化金额
 * @param number:要格式化的数字
 * @param decimals:保留几位小数 默认0位
 * @param decPoint:小数点符号 默认.
 * @param thousandsSep:千分位符号 默认为,
 */
export const formatMoney = (number, decimals = 0, decPoint = '.', thousandsSep = ',') => {
    number = (number + '').replace(/[^0-9+-Ee.]/g, '')
    let n = !isFinite(+number) ? 0 : +number
    let prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
    let sep = (typeof thousandsSep === 'undefined') ? ',' : thousandsSep
    let dec = (typeof decPoint === 'undefined') ? '.' : decPoint
    let s = ''
    let toFixedFix = function (n, prec) {
        let k = Math.pow(10, prec)
        return '' + Math.ceil(n * k) / k
    }
    s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.')
    let re = /(-?\d+)(\d{3})/
    while (re.test(s[0])) {
        s[0] = s[0].replace(re, '$1' + sep + '$2')
    }
    if ((s[1] || '').length < prec) {
        s[1] = s[1] || ''
        s[1] += new Array(prec - s[1].length + 1).join('0')
    }
    return s.join(dec)
}

//去除千分位中的‘,'
export const delcommafy = function (num) {
    if (!num) return num;
    num = num.toString();
    num = num.replace(/,/gi, "");
    return Number(num);
};

使用方法

import {formatMoney} from "@/utils/fixednumber";
...
formatMoney(‘9775’, 6);//格式化成小数点6位带千分位的货币金额9,775.500000
...

2.校验

// utils.js

// 全局函数
export function validateMobile(str) {
    // 检查手机号码格式
    return /^((13[0-9])|(14[5-9])|(15([0-3]|[5-9]))|(16[6-7])|(17[1-8])|(18[0-9])|(19[1|3])|(19[5|6])|(19[8|9]))\d{8}$/.test(
        str,
    );
}

export function validateEmail(str) {
    // 检查邮箱格式
    return /^([A-Za-z0-9_\-.])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,4})$/.test(str);
}

export function validateMoney(str) {
    // 检查金额格式
    return /^([1-9]\d*(\.\d{1,2})?|([0](\.([0][1-9]|[1-9]\d{0,1}))))$/.test(str);
}

export function validateBonMoney(str) {
    // 检查金额格式
    return /^([1-9]\d*(\.\d{1,6})?|([0](\.([0][1-9]|[1-9]\d{0,1}))))$/.test(str);
}

export function validatePhone(str) {
    // 检查电话格式
    return /^(0\d{2,4}-)?\d{8}$/.test(str);
}

export function validateQQ(str) {
    // 检查QQ格式
    return /^[1-9][0-9]{4,}$/.test(str);
}

// 检查验证码格式
export function validateSmsCode(str) {
    return /^\d4$/.test(str);
}
// 校验 URL
export function validURL(url) {
    const reg =
        /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
    return reg.test(url)
}

// 校验特殊字符
export function specialCharacter(str) {
    const reg = new RegExp(
        // eslint-disable-next-line quotes
        "[`~!@#$^&*()=|{}':;',\\[\\]<>《》/?~!@#¥……&*()——|{}【】‘;:”“'。,、? ]"
    )
    return reg.test(str)
}

/**
 * @param value
 * 测试密码是否满足条件,包括四种类型
 * 密码6-20位,必须包含大写字母,小写字母,数字及特殊字符
 */
export function validPassword(value) {
    const num = /^.*[0-9]+.*/
    const low = /^.*[a-z]+.*/
    const up = /^.*[A-Z]+.*/
    const spe = /^.*[^a-zA-Z0-9]+.*/
    const passLength = value.length > 5 && value.length < 21
    return num.test(value) && low.test(value) && up.test(value) && spe.test(value) && passLength
}

3.复制到粘贴板

export function copyToClipboard(content) {
    if (window.clipboardData) {
        window.clipboardData.setData('text', content);
    } else {
        (function (content) {
            document.oncopy = function (e) {
                e.clipboardData.setData('text', content);
                e.preventDefault();
                document.oncopy = null;
            }
        })(content);
        document.execCommand('Copy');
    }
};

四、待继续整理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新的一年里技术管理者(工作者)们如何做好技术规划?

技术管理者的主要工作 技术管理者的主要工作是带人、做事、看方向: 带人是指团队人员能力的培养、团队梯队的建设等等;做事是指完成各项业务需求;看方向是指明确团队未来的发展方向和目标。我们经常会辩论“做管理了还要不要写代码”这个话题,而“写代码”只是“做事”里面…

Java——Maven项目管理

目录Maven1&#xff0c;Maven1.1 Maven简介1.1.1 Maven模型1.1.2 仓库1.3 Maven基本使用1.3.1 Maven 常用命令1.3.2 Maven 生命周期1.4.2 Maven 坐标详解1.4.3 IDEA 创建 Maven项目1.4.4 IDEA 导入 Maven项目1.5 依赖管理1.5.1 使用坐标引入jar包1.5.2 依赖范围Maven 目标 能够…

服务器与客户端的一般套路

WinSocket 套接字 ————服务器与客户端的一般套路 一、开发环境 IDE: Red Panda Dev-C 6.5编程语言&#xff1a;C语言库&#xff1a;winsock2.h 二、套接字工作流程图 注意&#xff1a;这个工作流程图非常重要&#xff0c;后面的代码编写基本就是这个逻辑 三、服务器各…

前端基于DOM或者Canvas实现页面水印

&#x1f431; 个人主页&#xff1a;不叫猫先生 &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域新星创作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &#x1f4ab;系列专栏&#xff1a;vue3从入门…

AtCoder Beginner Contest 287(A~E)

比赛名称&#xff1a;UNIQUE VISION Programming Contest 2023 New Year (AtCoder Beginner Contest 287) 比赛链接&#xff1a;AtCoder Beginner Contest 287 目录 A - Majority B - Postal Card C - Path Graph? D - Match or Not E - Karuta A - Majority 问字…

工作和学习中都能用到的5款实用软件

如今&#xff0c;工作和学习都离不开电脑&#xff0c;所以电脑里的软件自然也是必不可少的&#xff0c;但是电脑软件那么多&#xff0c;不可能每个都装上吧&#xff0c;所以我们要装好用的、实用的&#xff0c;下面给大家分享5款好用到爆的软件&#xff0c;很多懂电脑的人都在用…

【算法】雪花算法

一.特点 1.全局唯一性&#xff1a;对于大数据量的分库分表场景&#xff0c;例如水平分表需要保证主键id的全局唯一性。 2.趋势递增&#xff1a;整体的id趋势是递增的&#xff0c;不是单调递增。 3.不规则性&#xff1a;id不连续&#xff0c;无规则&#xff0c;不规则。 4.包含…

乾元通多卡聚合通信设备保障生态环境监测网络

针对目前城市大气环境监测网格化建设&#xff0c;推出的新一代城市网格化大气环境监测系统&#xff0c;可以实现城市区域环境多维一体化监测管理&#xff0c;该设备主要用于监测大气环境中的PM10、TSP、PM2.5等颗粒物浓度&#xff0c;还可以实现环境监控&#xff0c;测噪音、大…

Node.JS 安装配置 | 安装排错解析

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Node.js下载 Node.js官方下载地址 官方下载如果慢&#xff0c;请用如下地址下载&#xff1a; Node.js 中文网 根据自己计算机配置下载 Next Next 安装地址可更换 Next…

年后找工作必看的自动化测试面试宝典,一般人我不告诉他

目录 前言 1.1 什么是 API&#xff1f; 1.2 什么是 API 测试&#xff1f; 1.3 常见的 API 测试类型有哪些&#xff1f; 1.4 列举 API 测试中使用的一些常用协议&#xff1f; 1.9API 常见测试有哪些&#xff1f; 1.10API 测试有哪些优势&#xff1f; 1.11API 测试中究竟…

【PHP 随记】—— Composer 安装项目以及项目的扩展

&#x1f449;总目录&#x1f448;\large\colorbox{skyblue}{&#x1f449;总目录&#x1f448;}&#x1f449;总目录&#x1f448;​ 文章目录1、Composer 安装项目① 项目安装示例② 相关问题解决③ 框架搜索指南2、Composer 安装项目的扩展使用 Composer 更轻松方便地安装 P…

RV1126笔记二十五:区域入侵检测

若该文为原创文章,转载请注明原文出处。 一、 前言 区域入侵检测是通过识别目标之后获取目标坐标位置,判断目标是否在所标定的区域内出现,常常被用在电子围栏,不安全区域入侵检测,智慧城市,安防监控等领域。具体使用场景有,在标定的区域内不能抽烟,进入工地区域必须佩…

Java快速上手Properties集合类

概念 Java中的Properties文件是一种配置文件&#xff0c;主要用于表达配置信息&#xff0c;格式是文本文件。该类主要用于读取Java的配置文件&#xff0c;也可以对properties文件进行修改 属性配置&#xff1a;以“键值”的方式书写一个属性的配置信息 注 释&#xff1a;在pro…

我建议大学生看一下阿凡达2,对离校后很有帮助

网上评价阿凡达2说剧情拉胯&#xff0c;但我却通过他的剧情看到了当代大学生的一些影子&#xff0c;尤其是对于离校的毕业生来说&#xff0c;相对吻合的场景还是很多的&#xff0c;让我来分析一下。 目录 阿发达2关键剧情点 1、在校期间 2、终于要离校了 3、离开学校&…

Flutter 安装踩坑记录 HTTP host https://pub.dev/ is not reachable.

Flutter安装踩坑记录安装Flutter SDK参考链接安装Flutter SDK Windows安装 flutter官网下载flutter sdk包 2.解压到C盘除去program file相关的目录&#xff08;最好自己创建一个新的目录&#xff0c;因为放在program file等目录需要特殊的权限&#xff0c;会出现问题&#xf…

【解读】CSA CISO研究报告: 零信任的部署现状及未来展望

零信任理念已经存在了十多年。然而对需要保护IT系统的企业来说&#xff0c;这个术语及其实施方式的关注度显著增加。 国际云安全联盟CSA发布调研报告《CISO研究报告&#xff1a;零信任的部署现状及未来展望》&#xff0c;本次调研的目的是使大家更好地理解组织机构内部的零信任…

【My Electronic Notes系列——放大电路与集成运算放大器】

目录 序言&#xff1a; &#x1f3c6;&#x1f3c6;人生在世&#xff0c;成功并非易事&#xff0c;他需要破茧而出的决心&#xff0c;他需要永不放弃的信念&#xff0c;他需要水滴石穿的坚持&#xff0c;他需要自强不息的勇气&#xff0c;他需要无畏无惧的凛然。要想成功&…

电脑怎么录制视频?如何录制页面上指定区域

你知道电脑怎么录制视频吗&#xff1f;有时候我们并不需要录制整个电脑屏幕&#xff0c;只需要对特定区域&#xff0c;这个时候该怎么办呢&#xff1f;其实我们只需要一款既支持全屏录制又支持区域录制的录屏工具&#xff0c;就可以轻松搞定。下面小编教您如何录制电脑上指定的…

azg携手Bubs出席2022未来母婴大会主题专场,探讨母婴品牌长红发展路径

近日&#xff0c;由母婴行业观察主办的“2022第八届未来母婴大会”在上海成功举办&#xff0c;来自行业内的近百位资深嘉宾受邀参与&#xff0c;共襄盛会。而在以“新周期 新机遇&#xff0c;母婴品牌可持续生长路径”的2022未来母婴大会主题专场中&#xff0c;Bubs中国区资深渠…

梦熊杯-十二月月赛-白银组题解-D.智慧

D. Problem D.智慧&#xff08;wisdom.cpp&#xff09; 内存限制&#xff1a;256 MiB 时间限制&#xff1a;1000 ms 标准输入输出 题目类型&#xff1a;传统 评测方式&#xff1a;文本比较 题目描述: 「须弥」是「智慧」的国度。 布耶尔认为&#xff0c;如果能只使用加…