XiNine

XiNine 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

记录从事开发以来,遇到的一些问题...

个人动态

XiNine 收藏了文章 · 11月20日

nodejs服务器部署教程二,把vue项目部署到线上

nodejs服务端部署教程一,https://segmentfault.com/a/11...
这篇文章主要介绍如何在服务端跑vuejs的项目,如果上一篇教程你成功输出了hello world,那这一篇更简单
首先你要有一个已经能在本地跑的基于vuejs的项目,我就以之前写的仿饿了么的项目为例来部署,如果你还没有已经写好的项目,可以用我的这个项目来学习,https://github.com/wmui/vue-elm
这次部署就用最古老最省心的方法,ftp来上传项目

项目打包

npm run build后会有一个dist目录,这个文件夹就是我们要部署上线的项目

写一个小脚本

如果你会点nodejs,脚本就很好理解了,下面是app.js启动脚本的内容

const fs = require('fs');
const path = require('path');
const express = require('express');
const app = express();
// 模拟数据,api服务
var appData = require('./data.json');
var seller = appData.seller;
var goods = appData.goods;
var ratings = appData.ratings;
// api接口
var apiRoutes = express.Router();
apiRoutes.get('/seller', function(req, res) {
    res.json({
        erron: 0,
        data: seller
    })
});

apiRoutes.get('/goods', function(req, res) {
    res.json({
        erron: 0,
        data: goods
    })
});

apiRoutes.get('/ratings', function(req, res) {
    res.json({
        erron: 0,
        data: ratings
    })
});
app.use('/api', apiRoutes);
app.use(express.static(path.resolve(__dirname, './dist')))

app.get('*', function(req, res) {
    const html = fs.readFileSync(path.resolve(__dirname, './dist/index.html'), 'utf-8')
    res.send(html)
})
app.listen(8082);

简单解释一下上面的代码,由于项目需要些数据,我的模拟数据都在data.json这个文件里,所有简单的写了三个路由,对应获取到模拟数据。然后把dist目录静态出来,读取dist目录下的index.html(因为是单页应用,只有这一个html文件),监听8082端口

我们先在本地把要上传的文件都准备好:
我们的这个脚本使用了express框架,所以我们可以生成一个package.json,把依赖项添加进去

{
  "name": "vue-elm-dist",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.15.3"
  }
}

完成以上操作,我们要上传的文件项目大概长这样
新建一个文件夹如elm,把整个项目通过ftp上传到根目录www文件夹下

启动服务

登陆到你的服务端,cd到elm文件夹,执行npm install 安装依赖,然后pm2 start app.js就成功启动服务了
现在通过ip加端口形式能正常访问,但是如果想通过域名访问就需要配置nginx映射

nginx 端口映射配置

首先你需要把一个二级域名解析到你的主机ip,比如我使用的elm.86886.wang这个二级域名
配置文件和之前的基本一致

upstream elm {
    server 127.0.0.1:8082;
}

server {
    listen 80;
    server_name elm.86886.wang;

    location / {
        proxy_set_header Host  $http_host;
        proxy_set_header X-Real-IP  $remote_addr;  
        proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header X-Nginx-proxy true;
        proxy_pass http://elm;
        proxy_redirect off;
    }
}

我把它命名为elm-8082.conf
然后通过ftp上传到/etc/nginx/conf.d/目录下
执行sudo nginx -s reload重启nginx服务器,过个十分钟就应该能正常访问了 点击访问

查看原文

XiNine 赞了文章 · 11月20日

nodejs服务器部署教程二,把vue项目部署到线上

nodejs服务端部署教程一,https://segmentfault.com/a/11...
这篇文章主要介绍如何在服务端跑vuejs的项目,如果上一篇教程你成功输出了hello world,那这一篇更简单
首先你要有一个已经能在本地跑的基于vuejs的项目,我就以之前写的仿饿了么的项目为例来部署,如果你还没有已经写好的项目,可以用我的这个项目来学习,https://github.com/wmui/vue-elm
这次部署就用最古老最省心的方法,ftp来上传项目

项目打包

npm run build后会有一个dist目录,这个文件夹就是我们要部署上线的项目

写一个小脚本

如果你会点nodejs,脚本就很好理解了,下面是app.js启动脚本的内容

