目录
刚做完第一期的模拟题目,第二期又开始发布了,花了点时间做完了,分享下自己的解题思路。
1.相不相等
1.1 题目要求
请你编写一个名为 expectFn
的函数,用于帮助开发人员测试他们的代码。它可以通过参数 val
接受任何值,并返回一个对象,该对象包含下面两个函数:
toBe(val)
:接受另一个值并在两个值相等(===
)时返回true
。如果它们不相等,则返回 "Not Equal" 。notToBe(val)
:接受另一个值并在两个值不相等(!==
)时返回true
。如果它们相等,则返回 "Equal" 。
1.2 题目分析
题目要求写的很清楚了,按照其要求做判断条件即可
1.3 源代码
var expectFn = function (val) {
// TODO
return {
toBe: function (value) {
if (val === value) {
return true
} else {
return 'Not Equal'
}
},
notToBe: function (value) {
if (val !== value) {
return true
} else {
return 'Equal'
}
}
}
}
2.三行情书
2.1 题目要求
请完善 style.css
的 TODO 部分,具体要求如下:
- 让第一行标题即
.content span
标签中的文字单行显示,多余溢出显示省略号。 - 请使用
-webkit-box
语法使得下面的文字即.content p
标签里的内容显示三行,多余溢出显示省略号。
2.2 题目分析
这里主要是如果写过案例的话,应该就知道怎么写,需要注意的是span元素是单行元素,要想达到效果,需要将其变为块级元素。
2.3 源代码
span {
font-size: 20px;
color: #837362;
/* TODO:补充下面的代码 */
/* 单行显示,多余溢出省略号 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
/* 使得溢出部分显示省略号 */
}
p {
color: #837362;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
/* 显示的行数 */
overflow: hidden;
}
3.电影院在线订票
3.1 题目要求
- 实现异步数据读取和渲染功能,使用
axios
请求./data.json
(必须使用该路径请求,否则可能会请求不到数据)的数据。- 将电影名字
name
渲染到 id 为movie-name
的节点中。 - 将电影售价
price
渲染到 id 为movie-price
的节点中。 - 将座位信息
seats
渲染到 id 为seat-area
的节点中,二维数组中每一个子数组代表一行,0 代表没有被他人占位,1 代表已经被订购。
- 将电影名字
- 实现座位选择和总价计算的功能:
- 被别人订过的座位不能再被选择。
- 座位被选中后,状态更新,为相应的座位添加选中样式(即
selected
),并更新已选择的座位数和总价。 - 自己所选择的座位可以被取消,并更新已选择的座位数和总价。
3.2 题目分析
- 第一点是使用axios发送请求获取到数据,这个不会的可以参考一下axios官网的案例学习一下
- 第二个是根据拿到的数据渲染html的结构,这个的就是创建对应的html结构,最后添加到对应的节点。
- 第三个是实现作为选择的功能,实现思路就是给所有的座位都绑定一个点击事件,通过事件对象身上是否被选中来替换类名,然后就是渲染数量。
3.3 源代码
/* TODO:
1. 完成数据请求,生成电影名,价格以及座位情况
2. 绑定点击事件,实现订票功能
*/
let data = {}
axios
.get('../data.json')
.then((res) => {
console.log(res)
data = res.data
movieNameNode.innerHTML = data.name
moviePriceNode.innerHTML = data.price
//创建节点渲染数据
data.seats.forEach((item) => {
let row = document.createElement('div')
row.className = 'row'
item.forEach((item) => {
let seat = document.createElement('div')
seat.className = 'seat'
row.appendChild(seat)
if (item) {
seat.classList.add('occupied')
}
})
seatAreaNode.appendChild(row)
})
})
.catch((err) => {
console.log(err)
})
// 获取座位区域节点
const seatAreaNode = document.getElementById('seat-area')
// 获取电影名节点
const movieNameNode = document.getElementById('movie-name')
// 获取电影票价节点
const moviePriceNode = document.getElementById('movie-price')
// 获取已订电影票数量节点
const count = document.getElementById('count')
// 获取已订电影票总价节点
const total = document.getElementById('total')
// 获取所有座位节点
const seatNodes = document.querySelectorAll('.seat')
// 初始化已选择座位数和总价
let selectedSeatsCount = 0
let totalPrice = 0
// 监听座位点击事件
seatAreaNode.addEventListener('click', (event) => {
const clickedSeat = event.target
// 检查是否点击的是座位
if (clickedSeat.classList.contains('seat') && !clickedSeat.classList.contains('occupied')) {
// 切换座位的选中状态
clickedSeat.classList.toggle('selected')
// 更新已选择座位数和总价
if (clickedSeat.classList.contains('selected')) {
selectedSeatsCount++
totalPrice += data.price
} else {
selectedSeatsCount--
totalPrice -= data.price
}
// 更新显示
updateDisplay()
}
})
// 更新显示函数
function updateDisplay() {
count.textContent = selectedSeatsCount
total.textContent = totalPrice
}
4.老虎坤(不然违规发不出来)
4.1 题目要求
找到 js/index.js
中的 GetResult
函数,完成此函数,实现以下目标:
点击开始后,可以通过 GetResult
的三个参数 r1
、r2
、r3
计算出滚动后每一列图片的停留位置。当最后停留的图片都相同时,意味着玩家中了大奖!文字框(class = textPanel
)显示“恭喜你,中奖了”,否则显示:“很遗憾,未中奖”。
参数介绍:r1
、r2
、r3
表示的是三列元素下的 li
的最后停留位置,分别对应第一列(id=sevenFirst
)、第二列(id=sevenSecond
)、第三列(id=sevenThird
)。以第一列为例,最终显示的元素是 sevenFirst
下的第 r
个 li
元素。请使用显示的 li
元素的 data-point
属性判断三张图片是否相同。当 data-point
属性对应的值相同时,说明这三张图片相同。
4.2 题目分析
这一题很明确的考察点就是获取元素身上的属性,需要用到getAttribute()方法,通过获取到的属性对比是否一样,如果三个相同则中奖。
4.3 源代码
if (sevenFirst.children[r1 - 1].getAttribute('data-point') == sevenSecond.children[r2 - 1].getAttribute('data-point')
&& sevenFirst.children[r1 - 1].getAttribute('data-point') == sevenThird.children[r3 - 1].getAttribute('data-point')) {
textPanel.innerHTML = '恭喜你,中奖了'
} else {
textPanel.innerHTML = '很遗憾,未中奖'
}
5.星际通讯
5.1 题目要求
完善 index.js
中的 translate
函数,完善其中的 TODO 部分:
translate
函数接收一个字符串参数 alienMessage
,其中包含一系列外星人的密文。函数根据特定的翻译规则将密文翻译成人类语言,并返回翻译后的结果字符串。外星人密文翻译规则存放在 codonTable
变量中。
5.2 题目分析
这个题目就是js的切割 遍历
5.3 源代码
// 密语规则
const codonTable = {
IIX: '人类',
VII: '哈喽',
III: '你好',
IXI: '赞',
XVI: '嗨',
CUV: '打击',
XII: '夜晚',
IVI: '我',
XIC: '想',
XIV: '交个朋友',
VIX: '月亮',
XCI: '代码',
XIX: '祈福',
XVI: '和',
XXI: 'stop'
}
/**
* @param {string} alienMessage 外星人的密文
* @return {string} 翻译结果
*/
const translate = (alienMessage) => {
// TODO:待补充代码
// 检查密文是否为空
if (!alienMessage) {
return ''
}
// 切分密文
const codons = []
for (let i = 0; i < alienMessage.length; i += 3) {
codons.push(alienMessage.slice(i, i + 3))
}
// 初始化翻译结果
let translation = ''
// 遍历密文
for (const codon of codons) {
// 检查是否为停止符号
if (codon === 'XXI') {
break
}
// 查找翻译表
const translationResult = codonTable[codon]
// 如果找到翻译结果,则添加到最终结果中
if (translationResult) {
translation += translationResult
} else {
// 未找到对应翻译结果,返回无效密语
return '无效密语'
}
}
return translation
}
// 请注意:以下代码用于检测,请勿删除。
try {
module.exports = translate
} catch (e) {}
6.蓝桥杯排位
6.1 题目要求
- 根据请求的数据正确完成左侧热力地图。
- 右侧战力榜中柱形图中,根据
power
字段的值对所有学校进行排序,取出排在前 10 名的学校,从左到右降序排列。 - 完成数据请求(数据来源
./mock/map.json
),map.json
中存放的数据为省市对应的学校数据
6.2 题目分析
根据要求获取数据,然后替换options中的配置即可
const { createApp, ref, onMounted } = Vue
const app = createApp({
setup() {
const chartsData = ref([])
onMounted(() => {
// TODO:待补充代码 请求数据,并正确渲染柱形图和地图
axios
.get('../mock/map.json')
.then((res) => {
chartsData.value = res.data
showChartBar()
showChinaMap()
})
.catch((err) => {
console.log(err)
})
})
// 展示柱状图
const showChartBar = () => {
const myChart = echarts.init(document.getElementById('chart'))
let data = chartsData.value.map((item, index) => {
return item.school_power
})
console.log(data)
let result = data.flat(1).sort((a, z) => {
return z.power - a.power
})
let arr = result.slice(0, 10)
let school = arr.map((item) => {
return item.name
})
let power = arr.map((item) => {
return item.power
})
console.log(school)
console.log(power)
// 指定配置和数据
const option = {
xAxis: {
type: 'category',
axisLabel: { interval: 0, rotate: 40 },
// TODO:待修改 柱状图 x 轴数据 -> 前 10 学校名称
data: school
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
yAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
series: [
{
// TODO:待修改 柱状图 y 轴数据->学校战力值
data: power,
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)'
},
itemStyle: {
color: '#8c7ae6'
}
}
]
}
// 把配置给实例对象
myChart.setOption(option)
// 根据浏览器大小切换图表尺寸
window.addEventListener('resize', function () {
myChart.resize()
})
}
// 展示地图
const showChinaMap = () => {
const chinaMap = echarts.init(document.getElementById('chinaMap'))
// 进行相关配置
const mapOption = {
tooltip: [
{
backgroundColor: '#fff',
subtext: 'aaa',
borderColor: '#ccc',
padding: 15,
formatter: (params) => {
return params.name + '热度值:' + params.value + '<br>' + params.data.school_count + '所学校已加入备赛'
},
textStyle: {
fontSize: 18,
fontWeight: 'bold',
color: '#464646'
},
subtextStyle: {
fontSize: 12,
color: '#6E7079'
}
}
],
geo: {
// 这个是重点配置区
map: 'china', // 表示中国地图
label: {
normal: {
show: false // 是否显示对应地名
}
},
itemStyle: {
normal: {
borderColor: 'rgb(38,63,168)',
borderWidth: '0.4',
areaColor: '#fff'
},
emphasis: {
//鼠标移入的效果
areaColor: 'rgb(255,158,0)',
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowBlur: 20,
borderWidth: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
},
visualMap: {
show: true,
left: 'center',
top: 'bottom',
type: 'piecewise',
align: 'bottom',
orient: 'horizontal',
pieces: [
{
gte: 80000,
color: 'rgb(140,122,230)'
},
{
min: 50000,
max: 79999,
color: 'rgba(140,122,230,.8)'
},
{
min: 30000,
max: 49999,
color: 'rgba(140,122,230,.6)'
},
{
min: 10000,
max: 29999,
color: 'rgba(140,122,230,.4)'
},
{
min: 1,
max: 9999,
color: 'rgba(140,122,230,.2)'
}
],
textStyle: {
color: '#000',
fontSize: '11px'
}
},
series: [
{
type: 'map',
geoIndex: 0,
// TODO:待修改 地图对应数据
data: chartsData.value.map((item) => {
return {
name: item.name,
school_count: item.school_count,
value: item.value
}
})
}
]
}
// 把配置给实例对象
chinaMap.setOption(mapOption)
}
return {
chartsData,
showChartBar,
showChinaMap
}
}
})
app.mount('#app')
7.拼出一个未来
题目要求和分析就不写了 ,代码中给了注释
// 声明一个数组,包含了所有的拼图块数据
var puzzlePieces = [
{ src: './images/img1.png', id: 1 },
{ src: './images/img2.png', id: 2 },
{ src: './images/img3.png', id: 3 },
{ src: './images/img4.png', id: 4 },
{ src: './images/img5.png', id: 5 },
{ src: './images/img6.png', id: 6 },
{ src: './images/img7.png', id: 7 },
{ src: './images/img8.png', id: 8 },
{ src: './images/img9.png', id: 9 }
]
// 定义一个打乱数组的函数
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[array[i], array[j]] = [array[j], array[i]]
}
return array
}
// 使用定义的函数打乱拼图块数组
puzzlePieces = shuffleArray(puzzlePieces)
// 获取拼图容器元素
var container = document.getElementById('puzzle-container')
// 遍历拼图块数据数组
puzzlePieces.forEach(function (pieceData) {
// 创建一个新的拼图块元素
var piece = document.createElement('div')
piece.classList.add('puzzle-piece')
piece.setAttribute('draggable', 'true')
// 创建一个新的图片元素
var image = document.createElement('img')
image.src = pieceData.src
image.dataset.id = pieceData.id
// 将图片元素添加到拼图块元素中
piece.appendChild(image)
// 将拼图块元素添加到父容器元素中
container.appendChild(piece)
})
// 获取所有的拼图块元素,并转换为数组
const puzzleArray = Array.from(document.querySelectorAll('.puzzle-piece'))
// 获取成功消息元素
const successMessage = document.getElementById('success-message')
// 为每个拼图块元素添加拖拽事件监听器
puzzleArray.forEach((piece) => {
piece.addEventListener('dragstart', dragStart)
piece.addEventListener('dragover', dragOver)
piece.addEventListener('drop', drop)
})
// 声明一个变量用来保存正在拖动的拼图块
let draggedPiece = null
// 定义开始拖动事件的处理函数
function dragStart(event) {
draggedPiece = this
event.dataTransfer.setData('text/plain', null)
}
// 定义在拖动过程中的处理函数,阻止默认行为
function dragOver(event) {
event.preventDefault()
}
// 定义拖放事件的处理函数
function drop(event) {
// 检查是否拖动的拼图块不是当前目标拼图块
// draggedPiece 被拖动的拼图块元素。this 目标位置的拼图块元素。
let num = 0
if (draggedPiece !== this) {
// TODO:待补充代码
// 交换图片的 src 属性和 data-id 属性
let tempSrc = draggedPiece.querySelector('img').src
let tempDataId = draggedPiece.querySelector('img').dataset.id
draggedPiece.querySelector('img').src = this.querySelector('img').src
draggedPiece.querySelector('img').dataset.id = this.querySelector('img').dataset.id
this.querySelector('img').src = tempSrc
this.querySelector('img').dataset.id = tempDataId
// 检查是否拼图成功
puzzleArray.forEach((item, index) => {
if (parseInt(item.children[0].getAttribute('data-id')) === index + 1) {
num++
}
})
if (num === 9) {
successMessage.classList.remove('hide')
successMessage.classList.add('show')
} else {
successMessage.classList.remove('show')
successMessage.classList.add('hide')
}
// 重置正在拖动的拼图块
draggedPiece = null
}
}
8.超能英雄联盟
HeroList:
// TODO:补全代码,实现目标效果
const HeroList = {
template: `
<div class="hero-list">
<h2>可选英雄</h2>
<ul>
<li class="hero-item" v-for="(item,index) in store.heroes" :key="item.id">
<span>{
{item.name}}</span>
<span>{
{item.ability}}</span>
<span>{
{item.strength}}</span>
<button @click=store.add(item.id) :disabled="item.btn">{
{ item.btn ? '已添加' : '添加至队伍' }}</button>
</li>
</ul>
</div>
`,
setup() {
//第一步获取数据
const store = useHeroStore()
axios
.get('./js/heroes.json')
.then((res) => {
store.heroes = res.data
})
.catch((err) => {
console.log(err)
})
return {
store
}
}
}
// TODOEnd
TeamList:
// TODO:补全代码,实现目标效果
const TeamList = {
template: `
<div class="team-list">
<h2>我的队伍</h2>
<ul>
<li class="team-item" v-for="(item,index) in store.team" :key="item.id">
<span>{
{item.name}}</span>
<span>{
{item.strength}}</span>
<button @click=store.removeHero(item.id)>移除</button>
</li>
</ul>
<button class="sort-button" @click=store.sort>按实力排序</button>
<p class="total-strength">当前队伍战斗力:{
{store.totalStrength}} </p>
</div>
`,
setup() {
const store = useHeroStore()
return {
store
}
}
}
// TODOEnd
store.js:
const { defineStore } = Pinia
const { ref } = Vue
const useHeroStore = defineStore('hero', {
state: () => ({
heroes: [], //英雄列表
team: [] // 队伍列表
}),
// TODO:补全代码,实现目标效果
getters: {
//计算出战力总和strength
totalStrength() {
return this.team.reduce((total, hero) => {
return total + hero.strength
}, 0)
}
},
actions: {
add(id) {
this.heroes[id - 1].btn = true
this.team.push(this.heroes[id - 1])
},
removeHero(id) {
this.heroes[id - 1].btn = false
//移出team中的元素
this.team = this.team.filter((hero) => hero.id !== id)
},
sort() {
//按照实力排序strength
this.team.sort((a, b) => {
return b.strength - a.strength
})
}
}
// TODOEnd
})
9.实时展示权限日志
这个题目我不知道为啥,明明在线测试基本上满足题目要求了,但是就是不能完全通过
node.js:
/**
* 请完成下面的 TODO 部分,其他代码请勿改动
*/
const fs = require('fs')
const http = require('http')
const path = require('path')
const dataUrl = path.resolve(__dirname, '../data.json')
const loggerUrl = path.resolve(__dirname, '../logger.json')
// 获取唯一的id
function getLoggerId() {
return Buffer.from(Date.now().toString()).toString('base64') + Math.random().toString(36).substring(2)
}
/**
* 该方法统一了服务器返回的消息格式,并返回给客户端
* @param {*} res 响应 response
* @param {*} code 状态码,默认为 0 代表没有错误,如果有错误固定为 404
* @param {*} msg 错误消息,固定为空字符串即可 ''
* @param {*} data 响应体,为 js 对象,若 data 为 utf-8 编码时需要使用 eval(data) 处理
*/
function send(res, code, msg, data) {
const responseObj = {
code,
msg,
data
}
const da = JSON.stringify(responseObj)
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.write(da)
res.end()
}
function handleStatic(res, pathName, part) {
const content = fs.readFileSync(path.resolve(__dirname, pathName))
let contentType = 'text/html'
switch (part) {
case 'css':
contentType = 'text/css'
break
case 'js':
contentType = 'text/js'
break
}
res.writeHead(200, 'Content-Type', contentType)
res.write(content)
res.end()
}
const server = http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
if (req.url === '/') {
handleStatic(res, '../index.html', '')
} else if (req.url === '/css/index.css') {
handleStatic(res, `..${req.url}`, 'css')
} else if (req.url === '/js/index.js') {
handleStatic(res, `..${req.url}`, 'js')
} else if (req.url === '/js/axios.min.js') {
handleStatic(res, `..${req.url}`, 'js')
} else if (req.url === '/js/vue3.global.min.js') {
handleStatic(res, `..${req.url}`, 'js')
}
if (req.method === 'GET' && req.url === '/users') {
// TODO 处理获取文件内容的操作
//读取data.json中的数据
let fileContent = fs.readFileSync(dataUrl, 'utf-8')
let data = JSON.parse(fileContent)
if (fileContent) {
//将读取到的数据转化为json格式
//将json格式的数据响应给客户端
send(res, 0, '', data)
}
} else if (req.method === 'PUT' && req.url === '/editUser') {
let fileContent = fs.readFileSync(dataUrl, 'utf-8')
let data = JSON.parse(fileContent)
let body = ''
req.on('readable', () => {
let chunk = ''
if (null !== (chunk = req.read())) {
body += chunk
}
})
req.on('end', () => {
if (body) {
// TODO 处理更改文件数据并将最新的文件数据响应给客户端
//处理put请求
let bodyData = JSON.parse(body)
//修改data.json中的数据
data.forEach((item) => {
if (item.id == bodyData.id) {
item.power = bodyData.power
}
})
//存储文件数据到data.json中
fs.writeFileSync(dataUrl, JSON.stringify(data))
send(res, 0, '', data)
}
})
} else if (req.method === 'POST' && req.url === '/logger') {
let body = ''
req.on('readable', () => {
let chunk = ''
if (null !== (chunk = req.read())) {
body += chunk
}
})
req.on('end', () => {
let fileContentLog = fs.readFileSync(loggerUrl, 'utf-8')
//判断是否有日志
let dataLog = []
if (fileContentLog) {
dataLog = JSON.parse(fileContentLog)
}
let fileContentUser = fs.readFileSync(dataUrl, 'utf-8')
let dataUser = JSON.parse(fileContentUser)
if (body) {
// TODO 处理新增日志
let bodyData = JSON.parse(body)
let dataJson = {
id: getLoggerId(),
msg: bodyData.data,
// 时间格式为:2023/6/6 上午8:10:35
time: `${getTime()}`
}
//存储日志
dataLog.unshift(dataJson)
// 并在该对象转化成 JSON 格式的末尾添加换行符(如不添加换行符会导致检测不通过)
fs.writeFileSync(loggerUrl, JSON.stringify(dataLog, null, 2) + '\n')
send(res, 0, '', dataJson)
}
})
}
})
function getTime() {
// 获取当前时间
const currentDate = new Date()
// 获取年、月、日、时、分、秒
const year = currentDate.getFullYear()
const month = currentDate.getMonth() + 1 // 月份是从 0 开始的,所以要加 1
const day = currentDate.getDate()
const hours = currentDate.getHours()
//获取是上午还是下午
const amPm = hours >= 12 ? '下午' : '上午'
const minutes = currentDate.getMinutes()
const seconds = currentDate.getSeconds()
// 格式化时间
const formattedTime = `${year}/${month}/${day} ${amPm}${hours}:${minutes}:${seconds}`
return formattedTime
}
server.listen(8080, () => {
console.log('Server running on port 8080')
})
index.js:
/**
* 请完成下面的 TODO 部分,其他代码请勿改动
*/
// 对响应进行统一处理,如果不调用该函数,可能导致判题出错
// 参数为服务器的响应对象
function parseRes(res) {
return (res.json && res.json()) || res.data
}
const App = {
setup() {
const { onMounted } = Vue
const data = Vue.reactive({
userList: [], //用户数组
loggerList: [] //日志数组
})
const getPowerText = (power) => {
return power ? '可以登录' : '禁止登录'
}
const handleChange = async (e) => {
if (e.target.tagName !== 'INPUT') {
return
}
// TODO 处理发送请求修改当前用户的权限并更新一条日志记录
//处理put请求
let res = await axios.put(`/editUser`, {
id: e.target.dataset.id,
power: e.target.checked
})
if (res.status == 200) {
data.userList = parseRes(res.data)
} else {
console.log('修改失败')
}
//调用post请求,添加一条修改日志
//用id找出用户名
let userName = data.userList.find((item) => item.id == e.target.dataset.id).name
let postRes = await axios.post('/logger', {
data: `超级管理员将用户${userName}设置为${getPowerText(e.target.checked)}权限`
})
if (postRes.status == 200) {
//将数据放在数组首
let a = parseRes(postRes.data)
data.loggerList.unshift(a)
} else {
console.log('添加日志失败')
}
}
// TODO 在页面挂载之前请求用户数据并修改对应的响应数据
//利用axios获取数据
const getUserData = async () => {
let res = await axios.get('/users')
if (res.status == 200) {
data.userList = res.data.data
} else {
getUserData()
}
}
onMounted(() => {
getUserData()
})
return {
data,
handleChange,
getPowerText,
getUserData
}
}
}
const app = Vue.createApp(App)
app.mount(document.querySelector('#app'))
10.账户验证
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>账户验证</title>
<link rel="stylesheet" type="text/css" href="./css/index.css" />
<link rel="stylesheet" href="./css/[email protected]/index.css">
<script src="./js/vue3.global.js"></script>
<script src="./css/[email protected]/index.full.js"></script>
<script type="importmap">
{
"imports": {
"vue-demi": "./js/index.mjs",
"vue": "./js/vue.esm-browser.prod.js",
"pinia": "./js/pinia.esm-browser.js"
}
}
</script>
<script src="./js/pinia.esm-browser.js" type="module"></script>
</head>
<body>
<!-- app根组件开始 -->
<div id="app">
<div class="header">
<img class="back-btn" src="images/arrow.png" />
<span id="main_title">使用手机号登录</span>
<span class="blank"></span>
</div>
<component :is="showName"></component>
</div>
<!-- app根组件结束 -->
<!-- phone组件开始 -->
<template id="phone">
<div>
<ul class="phone">
<span>输入手机号码</span>
<li>
<input v-model="phoneVal" type="text" autofocus id="numberInput" />
</li>
<li>
<input v-model="isSure" type="checkbox" name="" id="checkbox" />
<span>已阅读并同意
<a href="javascript:;">服务协议</a>
和
<a href="javascript:;">隐私保护指引</a>
</span>
</li>
<button id="btn" @click="nextStep">下一步</button>
</ul>
</div>
</template>
<!-- phone组件结束 -->
<!-- check组件开始 -->
<template id="check">
<ul class="number">
<span>输入短信验证码</span>
<li class="hassend">已向
<i>{
{ handlePhoneVal }}</i>
发送验证码
</li>
<li class="code-container">
<input v-for="(item, index) in verificationCodeInput" :key="index" v-model="item" @input="handleInput(index)"
@keydown="handleKeyDown(index)" class="code" type="number" min="0" max="9" ref="codeInput{
{index}}"
required />
</li>
<a href="javascript:;" id="resend" @click="resendCode">重新发送</a>
</ul>
</template>
<!-- check组件结束 -->
<!-- success组件开始 -->
<template id="success">
<div class="success">
<ul>
<div>验证成功!</div>
<div>5s后将自动跳转</div>
</ul>
</div>
</template>
<!-- success组件结束 -->
</body>
<script type="module">
import { createPinia } from 'pinia';
import { createApp, ref, reactive, provide, inject, onBeforeMount } from 'vue';
const { ElNotification } = ElementPlus;
const app = createApp({
setup() {
const data = reactive({
showName: 'phone',
});
const code = ref([]);
const phoneVal = ref('');
const createCode = function () {
let res = '';
function* _create() {
let count = 0;
while (++count <= 6) {
yield Math.floor(Math.random() * 10);
}
}
for (const iterator of _create()) {
res += iterator;
}
return res;
};
const handlePhone = (num) => {
let res = '';
for (let idx in num) {
if (idx > 2 && idx < num.length - 2) {
res += '*';
} else {
res += num[idx];
}
}
return res;
};
provide('code', code);
provide('phoneVal', phoneVal);
provide('createCode', createCode);
provide('data', data);
provide('handlePhone', handlePhone);
return {
...data,
};
},
});
app.use(ElementPlus);
app.use(createPinia());
app.component('phone', {
template: '#phone',
setup() {
const isSure = ref('');
const phoneVal = inject('phoneVal');
const code = inject('code');
const createCode = inject('createCode');
const data = inject('data');
function verifyPhone(num) {
if (num.length !== 11) return false;
return num[0] === '1' && num[1] === '8';
}
return {
isSure,
phoneVal,
nextStep() {
if (!isSure.value)
return ElNotification({
title: '发送失败',
message: '请先阅读并同意下方协议',
type: 'error',
});
if (!verifyPhone(phoneVal.value))
return ElNotification({
title: '发送失败',
message: '无效的手机号码',
type: 'error',
});
code.value = createCode();
ElNotification({
title: '发送成功',
message: '您在验证码为' + code.value,
type: 'success',
});
data.showName = 'check';
},
};
},
});
app.component('check', {
template: '#check',
setup() {
const phoneVal = inject('phoneVal');
const handlePhoneVal = inject('handlePhone')(phoneVal.value);
const data = inject('data');
const code = inject('code');
const createCode = inject('createCode');
const verificationCodeInput = Array(6).fill('');
onBeforeMount(() => {
setTimeout(() => {
const oCodeIptList = [...document.getElementsByClassName('code')];
oCodeIptList[0].focus();
oCodeIptList.map((item) => {
item.oninput = function () {
if (item.value) {
item?.nextElementSibling && item?.nextElementSibling.focus();
} else {
item?.previousElementSibling && item?.previousElementSibling.focus();
}
trackVal();
};
});
function trackVal() {
const val = verificationCodeInput.join('');
if (val.length === 6) {
if (val === code.value) {
ElNotification({
title: '验证成功',
message: '欢迎回来',
type: 'success',
});
data.showName = 'success';
} else {
ElNotification({
title: '验证失败',
message: '您输入的验证码有误',
type: 'error',
});
verificationCodeInput.fill('');
oCodeIptList[0].focus();
}
}
}
});
});
return {
handlePhoneVal,
verificationCodeInput,
handleInput(index) {
if (index < 5 && verificationCodeInput[index].length === 1) {
this.$refs[`codeInput${index + 1}`]?.focus();
} else if (index > 0 && verificationCodeInput[index].length === 0) {
this.$refs[`codeInput${index - 1}`]?.focus();
}
trackVal();
},
handleKeyDown(index) {
if (event.key === 'Backspace' && index > 0) {
this.$refs[`codeInput${index - 1}`]?.focus();
}
},
trackVal() {
const val = verificationCodeInput.join('');
if (val.length === 6) {
if (val === code.value) {
ElNotification({
title: '验证成功',
message: '欢迎回来',
type: 'success',
});
data.showName = 'success';
} else {
ElNotification({
title: '验证失败',
message: '您输入的验证码有误',
type: 'error',
});
verificationCodeInput.fill('');
this.$refs['codeInput0']?.focus();
}
}
},
resendCode() {
code.value = createCode();
ElNotification({
title: '发送成功',
message: '您的验证码为' + code.value,
type: 'success',
});
},
};
},
});
app.component('success', {
template: '#success',
});
app.mount('#app');
</script>
</html>