vue cli 4.5 结合ts thinkphp5.1 mamp

创建项目

vue create 项目名称

在main.ts引入and

import And from 'ant-design-vue'
import "ant-design-vue/dist/antd.css"

app.use(and)

找到home页面,其实是一个模板

<template>
  <div class="home">
    <button>subit</button>
    <ul>
      <li v-for="(item,index) in dd" :key="index">
        {{item}}
      </li>
    </ul>
    <button>{{message}}</button>
    <a-button type="link">Link Button</a-button>
    <a-button type="link" @click="mm">Link Button</a-button>
    <a-button type="link" @click="cc">Link Button</a-button>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
import axios from "axios";
import api from "@/api";

@Options({
  components: {
    HelloWorld,
  }
})
export default class Home extends Vue {
  message = 'Hello!'
  dd:any = []
  mounted() {
    // axios.get('https://www.fastmock.site/mock/a14f30a835adba71be928253ab4aedaf/xiaomi/products').then(res => {
    //   this.dd = res.data
    // })
  }
  async mm() {
     const nn = await api.getData()
     console.log(nn)
  }

  async cc() {
    const rr = await api.Login({})
    console.log(rr)
  }
}
</script>

测试中可以看出还是很方便的

数据,方法直接在home里面就可以直接调用了

api 统一调用的方法

/**
 * axios 封装
 */

import axios from 'axios'
const TOKEN_INVALID = 'Token认真失败,请重新认证'
const NETWORK_ERROR = '网络异常,请稍后重试'

const service = axios.create({
    timeout: 8000
})

service.interceptors.response.use((res) => {
    const {code,data} = res.data
    if (code === 200) return data
},err => {
    // this.$message.error(msg || NETWORK_ERROR)
    return Promise.reject( 'NETWORK_ERROR')
})

/**
 *
 * @param {*} options 请求的配置
 * @returns
 */
function request(options:any) {
    if (options.method.toLowerCase() === 'get') {
        options.params = options.data
    }
    return service(options)
}


export default request
import request from "@/request";

export default {
    Login (params:any) {
        return request({
            url: 'https://www.fastmock.site/mock/f2a0ab67ac81641c4be683b35bd94043/api/users/login',
            method: 'post',
            data: params
        })
    },
    getData () {
        return request({
            url: 'https://www.fastmock.site/mock/a14f30a835adba71be928253ab4aedaf/xiaomi/products',
            method: 'get',
            data: {}
        })
    }
}

代理   那么前面baseUlr: '/api'

module.exports = {
    devServer: {
        host: 'localhost',
        port: 8080,
        proxy: {
            '/api': {
                target: 'http://aqd.cn:7070',
                changeOrigin: true,
                pathRewrite: { // 对请求路径进行重定向以匹配到正确的请求地址
                    '/api': ''
                }
            }
        }
    }
}

在做的过程中又好多问题也是查了很久才找到的问题

axios.d.ts      由于是ts会检查故

import axios from "axios";
declare module 'axios' {
    interface AxiosInstance {
        (config: AxiosRequestConfig): Promise<any>
    }
}

dplayer的集成

npm i hls.js

dplayer.d.ts     不然就找不到dplayer   在src 下面创建即可

import DPlayer from "dplayer";

declare module 'dplayer' {

}

测试代码

如果网络请求中出现 403  

就在index.html  中添加 

<meta name="referrer" content="never">
<template>
  <div class="detail">
    <div id="dplayer" class="dplayer">
    </div>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import DPlayer from "dplayer";
import Hls from "hls.js";

@Options({
  props: {
    msg: String
  }
})


export default class Detail extends Vue {
  msg!: string
  mounted() {
    this.initDplayer()
  }
  initDplayer() {
    const dp = new DPlayer({
      container: document.getElementById('dplayer'),
      video: {
        url: 'https://xxx.m3u8',
        type: 'customHls',
        customType: {
          customHls: function (video:any, player:any) {
            const hls = new Hls();
            hls.loadSource(video.src);
            hls.attachMedia(video);
          },
        },
      }
    })
  }
}
</script>

<style lang="scss">
.detail {
  .dplayer {
    width: 1066px;
    height: 580px;
    margin: 0 auto;
  }
}

</style>

路由hash   那么你访问就要   a href="/#/detail"   

带上id     a :href="`/#/detail/$(item.id)`" 

