应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

用原生JS开发编程游戏机器人流水线

  作者:吴亮(月影)
  记得之前玩过一个flash编程小游戏,印象深刻,叫机器人流水线(manufactoria),不知道有没有同学也玩过。可惜的是,现在falsh已经停止运行了,这个原版的小游戏无法体验到。
  不过最近几天,我凭着之前的印象,复刻出了这个小游戏。
  这个小游戏的规则是,将左侧的元件放置到右侧的面板上,然后点击运行,机器人会沿着元件指定的路径运行,并影响地步序列的状态,最终按照任务的要求完成,即可过关。
  例如上面的截图是第五关,任务是队列里不能出现不同颜色的球,也就是说如果队列中只有红球或只有蓝球,要把机器人移动到处,否则将机器人移到任意其他空格。
  我们能将元件放置到在任意白色空格处,机器人走到元件上会根据元件的类型来产生相应的动作。
  manufactoria的元件非常简单,只有两种类型:传送器和比较器,但根据不同的作用一共分为7种:
  其中传送器有五种,四种带颜色的,机器人通过的时候会将对应颜色的球添加到序列的末尾,还有最后一种黑色的,机器人通过,序列不变。
  比较器有两种,分别是红蓝比较器和黄绿比较器。比较器的作用是,当机器人通过它时,判断序列头部的球颜色,若颜色是比较器允许的颜色,则机器人朝对应的加号方向前进,并将该序列头部的这个球取出,否则,机器人沿着弧形箭头方向前进,且序列保持不变。
  神奇的是有了这些简单的元件,我们就可以让机器人完成复杂的任务了。而且这和编程思想是一致的,我们可以通过元件构建出顺序,选择和循环结构体!
  如下图,在第22关,可以用绿色小球构建出循环体解决问题:
  好了,前面说了规则,有兴趣的同学可以自行挑战,目前有20多个关卡,我会不定期更新新的关卡,等待大家的挑战。
  接下来,我们看一下游戏是怎么实现的。
  首先是面板的HTML结构,这个结构非常简单:span第selectidlevelPickeroption1optionselect关span说明:鼠标选择上方元件添加到右侧面板中,键盘上下左右旋转,空格翻转。buttonclassbtnidrunBtnbuttonbuttonclassbtnidstopBtnbutton序列iiii结果
  在这里我就不多说了,元件是通过CSS样式绘制的,比如比较器:。comparator{margin:10px20px;borderbottomrightradius:50;borderbottomleftradius:50;}。comparator::before{content:;marginleft:10px;}。comparator::after{content:;marginleft:10px;}。comparator。red::before{color:red;}。comparator。green::before{color:green;}。comparator。blue::after{color:blue;}。comparator。yellow::after{color:orange;}
  因为所有的元件结构都不复杂,所以用一个HTML标签,加上before和after伪元素,就完全可以绘制出来的。
  右侧的网格是一个grid布局的棋盘:app{width:520px;height:520px;borderbottom:solid1px0002;borderright:solid1px0002;backgroundimage:lineargradient(90deg,rgba(0,0,0,0。15)2。5,transparent2。5),lineargradient(rgba(0,0,0,0。15)2。5,transparent2。5);backgroundsize:40px40px;backgroundrepeat:repeat;display:grid;gridtemplatecolumns:repeat(13,40px);gridtemplaterows:repeat(13,40px);}appp{textalign:center;fontsize:1。8rem;lineheight:48px;;}
  在网格中添加对应的元件,就只要找到对应的格子往里添加指定类型的元素就可以了。
  机器人是绝对定位的元素,它移动的时候的动画效果可以通过transition给出:robot{position:absolute;transition:alllinear。2s;}robot::after{fontsize:1。8rem;content:;margin:5px;}
  这样,基本的HTML和CSS就实现完成了。实际上,大部分UI和交互效果都可以通过HTML和CSS指定,让JS只需要负责控制逻辑,这样就简单很多。
  接下来我们看具体的逻辑。
  首先我们实现一个点击左侧面板的元件,将元件用鼠标拾取的效果:unctionenablePicker(){constbuttonspanel。querySelector(。buttons);buttons。addEventListener(mousedown,({target}){if(main。className!runningtarget!buttonstarget。className){constnodetarget。cloneNode(true);mousePick。innerHTML;mousePick。appendChild(node);}});window。addEventListener(mousemove,({x,y}){mousePick。style。left{x25}px;mousePick。style。top{y25}px;});window。addEventListener(contextmenu,(e){e。preventDefault();returnfalse;});window。addEventListener(mouseup,({target}){if(target。parentNode!buttonstarget。className!normal){mousePick。innerHTML;}});window。addEventListener(keydown,({key}){constelmousePick。children〔0〕;if(!elel。classNametrash)return;if(keyArrowRight){el。dataset。turn0;}elseif(keyArrowDown){el。dataset。turn1;}elseif(keyArrowLeft){el。dataset。turn2;}elseif(keyArrowUp){el。dataset。turn3;}elseif(key){letnNumber(el。dataset。flip)0;el。dataset。flipn2;}if(key。startsWith(Arrow)el。classList。contains(comparator)){el。dataset。turn(Number(el。dataset。turn)3)4;}});}
  这里,我们直接用cloneNode,将面板上的元素复制出来,做出一个透明效果,跟随鼠标移动。另外,我们还做了键盘控制,通过键盘控制元件的具体方向:
  注意,我们用JS控制元素方向的时候,通过设置turn和flip来表示元素翻转,至于元素具体的展现,则通过CSS来定义:〔dataturn1〕{transform:rotate(。25turn);}〔dataturn2〕{transform:rotate(。5turn);}〔dataturn3〕{transform:rotate(。75turn);}〔dataflip1〕{transform:scale(1,1);}〔dataturn1〕〔dataflip1〕{transform:rotate(。25turn)scale(1,1);}〔dataturn2〕〔dataflip1〕{transform:rotate(。5turn)scale(1,1);}〔dataturn3〕〔dataflip1〕{transform:rotate(。75turn)scale(1,1);}
  接着是设置和移动机器人的函数:functionsetRobot(){conststartapp。querySelector(。start);constrowNumber(start。dataset。x);constcolNumber(start。dataset。y);let{x,y}app。getBoundingClientRect();xxcol40;yyrow40;consteldocument。getElementById(robot)document。createElement(p);el。idrobot;el。style。left{x}px;el。style。top{y}px;el。dataset。xx;el。dataset。yy;el。dataset。rowrow;el。dataset。colcol;el。dataset。fromDirection;document。body。appendChild(el);}functionmoveRobot(direction){letxNumber(robot。dataset。x);letyNumber(robot。dataset。y);letrowNumber(robot。dataset。row);letcolNumber(robot。dataset。col);letfromDirection;if(directionleft){x40;col;fromDirectionright;}elseif(directionright){x40;col;fromDirectionleft;}elseif(directionup){y40;row;fromDirectiondown;}elseif(directiondown){y40;row;fromDirectionup;}robot。style。left{x}px;robot。style。top{y}px;robot。dataset。xx;robot。dataset。yy;robot。dataset。rowrow;robot。dataset。colcol;robot。dataset。fromDirectionfromDirection;console。log(row,col,robot);returnnewPromise(resolve{robot。addEventListener(transitionend,(){console。log(row,col,robot。dataset。row,robot。dataset。col);resolve(robot);},{once:true});防止浏览器transitionend事件有时候不被触发setTimeout(()resolve(robot),220);});}
  这里,setRobot将机器人设置到起始位置,起始位置在网格中是一个className包含start的p元素,这个元素的位置在后续调用loadLevel读取当前关卡的时候初始化。
  moveRobot实际上是一个异步方法,它返回一个Promise,在机器人执行完动作之后resolve。不过这里有个细节要注意,我一开始使用transitionend来判断动画结束,但是浏览器不能保证transitionend每次都被触发,所以有时候机器人会不明原因停下来,后来我就加了一个setTimeout来防止这种情况。
  接下来的一系列方法和底部序列有关,序列代表着输入输出,机器人就是通过移动来影响序列,从而达成指定任务。序列实际上是一个队列,操作比较简单。functionsetDataList(list〔〕){io。innerHTML序列;for(leti0;ilist。length;i){consteldocument。createElement(i);el。innerHTMLlist〔i〕;io。appendChild(el);}}functiongetTopData(){constitemio。querySelector(i);if(item)returnitem。innerHTML;elsereturnnull;}functionpopData(){constitemio。querySelector(i);item。style。width0;returnnewPromise(resolve{item。addEventListener(transitionend,(){item。remove();resolve(item);},{once:true});防止浏览器transitionend事件有时候不被触发setTimeout((){item。remove();resolve(item);},220);});}functionappendData(data){consteldocument。createElement(i);el。innerHTMLdata;io。appendChild(el);}functiongetIOData(){constlistio。querySelectorAll(i);letret;for(leti0;ilist。length;i){retlist〔i〕。innerHTML;}returnret;}
  然后是一个辅助方法,用来获得机器人所在位置的棋盘元素。我们在初始化棋盘的时候,会给每个元素设置x和y坐标,在机器人走动的时候,也会更新对应的row和col坐标,所以我们通过选择器就可以快速找到机器人所在位置的棋盘格子,从而判断其中的元件。functiongetRobotCell(){letxNumber(robot。dataset。row);letyNumber(robot。dataset。col);constcelldocument。querySelector(appp〔datax{x}〕〔datay{y}〕);returncell;}
  接下来就是代码最核心的部分了。functioncheckCell(cell,fromDirection){constret{direction:null,effect:null,type:null,data:false,};constchildrencell。children;if(children。length){for(leti0;ichildren。length;i){constelchildren〔i〕;constflipel。dataset。flip;constturnel。dataset。turn;if(el。classList。contains(pass)){ret。typepass;通道if(children。length1){交叉通道if(fromDirectionupfromDirectiondown){if(turn0turn2)continue;}if(fromDirectionleftfromDirectionright){if(turn1turn3)continue;}}if(turn0)ret。directionright;if(turn1)ret。directiondown;if(turn2)ret。directionleft;if(turn3)ret。directionup;if(el。classList。contains(red))ret。effect;if(el。classList。contains(green))ret。effect;if(el。classList。contains(yellow))ret。effect;if(el。classList。contains(blue))ret。effect;}elseif(el。classList。contains(comparator)){比较器ret。typecomparator;constdatagetTopData();if(datael。classList。contains(red)){if(turn0)ret。directionleft;if(turn1)ret。directionup;if(turn2)ret。directionright;if(turn3)ret。directiondown;ret。datatrue;}elseif(datael。classList。contains(green)){if(turn0)ret。directionleft;if(turn1)ret。directionup;if(turn2)ret。directionright;if(turn3)ret。directiondown;ret。datatrue;}elseif(datael。classList。contains(blue)){if(turn0)ret。directionright;if(turn1)ret。directiondown;if(turn2)ret。directionleft;if(turn3)ret。directionup;ret。datatrue;}elseif(datael。classList。contains(yellow)){if(turn0)ret。directionright;if(turn1)ret。directiondown;if(turn2)ret。directionleft;if(turn3)ret。directionup;ret。datatrue;}else{if(turn0)ret。directiondown;if(turn1)ret。directionleft;if(turn2)ret。directionup;if(turn3)ret。directionright;}}if(flip1){翻转交换if(turn0turn2){if(ret。directionleft)ret。directionright;elseif(ret。directionright)ret。directionleft;}else{if(ret。directionup)ret。directiondown;elseif(ret。directiondown)ret。directionup;}}}}console。log(ret);returnret;}functioncheckState(){constcellgetRobotCell();constfromDirectionrobot。dataset。fromDirection;letstate{direction:null,effect:null,accepted:false,fromDirection,};if(cell。classNameflag){state。acceptedtrue;}elseif(cell。className!start){state{。。。state,。。。checkCell(cell,fromDirection),};}returnstate;}
  当机器人移动到一个格子的时候,我们通过checkState判断他的状态,状态包括四个信息,direction:机器人当前可以移动的方向,effect:机器人操作序列的动作,accepted:机器人是否移动到,fromDirection:机器人上一步从哪里移动过来的。
  checkCell则是具体的判断逻辑,我们通过格子中的元件来具体判断机器人的这些状态,这部分逻辑虽然较繁琐,但其实也不太复杂,唯一需要注意的是,一个网格中可以放两个相互垂直的传送器,当机器人经过的时候,如果有两个方向,会默认选择直行的方向,这也是为什么我们需要fromDirection来判断机器人从哪个方向过来。
  接下来是展示结果,运行、停止按钮状态,sleep等细节,就不一一赘述了。functioninitResult(){result。innerHTML结果;}functionappendResult(successfalse){constrsuccess?A:E;consteldocument。createElement(span);el。innerHTMLr;if(success)el。classNameaccept;result。appendChild(el);}functionsleep(ms10){returnnewPromise(resolve{setTimeout(resolve,ms);});}runBtn。addEventListener(mousedown,async(){mousePick。innerHTML;runBtn。classNamebtntap;runBtn。disabledtrue;main。classNamerunning;awaitrun();});stopBtn。addEventListener(mousedown,(){mousePick。innerHTML;stopBtn。classNamebtntap;main。className;setRobot();});window。addEventListener(mouseup,(){if(stopBtn。classNamebtntap){stopBtn。classNamebtn;runBtn。disabledfalse;runBtn。classNamebtn;}});
  然后,我们根据关卡数据,读取和初始化对应的关卡:letcurrentLevel;functionloadLevel(level){constdatalevels〔level〕;currentLevel{。。。data,level,};taskInfo。innerHTMLp任务:{data。task}p提示:{data。hint};constitemsdocument。querySelectorAll(。buttonsp);for(leti0;iitems。length;i){if(!data。units。includes(i)){items〔i〕。classList。add(hide);}else{items〔i〕。classList。remove(hide);}}setDataList(〔。。。data。tests〔0〕。data〕);constboardnewArray(169);board。fill(1);constsizedata。size13;constv(13size)2;constrange〔v,v,vsize,vsize,〕;const〔a,b,c,d〕range;for(letia;ic;i){for(letjb;jd;j){constidxi13j;board〔idx〕0;}}constsvMath。floor(size2);conststartv13s;constend(vsize1)13s;board〔start〕1;board〔end〕2;init(board);constsavedDatalocalStorage。getItem(manufactorialevel{level});if(savedData){constdataJSON。parse(savedData);for(leti0;idata。cells。length;i){constcelldata。cells〔i〕;consteldocument。createElement(p);el。classNamecell。state;el。dataset。turncell。turn;el。dataset。flipcell。flip;app。children〔cell。idx〕。appendChild(el);}}setRobot();returncurrentLevel;}
  初始化之后,当放置好元件,点击运行时,让机器人运行起来:asyncfunctionrun(){levelPicker。disabledtrue;consttestscurrentLevel。tests;initResult();for(leti0;itests。length;i){const{data,accept}tests〔i〕;setDataList(〔。。。data〕);setRobot();awaitsleep();awaitmoveRobot(down);while(true){if(main。className!running)break;conststatecheckState();if(state。direction){if(state。typecomparatorstate。data){awaitPromise。all(〔moveRobot(state。direction),popData(),〕);}else{awaitmoveRobot(state。direction);if(state。effect){appendData(state。effect);}}}else{break;}}if(main。className!running)break;constcellgetRobotCell();if(accepttrue){appendResult(cell。classNameflag);}elseif(typeofacceptstring){if(cell。className!flag){appendResult(false);}else{appendResult(acceptgetIOData());}}else{appendResult(cell。className!flag);}awaitsleep(500);}runBtn。classNamebtn;runBtn。disabledfalse;if(main。classNamerunning){constsuccess!result。textContent。includes(E);consteldocument。createElement(span);el。innerHTMLsuccess?:成功::失败;if(success)el。classNameaccept;result。appendChild(el);setDataList(〔〕);}main。className;levelPicker。disabledfalse;setRobot();}
  因为有的关卡比较复杂,玩家也不希望好不容易通关的结果,下一次进游戏又没有了,所以我们做一个localStorage的本地保存机制:把数据保存到本地functionsaveLevel(){const{level}currentLevel;constdata{level,cells:〔〕};constcellsapp。children;for(leti0;i169;i){constcellcells〔i〕;if(cell。children。length){for(letj0;jcell。children。length;j){constitemcell。children〔j〕;constd{state:item。className,turn:item。dataset。turn,flip:item。dataset。flip,idx:Number(cell。dataset。x)13Number(cell。dataset。y),};data。cells。push(d);}}}localStorage。setItem(manufactorialevel{level},JSON。stringify(data));}
  最后的最后,我们做一个下拉列表来选择对应的关卡:functioninitLevelPicker(){constlenlevels。length;levelPicker。innerHTML;for(leti0;ilen;i){constoptionnewOption(i1,i);levelPicker。appendChild(option);}levelPicker。addEventListener(change,(){loadLevel(levelPicker。value);});loadLevel(levelPicker。value);}initLevelPicker();
  这样,我们的游戏就开发完成了。实际上这个游戏本身开发的难度并不高,但是玩法却很丰富,关卡也很有挑战性。这就是编程游戏的乐趣。
  有同学玩通关的话,欢迎戳下方链接,在代码评论区交流玩法心得
  manufactoria码上掘金manufactoria码上掘金
  关注字节前端ByteFE公众号,追更不迷路!

