Browse Source

add page

theme
npmrun 2 years ago
parent
commit
f11882fcd9
  1. 19
      public/js/common/flush.js
  2. 23
      public/js/common/main.js
  3. 251
      public/static/css/index.css
  4. 128
      public/static/css/prism.css
  5. BIN
      public/static/images/balloon.png
  6. BIN
      public/static/images/logo.png
  7. 7
      public/static/js/prism.js
  8. 366
      public/static/js/textarr.js
  9. 62
      public/static/js/write.js
  10. 7
      public/style/common/animate.min.css
  11. 5
      public/style/common/style.css
  12. 95
      public/style/views/color.css
  13. 4
      route.txt
  14. 3
      source/auth/index.ts
  15. BIN
      source/db/data.db
  16. 80
      source/models/Color.ts
  17. 37
      source/models/user.ts
  18. 84
      source/models/user_info.ts
  19. 19
      source/route/views/index.ts
  20. 8
      source/route/views/register.ts
  21. 10
      source/route/views/user.ts
  22. 18
      source/run.ts
  23. 18
      template/helper/flush.pug
  24. 3
      template/helper/helper.pug
  25. 10
      template/htmx/path/user.pug
  26. 5
      template/layout/layout.pug
  27. 51
      template/views/color.pug
  28. 75
      template/views/resume.pug
  29. 4
      types/global.d.ts

19
public/js/common/flush.js

@ -0,0 +1,19 @@
var $messages = Array.prototype.slice.call(document.querySelectorAll(".message-container .message button.delete"), 0)
$messages.forEach((el, index) => {
let timeID
function click() {
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target.remove()
clearTimeout(timeID)
}
timeID = setTimeout(() => {
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target && $target.remove()
}, (index + 1) * 6000)
el.addEventListener("click", click)
})

23
public/js/common/main.js

@ -14,27 +14,4 @@ document.addEventListener("DOMContentLoaded", () => {
$target.classList.toggle("is-active")
})
})
const $messages = Array.prototype.slice.call(
document.querySelectorAll(".message-container .message button.delete"),
0,
)
$messages.forEach((el, index) => {
let timeID
function click() {
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target.remove()
clearTimeout(timeID)
}
timeID = setTimeout(() => {
const target = el.dataset.target
const $target = document.getElementById(target)
el.removeEventListener("click", click)
$target.remove()
}, (index + 1) * 6000)
el.addEventListener("click", click)
})
})

251
public/static/css/index.css

@ -0,0 +1,251 @@