路由中定义   path:'/detail/:id'

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/detail',
    name: 'Detail',
    component: () => import('../views/Detail.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

分页:

 <a-pagination v-model:current="this.params.pageNum" :total="total"
                        v-model:pageSize="this.params.pageSize"
                        @change="onChange"/>
export default class Home extends Vue {
  params = {
    pageNum: 1,
    pageSize: 15
  }
  listD  = []
  total = 0
  mounted() {
    this.tt()
  }
  async tt() {
    const rr = await api.getData(this.params)
    this.listD = rr.list
    this.total = rr.total
  }
  onChange(page:any,pageSize:any) {
    this.params.pageNum = page
    this.tt()
  }
}
</script>

分页完成下面是完整代码

<template>
  <div class="home">
    <Head></Head>
    <div class="contain">
      <side></side>
      <div class="contain-content">
        <ul class="aqList">
          <li v-for="(item,index) in listD" :key="index">
            <a href="">
              <img :src="item.imgsmall" alt="">
              <span class="video-duration">{{ item.videoTime }}</span>
              <div class="mask"></div>
              <PlayCircleOutlined class="icFont"/>
            </a>
            <div class="video-title">{{item.title}}</div>
          </li>
        </ul>
        <div>
          <a-pagination v-model:current="this.params.pageNum" :total="total"
                        v-model:pageSize="this.params.pageSize"
                        @change="onChange"/>
        </div>
      </div>
    </div>

  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import Head from "@/components/Head.vue";
import api from "@/api";
import side from "@/components/side.vue";

import {
  PlayCircleOutlined
} from '@ant-design/icons-vue'

@Options({
  components: {
    Head,side,
    PlayCircleOutlined
  }
})
export default class Home extends Vue {
  params = {
    pageNum: 1,
    pageSize: 15
  }
  listD  = []
  total = 0
  mounted() {
    this.tt()
  }
  async tt() {
    const rr = await api.getData(this.params)
    this.listD = rr.list
    this.total = rr.total
  }
  onChange(page:any,pageSize:any) {
    this.params.pageNum = page
    this.tt()
  }
}
</script>

<style lang="scss">
.contain {
  .contain-content {
    margin-left: 240px;
    padding: 0 100px;
    .aqList {
      display: flex;
      flex-wrap: wrap;
      list-style: none;
      padding-left: 0;
      margin-top: 60px;
      li {
        width: 20%;
        margin-top: 10px;
        text-align: center;
        .video-title {
          font-size: 12px;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
        }
        &:hover {
          color: red;
        }
        a {
          display: inline-block;
          color: #000;
          position: relative;
          margin-right: 10px;
          &:hover .mask{
            opacity: .5;
          }
          &:hover .icFont {
            opacity: 1;
            transform: scale(1.2);
          }
          .video-duration {
            position: absolute;
            right: 10px;
            bottom: 4px;
            color: white;
            padding: 2px 4px;
            background-color: rgba(0, 0, 0, 0.5);
            border-radius: 5px;
            font-size: 12px;
          }
          .icFont {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%,-50%);
            font-size: 40px;
            color: white;
            transition: all .25s;
            margin: -20px 0 0 -20px;
            opacity: 0;
          }
          .mask {
            position: absolute;
            top: 0;
            left: 0;
            bottom: 0;
            right: 0;
            opacity: 0;
            border-radius: 5px;
            background: #000;
            transition: opacity 1s;
          }
          img {
            border-radius: 5px;
            width: 100%;
          }
        }

      }
    }
  }
}

</style>

最后一个播放页面

我是一张表所以用数据库ID来查询,2张表查询方式不变

<template>
  <div class="detail">
    <div id="dplayer" class="dplayer">
    </div>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import DPlayer from "dplayer";
import Hls from "hls.js";
import api from "@/api";

@Options({
  props: {
    msg: String
  }
})


export default class Detail extends Vue {
  msg!: string
  videoUrl = ''
  imgUrl = ''
  async getVideoUrl() {
    const id = this.$route.params.id
    const data =await api.getUrl({id})
    this.videoUrl = data.videoUrl
    this.imgUrl = data.imgBig
    this.initDplayer()
  }
  mounted() {
    this.getVideoUrl()
  }
  initDplayer() {
    const dp = new DPlayer({
      container: document.getElementById('dplayer'),
      video: {
        url: this.videoUrl,
        pic: this.imgUrl,
        type: 'customHls',
        customType: {
          customHls: function (video:any, player:any) {
            const hls = new Hls();
            hls.loadSource(video.src);
            hls.attachMedia(video);
          },
        },
      }
    })
  }
}
</script>