const fs = require('fs');
const path = require('path');
const express = require('express');
const app = express();
// 模拟数据,api服务
var appData = require('./data.json');
var seller = appData.seller;
var goods = appData.goods;
var ratings = appData.ratings;
// api接口
var apiRoutes = express.Router();
apiRoutes.get('/seller', function(req, res) {
    res.json({
        erron: 0,
        data: seller
    })
});

apiRoutes.get('/goods', function(req, res) {
    res.json({
        erron: 0,
        data: goods
    })
});

apiRoutes.get('/ratings', function(req, res) {
    res.json({
        erron: 0,
        data: ratings
    })
});
app.use('/api', apiRoutes);
app.use(express.static(path.resolve(__dirname, './dist')))

app.get('*', function(req, res) {
    const html = fs.readFileSync(path.resolve(__dirname, './dist/index.html'), 'utf-8')
    res.send(html)
})
app.listen(8082);

简单解释一下上面的代码,由于项目需要些数据,我的模拟数据都在data.json这个文件里,所有简单的写了三个路由,对应获取到模拟数据。然后把dist目录静态出来,读取dist目录下的index.html(因为是单页应用,只有这一个html文件),监听8082端口

我们先在本地把要上传的文件都准备好:
我们的这个脚本使用了express框架,所以我们可以生成一个package.json,把依赖项添加进去

{
  "name": "vue-elm-dist",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.15.3"
  }
}

完成以上操作,我们要上传的文件项目大概长这样
新建一个文件夹如elm,把整个项目通过ftp上传到根目录www文件夹下

启动服务

登陆到你的服务端,cd到elm文件夹,执行npm install 安装依赖,然后pm2 start app.js就成功启动服务了
现在通过ip加端口形式能正常访问,但是如果想通过域名访问就需要配置nginx映射

nginx 端口映射配置

首先你需要把一个二级域名解析到你的主机ip,比如我使用的elm.86886.wang这个二级域名
配置文件和之前的基本一致

upstream elm {
    server 127.0.0.1:8082;
}

server {
    listen 80;
    server_name elm.86886.wang;

    location / {
        proxy_set_header Host  $http_host;
        proxy_set_header X-Real-IP  $remote_addr;  
        proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header X-Nginx-proxy true;
        proxy_pass http://elm;
        proxy_redirect off;
    }
}

我把它命名为elm-8082.conf
然后通过ftp上传到/etc/nginx/conf.d/目录下
执行sudo nginx -s reload重启nginx服务器,过个十分钟就应该能正常访问了 点击访问

查看原文

赞 49 收藏 108 评论 19

XiNine 回答了问题 · 11月16日

div用margin 0 auto居中,怎么在向左偏移100PX

1.margin-left:-100px;
2.transform:translateX(-100px);

关注 3 回答 3

XiNine 发布了文章 · 11月6日

vue两个条件对同一份数据进行过滤处理

image

说明:
为了避免不必要的 http 请求,下拉筛选这些多频操作,改为本地处理。

场景:
1.废物名称:
(1)下拉列表数据根据表格的 “废物名称” 获取到;
(2)当下拉选项为 “全部” 时返回最原始的数据;

2.容器标签:
(1)数据要根据 “废物名称” 下拉列表的选项决定;
(2)当选项为 “全部” 时返回表格中的容器标签数据;
(3)当选项不为 “全部” 时返回不为 “全部” 那项的标签列表数据;
(4)当自身选项为 “全部” 时返回 “废物名称” 当前不为 “全部” 选项的 “容器标签” 数据;
(5)当选项为 “全部” 时,自身选项也为 “全部” 时返回最原始的表格数据;

data

data() {
    return {
      select: {
        selevalue2: "全部", //废物名称默认值
        selevalue3: "全部", //容器标签默认值
      },
      selectArr2: [], //废物名称
      selectArr3: [], //容器标签
      tableData: [], //绑定的表格数据
      tableData2: [], //表格深拷贝数据
      tableData3: [], //“废物名称”选项不等于“全部”的表格数据
    };
  },

computed

/* 废物名称 */
    getWasteName() {
      const wasteName = Array.from(
        new Set(this.tableData2.map((item) => item.wasteName))
      ).map((item) => {
        return { value: item };
      });
      wasteName.unshift({ value: "全部" });
      return wasteName;
    },

watch

