增加 注册 页面,sorry页面,修改部分bug

This commit is contained in:
simple321vip
2022-10-25 08:51:12 +08:00
parent 4e49cb87a2
commit 01652a527d
8 changed files with 341 additions and 74 deletions
+10 -1
View File
@@ -39,6 +39,15 @@ const removeBlogType = (data: Object) => {
}) })
} }
const sortBlogTypes = (data: Object) => {
return service({
url: '/auth/api/v1/author/blog_types',
method: 'POST',
headers: headers,
data: data
})
}
const getContent = (id: string) => { const getContent = (id: string) => {
return service({ return service({
url: '/auth/api/v1/author/blog/content/' + id, url: '/auth/api/v1/author/blog/content/' + id,
@@ -74,4 +83,4 @@ const deleteContent = (bid: string) => {
} }
export { listAll, updateBtName, putBlogType, removeBlogType, getContent, putBlog, updateContent, deleteContent } export { listAll, updateBtName, putBlogType, removeBlogType, sortBlogTypes, getContent, putBlog, updateContent, deleteContent }
+9 -1
View File
@@ -22,5 +22,13 @@ const logout = (id: string) => {
}) })
} }
const register_user = (postData: Object) => {
return service({
url: '/auth/api/v1/register',
method: 'POST',
data: postData
})
}
export { authorize, obtainUserInfo, logout }
export { authorize, obtainUserInfo, logout, register_user }
+1 -1
View File
@@ -13,7 +13,7 @@
<el-form-item label="用户凭证" prop="checkPass"> <el-form-item label="用户凭证" prop="checkPass">
<el-input v-model="loginForm.password" type="password" autocomplete="off" /> <el-input v-model="loginForm.password" type="password" autocomplete="off" />
</el-form-item> --> </el-form-item> -->
<p class="tang-pass-qrcode-title">百度网盘扫码登陆<a class="pass-link" :href="qrcode" target="new">请使用微信扫一扫登录</a></p> <p class="tang-pass-qrcode-title">百度网盘扫码登陆<a class="pass-link" :href="qrcode" target="_self">请使用微信扫一扫登录</a></p>
<!-- <el-button @click="loginWithBaiDu"> <!-- <el-button @click="loginWithBaiDu">
百度App 百度App
</el-button> --> </el-button> -->
+77
View File
@@ -0,0 +1,77 @@
<template>
該当システムのご利用はシステム管理者に許可する必要があります<br>
許可がなければユーザー情報を登録してもシステムのご利用はできませんのでご注意ください
<el-form :model="register_form" label-width="120px">
<el-form-item label="phone_number">
<el-input v-model="register_form.phone_number" />
</el-form-item>
<el-form-item label="auth_code">
<el-input v-model="register_form.code" />
</el-form-item>
<el-form-item label="tenantId">
<el-input v-model="register_form.tenant_id" :disabled="true" />
</el-form-item>
<el-form-item label="account">
<el-input v-model="register_form.account" :disabled="true" />
</el-form-item>
<el-form-item label="token">
<el-input v-model="register_form.token" :disabled="true" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Register</el-button>
<el-button>Cancel</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import { useRoute } from 'vue-router';
import { register_user } from "./../../api/user";
import router from '../../router/index'
import { setTenant, setToken } from '../../utils/auth';
const route = useRoute()
const params = route.query
// use phone number to register user(tenant)
const register_form = reactive({
phone_number: '',
code: '',
tenant_id: params.tenantId as string,
account: params.account as string,
token: params.token as string
})
const onSubmit = () => {
register_user(register_form).then((response) => {
if (response.status == 200) {
const tenant = {
id: params.tenantId,
account: params.account
}
setToken(params.token as string)
setTenant(tenant)
const { href } = router.resolve({
path: '/'
});
window.open(href, '_self');
}
})
}
</script>
<style scoped>
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<div class="yuliu">
这个人很懒什么也没写
</div>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>
+42 -25
View File
@@ -23,35 +23,52 @@ app.use(store)
import { getToken, getTenant, setToken, setTenant } from './utils/auth' import { getToken, getTenant, setToken, setTenant } from './utils/auth'
import { Tenant } from './entity/index' import { Tenant } from './entity/index'
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { obtainUserInfo } from './api/user' const whiteList = ['/login', '/register', '/sorryPage']
const whiteList = ['/login']
let url = window.location.href let url = window.location.href
async function checktoken(url: string) { async function checktoken(url: string) {
if (url.search("token") != -1) {
const list = url.split("?token=") if (url.search("register") != -1) {
let arr = url.split('?')
let base_url = list[0] let parameters = arr[1].split('&')
let authorizeToken = list[1] let query_data: { [key: string]: any } = {}
if (list.length < 2 || authorizeToken.length == 0) { parameters.forEach(parameter => {
router.push({ let parameter_list = parameter.split("=")
path: '/illustration', query_data[parameter_list[0]] = parameter_list[1]
})
return
}
await obtainUserInfo(authorizeToken).then(response => {
if (response.status == 200) {
const tenant = {
id: response.data.id,
account: response.data.account
}
setToken(authorizeToken)
setTenant(tenant)
window.open(base_url, "_self")
}
}) })
router.push({
path: '/register',
query: query_data
})
return
}
if (url.search("home") != -1) {
let arr = url.split('?')
let parameters = arr[1].split('&')
let query_data: { [key: string]: any } = {}
parameters.forEach(parameter => {
let parameter_list = parameter.split("=")
query_data[parameter_list[0]] = parameter_list[1]
})
const tenant = {
id: query_data.tenantId,
account: query_data.account
}
setToken(query_data.token as string)
setTenant(tenant)
const { href } = router.resolve({
path: '/'
});
window.open(href, '_self');
return
}
if (url.search("sorryPage") != -1) {
router.push({
path: '/sorryPage',
})
return
} }
} }
checktoken(url) checktoken(url)
@@ -96,7 +113,7 @@ router.beforeEach((to, from, next) => {
next() next()
} }
} else { } else {
if (whiteList.includes(to.path)) { if (whiteList.indexOf(to.path) !== -1) {
next() next()
} else { } else {
next('/login') next('/login')
+8
View File
@@ -104,6 +104,14 @@ const routes: Array<RouteRecordRaw> = [
path: "/illustration", path: "/illustration",
component: () => import('../components/illustration/index.vue') component: () => import('../components/illustration/index.vue')
}, },
{
path: "/register",
component: () => import('../components/login/register.vue')
},
{
path: "/sorryPage",
component: () => import('../components/login/sorry.vue')
},
] ]
+180 -46
View File
@@ -1,10 +1,35 @@
<template> <template>
<div class="blog_editer" v-if="isReady"> <div class="blog_editer" v-if="isReady">
<div class="bt_section" :style="btWholeStyle"> <div class="bt_section" :style="btWholeStyle">
<el-button class="bt-input" @click="contentDialogVisible = true">增加一个分类</el-button> <table class="tb">
<el-button class="bt-input" v-for="(value, key) of blogTypes" :key="key" @click="onclickTypeTab(value[0])"> <thead>
<draggable v-model="state.headers" animation="200" tag="tr" :item-key="(key: string) => key">
<template #item="{ element: header }">
<th class="move">
增加分类
</th>
</template>
</draggable>
</thead>
<!-- <draggable :list="blogTypes" handle=".move" animation="300" @start="onStart" @end="onEnd" tag="tbody" -->
<draggable :list="blogTypes" handle=".move" animation="300" @start="onStart" @end="onEnd" tag="tbody"
item-key="name">
<template #item="{ element }">
<tr>
<td class="move" v-for="(header, index) in state.headers" :key="header">
<el-icon v-show="checkedBlogIndex == element.order">
<edit />
</el-icon>
{{ element.btName }}
</td>
</tr>
</template>
</draggable>
</table>
<!-- <el-button class="bt-input" @click="contentDialogVisible = true">增加一个分类</el-button>
<el-button class="bt-input" v-for="(value, key) of blogTypes" :key="key" @click="onclickTypeTab(value.order)">
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<el-icon v-show="checkedBlogIndex == value[0]"> <el-icon v-show="checkedBlogIndex == value.order">
<edit /> <edit />
</el-icon> </el-icon>
<template #dropdown> <template #dropdown>
@@ -15,13 +40,14 @@
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
{{ value[1].btName }} {{ value.btName }}
</el-button> </el-button> -->
</div> </div>
<div class="b_section" :style="bWholeStyle"> <div class="b_section" :style="bWholeStyle">
<el-button class="bt-input" :disabled="isclicked" @click="insertBlog">增加一个博客</el-button> <el-button class="bt-input" :disabled="isclicked" @click="insertBlog">增加一个博客</el-button>
<el-button class="bt-input" v-for="(blog, index) of blogTypes.get(checkedBlogIndex)?.blogs" <el-button class="bt-input" v-for="(blog, index) of blogTypes[checkedBlogIndex].blogs"
@click="onclickBlogTab(blog[0])"> @click="onclickBlogTab(blog[1])">
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<el-icon v-show="checkedIndex == blog[0]"> <el-icon v-show="checkedIndex == blog[0]">
<edit /> <edit />
@@ -39,7 +65,7 @@
</div> </div>
<div class="c_section"> <div class="c_section">
<div class="title_style"> <div class="title_style">
<el-input v-model="((blogTypes.get(checkedBlogIndex) as BlogType).blogs.get(checkedIndex) as Blog).title" <el-input v-model="((blogTypes[checkedBlogIndex] as BlogType).blogs.get(checkedIndex) as Blog).title"
@keyup.enter.native="saveTitle" @blur="saveTitle"> @keyup.enter.native="saveTitle" @blur="saveTitle">
</el-input> </el-input>
<div style="width: 60px;"> <div style="width: 60px;">
@@ -49,12 +75,12 @@
</div> </div>
<div :style="cWholeStyle"> <div :style="cWholeStyle">
<md-editor theme="light" <md-editor theme="light"
v-model="((blogTypes.get(checkedBlogIndex) as BlogType).blogs.get(checkedIndex) as Blog).content" v-model="((blogTypes[checkedBlogIndex] as BlogType).blogs.get(checkedIndex) as Blog).content"
@save="saveContent" :style="{ height: innerHeight }" /> @save="saveContent" :style="{ height: innerHeight }" />
</div> </div>
</div> </div>
<el-dialog v-model="dialogVisible" title="修改博客分类名称" width="30%"> <el-dialog v-model="dialogVisible" title="修改博客分类名称" width="30%">
<el-input v-model="(blogTypes.get(checkedBlogIndex) as BlogType).btName"></el-input> <el-input v-model="(blogTypes[checkedBlogIndex] as BlogType).btName"></el-input>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button> <el-button @click="dialogVisible = false">取消</el-button>
@@ -73,8 +99,8 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="btdeleteDialogVisible" title="删除博客分类" width="30%"> <el-dialog v-model="btdeleteDialogVisible" title="删除博客分类" width="30%">
你正在对 {{ (blogTypes.get(checkedBlogIndex) as BlogType).btName }} 你正在对 {{ (blogTypes[checkedBlogIndex] as BlogType).btName }}
<el-input v-model="btName" :placeholder="'请输入<' + (blogTypes.get(checkedBlogIndex) as BlogType).btName + '>'"> <el-input v-model="btName" :placeholder="'请输入<' + (blogTypes[checkedBlogIndex] as BlogType).btName + '>'">
</el-input> </el-input>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@@ -84,7 +110,7 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="deleteDialogVisible" title="删除博客" width="30%"> <el-dialog v-model="deleteDialogVisible" title="删除博客" width="30%">
你正在对 {{ ((blogTypes.get(checkedBlogIndex) as BlogType).blogs.get(checkedIndex) as Blog).title }} 你正在对 {{ ((blogTypes[checkedBlogIndex] as BlogType).blogs.get(checkedIndex) as Blog).title }}
<el-input v-model="btName" :placeholder="'请输入<删除>'"> <el-input v-model="btName" :placeholder="'请输入<删除>'">
</el-input> </el-input>
<template #footer> <template #footer>
@@ -103,7 +129,18 @@ import $moment from "moment"
import MdEditor from 'md-editor-v3' import MdEditor from 'md-editor-v3'
import { Edit } from "@element-plus/icons-vue" import { Edit } from "@element-plus/icons-vue"
import 'md-editor-v3/lib/style.css' import 'md-editor-v3/lib/style.css'
import { updateBtName, updateContent, listAll, putBlogType, removeBlogType, getContent, putBlog, deleteContent } from '../../api/blog' import {
updateBtName,
updateContent,
listAll,
putBlogType,
removeBlogType,
getContent,
putBlog,
deleteContent,
sortBlogTypes
} from '../../api/blog'
import draggable from "vuedraggable"
type Blog = { type Blog = {
bid: string, bid: string,
@@ -112,40 +149,68 @@ type Blog = {
title: string, title: string,
blog_prex: string, blog_prex: string,
autosave_control: string, autosave_control: string,
content: string content: string,
order: number
} }
type BlogType = { type BlogType = {
btId: string, btId: string,
btName: string, btName: string,
blogs: Map<string, Blog> blogs: Map<string, Blog>,
order: number
} }
const blogTypes = reactive<Map<string, BlogType>>(new Map()) const blogTypes = reactive<BlogType[]>([])
const dialogVisible = ref(false) const dialogVisible = ref(false)
const contentDialogVisible = ref(false) const contentDialogVisible = ref(false)
const btdeleteDialogVisible = ref(false) const btdeleteDialogVisible = ref(false)
const deleteDialogVisible = ref(false) const deleteDialogVisible = ref(false)
let checkedIndex = ref<string>("") let checkedIndex = ref<string>("")
let checkedBlogIndex = ref<string>("") let checkedBlogIndex = ref<number>(0)
let isclicked = ref(false) let isclicked = ref(false)
let isReady = ref(false) let isReady = ref(false)
let updated = ref(false) let updated = ref(false)
let startTime = reactive($moment()); let startTime = reactive($moment());
/*
draggable 对CSS样式没有什么要求万物皆可拖拽
:list="state.list" //需要绑定的数组
animation="300" //动画效果
@start="onStart" //拖拽开始的事件
@end="onEnd" //拖拽结束的事件
*/
const state = reactive({
//列的名称
headers: ["blogType"],
//需要拖拽的数据,拖拽后数据的顺序也会变化
})
// 関数定義 // 関数定義
//拖拽开始的事件
const onStart = () => {
console.log("开始拖拽");
};
//拖拽结束的事件
const onEnd = () => {
blogTypes.forEach((blogType, index) => {
blogType.order = index
})
sortBlogType()
}
/** /**
* Blog一覧取得 * Blog一覧取得
*/ */
const getTestData = async () => { const getTestData = async () => {
await listAll().then(response => { await listAll().then(response => {
response.data.sort((x: any, y: any) => { x.order - y.order })
response.data.forEach((element: any) => { response.data.forEach((element: any) => {
const blog_type: BlogType = { const blog_type: BlogType = {
btId: element.btId, btId: element.btId,
btName: element.btName, btName: element.btName,
blogs: new Map<string, Blog>() blogs: new Map<string, Blog>(),
order: element.order
} }
element.blog_list.forEach((item: any) => { element.blog_list.forEach((item: any) => {
const blog: Blog = { const blog: Blog = {
@@ -155,15 +220,16 @@ const getTestData = async () => {
title: item.title, title: item.title,
blog_prex: item.blog_prex, blog_prex: item.blog_prex,
autosave_control: item.autosave_control, autosave_control: item.autosave_control,
content: item.content content: item.content,
order: item.order
} }
blog_type.blogs.set(item.bid, blog) blog_type.blogs.set(item.bid, blog)
}) })
blogTypes.set(element.btId, blog_type) blogTypes.push(blog_type)
}) })
}) })
isReady.value = true isReady.value = true
checkedBlogIndex.value = blogTypes.keys().next().value checkedBlogIndex.value = blogTypes[0].order
checkedIndex.value = blogTypes.values().next().value.blogs.keys().next().value checkedIndex.value = blogTypes.values().next().value.blogs.keys().next().value
// TODO 需要优化部分 // TODO 需要优化部分
/** /**
@@ -189,19 +255,18 @@ const getTestData = async () => {
* BlogTypeを選べる * BlogTypeを選べる
* @param key * @param key
*/ */
const onclickTypeTab = (key: string) => { const onclickTypeTab = (order: number) => {
const bid = blogTypes.get(key)?.blogs.values().next().value.bid const bid = blogTypes[order].blogs.values().next().value.bid
getContent(bid).then(response => { getContent(bid).then(response => {
((blogTypes.get(key) as BlogType).blogs.get(bid) as Blog).content = response.data.content (blogTypes[order].blogs.get(bid) as Blog).content = response.data.content
checkedBlogIndex.value = key checkedBlogIndex.value = order
checkedIndex.value = bid checkedIndex.value = bid
}) })
} }
const onclickBlogTab = (key: string) => { const onclickBlogTab = (blog: Blog) => {
checkedIndex.value = key getContent(blog.bid).then(response => {
getContent(key).then(response => { ((blogTypes[checkedBlogIndex.value] as BlogType).blogs.get(checkedIndex.value) as Blog).content = response.data.content
((blogTypes.get(checkedBlogIndex.value) as BlogType).blogs.get(checkedIndex.value) as Blog).content = response.data.content
}) })
} }
@@ -220,7 +285,7 @@ const saveContent = () => {
updated.value = true updated.value = true
const query = { const query = {
bid: checkedIndex.value, bid: checkedIndex.value,
content: ((blogTypes.get(checkedBlogIndex.value) as BlogType).blogs.get(checkedIndex.value) as Blog).content content: ((blogTypes[checkedBlogIndex.value] as BlogType).blogs.get(checkedIndex.value) as Blog).content
} }
updateContent(query).then(response => { updateContent(query).then(response => {
if (response.status == 200) { if (response.status == 200) {
@@ -236,7 +301,7 @@ const saveContent = () => {
const saveType = () => { const saveType = () => {
const query = { const query = {
btId: checkedBlogIndex.value, btId: checkedBlogIndex.value,
btName: (blogTypes.get(checkedBlogIndex.value) as BlogType).btName btName: (blogTypes[checkedBlogIndex.value] as BlogType).btName
} }
updateBtName(query).then(response => { updateBtName(query).then(response => {
dialogVisible.value = false dialogVisible.value = false
@@ -254,7 +319,7 @@ const saveTitle = () => {
updated.value = true updated.value = true
const query = { const query = {
bid: checkedIndex.value, bid: checkedIndex.value,
title: ((blogTypes.get(checkedBlogIndex.value) as BlogType).blogs.get(checkedIndex.value) as Blog).title title: ((blogTypes[checkedBlogIndex.value] as BlogType).blogs.get(checkedIndex.value) as Blog).title
} }
updateContent(query).then(response => { updateContent(query).then(response => {
updated.value = false updated.value = false
@@ -268,19 +333,23 @@ const insertBlog = () => {
isclicked.value = true isclicked.value = true
const data = { const data = {
btId: checkedBlogIndex.value, btId: checkedBlogIndex.value,
title: $moment().format("YYYY-MM-DD") title: $moment().format("YYYY-MM-DD"),
order: (blogTypes[checkedBlogIndex.value] as BlogType).blogs.size
} }
putBlog(data).then(response => { putBlog(data).then(response => {
const { bid, title, content } = response.data const { bid, btid, title, content, order, autosave_control } = response.data
const blog = { const blog = {
bid: bid, bid: bid,
btId: checkedBlogIndex.value, btId: btid,
btName: "", btName: "",
title: title, title: title,
blog_prex: "", blog_prex: "",
content: content content: content,
autosave_control: autosave_control,
order: order
} as Blog } as Blog
(blogTypes.get(checkedBlogIndex.value) as BlogType).blogs.set(bid, blog)
(blogTypes[checkedBlogIndex.value] as BlogType).blogs.set(bid, blog)
checkedIndex.value = bid checkedIndex.value = bid
isclicked.value = false isclicked.value = false
}) })
@@ -294,8 +363,8 @@ const deleteBlog = () => {
deleteContent(checkedIndex.value).then(response => { deleteContent(checkedIndex.value).then(response => {
if (response.status == 200) { if (response.status == 200) {
const deleteKey = checkedIndex.value const deleteKey = checkedIndex.value
checkedIndex.value = blogTypes.get(checkedBlogIndex.value)?.blogs.keys().next().value checkedIndex.value = blogTypes[checkedBlogIndex.value].blogs.keys().next().value
blogTypes.get(checkedBlogIndex.value)?.blogs.delete(deleteKey) blogTypes[checkedBlogIndex.value].blogs.delete(deleteKey)
deleteDialogVisible.value = false deleteDialogVisible.value = false
} }
}) })
@@ -307,13 +376,15 @@ const deleteBlog = () => {
let btName = ref("") let btName = ref("")
const insertBlogType = () => { const insertBlogType = () => {
const data = { const data = {
btName: btName.value btName: btName.value,
order: blogTypes.length
} }
putBlogType(data).then(response => { putBlogType(data).then(response => {
const blog_type: BlogType = { const blog_type: BlogType = {
btId: response.data.btId, btId: response.data.btId,
btName: response.data.btName, btName: response.data.btName,
blogs: new Map<string, Blog>() blogs: new Map<string, Blog>(),
order: response.data.order
} }
response.data.blog_list.forEach((item: any) => { response.data.blog_list.forEach((item: any) => {
const blog: Blog = { const blog: Blog = {
@@ -323,11 +394,12 @@ const insertBlogType = () => {
title: item.title, title: item.title,
blog_prex: item.blog_prex, blog_prex: item.blog_prex,
autosave_control: item.autosave_control, autosave_control: item.autosave_control,
content: item.content content: item.content,
order: item.order
} }
blog_type.blogs.set(item.bid, blog) blog_type.blogs.set(item.bid, blog)
}) })
blogTypes.set(response.data.btId, blog_type) blogTypes.push(blog_type)
checkedBlogIndex.value = response.data.btId checkedBlogIndex.value = response.data.btId
checkedIndex.value = blog_type.blogs.keys().next().value checkedIndex.value = blog_type.blogs.keys().next().value
contentDialogVisible.value = false contentDialogVisible.value = false
@@ -336,13 +408,13 @@ const insertBlogType = () => {
} }
const deleteBlogType = () => { const deleteBlogType = () => {
if (btName.value == (blogTypes.get(checkedBlogIndex.value) as BlogType).btName) { if (btName.value == (blogTypes[checkedBlogIndex.value] as BlogType).btName) {
const data = { const data = {
btId: checkedBlogIndex.value btId: checkedBlogIndex.value
} }
removeBlogType(data).then((response) => { removeBlogType(data).then((response) => {
if (response.status == 200) { if (response.status == 200) {
blogTypes.delete(checkedBlogIndex.value) blogTypes.splice(checkedBlogIndex.value, 1)
checkedBlogIndex.value = blogTypes.keys().next().value checkedBlogIndex.value = blogTypes.keys().next().value
checkedIndex.value = blogTypes.values().next().value.blogs.keys().next().value checkedIndex.value = blogTypes.values().next().value.blogs.keys().next().value
} else { } else {
@@ -356,6 +428,23 @@ const deleteBlogType = () => {
} }
} }
const sortBlogType = () => {
const sortData: BlogType[] = []
blogTypes.forEach((blogType: BlogType) => {
sortData.push({
btId: blogType.btId,
btName: blogType.btName,
blogs: new Map(),
order: blogType.order
})
})
sortBlogTypes(sortData).then(response => {
}).catch()
}
// Created 段階の関数呼び出す処理 // Created 段階の関数呼び出す処理
getTestData() getTestData()
@@ -436,4 +525,49 @@ innerHeight.value = (window.innerHeight - 50) + 'px'
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.itxst {
width: 600px;
}
.move {
cursor: move;
}
table.tb {
color: #333;
border: solid 1px #999;
font-size: 13px;
border-collapse: collapse;
min-width: 100px;
user-select: none;
}
table.tb th {
background: rgb(168 173 217);
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #999;
text-align: left;
}
table.tb th:nth-of-type(1) {
text-align: center;
}
table.tb td {
background: #d6c8c8;
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #999;
}
table.tb td:nth-of-type(1) {
text-align: center;
}
</style> </style>