/*
*项目名: 个人简历
*创建者: Edit
*创建时间:2020.2.15 20:40:41
*联系方式:15622749328(微信同号)
*描述: 酷炫个人简历
*/
*{margin:0;padding: 0;box-sizing: border-box;}
body{
height: 100vh;
}
li{list-style: none;}
html{font-family: ;}
#codeEdit{
width: 48%;
height: 97vh;
margin: auto 0;
}
#resume{
width: 48%;
min-width: 700px;
margin: auto 0;
position: relative;
height: 97vh;
overflow-y: scroll;
display: flex;
}
.ul-list li::before{
content: '♦';
color: #78B1E8;
padding-right: 15px;
font-size: 20px;
font-family: '-webkit-pictograph';
}
.ul-list + .ul-list{
margin-top: 10px;
}
.tag{
color: #ff4614;
}
.footer-height{
height: 200px;
}
.mr{
margin-right: 40px;
}
body{
display: flex;
justify-content: space-around;
}
.balloon-wrap{
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 1;
pointer-events: none;
animation: balloon-ani .8s;
}
@keyframes balloon-ani{
0%{
left: -100vw;
}
20%{
opacity: 0;
}
100%{
opacity: 1;
left: 0;
}
}
img[id*="bg-balloon-"]{
position: absolute;
}
img#bg-balloon-large{
transform: translateY(-17%);
right: -55px;
}
img#bg-balloon-small{
width: 135px;
right: 45px;
transform: translateY(-10%);
}
img#bg-balloon-logo{
height: 120px;
right: 0;
}
.line-wrap{
top: 0;
padding-left: 20px;
display: flex;
height: 100%;
animation: line-wrap-ani .8s;
}
@keyframes line-wrap-ani{
0%{
transform: translateX(100vw);
}
20%{
height: 0;
}
100%{
transform: translateX(0);
height: 100%;
}
}
.line-wrap > div{
width: 8px;
height: 100%;
}
.line-left{
background-color: #FFDF7F;
margin-right: 12px;
}
.line-right{
display: flex;
flex-direction: column;
}
.line-defColor{
background-color: #F3D383;
}
.line-midColor{
background-color: #F3C583;
}
.line-darkColor{
background-color: #EDAF45;
}
.line-item1{
height: 20%;
}
.line-item2{
height: 20px;
flex-shrink: 0;
}
.line-item3{
height: 30px;
flex-shrink: 0;
}
.line-item4{
height: 20%;
flex-shrink: 0;
}
.line-item5{
height: 25px;
margin-top: 10px;
margin-bottom: 15px;
flex-shrink: 0;
}
.line-item6{
height: 10%;
flex-shrink: 0;
}
.line-item7{
height: 8%;
flex-shrink: 0;
}
.line-item8{
height: 8%;
flex-shrink: 0;
}
.text-wrap{
margin-left: 30px;
padding: 40px 50px 40px 0;
font-size: 15px;
line-height: 1.5em;
}
.inten{
color: #EF8C06;
}
.text-title{
margin-top: 15px;
color: #548DD4;
margin-bottom: 5px;
}
.base-info{
display: flex;
}
.base-info>div:first-child{
margin-right: 100px;
}
.project-title{
font-weight: 700;
}
@media (min-width: 500px){
/* 滚动条美化 */
*::-webkit-scrollbar-track-piece {
background-color:#f8f8f8;
}
*::-webkit-scrollbar {
width: 5px;
height: 5px;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(150, 150, 150, .2);
background-clip:padding-box;
min-height:28px;
border-radius: 5px;
}
*::-webkit-scrollbar-thumb:hover {
background-color:#bbb;
}
.sbShow::-webkit-scrollbar-thumb {
background-color: #ccc;
background-clip:padding-box;
min-height: 28px;
border-radius: 5px;
}
}
@media (max-width: 500px){
body{
flex-direction: column;
}
#resume,
#codeEdit{
height: 50vh!important;
width: 100vw!important;
min-width: 100vw!important;
}
.line-wrap{
padding-left: 5px;
}
.line-left{
padding-left: 8px;
}
.text-wrap{
margin-left: 15px;
padding: 60px 30px 40px 0;
}
img#bg-balloon-small {
width: 85px;
right: 32px;
transform: translateY(-10%);
}
img#bg-balloon-large {
transform: translateY(-17%);
right: -40px;
width: 105px;
}
img#bg-balloon-logo {
height: 62px;
}
.base-info{
display: block;
}
.school span{
display: block;
}
}

128
public/static/css/prism.css