watch:{
   /* 监听废物名称选项 */
    "select.selevalue2"(val) {
      if (val == "全部") {
        this.getTableData(true, this.tableData2);
        return;
      }
      const wasteName = this.tableData2.filter((item) => {
        return item.wasteName == val;
      });
      this.getTableData(false, wasteName);
      this.tableData = wasteName;
      this.tableData3 = wasteName; //废物名称不是“全部”时的表格数据
    },
    
    /*监听容器标签选项*/
    "select.selevalue3"(val) {
      const { selevalue2 } = this.select;
      if (val == "全部") {
        if (selevalue2 != "全部") {
          this.tableData = this.tableData3;
          this.getTableData(false, this.tableData3);
          return;
        }
        if (selevalue2 == "全部") {
          this.getTableData(true, this.tableData2);
          return;
        } else {
          this.getTableData(true, this.tableData);
          return;
        }
      }
      const wasteName = this.tableData.filter((item) => {
        return item.epc == val;
      });
      this.getTableData(false, wasteName);
      this.tableData = wasteName;
    },
  },

methods

/* 处理数据与容器标签 */
    getTableData(status = true, data) {
      if (status) this.tableData = this.tableData2;
      const wasteLabel = Array.from(new Set(data.map((item) => item.epc))).map(
        (item) => {
          return { value: item };
        }
      );
      wasteLabel.unshift({ value: "全部" });
      this.selectArr3 = wasteLabel;
    },
查看原文

赞 1 收藏 1 评论 0

XiNine 赞了文章 · 11月6日

Javascript算法——快速排序

常见的内部排序算法有:插入排序、希尔排序选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。这里主要介绍快速排序

一图胜千言:

图片描述

1. 算法描述

快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高,因此经常被采用,再加上快速排序思想----分治法也确实实用。快速排序是一种既不浪费空间又可以快一点的排序算法。

2. 算法步骤

  • 先从数列中取出一个数作为“基准”。
  • 分区过程:将比这个“基准”大的数全放到“基准”的右边,小于或等于“基准”的数全放到“基准”的左边。
  • 再对左右区间重复第二步,直到各区间只有一个数。

图片描述

3. 算法实现