<style lang="scss">
.detail {
  margin-top: 15px;
  .dplayer {
    width: 1066px;
    height: 580px;
    margin: 0 auto;
  }
}

</style>

PHP制作api代码

    public function getUrl(Request $request) {
        $videoId = $request->param('id');
        $list = Db::table('aqd_video')->where('id',$videoId)->find();
        return json([
            'code' => 200,
            'data' => $list,
            'msg' => '请求数据成功'
        ]);
    }

    public function avData(Request $request)
    {
        $pageNum = $request->param('pageNum');
        $pageSize = $request->param('pageSize');
        $list = Db::table('aqd_video')->page($pageNum, $pageSize)->select();
        if ($list) {
            $count = Db::table('aqd_video')->count('id');
            $res = [
                'list' => $list,
                'pageNum' => $pageNum,
                'pageSize' => $pageSize,
                'total' => $count
            ];
            return json([
                'code' => 200,
                'data' => $res,
                'msg' => '请求数据成功'
            ]);
        } else {
            return json(['code'=>404, 'msg' => '请求失败', data=>[]]);
        }
    }

路由配置

Route::rule('avData','index/index/avData');
Route::rule('getUrl','index/index/getUrl');

router4.0

不够细有遇到问题了,如果你要做搜索页面肯定躲不过路由的跳转

看了好多网上的,官方的也看了,代码一样就是报错,人都嘛了,发现是自己没有理解它

{
    path: '/search',
    name: 'Search',
    component: () => import('../views/Search.vue')
  }

this.router.push({name:'',params:{}})

这个错误送给你Uncaught Error

修改   path: '/search/:title'       可以让你匹配携带参数

this.router.push({name:'Search',params:{title:xxx}})

取值 his.$route.params.title

name对应的一定是name的值   大小写要区分

params  的键只能是: 后面的字段


this.$router.push(`/search/${xx}`)   

取值 this.$route.query.title

搜索页面的代码

获取搜索的字段传入搜索页面

onSearch() {
    const key = this.value.trim()
    if (key !== '') {
      this.$router.push({
        name: 'Search',
        params: {
          title: key
        }
      })
    }

然后就是获取数据展示页面

<template>
  <Head @addN="adIndex"></Head>
  <div class="search">
    <div class="contain-content">
      <ul class="aqList">
        <li v-for="(item,index) in listD" :key="index">
          <a :href="`/#/detail/${item.id}`" target="_blank">
            <img :src="item.imgsmall" alt="">
            <span class="video-duration">{{ item.videoTime }}</span>
            <div class="mask"></div>
            <PlayCircleOutlined class="icFont"/>
          </a>
          <div class="video-title">{{item.title}}</div>
        </li>
      </ul>
    </div>
  </div>
  <div class="p-pagin">
    <a-pagination v-model:current="this.params.pageNum" :total="this.total"
                  v-model:pageSize="this.params.pageSize"
                  @change="onChange"/>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import api from "@/api";
import {
  MenuFoldOutlined,MenuUnfoldOutlined,DisconnectOutlined,
  StarOutlined,TableOutlined,PlayCircleOutlined
} from '@ant-design/icons-vue'
import Head from "@/components/Head.vue";
import {message} from "ant-design-vue";

@Options({
  props: {
    msg: String
  },
  components: {
    MenuFoldOutlined,MenuUnfoldOutlined,DisconnectOutlined,
    StarOutlined,TableOutlined,PlayCircleOutlined,
    Head
  }
})
export default class Search extends Vue {
  msg!: string
  listD = []
  total = 0
  searchKye = ''
  searBollen = false
  params = {
    pageNum:1,
    pageSize:10,
    keyword: ''
  }
  mounted() {
    this.getTitle(this.searBollen)
  }
  async getTitle(b:boolean) {
    if (!this.searBollen) {
      this.params.keyword = this.$route.params.title as string
    } else {
      this.params.keyword = this.searchKye
    }
    const data =await api.getKey(this.params)
    this.listD = data.list
    this.total = data.total
  }
  adIndex(v:string) {
    if (v !== '') {
      this.searchKye = v
      this.searBollen = true
      this.$router.push(`/search/${v}`)
      this.getTitle(this.searBollen)
    } else {
      message.warning('请输入搜索字段')
    }
  }
  onChange(page:any,pageSize:any) {
    this.params.pageNum = page
    if (!this.searBollen) {
      this.getTitle(this.searBollen)
    } else {
      this.getTitle(this.searBollen)
    }

  }
}
</script>

<style lang="scss">
.search {
  .contain-content {
    margin-left: 240px;
    padding: 0 100px;
    .aqList {
      display: flex;
      flex-wrap: wrap;
      list-style: none;
      padding-left: 0;
      margin-top: 60px;
      li {
        width: 20%;
        margin-top: 10px;
        text-align: center;
        .video-title {
          font-size: 12px;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
        }
        &:hover {
          color: red;
        }
        a {
          display: inline-block;
          color: #000;
          position: relative;
          margin-right: 10px;
          &:hover .mask{
            opacity: .5;
          }
          &:hover .icFont {
            opacity: 1;
            transform: scale(1.2);
          }
          .video-duration {
            position: absolute;
            right: 10px;
            bottom: 4px;
            color: white;
            padding: 2px 4px;
            background-color: rgba(0, 0, 0, 0.5);
            border-radius: 5px;
            font-size: 12px;
          }
          .icFont {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%,-50%);
            font-size: 40px;
            color: white;
            transition: all .25s;
            margin: -20px 0 0 -20px;
            opacity: 0;
          }
          .mask {
            position: absolute;
            top: 0;
            left: 0;
            bottom: 0;
            right: 0;
            opacity: 0;
            border-radius: 5px;
            background: #000;
            transition: opacity 1s;
          }
          img {
            border-radius: 5px;
            width: 100%;
          }
        }

      }
    }
  }
}
.p-pagin {
  text-align: center;
}
</style>