历史通鉴学堂历史上真实的唐朝时期是怎样的局面?(中篇)历史通鉴学堂谈古论今,笑看人生;以史为镜,好学做人。大家好,这里是历史通鉴学堂。中国是一个古老神奇的国度,身为华夏儿女,我们感到无比的骄傲与自豪,华夏文明上下……掉发多,害怕哪天就秃了?原来与这3个营养问题有关系与其羡慕别人一头乌黑浓密的毛发,还不如多关注自己的毛发健康,通过各种良好习惯的养成让自己头发长势良好,保持乌黑。然而,有部分人头发脱落数量多,脱落速度快,甚至面临秃顶,有这种情……父亲,是你决定了孩子20年后的命运,爸爸对孩子的意义,值得深大家好,我是布谷妈从游乐场回来,宝爸感慨说:你有没有发现,游乐场里竟然是爸爸带娃居多?他这么一说,我想想,还真是。除了一部分老人带娃,以及少数几位宝妈,其余真的都是……如何确保和确认NASA的DART动能小行星偏转器击中其目标?尽管小行星撞击地球的几率很小,但即使是直径约500英尺(约150米)的相对较小的小行星也会携带足够的能量,在撞击地点周围造成广泛的破坏。美国国家航空航天局在美国和世界范围内领导……四川1岁女娃摔倒趴地大哭不止,爸爸冷漠旁观,妈妈看监控怒了关于育儿的知识,由于网络上众说纷纭,所以在应对孩子调皮、哭泣的时候,许多家长都会做出不同的选择。比方说,孩子哭闹的时候,有的家长会赶紧安抚。而有的家长会选择视而不见,并且理由充……2021年诺贝尔物理学奖揭晓3位科学家分享,6位华人科学家曾今日晚间,瑞典皇家科学院宣布,日本出身的美国籍普林斯顿大学科学家真锅淑郎(SyukuroManabe)、德国科学家克劳斯哈塞尔曼(KlausHasselmann)和意大利科学家……做完喝到的第一口就转圈圈了!怎么会这么鲜这么好喝好久没喝粥了,翻翻食材做了这个皮蛋鲜虾火腿粥做完喝到的第一口就转圈圈了!怎么会这么鲜这么好喝!!拿去开店就凭这碗粥就能火准备食材皮蛋1个大虾8个火腿2片……申花旧将夺冠了,沙拉维晒捧杯照庆祝胜利新闻晨报记者甘慧今天,前申花外援沙拉维在社交平台上晒出了自己手捧欧协联冠军奖杯的照片。在凌晨进行的本赛季欧协联决赛中,罗马1:0战胜费耶诺德夺得冠军。这是沙拉维时隔两年半……北京樱花界的大熊猫你见过吗?玉渊潭公园内绿樱花竞相绽放北京玉渊潭公园的樱花园内,一棵绿色的樱花格外的吸引游客,绿色樱花被视为樱花界的大熊猫,极其珍贵,引来众多的游客前来观赏、拍摄绿樱花。玉渊潭公园共有绿色樱花10余棵,均为人……女明星们个个减肥不要命,面如黄蜡身似竹竿,瘦骨嶙峋像纸片人不知道是从什么时候开始,女性都开始追求以白瘦幼的风气为重,女生们为了追求美丽,不停尝试着各种减肥餐、瘦身运动,外貌焦虑也是越来越明显。其实,回顾早些年前的娱乐圈,大家会发……NASAArtemisI登月任务的最后一次关键测试再次被推迟据CNET报道,在过去的几天里,美国宇航局(NASA)一直在与围绕ArtemisI登月任务的突然的、意想不到的技术问题作斗争,这是一项将人类带回月球表面的开拓性努力。自上周五以……全谷物对人体的营养价值我们之前说了平衡膳食的五大类,其中第一大类就是谷薯类,占人体总能量的5065,很多人都疑惑,那怎么算才能知道自己吃几个馒头,几碗米饭能达到一天的用餐标准呢?下面我们来说一……
关节炎和秋裤的关系即便是在夏天,有的人也会穿上秋裤,防止膝关节受凉、疼痛。如果到了秋冬季节还有谁倔强地不穿秋裤,长辈们就会郑重地告诫你:等你老了,就知道什么叫关节炎了。不听老人言,吃亏在眼……32分钟28分3抢断!广东外租小将打出惊艳一战前途无量?7598广州男篮,福建男篮又遭遇一场溃败。但对于福建小将刘旭乘来说,这却是一场值得庆祝的比赛。出战32分钟,刘旭乘拿到28分3次助攻3次抢断2个篮板。效率也高,刘旭乘出手14次……点球大战取胜只是运气?巴西荷兰的败因早就埋下了卡塔尔世界杯,进入到14决赛,终于诞生了两场名局。克罗地亚的坚韧、阿根廷的斗志,都给人留下了很深的印象,但所对应的则是巴西足球的拖沓和荷兰队的悲壮。看似获胜两队只是拥有了……乐园5折起!马蜂窝春季大促开启本地玩乐新主张疫情尚未消散,但追寻美好生活的脚步不能停。马蜂窝近期上线的春季大促瞄准了用户的春游需求,本地游、周边游成为今年春游季释放热情的主要对象。大促通过丰富有趣的本地游和周边游玩乐产品……张柏芝不愧是时髦精,回顾年轻时的穿搭示范,网友照穿也一样美岁月从不败美人,美人在骨不在皮!张柏芝作为90年代名列前茅的港风颜值代表,她有着五官端正,骨相干净立体、精致又气质,侧脸更是无敌,天生丽质的她如今已经42岁了,已是3个孩……惨遭逆转,詹姆斯冲历史第一!湖人无人可用,佩林卡浓眉都该反思客场凭借着拉塞尔威斯布鲁克的抢断以及绝平三分艰难战胜多伦多猛龙之后,洛杉矶湖人没有调整时间,他们马不停蹄地赶往了华盛顿,背靠背挑战奇才。不过,这场比赛湖人在体能上并没有吃太多的……河西走廊骑行记(4)玄奘之路旅行第三天。敦煌以东120km,瓜州县。做了一夜烈日炎炎,口如焦釜的噩梦,这自然是前一天被暴晒的后果,但最终却居然从这酷热的噩梦中被冻醒过来。哆哆嗦嗦走到窗口一看,外面竟……RedmiK50Pro配置强劲价格合理,但是缺点也很明显RedmiK50系列的第二场发布会,发布了多款产品,其中包括了笔记本、电视、手机等产品,但最热门的还是K系列的手机,在这次发布会当中一共发布了3款手机,分别是RedmiK40S……关晓彤浪费好身材!三千卫衣穿出某宝风,女星衣品差太灾难了女明星们为了保持上镜形象,既要提升颜值,又要节食瘦身,但很容易忽视关键的一点:穿搭审美。所以,女星们的街拍时刻也是比拼个人审美的时尚秀场。近日,女星关晓彤现身机场,她身穿……仲春,红薯和银耳才是绝配,坚持每周吃3次,好多人不知道有啥用农历二月就是春天中的仲春之月,此时草长莺飞,花香鸟语,处处洋溢着一派春日的生机景象。随着春日阳光愈加温暖,昼夜温差加大,春燥也是悄悄来作祟,最明显的就是春天很多人容易上火……倪妮片场可真威风!穿披肩式连衣裙拍戏,助理伺候系鞋带派头大连衣裙是女性穿衣打扮里的必备单品,尤其是女明星,不管是上班还是生活中,满满都是连衣裙的影子,但是想把连衣裙穿出好的效果出来,款式的选择肯定是首要条件,其次是衣品装饰,结合自身条……XPS1797004k32G1TB触摸屏戴尔国行顶配在保保到XPS1797004k32G1TB触摸屏戴尔国行顶配在保保到2023年1月,官网可查目前所有超极本中颜值最高的一款,4边超窄边框17寸大屏,17寸的屏只有15……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网