var quickSort = function(arr) {
    if (arr.length <= 1) { return arr; }
    var pivotIndex = Math.floor(arr.length / 2);   //基准位置(理论上可任意选取)
    var pivot = arr.splice(pivotIndex, 1)[0];  //基准数
    var left = [];
    var right = [];
    for (var i = 0; i < arr.length; i++){
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    return quickSort(left).concat([pivot], quickSort(right));  //链接左数组、基准数构成的数组、右数组
};
查看原文

赞 103 收藏 155 评论 11

XiNine 发布了文章 · 11月6日

ECharts饼图隐藏数据为0的数据

思路:
1.每一项数据添加默认项 show 为 true
label: { show: true },
labelLine: { show: true }

2.在遍历 data 的数据为0的就把 show 赋值为 false

/* 预警事件 */
    initCensus() {
      this.census = {
        tooltip: {
          trigger: "item",
          formatter: `{a} <br/>{b} <br/> {c} ({d}%)`,
        },
        series: [
          {
            name: "",
            type: "pie",
            radius: "60%",
            center: ["50%", "50%"],
            data: [
              {
                value: this.ChartData["预警"]["贮存时间"],
                name: "贮存时间",
                itemStyle: { color: "#2498ff" },
                label: { show: true },//添加项
                labelLine: { show: true },//添加项
              },
              {
                value: this.ChartData["预警"]["可燃气体"],
                name: "可燃气体",
                itemStyle: { color: "#18e3ff" },
                label: { show: true },
                labelLine: { show: true },
              },
              {
                value: this.ChartData["预警"]["TVOC"],
                name: "TVOC",
                itemStyle: { color: "#1fb4ff" },
                label: { show: true },
                labelLine: { show: true },
              },
            ].sort(function (a, b) {
              return a.value - b.value;
            }),
            roseType: "radius",
            label: {
              position: "outer",
              alignTo: "none",
              color: "#f1f1f1",
            },
            labelLine: {
              lineStyle: {
                color: "#f1f1f1",
              },
              smooth: 0.1,
              length: 10,
              length2: 12,
            },
            animationType: "scale",
            animationEasing: "elasticOut",
            animationDelay: function (idx) {
              return Math.random() * 200;
            },
          },
        ],
      };
      this.setEchartsHide(this.census.series[0].data);
    },
 /* 隐藏数据为0的数据 */
    setEchartsHide(data) {
      const Arr = data.map((item) => item.value).join("");
      //如果每项都为(图表为消失)0则不隐藏
      if (+Arr) {
        data.forEach((item) => {
          if (!item.value) {
            item.label.show = false;
            item.labelLine.show = false;
          }
        });
      }
    },
查看原文

赞 0 收藏 0 评论 0

XiNine 回答了问题 · 10月29日

echarts中百度地图使用问题

什么项目直接使用百度,或者高德地图无法完成需求吗

关注 1 回答 2

XiNine 赞了文章 · 10月23日

Vue 3 的组合 API 如何请求数据?

前言

之前在学习 React Hooks 的过程中,看到一篇外网文章,通过 Hooks 来请求数据,并将这段逻辑抽象成一个新的 Hooks 给其他组件复用,我也在我的博客里翻译了一下:《在 React Hooks 中如何请求数据?》,感兴趣可以看看。虽然是去年的文章,在阅读之后一下子就掌握了 Hooks 的使用方式,而且数据请求是在业务代码中很常用的逻辑。

Vue 3 已经发布一段时间了,其组合 API 多少有点 React Hooks 的影子在里面,今天我也打算通过这种方式来学习下组合 API。

项目初始化

为了快速启动一个 Vue 3 项目,我们直接使用当下最热门的工具 Vite 来初始化项目。整个过程一气呵成,行云流水。

npm init vite-app vue3-app
# 打开生成的项目文件夹
cd vue3-app
# 安装依赖
npm install
# 启动项目
npm run dev

我们打开 App.vue 将生成的代码先删掉。

组合 API 的入口

接下来我们将通过 Hacker News API 来获取一些热门文章,Hacker News API返回的数据结构如下:

{
  "hits": [
    {
      "objectID": "24518295",
      "title": "Vue.js 3",
      "url": "https://github.com/vuejs/vue-next/releases/tag/v3.0.0",
    },
    {...},
    {...},
  ]
}

我们通过 ui > li 将新闻列表展示到界面上,新闻数据从 hits 遍历中获取。

<template>
  <ul>
    <li
      v-for="item of hits"
      :key="item.objectID"
    >
      <a :href="item.url">{{item.title}}</a>
    </li>
  </ul>
</template>

<script>
import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({
      hits: []
    })
    return state
  }
}
</script>

在讲解数据请求前,我看先看看 setup() 方法,组合 API 需要通过 setup() 方法来启动,setup() 返回的数据可以在模板内使用,可以简单理解为 Vue 2 里面 data() 方法返回的数据,不同的是,返回的数据需要先经过 reactive() 方法进行包裹,将数据变成响应式。

组合 API 中请求数据

在 Vue 2 中,我们请求数据时,通常需要将发起请求的代码放到某个生命周期中(createdmounted)。在 setup() 方法内,我们可以使用 Vue 3 提供的生命周期钩子将请求放到特定生命周期内,关于生命周期钩子方法与之前生命周期的对比如下:

生命周期

可以看到,基本上就是在之前的方法名前加上了一个 on,且并没有提供 onCreated 的钩子,因为在 setup() 内执行就相当于在 created 阶段执行。下面我们在 mounted 阶段来请求数据:

import { reactive, onMounted } from 'vue'

export default {
  setup() {
    const state = reactive({
      hits: []
    })
    onMounted(async () => {
      const data = await fetch(
        'https://hn.algolia.com/api/v1/search?query=vue'
      ).then(rsp => rsp.json())
      state.hits = data.hits
    })
    return state
  }
}

最后效果如下:

Demo

监听数据变动

Hacker News 的查询接口有一个 query 参数,前面的案例中,我们将这个参数固定了,现在我们通过响应式的数据来定义这个变量。

<template>
  <input type="text" v-model="query" />
  <ul>
    <li
      v-for="item of hits"
      :key="item.objectID"
    >
      <a :href="item.url">{{item.title}}</a>
    </li>
  </ul>
</template>

<script>
import { reactive, onMounted } from 'vue'