理一下逻辑分页逻辑:首先你从主页跳转到搜索页面获取到搜索字段,定义变量确定是从主页获取的字段为FALSE,获取后台数据,展示数据,如果你是从本页面获取的搜索字段,将变量改为TRUE,相同操作

 

PHP代码 分页

 

   public function dd(Request $request) {
        $t2 = $request->param('keyword');
        $pageNum = $request->param('pageNum');
        $pageSize = $request->param('pageSize');
        $list = Db::table('aqd_video')
            ->where('title','like',"%$t2%")->page($pageNum,$pageSize)
            ->select();
        if ($list) {
            $count = Db::table('aqd_video')
                ->where('title','like',"%$t2%")
                ->count();
            $res = [
                'list' => $list,
                'pageNum' => $pageNum,
                'pageSize' => $pageSize,
                'total' => $count
            ];
            return json(['code'=>200, 'data'=> $res]);
        } else {
            return json(['code'=>404, 'msg'=> 'no data' ,data=>[]]);
        }
    }

 


父子组件值的传递     父传给子一个字段    子接收后将方法传给父监听(单项数据流)

子组件   简写@cliek="this.$emit('方法名')"     带参数就用下面的写法

添加向外传递的方法

emits: ['add','ads']   下面的是校验参数

<template>
  <div @click="handleClick" style="color: red">{{msg}}</div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';

@Options({
  props: {
    msg: String
  },
  methods: {
    handleClick() {
      this.$emit('add','d')
    }
  },
emits: {
    ads: (msg:string) => {
      if (msg.trim().length >0) {
        return true
      }
      return false
    }
  }
})

</script>

父组件

<template>
  <div class="search">
    <Head :msg="keyword1" @add="handAdd"/>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import api from "@/api";
import Head from "@/components/Head.vue";

@Options({
  props: {
    msg: String
  },
  components: {
    Head
  }
})
export default class Search extends Vue {
  msg!: string
  keyword1 = 'df'
 
  handAdd(t:string) {
    this.keyword1 += t
  }
}
</script>



下面是父子组件传值简化版本

利用v-model双向绑定

子组件

<template>
  <div @click="handleClick" style="color: red">{{modelValue}}</div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';

@Options({
  props: ['modelValue'],
  methods: {
    handleClick() {
      this.$emit('update:modelValue',this.modelValue+'d')
    }
  }
})

父组件

<template>
  <div class="search">
    <Head v-model='keyword1'/>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import api from "@/api";
import Head from "@/components/Head.vue";

@Options({
  props: {
    msg: String
  },
  components: {
    Head
  }
})
export default class Search extends Vue {
  msg!: string
  keyword1 = 'df'
 
}
</script>

