diff --git a/public/style/views/color.css b/public/style/views/color.css index 69cb119..bd816b5 100644 --- a/public/style/views/color.css +++ b/public/style/views/color.css @@ -65,6 +65,7 @@ .color_list .color_item .color_toggle_list .color_toggle_item { display: inline-block; + height: 25px; padding: 2px 6px; } diff --git a/public/upload/00018-4071989711_2023_05_16_1684169231914.png b/public/upload/00018-4071989711_2023_05_16_1684169231914.png new file mode 100644 index 0000000..ccd87b8 Binary files /dev/null and b/public/upload/00018-4071989711_2023_05_16_1684169231914.png differ diff --git a/route.txt b/route.txt index 31b9f77..1ea29c9 100644 --- a/route.txt +++ b/route.txt @@ -1,6 +1,6 @@ -D:\1XYX\pro\hapi-demo\source\route\htmx对应路径: +/home/topuser/Code/@project/hapi-demo/source/route/htmx对应路径: 不需权限 : GET /htmx/path/{path*} -D:\1XYX\pro\hapi-demo\source\route\views对应路径: +/home/topuser/Code/@project/hapi-demo/source/route/views对应路径: 不需权限(提供无需验证): GET /404 不需权限(提供无需验证): GET / 不需权限(提供无需验证): GET /about @@ -11,6 +11,7 @@ D:\1XYX\pro\hapi-demo\source\route\views对应路径: 不需权限 : GET /nav 不需权限(提供无需验证): GET /register 不需权限 : POST /register + 需要权限 : POST /upload 需要权限 : GET /user 需要权限 : GET /user/logout 需要权限 : POST /user/del \ No newline at end of file diff --git a/source/db/data.db b/source/db/data.db index a4f5276..7dd26e0 100644 Binary files a/source/db/data.db and b/source/db/data.db differ diff --git a/source/route/views/index.ts b/source/route/views/index.ts index e08ae05..d8eb13b 100644 --- a/source/route/views/index.ts +++ b/source/route/views/index.ts @@ -2,16 +2,15 @@ import { auth, config, method, route, swagger, validate } from "@noderun/hapi-ro import { Req, Res, ReturnValue } from "#/global" import path from "path" import fs from "fs-extra" -import { baseDir, gFail } from "@/util" +import { baseDir, gFail, sourceDir } from "@/util" import MarkdownIt from "markdown-it" export default class Index { @auth("try") async index(request: Req, h: Res): ReturnValue { - if (request.auth.isAuthenticated) { - // 登录了 - } else { - // 未登录 + const isRenderHtmx = Reflect.has(request.query, "htmx") + if (isRenderHtmx) { + return h.view("htmx/path/index.pug", { isLogin: request.auth.isAuthenticated }) } return h.view("views/index.pug", { isLogin: request.auth.isAuthenticated }) } @@ -29,7 +28,9 @@ export default class Index { return h.redirect(`/login?next=${encodeURIComponent(request.path)}`).takeover() } const md = new MarkdownIt() - var result = md.render("# markdown-it rulezz!") + console.log(path.resolve(baseDir, "./docs/a.md")); + + var result = md.render(fs.readFileSync(path.resolve(baseDir, "./docs/a.md"), "utf-8")) const data = { md: result } diff --git a/source/route/views/upload/_upload.ts b/source/route/views/upload/_upload.ts new file mode 100644 index 0000000..5b30500 --- /dev/null +++ b/source/route/views/upload/_upload.ts @@ -0,0 +1,82 @@ +import path from "path" +import { gSuccess, gFail, uploadDir, uploadPath } from "@/util" +import { dateTimeFormat } from "@/util/util" +import {fileTypeFromFile} from 'file-type'; +const fs = require("fs") +const multiparty = require("multiparty") + +function saveFile(file) { + return new Promise(async (resolve, reject) => { + const filename = file.originalFilename + const uploadedPath = file.path + const filetype = await fileTypeFromFile(uploadedPath) + const _file = path.parse(filename) + if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) { + let _name = + _file.name + "_" + dateTimeFormat(new Date(), "yyyy_MM_dd") + "_" + new Date().getTime() + _file.ext + const dstPath = path.resolve(uploadDir, _name) + fs.rename(uploadedPath, dstPath, function (err) { + if (err) { + console.log("rename error: " + err) + reject() + } else { + resolve(path.resolve("/public/upload/"+_name)) + } + }) + } else { + fs.unlinkSync(uploadedPath) + reject(new Error(filename + "文件不是图片")) + } + }) +} + +export default function (payload) { + const form = new multiparty.Form({ + uploadDir: uploadDir, //路径需要对应自己的项目更改 + /*设置文件保存路径 */ + encoding: "utf-8", + /*编码设置 */ + maxFilesSize: 20000 * 1024 * 1024, + /*设置文件最大值 20MB */ + keepExtensions: true, + /*保留后缀*/ + }) + return new Promise(async (resolve, reject) => { + form.on("part", function (part) { + console.log(part.filename) + }) + form.on("progress", function (bytesReceived, bytesExpected) { + if (bytesExpected === null) { + return + } + + var percentComplete = (bytesReceived / bytesExpected) * 100 + console.log("the form is " + Math.floor(percentComplete) + "%" + " complete") + }) + form.parse(payload, async function (err, fields, files) { + // console.log(err, fields, files); + + if (err) { + resolve(err.message) + return + } + const errList = [] + const fileList = [] + for (let i = 0; i < files.file.length; i++) { + const file = files.file[i] + try { + const dstPath = await saveFile(file) + fileList.push(dstPath) + } catch (error) { + errList.push(error.message) + } + } + if (errList.length) { + resolve(gFail(null, errList.join("\n"))) + return + } + // resolve(h.view("views/upload.ejs")); + resolve([...new Set(fileList)]) + }) + }) +} diff --git a/source/route/views/upload/index.ts b/source/route/views/upload/index.ts new file mode 100644 index 0000000..cef2ba1 --- /dev/null +++ b/source/route/views/upload/index.ts @@ -0,0 +1,125 @@ +import { auth, config, method, route, swagger, validate } from "@noderun/hapi-router" +import UploadFunc from "./_upload" +import path, { resolve } from "path"; +import { gFail, uploadDir } from "@/util"; +import { fileTypeFromFile, fileTypeFromStream } from "file-type"; +import { dateTimeFormat } from "@/util/util"; +import fs from "fs-extra"; +import { Req, Res } from "#/global"; +const multiparty = require("multiparty") + +export default class { + @config({ + payload: { + maxBytes: 20000 * 1024 * 1024, + output: "stream", + parse: false, + multipart: true, + timeout: false, + allow: ["multipart/form-data", "application/x-www-form-urlencoded"], + }, + }) + @method("POST") + @auth() + async index(request: Req, h: Res) { + const { id } = request.auth.credentials + const { filelist, fields } = await Save(request.payload) + const result = {} + if (fields["username"] && fields["username"][0]) { + result["username"] = fields["username"][0] + } + if (filelist && filelist[0]) { + result["avatar"] = filelist[0] + } + if (JSON.stringify(result) !== "{}") { + const UserModel = request.getModel("user") + UserModel.update(result, { where: { id } }) + } + return h.redirect("/user") + } +} + +function saveFile(file) { + return new Promise(async (resolve, reject) => { + const filename = file.originalFilename + const uploadedPath = file.path + const filetype = await fileTypeFromFile(uploadedPath) + const _file = path.parse(filename) + if (filetype && (filetype.ext == "jpg" || filetype.ext == "png")) { + let _name = + _file.name + "_" + dateTimeFormat(new Date(), "yyyy_MM_dd") + "_" + new Date().getTime() + _file.ext + const dstPath = path.resolve(uploadDir, _name) + fs.rename(uploadedPath, dstPath, function (err) { + if (err) { + console.log("rename error: " + err) + reject() + } else { + resolve(path.resolve("/public/upload/" + _name)) + } + }) + } else { + fs.unlinkSync(uploadedPath) + reject(new Error(filename + "文件不是图片")) + } + }) +} + +function Save(payload) { + const form = new multiparty.Form({ + uploadDir: uploadDir, //路径需要对应自己的项目更改 + /*设置文件保存路径 */ + encoding: "utf-8", + /*编码设置 */ + maxFilesSize: 20000 * 1024 * 1024, + /*设置文件最大值 20MB */ + keepExtensions: true, + /*保留后缀*/ + }) + return new Promise(async (resolve, reject) => { + form.on("part", function (part) { + console.log(1111) + console.log(part.filename) + }) + form.on("progress", function (bytesReceived, bytesExpected) { + if (bytesExpected === null) { + return + } + + // var percentComplete = (bytesReceived / bytesExpected) * 100 + // console.log("the form is " + Math.floor(percentComplete) + "%" + " complete") + }) + form.parse(payload, async function (err, fields, files) { + // console.log(err, fields, files); + if (err) { + reject(err.message) + return + } + const errList = [] + const fileList = [] + if (files && files.file && files.file.length) { + for (let i = 0; i < files.file.length; i++) { + const file = files.file[i] + if (file.originalFilename === "" && file.size === 0) { + const uploadedPath = file.path + fs.unlinkSync(uploadedPath) + continue + } + try { + const dstPath = await saveFile(file) + fileList.push(dstPath) + } catch (error) { + errList.push(error.message) + } + } + } + if (errList.length) { + resolve(gFail(null, errList.join("\n"))) + return + } + resolve({ + fields, + filelist: [...new Set(fileList)] + }) + }) + }) +} diff --git a/template/htmx/path/user.pug b/template/htmx/path/user.pug index a2b3dd9..4aab823 100644 --- a/template/htmx/path/user.pug +++ b/template/htmx/path/user.pug @@ -4,12 +4,67 @@ block var -title=user.nickname || "Welcome" // 网页标题 title #{user.nickname || "Welcome"} -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"} - \ No newline at end of file +form(action="/upload" method="post" enctype="multipart/form-data" style="margin: 0 auto; width: 500px;") + .field + .label 用户名 + .control + input.input(type="text" name="username" placeholder="请输入用户名" value=user.username) + .field + .label 头像 + .control + .file.is-primary + label.file-label + input.file-input(type="file", name="file") + span.file-cta + span.file-label + | 上传头像 + if user.avatar + .field + .control + .image.is-128x128 + img.is-rounded(src=user.avatar alt=user.username) + +security + .field.is-grouped + .control + button.button.is-link(type="submit") 提交 + .control + button.button.is-link.is-light 取消 + + +form(action="", method="post" style="margin: 0 auto; width: 500px;margin-top:20px") + .field + .label 用户名 + .control + input.input(type="text" placeholder="请输入用户名" value=user.username) + .field + .label 昵称 + .control + input.input(type="text" placeholder="请输入用户名" value=user.nickname) + .field + .label 邮箱 + .control + input.input(type="email" placeholder="请输入邮箱" value=user.email) + .field + .label 手机号 + .control + input.input(type="tel" placeholder="请输入手机号" value=user.tel) + .field + .label 头像 + .control + .file.is-primary + label.file-label + input.file-input(type="file", name="file") + span.file-cta + span.file-label + | 上传头像 + if user.avatar + .field + .control + .image.is-128x128 + img.is-rounded(src=user.avatar alt=user.username) + .field.is-grouped + .control + button.button.is-link(type="submit") 提交 + .control + button.button.is-link.is-light 取消 + \ No newline at end of file diff --git a/template/ui/header.pug b/template/ui/header.pug index fa7a136..f9bb09e 100644 --- a/template/ui/header.pug +++ b/template/ui/header.pug @@ -10,8 +10,8 @@ nav.is-fixed-top.navbar(role='navigation', aria-label='main navigation', style=" span(aria-hidden='true') #navbarBasicExample.navbar-menu .navbar-start - a.navbar-item - | 文档 + a.navbar-item(hx-get="/?htmx" hx-push-url="/" hx-trigger="click" hx-target="#single-page" hx-swap="innerHTML") + | 首页 .navbar-item.has-dropdown.is-hoverable a.navbar-link | 更多 @@ -34,6 +34,8 @@ nav.is-fixed-top.navbar(role='navigation', aria-label='main navigation', style=" else .navbar-item.has-dropdown.is-hoverable a.navbar-link + .image.is-28x28(style="margin-right: 8px;") + img.is-rounded(src=user.avatar alt=user.username) div #{user.username} .navbar-dropdown.is-right a.navbar-item(hx-get="/user?htmx" hx-push-url="/user" hx-trigger="click" hx-target="#single-page" hx-swap="innerHTML") diff --git a/template/views/color.pug b/template/views/color.pug index 377b300..60fadd1 100644 --- a/template/views/color.pug +++ b/template/views/color.pug @@ -43,7 +43,7 @@ block 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") + //- 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")