export default {
  setup() {
    const state = reactive({
      query: 'vue',
      hits: []
    })
    onMounted((async () => {
      const data = await fetch(
        `https://hn.algolia.com/api/v1/search?query=${state.query}`
      ).then(rsp => rsp.json())
      state.hits = data.hits
    })
    return state
  }
}
</script>

现在我们在输入框修改,就能触发 state.query 同步更新,但是并不会触发 fetch 重新调用,所以我们需要通过 watchEffect() 来监听响应数据的变化。

import { reactive, onMounted, watchEffect } from 'vue'

export default {
  setup() {
    const state = reactive({
      query: 'vue',
      hits: []
    })
    const fetchData = async (query) => {
      const data = await fetch(
        `https://hn.algolia.com/api/v1/search?query=${query}`
      ).then(rsp => rsp.json())
      state.hits = data.hits
    }
    onMounted(() => {
      fetchData(state.query)
      watchEffect(() => {
        fetchData(state.query)
      })
    })
    return state
  }
}

由于 watchEffect() 首次调用的时候,其回调就会执行一次,造成初始化时会请求两次接口,所以我们需要把 onMounted 中的 fetchData 删掉。

onMounted(() => {
- fetchData(state.query)
  watchEffect(() => {
    fetchData(state.query)
  })
})

Demo

watchEffect() 会监听传入函数内所有的响应式数据,一旦其中的某个数据发生变化,函数就会重新执行。如果要取消监听,可以调用 watchEffect() 的返回值,它的返回值为一个函数。下面举个例子:

const stop = watchEffect(() => {
  if (state.query === 'vue3') {
    // 当 query 为 vue3 时,停止监听
    stop()
  }
  fetchData(state.query)
})

当我们在输入框输入 "vue3" 后,就不会再发起请求了。

Demo

返回事件方法

现在有个问题就是 input 内的值每次修改都会触发一次请求,我们可以增加一个按钮,点击按钮后再触发 state.query 的更新。

<template>
  <input type="text" v-model="input" />
  <button @click="setQuery">搜索</button>
  <ul>
    <li
      v-for="item of hits"
      :key="item.objectID"
    >
      <a :href="item.url">{{item.title}}</a>
    </li>
  </ul>
</template>

<script>
import { reactive, onMounted, watchEffect } from 'vue'

export default {
  setup() {
    const state = reactive({
      input: 'vue',
      query: 'vue',
      hits: []
    })
    const fetchData = async (query) => {
      const data = await fetch(
        `https://hn.algolia.com/api/v1/search?query=${query}`
      ).then(rsp => rsp.json())
      state.hits = data.hits
    }
    onMounted(() => {
      watchEffect(() => {
        fetchData(state.query)
      })
    })
    
    const setQuery = () => {
      state.query = state.input
    }
    return { setQuery, state }
  }
}
</script>

可以注意到 button 绑定的 click 事件的方法,也是通过 setup() 方法返回的,我们可以将 setup() 方法返回值理解为 Vue2 中 data() 方法和 methods 对象的合并。

原先的返回值 state 变成了现在返回值的一个属性,所以我们在模板层取数据的时候,需要进行一些修改,在前面加上 state.

<template>
  <input type="text" v-model="state.input" />
  <button @click="setQuery">搜索</button>
  <ul>
    <li
      v-for="item of state.hits"
      :key="item.objectID"
    >
      <a :href="item.url">{{item.title}}</a>
    </li>
  </ul>
</template>

Demo

返回数据修改

作为强迫症患者,在模板层通过 state.xxx 的方式获取数据实在是难受,那我们是不是可以通过对象解构的方式将 state 的数据返回呢?

<template>
  <input type="text" v-model="input" />
  <button class="search-btn" @click="setQuery">搜索</button>
  <ul class="results">
    <li
      v-for="item of hits"
      :key="item.objectID"
    >
      <a :href="item.url">{{item.title}}</a>
    </li>
  </ul>
</template>

<script>
import { reactive, onMounted, watchEffect } from 'vue'

export default {
  setup(props, ctx) {
    const state = reactive({
      input: 'vue',
      query: 'vue',
      hits: []
    })
    // 省略部分代码...
    return {
      ...state,
      setQuery,
    }
  }
}
</script>

答案是『不可以』。修改代码后,可以看到页面虽然发起了请求,但是页面并没有展示数据。

state 在解构后,数据就变成了静态数据,不能再被跟踪,返回值类似于:

export default {
  setup(props, ctx) {
    // 省略部分代码...
    return {
      input: 'vue',
      query: 'vue',
      hits: [],
      setQuery,
    }
  }
}

Demo

为了跟踪基础类型的数据(即非对象数据),Vue3 也提出了解决方案:ref()

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

上面为 Vue 3 的官方案例,ref() 方法返回的是一个对象,无论是修改还是获取,都需要取返回对象的 value 属性。

我们将 state 从响应对象改为一个普通对象,然后所有属性都使用 ref 包裹,这样修改后,后续的解构才做才能生效。这样的弊端就是,state 的每个属性在修改时,都必须取其 value 属性。但是在模板中不需要追加 .value,Vue 3 内部有对其进行处理。

import { ref, onMounted, watchEffect } from 'vue'
export default {
  setup() {
    const state = {
      input: ref('vue'),
      query: ref('vue'),
      hits: ref([])
    }
    const fetchData = async (query) => {
      const data = await fetch(
        `https://hn.algolia.com/api/v1/search?query=${query}`
      ).then(rsp => rsp.json())
      state.hits.value = data.hits
    }
    onMounted(() => {
      watchEffect(() => {
        fetchData(state.query.value)
      })
    })
    const setQuery = () => {
      state.query.value = state.input.value
    }
    return {
      ...state,
      setQuery,
    }
  }
}

有没有办法保持 state 为响应对象,同时又支持其对象解构的呢?当然是有的,Vue 3 也提供了解决方案:toRefs()toRefs() 方法可以将一个响应对象变为普通对象,并且给每个属性加上 ref()

import { toRefs, reactive, onMounted, watchEffect } from 'vue'

export default {
  setup() {
    const state = reactive({
      input: 'vue',
      query: 'vue',
      hits: []
    })
    const fetchData = async (query) => {
      const data = await fetch(
        `https://hn.algolia.com/api/v1/search?query=${query}`
      ).then(rsp => rsp.json())
      state.hits = data.hits
    }
    onMounted(() => {
      watchEffect(() => {
        fetchData(state.query)
      })
    })
    const setQuery = () => {
      state.query = state.input
    }
    return {
      ...toRefs(state),
      setQuery,
    }
  }
}

Loading 与 Error 状态

通常,我们发起请求的时候,需要为请求添加 Loading 和 Error 状态,我们只需要在 state 中添加两个变量来控制这两种状态即可。

export default {
  setup() {
    const state = reactive({
      input: 'vue',
      query: 'vue',
      hits: [],
      error: false,
      loading: false,
    })
    const fetchData = async (query) => {
      state.error = false
      state.loading = true
      try {
        const data = await fetch(
          `https://hn.algolia.com/api/v1/search?query=${query}`
        ).then(rsp => rsp.json())
        state.hits = data.hits
      } catch {
        state.error = true
      }
      state.loading = false
    }
    onMounted(() => {
      watchEffect(() => {
        fetchData(state.query)
      })
    })
    const setQuery = () => {
      state.query = state.input
    }
    return {
      ...toRefs(state),
      setQuery,
    }
  }
}

同时在模板使用这两个变量:

<template>
  <input type="text" v-model="input" />
  <button @click="setQuery">搜索</button>
  <div v-if="loading">Loading ...</div>
  <div v-else-if="error">Something went wrong ...</div>
  <ul v-else>
    <li
      v-for="item of hits"
      :key="item.objectID"
    >
      <a :href="item.url">{{item.title}}</a>
    </li>
  </ul>
</template>

展示 Loading、Error 状态:

Demo

将数据请求逻辑抽象

用过 umi 的同学肯定知道 umi 提供了一个叫做 useRequest 的 Hooks,用于请求数据非常的方便,那么我们通过 Vue 的组合 API 也可以抽象出一个类似于 useRequest 的公共方法。

接下来我们新建一个文件 useRequest.js

import {
  toRefs,
  reactive,
} from 'vue'

export default (options) => {
  const { url } = options
  const state = reactive({
    data: {},
    error: false,
    loading: false,
  })

  const run = async () => {
    state.error = false
    state.loading = true
    try {
      const result = await fetch(url).then(res => res.json())
      state.data = result
    } catch(e) {
      state.error = true
    }
    state.loading = false
  }

  return {
    run,
    ...toRefs(state)
  }
}

然后在 App.vue 中引入:

<template>
  <input type="text" v-model="query" />
  <button @click="search">搜索</button>
  <div v-if="loading">Loading ...</div>
  <div v-else-if="error">Something went wrong ...</div>
  <ul v-else>
    <li
      v-for="item of data.hits"
      :key="item.objectID"
    >
      <a :href="item.url">{{item.title}}</a>
    </li>
  </ul>
</template>

<script>
import { ref, onMounted } from 'vue'
import useRequest from './useRequest'

export default {
  setup() {
    const query = ref('vue')
    const { data, loading, error, run } = useRequest({
      url: 'https://hn.algolia.com/api/v1/search'
    })
    onMounted(() => {
      run()
    })
    return {
      data,
      query,
      error,
      loading,
      search: run,
    }
  }
}
</script>

当前的 useRequest 还有两个缺陷:

  1. 传入的 url 是固定的,query 修改后,不能及时的反应到 url 上;
  2. 不能自动请求,需要手动调用一下 run 方法;
import {
  isRef,
  toRefs,
  reactive,
  onMounted,
} from 'vue'

export default (options) => {
  const { url, manual = false, params = {} } = options

  const state = reactive({
    data: {},
    error: false,
    loading: false,
  })

  const run = async () => {
    // 拼接查询参数
    let query = ''
    Object.keys(params).forEach(key => {
      const val = params[key]
      // 如果去 ref 对象,需要取 .value 属性
      const value = isRef(val) ? val.value : val
      query += `${key}=${value}&`
    })
    state.error = false
    state.loading = true
    try {
      const result = await fetch(`${url}?${query}`)
          .then(res => res.json())
      state.data = result
    } catch(e) {
      state.error = true
    }
    state.loading = false
  }

  onMounted(() => {
    // 第一次是否需要手动调用
    !manual && run()
  })

  return {
    run,
    ...toRefs(state)
  }
}

经过修改后,我们的逻辑就变得异常简单了。

import useRequest from './useRequest'

export default {
  setup() {
    const query = ref('vue')
    const { data, loading, error, run } = useRequest(
      {
        url: 'https://hn.algolia.com/api/v1/search',
        params: {
          query
        }
      }
    )
    return {
      data,
      query,
      error,
      loading,
      search: run,
    }
  }
}

当然,这个 useRequest 还有很多可以完善的地方,例如:不支持 http 方法修改、不支持节流防抖、不支持超时时间等等。最后,希望大家看完文章后能有所收获。

image

查看原文

赞 10 收藏 7 评论 0

XiNine 发布了文章 · 10月23日

vue中使用海康视频插件(1.监控)

使用条件
一.、引入依赖文件
jsencrypt.min.js
jsWebControl-1.0.0.min.js
注:必须在与 vue 的 根 index.html 文件中引入, main.js 中引入无效;
依赖文件及demo:下载

二、 template

<div class="video-wrap mt-16">
  <wrap
    class="video-box"
    v-for="(item, index) in realTimeData.videoConfig"
    :key="item.id"
  >
    <div :id="`playWnd${index}`" class="playWnd"></div>
  </wrap>
</div>

三、methods

/* 获取视频code */
    async getVideoCode() {
      this.realTimeData.videoLoad = true;
      const params = {
        area: "",
        enterprise: this.realTimeData.Warehouse.enterpriseName,
        street: "",
      };
      await getCamera(params).then((res) => {
        const videoCode = res.data.list.map(({ cameraIndexCode }) => ({
          cameraIndexCode,
        }));
        this.realTimeData.videoConfig = videoCode.map((item) => {
          return {
            code: item.cameraIndexCode,
            id: Math.floor(Math.random() * 100),
          };
        });
        this.initPlugin(videoCode);
      });
    },
    /* 创建插件实例 */
    initPlugin(codeArr=this.realTimeData.videoConfig) {
      const dll = { dllPath: "./VideoPluginConnect.dll" };
      codeArr.forEach((item, index) => {
        let oWebControl = new WebControl({
          szPluginContainer: `playWnd${index}`, // 指定容器id
          iServicePortStart: 15900,
          iServicePortEnd: 15909,
          szClassId: "23BF3B0A-2C56-4D97-9C03-0CB103AA8F11", // 用于IE10使用ActiveX的clsid
          cbConnectSuccess: () => {
            oWebControl.JS_StartService("window", dll).then(() => {
              oWebControl.JS_SetWindowControlCallback({
                cbIntegrationCallBack: (msg) => {
                  //注:不能使用外部函数调用,无效
                  if (msg?.responseMsg?.msg?.result) {
                    const { result } = msg.responseMsg.msg;
                    if (result == 1024) {
                      oWebControl.JS_HideWnd();//放大隐藏其它视频窗口
                    } else if (result == 1025) {
                      oWebControl.JS_ShowWnd();//缩小显示全部窗口
                    }
                  }
                },
              });
              //启动插件服务成功
              oWebControl.JS_CreateWnd(`playWnd${index}`, 260, 160).then(() => {
                //JS_CreateWnd创建视频播放窗口,宽高可设定
                this.initVideo(oWebControl, item.cameraIndexCode); // 创建播放实例成功后初始化
              });
            });
          },
          //插件服务启动失败,尝试自行启动
          cbConnectError:()=>{
            oWebControl = null;
            WebControl.JS_WakeUp("VideoWebPlugin://");
            setTimeout(()=>{
              this.initPlugin();
            },3000)
          }
        });
        this.plug.example.push(oWebControl);
      });
    },
    /* 获取公钥 */
    initVideo(oWebControl, code) {
      const params = {
        funcName: "getRSAPubKey",
        argument: JSON.stringify({ keyLength: 1024 }),
      };
      oWebControl.JS_RequestInterface(params).then((res) => {
        if (res.responseMsg.data) {
          this.plug.pubKey = res.responseMsg.data;
          this.getVideoConfig(oWebControl);
          this.getClickAction(oWebControl, code);
        }
      });
    },
    /* 视频插件配置 */
    getVideoConfig(oWebControl) {
      const { offsetWidth, offsetHeight } = document.getElementById("playWnd0");
      const configObj = {
        funcName: "init",
        argument: JSON.stringify({
          appkey: "xxxxxx", //API网关提供的appkey
          secret: this.setEncrypt("xxxxxx"), //API网关提供的secret
          ip: "xxx.xxx.xxx.xxx", //API网关IP地址
          playMode: 0, //播放模式(决定显示预览还是回放界面)
          port: 8443, //端口
          snapDir: "D:\\SnapDir", //抓图存储路径
          videoDir: "D:\\VideoDir", //紧急录像或录像剪辑存储路径
          layout: "1x1", //布局
          enableHTTPS: 1, //是否启用HTTPS协议
          encryptedFields: "secret", //加密字段
          showToolbar: 1, //是否显示工具栏
          showSmart: 1, //是否显示智能信息
          buttonIDs: "0,16,256,257,258,259,260,512,513,514,515,516,517,768,769", //自定义工具条按钮
        }),
      };
      oWebControl.JS_RequestInterface(configObj).then(() => {
        oWebControl.JS_Resize(offsetWidth, offsetHeight);
      });
    },
    /* 视频流RSA加密 */
    setEncrypt(value) {
      const encrypt = new JSEncrypt();
      encrypt.setPublicKey(this.plug.pubKey);
      return encrypt.encrypt(value);
    },
    /* 视频播放 */
    getClickAction(oWebControl, code) {
      code = code.trim();
      oWebControl.JS_RequestInterface({
        funcName: "startPreview",
        argument: JSON.stringify({
          cameraIndexCode: code, //监控点编号
          streamMode: 0, //主子码流标识:0-主码流,1-子码流
          transMode: 1, //传输协议:0-UDP,1-TCP
          gpuMode: 0, //是否启用GPU硬解,0-不启用,1-启用
          wndId: -1, //播放窗口序号(在2x2以上布局下可指定播放窗口)
        }),
      });
      this.realTimeData.videoLoad = false;
    },
    /* 销毁视频实例 */
    getDestruction() {
      this.plug.example.forEach((item) => {
        item.JS_HideWnd();
        item.JS_DestroyWnd().then((res) => {});
        // item.JS_Disconnect().then((res) => {});
      });
    },

四、使用效果图、功能:
1.实时监控 / 2.截图 / 3.录屏 / 4.摄像头控制 / 5.语音指挥(硬件支持)/ 6.即时回放
image

查看原文

赞 1 收藏 1 评论 0

XiNine 回答了问题 · 10月19日

文件上传发起POST请求,但是“预处理”OPTION请求在POST请求之后,导致上传失败

请求函数异步了,试试把预处理请求处理200成功后,在发起 post请求。或者套上 async...await...

关注 3 回答 2

认证与成就

  • 获得 6 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-01-15
个人主页被 507 人浏览