@ -0,0 +1,128 @@
/* PrismJS 1.10.0
http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
/*
*项目名: 个人简历
*创建者: Edit
*创建时间:2020.2.15 20:40:41
*联系方式:15622749328(微信同号)
*描述: 酷炫个人简历
*/
code[class*="language-"],
pre[class*="language-"] {
background: none;
/* text-shadow: 0 1px white; */
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
/* background: #f5f2f0; */
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.punctuation {
color: #fff;
}
.namespace {
opacity: .7;
}
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #a67f59;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

BIN
public/static/images/balloon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
public/static/images/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

7
public/static/js/prism.js

File diff suppressed because one or more lines are too long

366
public/static/js/textarr.js

@ -0,0 +1,366 @@
let textArr = [
{
name: 'h2',
class: 'inten',
text: '求职意向:前端开发工程师'
}, {
name: 'h4',
class: 'text-title',
text: '基本信息'
}, {
name: 'div',
class: 'base-info',
children: [{
name: 'div',
children: [{
name: 'div',
text: '姓名:xieyaxin'
},{
name: 'div',
text: '年龄:23'
}]
}, {
name: 'div',
children: [{
name: 'div',
text: '毕业院校:中山大学南方学院'
},{
name: 'div',
text: '联系电话:15622749328'
}]
}]
}, {
name: 'h4',
class: 'text-title',
text: '教育背景'
}, {
name: 'div',
class: 'school',
children: [{
name: 'span',
class: 'mr',
text: '学校:中山大学南方学院'
}, {
name: 'span',
text: '专业:计算机科学与技术'
}, {
name: 'div',
text: '主修课程:HTML、CSS、JavaScript、Vue、JavaEE、MySQL',
}]
}, {
name: 'h4',
class: 'text-title',
text: '专业技能'
}, {
name: 'ul',
class: 'ul-list',
children: [{
name: 'li',
text:'熟练掌握',
children: [{
name: 'span',
class: 'tag',
text: 'H5、CSS3、ES6'
}]
}, {
name: 'li',
text:'能熟练运用不同主流UI框架ElementUI及多个移动端UI框架'
}, {
name: 'li',
text:'掌握前端主流',
children: [{
name: 'span',
class: 'tag',
text: 'Vue框架'
}]
}, {
name: 'li',
text:'熟练掌握',
children: [{
name: 'span',
class: 'tag',
text: 'H5混合APP开发'
}, {
name: 'span',
text: ',跨多端技术uni-app、apicloud'
}]
}, {
name: 'li',
text:'了解微信小程序开发'
}, {
name: 'li',
text:'熟悉后端语言Java、node.js'
}, {
name: 'li',
text: '熟悉',
children: [{
name: 'span',
class: 'tag',
text: 'css预处理器'
}, {
name: 'span',
text: 'sass、stylus以及前端构建工具webpack和npm包管理库'
}]
}]
}, {
name: 'h4',
class: 'text-title',
text: '工作经历'
}, {
name: 'div',
class: 'work',
children:[{
name: 'span',
class: 'mr',
text: '2018.08 — 2019.06'
}, {
name: 'span',
text: 'web前端开发'
}]
}, {
name: 'ul',
class: 'ul-list',
children: [{
name: 'li',
text:'担任公司前端技术部主管,负责',
children: [{
name: 'span',
class: 'tag',
text: '带领新人、项目安排、BUG解决、产品优化、定期开展技术交流会议'
}]
}, {
name: 'li',
text: '负责公司项目开发(web hybridApp、微信公众号H5),其他公司基础项目开发,如PC端商城,企业响应式网站,APP开发!'
}]
}, {
name: 'h4',
class: 'text-title',
text: '项目经验'
}, {
name: 'div',
class: 'item-lv',
children: [{
name: 'ul',
class: 'ul-list',
children: [{
name: 'li',
class: 'project-title',
text: '项目一:宠物商城(混合APP开发)'
}, {
name: 'li',
text: '使用技术栈:YDUI框架、',
children: [{
name: 'span',
class: 'tag',
text: 'Vue全家桶、ApiCloud跨多端打包'
}]
}, {
name: 'li',
text: '项目描述:基于vue-cli搭建的web应用,多用户商城。功能主要包括商家入驻平台上传个人身份信息、发布宠物信息、会员商家一对一聊天、商城买卖、城市筛选。其中包含收货地址、搜索记录、宠物和商家店铺收藏的CRUD操作'
}]
}, {
name: 'ul',
class: 'ul-list',
children: [{
name: 'li',
class: 'project-title',
text: '项目二:智慧校园类(混合APP开发)'
}, {
name: 'li',
text: '使用技术栈:',
children: [{
name: 'span',
class: 'tag',
text: 'Vue全家桶、'
}, {
name: 'span',
text: '滴滴官方'
}, {
name: 'span',
class: 'tag',
text: 'cube-ui'
}, {
name: 'span',
text: '框架、'
}, {
name: 'span',
class: 'tag',
text: 'ApiCloud跨多端打包'
}]
}, {
name: 'li',
text: '项目描述:老师发布作业、考勤点到、发布校园动态...家长接收信息,与老师端进行交互'
}]
}, {
name: 'ul',
class: 'ul-list',
children: [{
name: 'li',
class: 'project-title',
text: '项目三:微信公众号,亲子游商城'
}, {
name: 'li',
text: '使用技术栈:',
children: [{
name: 'span',
class: 'tag',
text: 'Vue、Vue-Router、Axios、Stylus'
}, {
name: 'span',
text: '、YDUI、Vue-awesome-swiper'
}]
}, {
name: 'li',
text: '项目描述:1. Vue-cli脚手架搭建项目'
}, {
name: 'li',
text: '2. 使用Vue-Router做应用页面跳转,路由导航守卫权限控制,params、query传参'
}, {
name: 'li',
text: '3. Axios用做Ajax数据交互,dev环境下配置代理解决跨域,使用createAPI配置baseURL。'
}, {
name: 'li',
text: '4. 使用CSS预处理器stylus简化CSS代码编写,浏览器兼容前缀,rem函数封装。'
}, {
name: 'li',
text: '5. 使用UI框架YDUI,减少不必要的造轮子,提高代码编写效率,用户体验。'
}, {
name: 'li',
text: '6. 使用vue-awesome-swiper轮播图插件。'
}]
}, {
name: 'ul',
class: 'ul-list',
children: [{
name: 'li',
class: 'project-title',
text: '项目四:企业官网(响应式)、商城(PC)兼容IE8以及多端浏览器'
}, {
name: 'li',
text: '使用技术栈:',
children: [{
name: 'span',
class: 'tag',
text: 'jQuery'
}]
}, {
name: 'li',
text: 'PC使用兼容写法兼容Ie8、使用渐进增强逐渐增添些许特效,mediaQuery实现网页自适应,移动端样式权重高于PC,完成移动端页面布局。'
}]
}]
}, {
name: 'h4',
class: 'text-title',
text: '自我评价'
}, {
name: 'ul',
class: 'ul-list',
children: [{
name: 'li',
text:'具有',
children: [{
name: 'span',
class: 'tag',
text: '团队管理经验'
}, {
name: 'span',
text: ',拥有良好的'
}, {
name: 'span',
class: 'tag',
text: '团队协调能力'
}, {
name: 'span',
text: ',工作当中和同事融洽相处'
}]
}, {
name: 'li',
text:'常混迹于',
children: [{
name: 'span',
class: 'tag',
text: '前端主流社区'
}, {
name: 'span',
text: '(github、掘金、知乎、简书),翻阅前端'
}, {
name: 'span',
class: 'tag',
text: '大咖'
}, {
name: 'span',
text: '博客(张鑫旭、阮一峰、黄轶)'
}]
}, {
name: 'li',
text:'热爱前端、思维活跃、学习能力强,抗压能力强。'
}, {
name: 'li',
text:'性格随和、诚恳稳重、身体素质较好、能够很快地适应新环境。'
}]
}
]
let style = `
/*
* 面试官你好我是Edit广东惠州人
* 为您精心准备一份不一样的简历来介绍自己
* 首先准备一些样式
*/
*{
transition: all .8s;
}
/* 稍等,在容器上添加点样式 */
#codeEdit{
color: #fff;
background: #1E1E1E;
}
#resume{
box-shadow: -1px 4px 9px 3px rgba(0, 0, 0, .15);
}
/* 我需要一点代码高亮 */
pre#codeEdit{
color: #CE9e78;
}
.token.selector{
color: rgb(230, 155, 43);
}
.token.comment{
color: #6A9955;
font-size: 14px;
}
.token.property{
color: #60C8FE;
}
.token.function {
color: #DD4A68;
}
/* 接下来请欣赏我的个人简历吧 */
`
let balloon = `
<div class="balloon-wrap">
<img src="/public/static/images/balloon.png" id="bg-balloon-small">
<img src="/public/static/images/balloon.png" id="bg-balloon-large">
<img src="/public/static/images/logo.png" id="bg-balloon-logo">
</div>
<div class="connect" style="width: 100%; display: flex;"></div>`
let line = `
<div class="line-wrap">
<div class="line-left"></div>
<div class="line-right">
<p class="line-defColor line-item1"></p>
<p class="line-darkColor line-item2"></p>
<p class="line-defColor line-item3"></p>
<p class="line-midColor line-item4"></p>
<p class="line-darkColor line-item5"></p>
<p class="line-midColor line-item6"></p>
<p class="line-darkColor line-item7"></p>
<p class="line-midColor line-item7"></p>
</div>
</div>
<div class="connect"></div>`
let text = `
<div class="text-wrap"></div>
`

62
public/static/js/write.js

@ -0,0 +1,62 @@
// Array[Object], ance: 当前children列表.
let parentList = []
let List = [] // Array[Object], ance: 当前children列表. current: 当前子元素遍历下标. 遍历方式从深处开始遍历,"树"的数据结构
function writeText(item, childArr=[], childIndex, parent=document.querySelector('.text-wrap')) {
let data = {}
if(childArr.length > 0){
data = childArr[childIndex]
}else {
data = item
}
let num = 0;
let text = data.text ? data.text : ''
let dom = document.createElement(data.name)
dom.setAttribute('class', data.class || '')
parent.appendChild(dom)
let timer = setInterval(() => {
if(num <= text.length){
dom.innerHTML = text.substr(0, num)
resume.scrollTop = 100000
num++
}else{
clearInterval(timer)
if (data.children) {
List.push({
ance: data.children,
current: 0,
length: data.children.length
})
parentList.push(dom)
writeText('', data.children, 0, dom)
}else if (childArr.length > 0) {
if(childArr.length-1 > childIndex){
writeText('', childArr, ++childIndex, parentList[parentList.length-1])
List[List.length-1].current = List[List.length-1].current + 1
}else {
parentList.pop()
filterLength()
}
}else if (textArr.length-1 > currentIndex_) { //
writeText(textArr[++currentIndex_])
parentList = []
}
}
}, 20);
}
function filterLength () {
for (let i = List.length-1; i >=0; i--) {
if(List[i].length-1 == List[i].current){
List.pop()
}else{
List[List.length-1].current = List[List.length-1].current + 1
break;
}
}
if(List.length>0){
writeText('', List[List.length-1].ance, List[List.length-1].current,parentList[parentList.length-1])
}else if(textArr.length-1 > currentIndex_){
writeText(textArr[++currentIndex_])
parentList = []
}
}

7
public/style/common/animate.min.css

File diff suppressed because one or more lines are too long

5
public/style/common/style.css

@ -23,3 +23,8 @@ html {
.navbar-dropdown{
min-width: auto;
}
:root {
--animate-duration: 400ms;
--animate-delay: 0.2s;
}

95
public/style/views/color.css

@ -0,0 +1,95 @@
.page {
min-height: 100%;
padding-top: 20px;
position: relative;
}
.color_list {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.color_list .color_item {
position: relative;
display: inline-block;
border-radius: 5px;
overflow: hidden;
padding: 5px 10px;
cursor: pointer;
width: 100px;
height: 125px;
}
.color_list .color_item:hover .color_add {
-webkit-transform: translate(-50%, -50%) scale(1.3);
transform: translate(-50%, -50%) scale(1.3);
}
.color_list .color_item .color_add {
width: 50px;
height: 50px;
position: absolute;
top: 50%;
left: 50%;
-webkit-transition: -webkit-transform .3s linear;
transition: -webkit-transform .3s linear;
transition: transform .3s linear;
transition: transform .3s linear, -webkit-transform .3s linear;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.color_list .color_item:hover {
background-color: rgba(0, 0, 0, 0.041);
}
.color_list .color_item:hover .color_toggle_list {
max-height: 99px;
}
.color_list .color_item .color_toggle_list {
overflow: hidden;
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
max-height: 0;
-webkit-transition: max-height .5s linear;
transition: max-height .5s linear;
background-color: rgba(180, 180, 180, 0.274);
}
.color_list .color_item .color_toggle_list .color_toggle_item {
display: inline-block;
padding: 2px 6px;
}
.color_list .color_item .color_toggle_list .color_toggle_item:hover {
background-color: rgba(0, 0, 0, 0.185);
}
.color_list .color_item .color_item_bg {
height: 50px;
background-color: red;
position: relative;
}
.color_list .color_item .color_item_content .color_item_title {
font-size: 1.2em;
font-weight: bold;
}
.color_list .color_item .color_item_content .color_item_desc {
color: #a7a6a6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
/*# sourceMappingURL=color.css.map */

4
route.txt

@ -1,6 +1,6 @@
/home/topuser/Code/@project/hapi-demo/source/route/htmx对应路径:
D:\1XYX\pro\hapi-demo\source\route\htmx对应路径:
不需权限 : GET /htmx/path/{path*}
/home/topuser/Code/@project/hapi-demo/source/route/views对应路径:
D:\1XYX\pro\hapi-demo\source\route\views对应路径:
不需权限(提供无需验证): GET /404
不需权限(提供无需验证): GET /
不需权限(提供无需验证): GET /about

3
source/auth/index.ts

@ -22,8 +22,9 @@ export async function validateSession(request: Req, session) {
loggerSite.debug(`${"cooike中存储的用户不存在"}`)
return { isValid: false }
}
const user = result.toJSON()
loggerSite.debug(`${"当前登录ID:" + session.id}`)
return { isValid: true, credentials: result }
return { isValid: true, credentials: user }
} else {
loggerSite.debug(`${"用户未登录兵器cooike中不存在信息"}`)
return { isValid: false }

BIN
source/db/data.db

Binary file not shown.

80
source/models/Color.ts

@ -1,24 +1,84 @@
module.exports = function (sequelize, DataTypes) {
const Color = sequelize.define(
"Color",
import { Sequelize, DataTypes, Optional, Model } from "sequelize"
interface ColorAttributes {
id: number
/**
*
*/
name: string
/**
*
*/
value: string
/**
*
*/
desctibe: string
/**
*
*/
example_link: string
createdAt?: Date
updatedAt?: Date
deletedAt?: Date
}
export interface ColorInput extends Optional<ColorAttributes, "id"> {}
export interface ColorOutput extends Required<ColorAttributes> {}
export type TColorModel = ReturnType<typeof ColorModel>
type DT = typeof DataTypes
export default function ColorModel(sequelize: Sequelize, DataTypes: DT) {
class Color extends Model<ColorAttributes, ColorInput> implements ColorAttributes {
public id!: number
public name!: string
public value!: string
public desctibe: string
public example_link: string
// timestamps!
public readonly createdAt!: Date
public readonly updatedAt!: Date
public readonly deletedAt!: Date
}
Color.init(
{
// 图片地址
color: {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
value: {
type: DataTypes.STRING,
allowNull: false,
},
title: {
desctibe: {
type: DataTypes.STRING,
allowNull: true,
},
describe: {
example_link: {
type: DataTypes.STRING,
allowNull: true,
},
},
{
modelName: "color",
sequelize,
underscored: true,
timestamps: false,
deletedAt: true,
timestamps: true,
paranoid: true, // 对模型施加了一个软删除
},
)
// 覆盖Color的toJSON方法
Color.prototype.toJSON = function () {
const values = Object.assign({}, this.get()) as ColorAttributes
delete values.deletedAt
return values
}
Color.associate = function (models) {}
return Color
}

37
source/models/user.ts

@ -4,10 +4,14 @@ interface UserAttributes {
id: number
username: string
password: string
nickname: string
email: string
avatar: string
tel: string
created_at?: Date
updated_at?: Date
deleted_at?: Date
createdAt?: Date
updatedAt?: Date
deletedAt?: Date
}
export interface UserInput extends Optional<UserAttributes, "id"> { }
@ -20,11 +24,15 @@ export default function UserModel(sequelize: Sequelize, DataTypes: DT) {
public id!: number
public username!: string
public password!: string
public nickname: string
public email: string
public avatar: string
public tel: string
// timestamps!
public readonly created_at!: Date
public readonly updated_at!: Date
public readonly deleted_at!: Date
public readonly createdAt!: Date
public readonly updatedAt!: Date
public readonly deletedAt!: Date
}
User.init(
{
@ -41,6 +49,19 @@ export default function UserModel(sequelize: Sequelize, DataTypes: DT) {
type: DataTypes.STRING,
allowNull: false,
},
nickname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
},
avatar: {
type: DataTypes.STRING,
},
tel: {
type: DataTypes.STRING,
},
},
{
modelName: "user",
@ -55,11 +76,11 @@ export default function UserModel(sequelize: Sequelize, DataTypes: DT) {
User.prototype.toJSON = function () {
const values = Object.assign({}, this.get()) as UserAttributes
delete values.password
delete values.deleted_at
delete values.deletedAt
return values
}
User.associate = function (models) {
User.hasOne(models["user_info"])
}
return User
}

84
source/models/user_info.ts

@ -1,84 +0,0 @@
import { Sequelize, DataTypes, Optional, Model } from "sequelize"
interface UserInfoAttributes {
id: number
user_id: number
nickname: string
email: string
avatar: string
tel: string
created_at?: Date
updated_at?: Date
deleted_at?: Date
}
export interface UserInfoInput extends Optional<UserInfoAttributes, "id" | "email" | "nickname" | "avatar" | "tel"> { }
export interface UserInfoOuput extends Required<UserInfoAttributes> { }
export type TUserInfoModel = ReturnType<typeof UserInfoModel>
type DT = typeof DataTypes
export default function UserInfoModel(sequelize: Sequelize, DataTypes: DT) {
class UserInfo extends Model<UserInfoAttributes, UserInfoInput> implements UserInfoAttributes {
public id: number
public user_id: number
public nickname: string
public email: string
public avatar: string
public tel: string
// timestamps!
public readonly created_at!: Date
public readonly updated_at!: Date
public readonly deleted_at!: Date
}
UserInfo.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: "user",
key: 'id',
},
},
nickname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
},
avatar: {
type: DataTypes.STRING,
},
tel: {
type: DataTypes.STRING,
},
},
{
modelName: "user_info",
sequelize,
underscored: true,
deletedAt: true,
timestamps: true,
paranoid: true, // 对模型施加了一个软删除
},
)
// 覆盖User的toJSON方法
UserInfo.prototype.toJSON = function () {
const values = Object.assign({}, this.get()) as UserInfoOuput
delete values.deleted_at
return values
}
UserInfo.associate = function (models) {
// User删除时对应的UserInfo同步删除
models["user_info"].belongsTo(models["user"], { foreignKey: "user_id", onDelete: "CASCADE", hooks: true })
}
return UserInfo
}

19
source/route/views/index/index.ts → source/route/views/index.ts

@ -28,21 +28,10 @@ export default class Index {
}
return h.redirect(`/login?next=${encodeURIComponent(request.path)}`).takeover()
}
const { id } = request.auth.credentials
const UserInfoModel = request.getModel("user_info")
let res = await UserInfoModel.findOne({ where: { user_id: id } })
if (res == null) {
request.yar.flash("warning", "不存在此用户信息")
return h.redirect(`/`).takeover()
}
const userinfo = res.toJSON()
const md = new MarkdownIt()
var result = md.render("# markdown-it rulezz!")
const data = {
md: result,
user: userinfo,
md: result
}
if (isRenderHtmx) {
return h.view("htmx/path/about.pug", data)
@ -81,6 +70,12 @@ export default class Index {
}
@route("/{path*}")
async any(req: Req, h: Res): ReturnValue {
if (req.path) {
const filePath = path.resolve(baseDir, "template/views", "."+req.path+".pug")
if(fs.existsSync(filePath)){
return h.view(`views${req.path}.pug`)
}
}
console.log("404: ", req.raw.req.url)
return h.redirect("/404?r=" + encodeURIComponent(req.raw.req.url))
}

8
source/route/views/register.ts

@ -33,7 +33,6 @@ export default class {
async register_POST(request: Req, h: Res): ReturnValue {
let { username, password } = request.payload as any
const User = request.getModel("user")
const UserInfoModel = request.getModel("user_info")
logger.trace("当前注册用户:" + username)
try {
const result = await User.findOne({ where: { username: username } })
@ -43,12 +42,7 @@ export default class {
}
let salt = bcrypt.genSaltSync(10)
let pwdLock = bcrypt.hashSync(password, salt)
// 使用事务插入
await sequelize.transaction(async t => {
const user = await User.create({ username, password: pwdLock }, { transaction: t })
await UserInfoModel.create({ nickname: username, user_id: user.id }, { transaction: t })
return user
})
await User.create({ username, password: pwdLock, nickname: username })
request.yar.flash("success", "用户注册成功")
return h.redirect("/login")
} catch (e) {

10
source/route/views/user.ts

@ -13,16 +13,10 @@ export default class {
async index(request: Req, h: Res): ReturnValue {
const isRenderHtmx = Reflect.has(request.query, "htmx")
const { id } = request.auth.credentials
const UserInfoModel = request.getModel("user_info")
let result = await UserInfoModel.findOne({ where: { user_id: id } })
if (result == null) {
return gFail(null, "不存在该用户")
}
const userinfo = result.toJSON()
if (isRenderHtmx) {
return h.view("htmx/path/user.pug", { userinfo })
return h.view("htmx/path/user.pug")
}
return h.view("views/user.pug", { userinfo })
return h.view("views/user.pug")
}
@method("GET")

18
source/run.ts

@ -1,7 +1,7 @@
"use strict"
import plugins from "@/plugins"
import path from "path"
import { baseDir, isDev, templateDir } from "@/util"
import { baseDir, isDev, sourceDir, templateDir } from "@/util"
import { validateJwt, validateSession } from "./auth"
import Hapi, { Server } from "@hapi/hapi"
import { Sequelize } from "sequelize"
@ -21,13 +21,27 @@ const run = async (): Promise<Server> => {
request: ['error']
} : false
})
server.events.on('request', (request, event, tags) => {
if (tags.error) {
loggerSite.error(event);
} else {
loggerSite.info(event);
}
});
server.events.on('log', (event, tags) => {
if (tags.error) {
loggerSite.error(event);
} else {
loggerSite.info(event);
}
});
await server.register([
{
plugin: require('hapi-sequelizejs'),
options: [
{
name: "data", // identifier
models: [__dirname + "/models/**/*.ts"], // paths/globs to model files
models: ["source/models/**/*.ts"], // paths/globs to model files
// ignoredModels: [__dirname + "/server/models/**/*.js"], // OPTIONAL: paths/globs to ignore files
sequelize: sequelize, // sequelize instance
sync: true, // sync models - default false

18
template/helper/flush.pug

@ -1,41 +1,42 @@
//- 服务器反馈UI
include @/helper/helper.pug
if flash
.message-container
- index = 0
if flash.error
each item in flash.error
- index++
.message.is-danger(id="message"+index)
.message.is-danger.animate__animated.animate__slideInRight(id="message"+index)
.message-header
p 错误
button.delete(aria-label='delete' data-target="message"+index)
button.delete.messagec(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.success
each item in flash.success
- index++
.message.is-success(id="message"+index)
.message.is-success.animate__animated.animate__slideInRight(id="message"+index)
.message-header
p 成功
button.delete(aria-label='delete' data-target="message"+index)
button.delete.messagec(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.info
each item in flash.info
- index++
.message.is-info(id="message"+index)
.message.is-info.animate__animated.animate__slideInRight(id="message"+index)
.message-header
p 信息
button.delete(aria-label='delete' data-target="message"+index)
button.delete.messagec(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
if flash.warning
each item in flash.warning
- index++
.message.is-warning(id="message"+index)
.message.is-warning.animate__animated.animate__slideInRight(id="message"+index)
.message-header
p 警告
button.delete(aria-label='delete' data-target="message"+index)
button.delete.messagec(aria-label='delete' data-target="message"+index)
.message-body
| #{item}
//- .toast-container.top-0.end-0.p-3
@ -60,3 +61,4 @@ if flash
//- ul
//- each item in flash.error
//- li #{item}
+script("js/common/flush.js")

3
template/helper/helper.pug

@ -5,6 +5,3 @@ mixin script(src)
mixin security
include ./form_security.pug
mixin security
include ./form_security.pug

10
template/htmx/path/user.pug

@ -4,4 +4,12 @@ block var
-title=user.nickname || "Welcome" // 网页标题
title #{user.nickname || "Welcome"}
div user
div user
section.section
.container
if user
div nickname: #{user.nickname || "None"}
div email: #{user.email || "None"}
div username: #{user.username || "None"}
div id: #{user.id || "None"}

5
template/layout/layout.pug

@ -10,11 +10,14 @@ html(lang="zh-cn" class=hideHeader?"":"has-navbar-fixed-top")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title #{title || 'WEB'}
//- link(rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css")
+css("style/common/animate.min.css")
+css("style/common/bulma.min.css")
+css("style/common/style.css")
block head
script!= "window.STATIC_USER = "+JSON.stringify(user)
//- window.STATIC_DATA = #{JSON.stringify(user)}
body
include @/helper/flush.pug
//- include @/helper/flush.pug
if !hideHeader
include @/ui/header.pug
block content

51
template/views/color.pug

@ -0,0 +1,51 @@
extends /layout/layout
block var
-title="颜色表" // 网页标题
block head
link(rel="stylesheet", href="/public/style/views/color.css")
block content
div(class="container page")
div(class="color_list")
-
var list=[
{ color:"#999999",title: "辅助色",describe:"灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色"},
{ color:"#999999",title: "辅助色",describe:"灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色"},
{ color:"#999999",title: "辅助色",describe:"灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色灰色"},
{ color:"#000000",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
{ color:"#999999",title: "辅助色",describe:""},
]
each item in list
div(class="color_item")
div(class="color_item_bg" style=`background: ${item.color}`)
div(class="color_toggle_list")
div(class="color_toggle_item") hex
div(class="color_toggle_item") rgba
div(class="color_item_name") #{item.color}
div(class="color_item_content")
div(class="color_item_title") #{item.title}
div(class="color_item_desc") #{item.describe || "暂无描述"}
div(class="color_item")
img(src="/public/image/add.png", alt="添加" title="添加" class="color_add")
div
form(action="POST" method="post")
div(class=".wrapper_input"): input(type="text" tabindex="1" value="sadsa" name="a")
div(class=".wrapper_input"): input(type="text" tabindex="3" value="sadsa" name="b")
button(type="submit" tabindex="2") 提交

75
template/views/resume.pug

@ -0,0 +1,75 @@
doctype html
html(lang='zh')
head
meta(charset='UTF-8')
meta(name='viewport', content='maximum-scale=1,minimum-scale=1,user-scalable=0,width=device-width,initial-scale=1')
meta(http-equiv='X-UA-Compatible', content='ie=edge')
title Edit-个人简历
link(rel='stylesheet', href='/public/static/css/prism.css')
link(rel='stylesheet', href='/public/static/css/index.css')
style#styleContent
body
pre#codeEdit.language-css(onscroll='toggleScrollBar(event)')
#resume(onscroll='toggleScrollBar(event)')
script(src='/public/static/js/textarr.js')
script(src='/public/static/js/write.js')
script.
let currentIndex_ = 0
function writeIntro (intro='', time, callback) {
let connect = document.querySelectorAll('.connect')
if(connect.length < 1){
resume.innerHTML = intro
}else{
connect[connect.length-1].innerHTML = intro
}
setTimeout(() => {
callback()
}, time);
}
function startResume () {
writeIntro(balloon, 700, ()=>{
writeIntro(line, 200, ()=>{
writeIntro(text, 0, ()=>{
writeText(textArr[currentIndex_])
})
})
})
}
let num = 0
let t = setInterval(() => {
if(num <= style.length){
codeEdit.innerHTML = Prism.highlight(style.substr(0, num), Prism.languages.css);
codeEdit.scrollTop = 100000
styleContent.innerHTML = style.substr(0, num)
num++
}else{
clearInterval(t)
}
}, 70)
setTimeout(() => {
if(document.body.clientWidth < 500){
setTimeout(() => {
resume.style.cssText = 'height:70vh!important'
}, 5000);
}
startResume()
}, 10000);
script(src='/public/static/js/prism.js')
script.
let sbTimer = null
function toggleScrollBar (self) {
self = self.target
clearTimeout(sbTimer)
if(self.getAttribute('class') != 'sbShow')
self.classList.add('sbShow')
sbTimer = setTimeout(() => {
self.classList.remove('sbShow')
}, 800);
}

4
types/global.d.ts

@ -2,8 +2,8 @@ import { Logger } from "log4js"
import { Server } from "@hapi/hapi"
import { Request, ResponseToolkit, Lifecycle } from "@hapi/hapi"
import { TUserModel } from "@/models/user"
import { TColorModel } from "@/models/color"
import yar from "@hapi/yar"
import { TUserInfoModel } from "@/models/user_info"
declare global {
var server: Server
@ -14,7 +14,7 @@ declare global {
interface Models {
user: TUserModel
user_info: TUserInfoModel
color: TColorModel
}
declare module "sequelize" {

Loading…
Cancel
Save