创建项目
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 遇到好多坑,希望遇到的人可以不用到处找问题了,项目基本就可以跑通了,剩下的就是慢慢练习了