如果你不想用modelValue直接改成   v-model:app="keyword"

modelValue  替换成app就可以了

其实如果你想传入要搜索的字段,参数就可以了,

<a-input-search
              v-model:value="value"
              placeholder="input search text"
              @search="onSearch"
          />



@Options({
  props: ['modelValue'],
  components: {
    MenuFoldOutlined,
    MenuUnfoldOutlined,TableOutlined,
    DisconnectOutlined,StarOutlined
  },
  emits:['addN'],
  methods:{
    onSearch() {
      this.$emit("addN",this.value)
    }
  }
})

export default class Head extends Vue {
  collapsed = false
  value = ''
  toggleCollapsed(e:any) {
    this.collapsed = !this.collapsed
  }
}
</script>

父组件接收方式不变

<Head @addN="ad"></Head>

ad(v:string) {
    console.log(v)
  }


login

真的蛋碎了,ts 中 ref 属性要检测类型找到了最简单的写法

<template>
  <div class="login">
    <div class="model">
      <a-form ref="formRef"
              :model="formState"
              :rules="rules"
      >
        <a-form-item hasFeedback name="userName">
          <a-input v-model:value="formState.userName" placeholder="请输入用户名">
            <template #prefix>
              <user-outlined type="user" />
            </template>
          </a-input>
        </a-form-item>
        <a-form-item hasFeedback name="passWord">
          <a-input-password v-model:value="formState.passWord" type="password"
                            autocomplete="off"
                            placeholder="请输入用户密码">
            <template #prefix>
              <eye-outlined type="eye" />
            </template>
          </a-input-password>
        </a-form-item>
        <a-form-item>
          <a-button class="btn" type="primary" style="margin-right: 40px"
          @click="onSubmit"
          >登录</a-button>
          <a-button class="btn" type="primary" @click="resetForm">重置</a-button>
        </a-form-item>
      </a-form>
    </div>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import { UserOutlined,EyeOutlined } from '@ant-design/icons-vue';
import api from "@/api";
import {message} from "ant-design-vue";

@Options({
  props: {
    msg: String
  },
  components: {
    UserOutlined,
    EyeOutlined
  }
})

export default class Login extends Vue {
  msg!: string
  formState = {
    userName: '',
    passWord: ''
  }
  rules = {
    userName: [
      { required: true, message: 'Please input Activity name', trigger: 'blur' }
    ],
    passWord: [
      { required: true, message: 'Please input Activity name', trigger: 'blur' }
    ]
  }
  onSubmit() {
    this.$refs.formRef.validate().then(async () => {
      const data = await api.login(this.formState)
      if (data) {
        message.success('登录成功')
        setTimeout(() => {
          this.$router.push('/')
        },2000)
      } else {
        message.error('登录失败')
        setTimeout(() => {
          this.resetForm()
        },2000)
      }
    }).catch(() => {
      console.log('shibai')
    })
  }

  $refs!: {
    formRef: HTMLFormElement  //写法1 - 推荐
  };

  resetForm() {
    this.$refs.formRef.resetFields()
  }
}
</script>

<style lang="scss">
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #f9fcff;
  width: 100vw;
  height: 100vh;
  .model {
    width: 500px;
    padding: 50px;
    border-radius: 5px;
    background: white;
    box-shadow: 0 0 10px 3px #c7c9cb4d;
    .btn {
      width: 180px;
    }
  }
}
</style>

登录页面实现校验反馈,相对来说没有那么多繁琐的操作,定义对象Vue就全不帮你完成简直不要太爽

php登录代码,没有加中间件,jwt,md5验证,就简单登录

 public function login(Request $request) {
        $userName = $request->param('userName');
        $passWord = $request->param('passWord');
        $info = Db::table('aqd_user')->where('username',$userName)->find();
        if (!$info) {
            return json(['code' => 0, 'msg' => '账号不存在']);
        }
        if ($info['password'] !== $passWord) {
            return json(['code' =>0, 'msg' => '账号或密码错误']);
        }
        return json(['code' =>200, 'msg' => '登录成功','data'=>$userName]);
    }

搜索页面的分页,自己理解写的,大佬有更简单的方法可以交流下

第一次用这个ts 遇到好多坑,希望遇到的人可以不用到处找问题了,项目基本就可以跑通了,剩下的就是慢慢练习了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xiaodunmeng

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值