{ "version": "https://jsonfeed.org/version/1.1", "title": "Seviche.cc", "home_page_url": "https://seviche.cc", "feed_url": "https://seviche.cc/feed.json", "description": "Tech / Code / Random Life", "icon": "https://seviche.cc/assets/any@512.png", "favicon": "https://seviche.cc/favicon.png", "authors": [ { "name": "酸橘汁腌鱼", "url": "https://seviche.cc", "avatar": "/assets/avatar.jpg" } ], "language": "zh", "hubs": [ { "type": "WebSub", "url": "https://pubsubhubbub.appspot.com" }, { "type": "WebSub", "url": "https://bridgy-fed.superfeedr.com" } ], "items": [ { "id": "2022-08-12-vue-challenges", "url": "https://seviche.cc/2022-08-12-vue-challenges", "title": "Vue.js 挑战练习", "content_html": "

最近做了一下这个Vue.js 挑战,其中的题目大多出自Vue3 文档,都不是很难,但涉及到的知识点比较琐碎,用来复习挺好的。

然后这是我的答案和题目涉及到的知识点,除了鼠标指针这个部分没通过单元测试之外,其他都都通过了,然后这个鼠标指针为什么没通过单元测试我也没弄明白,试了下其他人的也通过不了,好奇怪……

这里省去部分题目,主要写答案。

Built-ins

DOM 传送门

Vue.js 提供了一个内置组件,将其插槽内容渲染到另一个 DOM,成为该 DOM 的一部分。

vue
<script setup>
const msg = 'Hello World'
</script>
<template>
<teleport to=\"body\">
<span>{{ msg }}</span>
</teleport>
</template>

相关知识点 :Teleport | Vue.js

有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置1

优化性能的指令

Vue.js 提供了一个指令,以便只渲染一次元素和组件,并且跳过以后的更新。

vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
setInterval(() => {
count.value++
}, 1000)
</script>
<template>
<span v-once>使它从不更新: {{ count }}</span>
</template>

相关知识点:Vue-事件修饰符

CSS Features

动态 CSS

Vue 单文件组件 <style> 模块支持给 CSS 绑定动态值。

vue
<script setup>
import { ref } from 'vue'
const theme = ref('red')
const colors = ['blue', 'yellow', 'red', 'green']
setInterval(() => {
theme.value = colors[Math.floor(Math.random() * 4)]
}, 1000)
</script>
<template>
<p>hello</p>
</template>
<style scoped>
/* Modify the code to bind the dynamic color */
p {
color: v-bind(theme);
}
</style>

相关知识点:v-bind Dynamic Styling动态绑定样式

全局 CSS

给具有 CSS 作用域的 Vue 单文件组件设置全局 CSS 样式

vue
<template>
<p>Hello Vue.js</p>
</template>
<style scoped>
p {
font-size: 20px;
color: red;
text-align: center;
line-height: 50px;
}
/* Make it work */
:global(body) {
width: 100vw;
height: 100vh;
background-color: burlywood;
}
</style>

或者

vue
<template>
<p>Hello Vue.js</p>
</template>
<style scoped>
p {
font-size: 20px;
color: red;
text-align: center;
line-height: 50px;
}
</style>
<style>
/* Make it work */
body {
width: 100vw;
height: 100vh;
background-color: burlywood;
}
</style>

相关知识点:单文件组件 CSS 功能 | Vue.js

Components

DOM 传送门

见上面

Props 验证

验证 Button 组件的 Prop 类型 ,使它只接收: primary | ghost | dashed | link | text | default ,且默认值为 default

vue
<script setup>
import Button from './Button.vue'
defineProps({
type: {
type: String,
default: 'default',
validator: value => {
;['primary', 'ghost', 'dashed', 'link', 'text', 'default'].includes(value)
}
}
})
</script>
<template>
<Button type=\"dashed\" />
</template>

相关知识点:Props | Vue.js

函数式组件

这题我不是很懂,翻了一下大家的解决方案,感觉这个比较能看懂:21 - functional component · Issue #322 · webfansplz/vuejs-challenges · GitHub

vue
<script setup lang=\"ts\">
import { ref, h } from 'vue'
/**
* Implement a functional component :
* 1. Render the list elements (ul/li) with the list data
* 2. Change the list item text color to red when clicked.
*/
const ListComponent = (props, { emit }) =>
h(
// 创建 ul
'ul',
// 根据传入的props创建li
props.list.map((item: { name: string }, index: number) =>
h(
'li',
{
// 点击时处罚toggle。并将当前index作为参数传入toggle
onClick: () => emit('toggle', index),
// 将当前点击的li颜色设置为红色
style: index === props.activeIndex ? { color: 'red' } : null
},
// li 默认值
item.name
)
)
)
ListComponent.props = ['list', 'activeIndex']
ListComponent.emits = ['toggle']
const list = [
{
name: 'John'
},
{
name: 'Doe'
},
{
name: 'Smith'
}
]
const activeIndex = ref(0)
function toggle(index: number) {
activeIndex.value = index
}
</script>
<template>
<list-component :list=\"list\" :active-index=\"activeIndex\" @toggle=\"toggle\" />
</template>

相关知识点:

渲染函数[h()]

使用 h 渲染函数来实现一个组件。

vue
import { defineComponent, h } from 'vue'; export default defineComponent({ name: 'MyButton', props: { disabled: { type: Boolean,
default: false, }, }, emits: ['custom-click'], setup(props, { emit, slots }) { return () => h( 'button', { disabled:
props.disabled, onClick: () => emit('custom-click'), }, slots.default?.() ); }, });

树组件

实现一个树组件

vue
<script setup lang=\"ts\">
import { defineComponent } from 'vue'
interface TreeData {
key: string
title: string
children: TreeData[]
}
defineProps<{ data: TreeData[] }>()
</script>
<template>
<ul v-for=\"node in data\">
<li>{{ node.title }}</li>
<template v-if=\"node.children && node.children.length\">
// 用递归的方法来实现
<TreeComponent :data=\"node.children\" />
</template>
</ul>
</template>

参考:

Composable Function

本节相关知识点:组合式函数 | Vue.js

切换器

尝试编写可组合函数

vue
<script setup lang=\"ts\">
import { ref } from 'vue'
/**
* Implement a composable function that toggles the state
* Make the function work correctly
*/
function useToggle(init: boolean) {
const state = ref(init)
const toggle = () => (state.value = !state.value)
return [state, toggle]
}
const [state, toggle] = useToggle(false)
</script>
<template>
<p>State: {{ state ? 'ON' : 'OFF' }}</p>
<p @click=\"toggle\">Toggle state</p>
</template>

计数器

实现一个计数器

vue
<script setup lang=\"ts\">
import { ref } from 'vue'
interface UseCounterOptions {
min?: number
max?: number
}
/**
* Implement the composable function
* Make sure the function works correctly
*/
function useCounter(initialValue = 0, options: UseCounterOptions = {}) {
const count = ref(initialValue)
const inc = () => {
if (count.value < options.max) count.value++
}
const dec = () => {
if (count.value > options.min) count.value--
}
const reset = () => (count.value = initialValue)
return { count, inc, dec, reset }
}
const { count, inc, dec, reset } = useCounter(0, { min: 0, max: 10 })
</script>

实现本地存储函数

封装一个localStorageAPI

vue
<script setup lang=\"ts\">
import { ref, watchEffect } from 'vue'
/**
* Implement the composable function
* Make sure the function works correctly
*/
function useLocalStorage(key: string, initialValue: any) {
const value = ref(localStorage.getItem(key) || initialValue)
watchEffect(() => {
localStorage.setItem(key, value.value)
})
return value
}
const counter = useLocalStorage('counter', 0)
// We can get localStorage by triggering the getter:
console.log(counter.value)
// And we can also set localStorage by triggering the setter:
const update = () => counter.value++
</script>
<template>
<p>Counter: {{ counter }}</p>
<button @click=\"update\">Update</button>
</template>

相关知识点:

鼠标坐标

这个没通过单元测试,不知道什么原因,试了下其他人的也没能通过……

vue
<script setup lang=\"ts\">
import { ref, onMounted, onUnmounted } from 'vue'
// Implement ...
function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
// Implement ...
function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', e => {
x.value = e.pageX
y.value = e.pageY
})
return { x, y }
}
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>

Composition API

生命周期钩子

vue
<script setup lang=\"ts\">
import { onMounted, inject, onUnmounted } from 'vue'
const timer = inject('timer')
const count = inject('count')
onMounted(() => {
timer.value = window.setInterval(() => {
count.value++
}, 1000)
})
// 计时器要清除
onUnmounted(() => {
window.clearInterval(timer.value)
})
</script>
<template>
<div>
<p>Child Component: {{ count }}</p>
</div>
</template>

ref 全家桶

vue
<script setup lang=\"ts\">
import { ref, Ref, reactive, isRef, unref, toRef } from 'vue'
const initial = ref(10)
const count = ref(0)
// Challenge 1: Update ref
function update(value) {
count.value = value
}
/**
* Challenge 2: Check if the `count` is a ref object.
* Make the output be 1
*/
console.log(isRef(count) ? 1 : 0)
/**
* Challenge 3: Unwrap ref
* Make the output be true
*/
function initialCount(value: number | Ref<number>) {
// Make the output be true
console.log(unref(value) === 10)
}
initialCount(initial)
/**
* Challenge 4:
* create a ref for a property on a source reactive object.
* The created ref is synced with its source property:
* mutating the source property will update the ref, and vice-versa.
* Make the output be true
*/
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo') // change the impl...
// mutating the ref updates the original
fooRef.value++
console.log(state.foo === 2)
// mutating the original also updates the ref
state.foo++
console.log(fooRef.value === 3)
</script>
<template>
<div>
<p>
<span @click=\"update(count - 1)\">-</span>
{{ count }}
<span @click=\"update(count + 1)\">+</span>
</p>
</div>
</template>

相关知识点:

响应性丢失

保证解构/扩展不丢失响应性

vue
<script setup lang=\"ts\">
import { reactive, toRefs } from 'vue'
function useCount() {
const state = reactive({
count: 0
})
function update(value: number) {
state.count = value
}
return {
state: toRefs(state),
update
}
}
// Ensure the destructured properties don't lose their reactivity
const {
state: { count },
update
} = useCount()
</script>
<template>
<div>
<p>
<span @click=\"update(count - 1)\">-</span>
{{ count }}
<span @click=\"update(count + 1)\">+</span>
</p>
</div>
</template>

相关知识点:toRefs

可写的计算属性

vue
<script setup lang=\"ts\">
import { ref, computed } from 'vue'
const count = ref(1)
const plusOne = computed({
get() {
return count.value + 1
},
set(newValue) {
count.value = newValue - 1
}
})
/**
* Make the `plusOne` writable.
* So that we can get the result `plusOne` to be 3, and `count` to be 2.
*/
plusOne.value++
</script>
<template>
<div>
<p>{{ count }}</p>
<p>{{ plusOne }}</p>
</div>
</template>

相关知识点:可写的计算属性

watch 全家桶

vue
<script setup lang=\"ts\">
import { ref, watch } from 'vue'
const count = ref(0)
/**
* Challenge 1: Watch once
* Make sure the watch callback only triggers once
*/
const watchOnce = watch(count, () => {
console.log('Only triggered once')
watchOnce()
})
count.value = 1
setTimeout(() => (count.value = 2))
/**
* Challenge 2: Watch object
* Make sure the watch callback is triggered
*/
const state = ref({
count: 0
})
watch(
state,
() => {
console.log('The state.count updated')
},
{ deep: true }
)
state.value.count = 2
/**
* Challenge 3: Callback Flush Timing
* Make sure visited the updated eleRef
*/
const eleRef = ref()
const age = ref(2)
watch(
age,
() => {
console.log(eleRef.value)
},
{
flush: 'post'
}
)
age.value = 18
</script>
<template>
<div>
<p>
{{ count }}
</p>
<p ref=\"eleRef\">
{{ age }}
</p>
</div>
</template>

相关知识点:侦听器 | Vue.js

浅层 ref

响应式 API: shallowRef

vue
<script setup lang=\"ts\">
import { shallowRef, watch } from 'vue'
const state = shallowRef({ count: 1 })
// Does NOT trigger
watch(
state,
() => {
console.log('State.count Updated')
},
{ deep: true }
)
/**
* Modify the code so that we can make the watch callback trigger.
*/
state.value = { count: 2 }
</script>
<template>
<div>
<p>
{{ state.count }}
</p>
</div>
</template>

相关知识点:shallowRef()

依赖注入

child.vue

vue
<script setup lang=\"ts\">
import { inject } from 'vue'
const count = inject('count')
</script>
<template>
{{ count }}
</template>

相关知识点:组合式 API:依赖注入 | Vue.js

Effect 作用域 API

vue
<script setup lang=\"ts\">
import { ref, computed, watch, watchEffect, effectScope } from 'vue'
const counter = ref(1)
const doubled = computed(() => counter.value * 2)
// use the `effectScope` API to make these effects stop together after being triggered once
const scope = effectScope()
scope.run(() => {
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log(`Count: ${doubled.value}`))
counter.value = 2
})
setTimeout(() => {
counter.value = 4
scope.stop()
})
</script>
<template>
<div>
<p>
{{ doubled }}
</p>
</div>
</template>

相关知识点:effectScope

自定义 ref

vue
<script setup>
import { watch, customRef } from 'vue'
/**
* Implement the function
*/
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
const text = useDebouncedRef('hello')
/**
* Make sure the callback only gets triggered once when entered multiple times in a certain timeout
*/
watch(text, value => {
console.log(value)
})
</script>
<template>
<input v-model=\"text\" />
</template>

相关知识点:customRef

Directives

大写

创建一个自定义的修饰符  capitalize,它会自动将  v-model  绑定输入的字符串值首字母转为大写:App.vue

vue
<script setup>
import { ref } from 'vue'
import Input from './Input.vue'
const value = ref('')
</script>
<template>
<Input type=\"text\" v-model.capitalize=\"value\" />
</template>

Input.vue

vue
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
modelValue: String,
modelModifiers: {
default: () => ({})
}
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type=\"text\" :value=\"modelValue\" @input=\"emitValue\" />
</template>

相关知识点:处理 v-model 修饰符

优化性能的指令

见上面。v-once

切换焦点指令

vue
<script setup lang=\"ts\">
import { ref } from 'vue'
const state = ref(false)
/**
* Implement the custom directive
* Make sure the input element focuses/blurs when the 'state' is toggled
*
*/
const VFocus = {
updated: (el, state) => (state.value ? el.focus() : el.blur())
}
setInterval(() => {
state.value = !state.value
}, 2000)
</script>
<template>
<input v-focus=\"state\" type=\"text\" />
</template>

相关知识点:自定义指令 | Vue.js

防抖点击指令

尝试实现一个防抖点击指令

vue
<script setup lang=\"ts\">
/**
* Implement the custom directive
* Make sure the `onClick` method only gets triggered once when clicked many times quickly
* And you also need to support the debounce delay time option. e.g `v-debounce-click:ms`
*
*/
function debounce(fn, delay) {
let timeout
let count = 0
return (...args) => {
if (count === 0) {
count++
fn(...args)
}
clearTimeout(timeout)
timeout = setTimeout(() => {
fn(...args)
}, delay)
}
}
const VDebounceClick = {
mounted: (el, binding) => {
const { value, arg } = binding
el.addEventListener('click', debounce(value, arg))
}
}
function onClick() {
console.log('Only triggered once when clicked many times quickly')
}
</script>
<template>
<button v-debounce-click:200=\"onClick\">Click on it many times quickly</button>
</template>

相关知识点:指令钩子

激活的样式-指令

vue
<script setup lang=\"ts\">
import { ref, watchEffect } from 'vue'
/**
* Implement the custom directive
* Make sure the list item text color changes to red when the `toggleTab` is toggled
*
*/
const VActiveStyle = {
mounted: (el, binding) => {
const [styles, fn] = binding.value
watchEffect(() => {
Object.keys(styles).map(key => (el.style[key] = fn() ? styles[key] : ''))
})
}
}
const list = [1, 2, 3, 4, 5, 6, 7, 8]
const activeTab = ref(0)
function toggleTab(index: number) {
activeTab.value = index
}
</script>
<template>
<ul>
<li
v-for=\"(item, index) in list\"
:key=\"index\"
v-active-style=\"[{ color: 'red' }, () => activeTab === index]\"
@click=\"toggleTab(index)\">
{{ item }}
</li>
</ul>
</template>

实现简易版v-model指令

vue
<script setup lang=\"ts\">
import { ref } from 'vue'
/**
* Implement a custom directive
* Create a two-way binding on a form input element
*
*/
const VOhModel = {
mounted: (el, binding) => {
el.value = binding.value
el.addEventListener('input', () => {
value.value = el.value
})
}
}
const value = ref('Hello Vue.js')
</script>
<template>
<input v-oh-model=\"value\" type=\"text\" />
<p>{{ value }}</p>
</template>

Event Handling

阻止事件冒泡

vue
<script setup lang=\"ts\">
const click1 = () => {
console.log('click1')
}
const click2 = e => {
console.log('click2')
}
</script>
<template>
<div @click=\"click1()\">
<div @click.stop=\"click2()\">click me</div>
</div>
</template>

相关知识点:事件修饰符

按键修饰符

vue
<template>
<!-- Add key modifiers made this will fire even if Alt or Shift is also pressed -->
<button @click.alt=\"onClick1\" @click.shift=\"onClick1\">A</button>
<!-- Add key modifiers made this will only fire when Shift and no other keys are pressed -->
<button @click.shift.exact=\"onCtrlClick\">A</button>
<!-- Add key modifiers made this will only fire when no system modifiers are pressed -->
<button @click.exact=\"onClick2\">A</button>
</template>
<script setup>
function onClick1() {
console.log('onClick1')
}
function onCtrlClick() {
console.log('onCtrlClick')
}
function onClick2() {
console.log('onClick2')
}
</script>

相关知识点:按键修饰符

Global API:General

下一次 DOM 更新

Vue.js中改变响应式状态时,DOM 不会同步更新。 Vue.js  提供了一个用于等待下一次 DOM 更新的方法

vue
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
const counter = ref(null)
async function increment() {
count.value++
/**
* DOM is not yet updated, how can we make sure that the DOM gets updated
* Make the output be true
*/
await nextTick()
console.log(+counter.value.textContent === 1)
}
</script>
<template>
<button ref=\"counter\" @click=\"increment\">
{{ count }}
</button>
</template>

相关知识点:nextTick()

Lifecycle

生命周期钩子

同上:生命周期钩子

Reactivity:Advanced

浅层 ref

同上:浅层 ref

原始值 API

vue
<script setup lang=\"ts\">
import { reactive, isReactive, toRaw, markRaw } from 'vue'
const state = { count: 1 }
const reactiveState = toRaw(reactive(state))
/**
* Modify the code so that we can make the output be true.
*/
console.log(reactiveState === state)
/**
* Modify the code so that we can make the output be false.
*/
const info = markRaw({ count: 1 })
const reactiveInfo = reactive(info)
console.log(isReactive(reactiveInfo))
</script>
<template>
<div>
<p>
{{ reactiveState.count }}
</p>
</div>
</template>

相关知识点:

Effect 作用域 API

同上:Effect 作用域 API

自定义 ref

同上:自定义 ref

Reactivity:Core

ref 全家桶

同上:ref 全家桶

可写的计算属性

同上:可写的计算属性

watch 全家桶

同上:watch 全家桶

Reactivity:Utilities

响应性丟失

同上:响应性丟失

Utility Function

until

vue
<script setup lang=\"ts\">
import { ref } from 'vue'
const count = ref(0)
/**
* Implement the until function
*/
function until(initial) {
function toBe(value) {
return new Promise(resolve => {
initial.value = value
resolve(initial.value)
})
}
return {
toBe
}
}
async function increase() {
count.value = 0
setInterval(() => {
count.value++
}, 1000)
await until(count).toBe(3)
console.log(count.value === 3) // Make sure the output is true
}
</script>
<template>
<p @click=\"increase\">Increase</p>
</template>

Web Components

自定义元素

vue
<script setup lang=\"ts\">
import { onMounted, defineCustomElement } from 'vue'
/**
* Implement the code to create a custom element.
* Make the output of page show \"Hello Vue.js\".
*/
const VueJs = defineCustomElement({
props: { message: String },
template: '<span>{{message}}</span>'
})
customElements.define('vue-js', VueJs)
onMounted(() => {
document.getElementById('app')!.innerHTML = '<vue-js message=\"Hello Vue.js\"></vue-js>'
})
</script>
<template>
<div id=\"app\"></div>
</template>

并且 vite.config.js 里要做相关设置相关知识点:Vue 与 Web Components | Vue.js

", "summary": "我的答案以及相关知识点", "date_published": "2022-08-12T00:00:00.000Z", "date_modified": "2022-08-13T08:21:08.878Z", "tags": [ "Vue" ], "_indieweb": { "type": "article" } }, { "id": "2022-08-06-notes", "url": "https://seviche.cc/2022-08-06-notes", "title": "Monkey Patch", "content_html": "

Monkey Patch - Wikipedia

A Monkey patch is a way for a program to extend or modify supporting system software locally (affecting only the running instance of the program).”

也可以可以理解为基于原生功能(API)的一些自定义拓展?这个概念常在 Python 里面出现,也叫鸭子双关

Checking if a JavaScript native function is monkey patched

Monkey patching is a powerful but dangerous technique because the code you’re overriding is not in your control: future updates to the JavaScript engine may break some assumptions made in your patch and cause serious bugs.Additionally, by monkey patching code you don’t own, you might override some code that has already been monkey patched by another developer, introducing a potential conflict.

对于 JS 来说,Monkey patch 的问题在于:如果 JS 引擎更新了,可能会带来一些难以预计的 bug,这篇文章说了几种怎么检测是否存在 monkey patch 函数的方法,比如用 toString、iframe、proxy 等等,但都不是很完美,可以根据使用场景来决定用哪种方法

", "summary": "猴子打补丁", "date_published": "2022-08-06T00:00:00.000Z", "date_modified": "2022-08-07T04:36:02.907Z", "_indieweb": { "type": "article" } }, { "id": "2022-07-31-reading-7", "url": "https://seviche.cc/2022-07-31-reading-7", "title": "我在看什么 · 7 月", "content_html": "

前端

后端

什锦

Commonplace Book

  1. Intentionality

    every idea matters

    just collect very succinct quotes from biographies that i’ve read from books that i’ve read or from articles from of the internet that i’ve read and over time i ended up with this collection of very succinct sentences that but basically every one of these sentences they mean something every one of these words they mean something every expression that i put into this book they’re all very deeply personal for me and also the act of writing for me is very personal because writing for me and journaling for me is a form of calming the brain because my brain does not shut up so point one keeping a commonplacebook on a physical notebook is a lot more deliberate

  2. better editing

    writing on the paper at leastfor me liberates me from that uh illusion ofperfection illusion of cleanliness that i get on a computer screen because i cancross everything out i can cross things out straight away and i can spotmistakes and you know weak sentences straight away

  3. Crossing Disciplines

", "summary": "eval / CSP&XSS / Commonplace Book ……", "image": "/2022-07-31-reading-7/july.webp", "date_published": "2022-07-31T00:00:00.000Z", "date_modified": "2022-08-06T11:32:41.119Z", "tags": [ "我在看什么" ], "_indieweb": { "type": "article" } }, { "id": "2022-07-27-bin", "url": "https://seviche.cc/2022-07-27-bin", "title": "JS中的二进制数字", "content_html": "

参考:How to Represent Binary Numbers in JavaScript? - Designcise

ES6+

在 ES6 之后的版本,在二进制数字前加0b 或者0B来标识这是一个二进制数字,比如:

js
let number5 = ob101
let number5 = oB101

Before ES6

js
const number = '0101'
Number.parseInt(number, 2)

相关:JavaScript · 十进制数转二进制

", "summary": "0b/0B和paresInt", "date_published": "2022-07-27T00:00:00.000Z", "date_modified": "2022-08-11T17:12:21.128Z", "tags": [ "JavsScript" ], "_indieweb": { "type": "article" } }, { "id": "2022-07-23-notes-xss", "url": "https://seviche.cc/2022-07-23-notes-xss", "content_html": "

XSS 学习

介绍文章:

练习:

相关文章:

", "date_published": "2022-07-26T00:00:00.000Z", "date_modified": "2022-08-11T17:39:51.841Z", "tags": [ "XSS", "Notes" ], "_indieweb": { "type": "note" } }, { "id": "2022-07-23-d3", "url": "https://seviche.cc/2022-07-23-d3", "title": "D3.js 基础笔记", "content_html": "

内容出自:FreeCodeCamp

D3 或 D3.js 表示数据驱动文档。它是一个用于在浏览器中创建动态和交互式数据视觉化的 JavaScript 库。1

学习资源

基础操作

修改元素

js
const anchor = d3.select('a')

在 D3 中可以串联多个方法,连续执行一系列操作。->[[function chaining|链式调用]]

使用数据

d3-selection

当 enter() 和 data() 方法一起使用时,它把从页面中选择的元素和数据集中的元素作比较。 如果页面中选择的元素较少则创建缺少的元素。

可以理解为管理仓库的,选择的元素是货架,数据是货,如果货架不够了,就再补几个货架,如果多了就不管

html
<body>
<ul></ul>
<script>
const dataset = ['a', 'b', 'c']
d3.select('ul').selectAll('li').data(dataset).enter().append('li').text('New item')
</script>
</body>

使用动态数据

text 中设置回调处理数据如:

html
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9]
d3.select('body')
.selectAll('h2')
.data(dataset)
.enter()
.append('h2')
.text(d => d + ' USD')
</script>
</body>

给元素添加内联样式

js
selection.style('color', 'blue')
//用回调过滤
selection.style('color', d => {
return d < 20 ? 'red' : 'green'
})
// 动态设置样式
selection.style('height', d => d + 'px') // 动态设置高度

添加 Class

js
selection.attr('class', 'container')
// 回调
selection.attr('property', (d, i) => {})
// 比如改变间距
svg
.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', (d, i) => {
return i * 30 //改变间距
})
.attr('y', (d, i) => {
return d * 3 //改变高度
})
//悬停效果
.attr('class', 'bar')

标签

js
//添加标签
svg
.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr('x', (d, i) => i * 30)
.attr('y', (d, i) => h - 3 * d - 3) // 高三个单位是减
.text(d => d)
// 添加样式
.attr('fill', 'red')
.style('font-size', '25px')
//悬停效果
.attr('class', 'bar')
/** css中
.bar:hover {
fill: brown;
}
**/

添加工具提示 tooltip

  1. title
js
svg
.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', (d, i) => i * 30)
.attr('y', (d, i) => h - 3 * d)
.attr('width', 25)
.attr('height', (d, i) => d * 3)
// 每个 rect 节点下附加 title 元素。 然后用回调函数调用 text() 方法使它的文本显示数据值。
.append('title')
.text(d => d)

SVG

创建 SVG

js
//创建svg
selection.append('svg')

反转 SVG 元素

SVG 区域的高度为 100。 如果在集合中一个数据点的值为 0,那么条形将从 SVG 区域的最底端开始(而不是顶端)。 为此,y 坐标的值应为 100。 如果数据点的值为 1,你将从 y 坐标为 100 开始来将这个条形设置在底端, 然后需要考虑该条形的高度为 1,所以最终的 y 坐标将是 99。

(高度从下面开始计算,坐标轴从上面开始)

更改 SVG 元素的颜色

js
svg
.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', (d, i) => i * 30)
.attr('y', (d, i) => h - 3 * d)
.attr('width', 25)
.attr('height', (d, i) => 3 * d)
//将所有条形图的 fill 设置为海军蓝。
.attr('fill', 'navy')

SVG 图形

矩形

SVG 的 rect 有四个属性。 x 和 y 坐标指定图形放在 svg 区域的位置, height 和 width 指定图形大小

js
const svg = d3
.select('body')
.append('svg')
.attr('width', w)
.attr('height', h)
.append('rect') //rect矩形
.attr('width', 25)
.attr('height', 100)
.attr('x', 0)
.attr('y', 0)

圆形

js
svg
.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
//散点图
.attr('cx', d => d[0])
.attr('cy', d => h - d[1])
.attr('r', '5')

比例尺

创建线性比例

条形图和散点图都直接在 SVG 画布上绘制数据。 但是,如果一组的高或者其中一个数据点比 SVG 的高或宽更大,它将跑到 SVG 区域外。

D3 中,比例尺可帮助布局数据。 scales 是函数,它告诉程序如何将一组原始数据点映射到 SVG 画布上。

线性缩放

js
const scale = d3.scaleLinear()

例子

js
const scale = d3.scaleLinear() // 在这里创建轴
const output = scale(50) // 调用 scale,传入一个参数
d3.select('body').append('h2').text(output)

按比例设置域和范围

例子:

js
scale.domain([50, 480]); //域
scale.range([10, 500]); //范围
scale(50) //10
scale(480) //500
scale(325) //323.37
scale(750)。// 807.。67
d3.scaleLinear()

按顺序,将在控制台中显示以下值:10、500、323.37 和 807.67。

注意,比例尺使用了域和范围之间的线性关系来找出给定数字的输出值。 域中的最小值(50)映射为范围中的最小值(10)。

(也就是给定范围,用线性关系缩小,比如图片放大缩小,给了原图大小和缩小后的图片大小,根据线性关系比例来计算每个像素的位置,元素的大小)

最小值最大值

例子:找到二维数组的最大值和最小值

js
const locationData = [
[1, 7],
[6, 3],
[8, 3]
]
const minX = d3.min(locationData, d => d[0]) //查找在d[0]位置上最小的值

使用动态比例

js
const dataset = [
[34, 78],
[109, 280],
[310, 120],
[79, 411],
[420, 220],
[233, 145],
[333, 96],
[222, 333],
[78, 320],
[21, 123]
]
const w = 500
const h = 500
const padding = 30
const xScale = d3
.scaleLinear()
.domain([0, d3.max(dataset, d => d[0])])
.range([padding, w - padding])

使用预定义的比例放置元素

js
svg
.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.attr('cx', d => xScale(d[0]))
.attr('cy', d => yScale(d[1]))
.attr('r', '5')

添加坐标轴

例子:

js
// X轴
const xAxis = d3.axisBottom(xScale)
svg
.append('g')
.attr('transform', 'translate(0, ' + (h - padding) + ')') // translate(0,x)
.call(xAxis) // x轴作为参数被传递给 call() 方法
// Y轴
const yAxis = d3.axisLeft(yScale)
svg
.append('g')
.attr('transform', 'translate(0,' + (h - padding) + ')')
.call(xAxis)
svg
.append('g')
.attr('transform', 'translate(' + padding + ',0)')
.call(yAxis)

常见图表

散点图

圆圈来映射数据点,每个数据点有两个值。 这两个值和 x 和 y 轴相关联,在可视化中用来给圆圈定位。

js
svg
.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.attr('cx', d => d[0])
//反转图像
.attr('cy', d => h - d[1])
.attr('r', '5')
// 散点图加标签
svg
.selectAll('text')
.data(dataset)
.enter()
.append('text')
// Add your code below this line
.attr('x', d => d[0] + 5)
.attr('y', d => h - d[1])
.text(d => d[0] + ', ' + d[1])

  1. 数据可视化 认证 | freeCodeCamp.org
", "summary": "即使是FreeCodeCamp也要做笔记", "date_published": "2022-07-23T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.463Z", "tags": [ "D3.js", "数据可视化" ], "_indieweb": { "type": "article" } }, { "id": "2022-07-20-animation", "url": "https://seviche.cc/2022-07-20-animation", "title": "css和js两种方式实现div右移1000px动画", "content_html": "

参考:JavaScript 动画

", "summary": "用keyframes和timer", "date_published": "2022-07-20T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.463Z", "tags": [ "CSS", "JavaScript", "animation" ], "_indieweb": { "type": "article" } }, { "id": "2022-07-16-reading-5", "url": "https://seviche.cc/2022-07-16-reading-5", "title": "我在看什么 · 5月", "content_html": "

Productivity

有趣的

", "summary": "Anki / JSDoc / 蘑菇 / Music-Map ……", "image": "/2022-07-16-reading-5/May1.jpg", "date_published": "2022-07-16T00:00:00.000Z", "date_modified": "2022-08-11T16:29:56.272Z", "tags": [ "我在看什么" ], "_indieweb": { "type": "article" } }, { "id": "2022-07-15-reading-6", "url": "https://seviche.cc/2022-07-15-reading-6", "title": "我在看什么 · 6月", "content_html": "

编程相关

前端

  1. Learn Debounce And Throttle In 16 Minutes

    讲得挺好的一个视频。节流(debounce)和防抖(Throttle)是优化高频率执行代码的一种手段1 如果把执行代码比喻成下楼拿快递的话:

  2. 基于 Vue 实现一个简易 MVVM

  3. Websocket 原理及具体使用(ws+socket.io)

  4. String.prototype.localeCompare()

  5. Pinia 和 Optional API 的结合使用

  6. How to Migrate from Vue CLI to Vite

  7. 使用 pnpm 替换 npm 优化项目开发

  8. 剪贴板操作 Clipboard API 教程

  9. 如何做前端单元测试

  10. Vue-测试

  11. 前后端加密(一):前端请求加密封装

  12. 深入浅出 npm & yarn & pnpm 包管理机制

    很多人认为 npm 是 node package manager 的缩写,其实不是,而且 npm 根本也不是任何短语的缩写。它的前身其实是名为 pm(pkgmakeinst) 的 bash 工具,它可以在各种平台上安装各种东西。硬要说缩写的话,也应该是 node pm 或者 new pm。

  13. 过度使用懒加载对 Web 性能的影响

  14. Vue 项目性能优化 — 实践指南(网上最全 / 详细)

  15. Vue 打包 chunk-vendors.js 文件过大解决方案(compression-webpack-plugin)

  16. 解决 NPM 安装应用出现 ERESOLVE 错误

  17. Web 性能优化:FOUC

  18. 傻傻分不清之 Cookie、Session、Token、JWT

  19. [译文]为什么 Angular 比 React 更适合企业级应用程序

后端

  1. Docker 从入门到实践
  2. Ubuntu 安装新版本 nodejs 的 5 种姿势
  3. npm does not support Node.js v10.19.0

其他

  1. 详细的全自动追番教程:Sonarr+Jackett+qBittorrent+Jellyfin
  2. Open Source Alternative to
  3. 在 GitHub 公开仓库中隐藏自己的私人邮箱地址
  4. 各种开发者文档的以往版本
  5. Github 删除某个文件的所有提交记录

互联网哲学

  1. Who Actually Owns Your Device?

    What it means to truly own your device is having the power and ability to modify it at a system level should you choose to – even if you don’t know what you’re doing, you should have the right.

  2. Information forest

    在产品沉思录周刊看到的推荐

    What should the browser of the 2020s be?

    What will a browser built for research, analysis, rabbit-hole exploration, messy thinking, and collaboration look like? These features are listed in the order I thought of them in, not necessarily by importance.

    1. Graph visualization and mind mapping.
    2. Interactive history and version control.
    3. Predictive search paths.
    4. Super Command-F (Superf).
    5. Collaboration
    6. Automatic scraping and clusterin
    7. Built in word processing.
    8. Backlinks.
    9. An infinitely zoomable interface (ZUI)

    相关阅读:Next Browser


  1. 什么是防抖和节流?有什么区别?如何实现?
", "summary": "Vue / 网页性能优化 / npm ……", "image": "/2022-07-15-reading-6/june.png", "date_published": "2022-07-15T00:00:00.000Z", "date_modified": "2022-08-06T11:32:41.091Z", "tags": [ "我在看什么" ], "_indieweb": { "type": "article" } }, { "id": "2022-07-13-sql", "url": "https://seviche.cc/2022-07-13-sql", "title": "SQL 基础笔记", "content_html": "

SQL vs. NOSQL

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/b67c213a42175acc16073cbad16acaf4.png\"

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/01b1f9ba940268cf5c4108d9a4a94dc0.png\"

CRUD

Create

sql
CREATE TABLE product(
id Int NOT NULL,
name STRING,
price MONEY,
PRIMARY KEY(id)
)

插入数值第一种形式无需指定要插入数据的列名,只需提供被插入的值即可

sql
INSERT INTO _table_name_
VALUES (_value1_,_value2_,_value3_,...);

第二种形式需要指定列名及被插入的值:

sql
INSERT INTO _table_name_ (_column1_,_column2_,_column3_,...)
VALUES (_value1_,_value2_,_value3_,...);

Read

SELECT 语句用于从数据库中选取数据。

sql
SELECT _column_name_,_column_name_
FROM _table_name_;
sql
SELECT * FROM _table_name_;

* 表示选择全部

可以用WHERE 筛选选择结果,如:

sql
SELECT _column_name_,_column_name_
FROM _table_name_
WHERE _column_name operator value_;

Update

sql
UPDATE _tablse_name_
SET _column1_=_value1_,_column2_=_value2_,...
WHERE _some_column_=_some_value_;

ALTER TABLE 语句用于在已有的表中添加、删除或修改列。如需在表中添加列,请使用下面的语法:

sql
ALTER TABLE table_name
ADD column_name datatype

如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):

sql
ALTER TABLE table_name
DROP COLUMN column_name

Destory

sql
DELETE FROM _table_name_
WHERE _some_column_=_some_value_;

Understanding SQL Relationships, Foreign Keys and Inner Joins

FOREIGN KEY

INNER JOIN

选择相应列并合并表,ON后面写的是条件

sql
SELECT _column_name(s)_
FROM _table1_
INNER JOIN _table2_
ON _table1.column_name_=_table2.column_name_;

或:

sql
SELECT _column_name(s)_
FROM _table1_
JOIN _table2_
ON _table1.column_name_=_table2.column_name_;
", "summary": "关于增改删查的方式", "date_published": "2022-07-13T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.359Z", "tags": [ "SQL" ], "_indieweb": { "type": "article" } }, { "id": "2022-07-13-work", "url": "https://seviche.cc/2022-07-13-work", "title": "一些设计作品", "content_html": "

二十四节气茶包

\"2022-07-13-work/1.jpg\"\"2022-07-13-work/2.jpg\"\"2022-07-13-work/3.jpg\"\"2022-07-13-work/4.jpg\"

UI 设计

\"/2022-07-13-work/famic2.png\"\"/2022-07-13-work/famic1.png\"\"/2022-07-13-work/card.png\"

饼干包装

升级前\"/2022-07-13-work/%E5%8E%9F%E5%8C%85%E8%A3%85.JPG\"升级后(面对年轻人重新设计\"/2022-07-13-work/after.png\"

Logo

\"/2022-07-13-work/%E6%A0%B7%E6%9C%BA3.jpg\"\"/2022-07-13-work/%E6%A0%B7%E6%9C%BA4.jpg\"

排版装帧

\"/2022-07-13-work/book1.png\"\"/2022-07-13-work/book2.png\"\"/2022-07-13-work/book3.png\"\"/2022-07-13-work/book4.png\"

儿童玩具

\"/2022-07-13-work/game.webp\"

未完待续……(也不知道续不续)

", "summary": "logo / UI / 包装 / 儿童玩具 ……", "date_published": "2022-07-13T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.433Z", "_indieweb": { "type": "article" } }, { "id": "2022-06-15-jellyfin", "url": "https://seviche.cc/2022-06-15-jellyfin", "title": "VPS · Jellyfin结合Cloudreve开启线上影院", "content_html": "

昨天搭了一下 Pleroma,完全失败,完全没信心了,所以今天决定随便在Awesome Self Hosted | LibHunt找了一个排名很靠前的东西搭一下,给自己建立一点没有必要的信心……

刚好最近朋友找电影资源很犯难,所以我决定拿排在 Media 第一的Jellyfin试试手。

关于 Jellyfin

Jellyfin 是一个在线的流媒体影音库,对电影、音乐、有声书都有比较好的支持。除了在 web 端观看之外,它还支持很多的客户端,几乎是随处可用,目前我只试过安卓端的,其功能与网页端无异,适配得很好,体验流畅。

可以在这里试一下 Demo:Jellyfin

(用户名 demo,密码留空)

下面是我的成果:-D\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/f83e0e4eca39ffd0304f847c6ad7b7e2.png\"

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/820aa9841a44a52a29436549463e3355.png\"

其实我也是搭完才知道它有什么功能的

Cloudrever

Jellyfin 目前不支持 S3 存储,所以我需要一个网盘来存储和管理 VPS 上的媒体资源。看了塔塔的 音乐库搭建 文章后我决定试一下 Cloudreve ,具体的搭建过程这里按下不表,是完全照着塔塔的教程和 Cloudreve 文档 做的

反代

需要注意的是,配置 Nginx 反代时,与往常不同,需要设置一下最大的文件大小,以免后期上传失败:

/etc/nginx/conf.d/jellyfin.conf
bash
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://127.0.0.1:5212;
# 如果您要使用本地存储策略,请将下一行注释符删除,并更改大小为理论最大文件尺寸
client_max_body_size 20000m;
}

一些配置

  1. 最大容量

    管理面板-> 用户组 里可修改\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/7aa6d1e4d5d539d725929075b4cf2c5a.png\"

  2. 配置离线下载

    配置好离线下载就可以用它在后台下载种子资源了。如果用的是 docker-compose 来安装,下面的应该这样配置:\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/4a316f6d84f90c58af4d7da1c2480447.png\"

最后在 cloudreve 面板里创建一个用来存放 jelly 用的文件夹,比如 jellyfin

Jellyfin

搭建

jellyfin 的搭建非常简单,给了我做人的很大信心(没有),我这里依旧选择用 docker-compose 来搭建。

官方文档:Installing Jellyfin | Documentation - Jellyfin Project

首先创建一个配置文件夹并进入:

bash
sudo mkdir /opt/jellyfin && cd /opt/jellyfin

创建配置文件

bash
sudo nano docker-compose.yml

写入:

docker-compose.yml
yaml
version: '3.5'
services:
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
user: root
network_mode: 'host'
volumes:
- /path/to/config:/config
- /path/to/cache:/cache
- /path/to/media:/media #修改为cloudreve的文件夹
- /path/to/media2:/media2:ro #修改为cloudreve的文件夹
restart: 'unless-stopped'
# Optional - alternative address used for autodiscovery
environment:
- JELLYFIN_PublishedServerUrl=http://你的域名

其中需要修改 /path/to/media:/media 的前半部分为 cloudreve 中在 VPS 中的存储路径,比如改为/opt/drive/cloudreve/uploads/ ,如果有多个 cloudreve 用户则可以在后面加用户 id,比如 /opt/drive/cloudreve/uploads/1 这样

然后/path/to/media2 也是同样的修改,改为 /opt/drive/cloudreve/uploads/

改完域名之后,执行 sudo docker-compose up -d 就可以在端口 你的ip:8096 看到界面了。

反代

我用的是 Nginx 来进行反代,虽然每次都是一样的操作,但是直接写可以复制粘贴会比较简单,所以我现在准备说废话。

首先,创建配置文件:

bash
sudo nano /etc/nginx/conf.d/jellyfin.conf

写入:

/etc/nginx/conf.d/jellyfin.conf
bash
server {
listen 80;
server_name 你的域名;
location / {
proxy_pass http://127.0.0.1:8096;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

SSL/TLS 配置可以看我之前这篇:配置 Cloudflare 的免费 SSL 证书,或者用 certbot 配置

最后 sudo nginx -t 以及 sudo systemctl reload nginx 一下就可以在相应域名看到初始界面啦。

媒体库路径

这个地方我摸了好一会才明白.

路径选择 media 或者 media2 下面的文件,然后后面的跟 cloudreve 里面的是一样的,比如在 cloudreve 中用户 1 创建的文件夹叫 jellyfin,则媒体库中路径为 /media/1/jellyfin

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/6fc496318737b099af362731dc5958c6.png\"

插件安装

Jellyfin 有很多实用的插件可以爬电影/音乐等元数据,可以在 控制台-> 插件 安装,需要注意的是,安装完插件需要重启一下才可以生效,也就是先docker-compose downdocker-compose up -d

目前感觉比较好用的两个插件:

主题和语言

可以在display 里面更改界面语言和主题,我比较喜欢的主题是 Purple Haze, 感觉是有些克制的赛博朋克风格

", "summary": "基于 Docker 和 Nginx 的信心搭建过程", "image": "/2022-06-15-jellyfin/1.jpg", "date_published": "2022-06-15T00:00:00.000Z", "date_modified": "2022-08-06T11:32:41.091Z", "tags": [ "Nginx", "VPS", "Docker" ], "_indieweb": { "type": "article" } }, { "id": "2022-06-12-cloudflare", "url": "https://seviche.cc/2022-06-12-cloudflare", "title": "VPS · 配置 Cloudflare 的免费 SSL 证书", "content_html": "

声明:我不知道这样安不安全哈,It just works,个人笔记,操作有风险

参考:申请 CloudFlare 免费 SSL 证书并应用到 nginx – 65536.io | 自娱自乐

先设置加密方式为完全 ,否则之后可能会出现 526 错误

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/45ff9edefcc93a134b8478525bf6767b.png\"

1. 创建证书

首先将主域名绑定到 Cloudflare,然后在SSL/TLS 下的源服务器证书处,点击创建证书\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/b693acbf458e0efaf1c18037e563060e.png\"

然后选择私钥和 CSR 生成方式,以及证书的有效期(也可以不改),点击创建\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/6925f00b181e60af9b8c211942359b6d.png\"

将下面的证书和私钥暂时复制到某个安全的地方,点击确定\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/bc66277a7ebfce59a1cc67ab2947b948.png\"

2. 密钥上传到 VPS

连接 VPS 之后,创建一个文件夹存入密钥,我将其存到/etc/nginx/cert/ 路径下

bash
sudo mkdir /etc/nginx/cert && cd /etc/nginx/cert

写入证书,粘贴入刚刚保存的证书 栏里面的内容

bash
sudo nano public.pem

修改权限

bash
sudo chmod 644 public.pem

写入私钥,粘贴入刚刚保存的私钥 栏里面的内容

bash
sudo nano private.key

修改权限

bash
sudo chmod 600 private.key

3. 修改 Nginx 配置

参考:How to Redirect HTTP to HTTPS in Nginx

如果有用防火墙,请先打开 80 端口和 443 端口,不然可能会像我一样,卡在一个毫无意义的 522 Error 上 ^ ^

bash
sudo ufw allow 80
sudo ufw allow 443

打开 Nginx 配置

bash
sudo nano /etc/nginx/nginx.conf

在 http 块里面配置一个默认 server,将 http 重定向到 https

bash
# 默认server
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
# ssl配置
server {
listen 443 ssl default_server;
server_name *.example1.com;
ssl_certificate /etc/nginx/cert/public.pem;
ssl_certificate_key /etc/nginx/cert/private.key;
}
# 如果有多个域名,可以这样配置,证书也要按之前的添加一下
server {
listen 443 ssl;
server_name *.example2.com;
ssl_certificate /etc/nginx/cert/example2/public.pem;
ssl_certificate_key /etc/nginx/cert/example2/private.key;
}

然后sudo nginx -t 测试一下,没有问题的话就可以 Nginx 了:

bash
sudo systemctl reload nginx

之后如果有域名要配置 ssl ,如 example.conf 中,可以直接将 listen 80 改为listen 443 ssl

我常用的一个反代配置:

server {listen 443 ssl;server_name 域名location / {proxy_pass http://127.0.0.1:反代端口;proxy_set_header HOST $host;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}
", "summary": "关于如何为多个域名配置SSL证书的操作笔记", "date_published": "2022-06-12T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.353Z", "tags": [ "Nginx", "VPS" ], "_indieweb": { "type": "article" } }, { "id": "2022-06-10-backtotop", "url": "https://seviche.cc/2022-06-10-backtotop", "title": "实现一个返回页面顶部的 Vue3 组件", "content_html": "

主要参考:Simple Vue.js and Tailwind.css Scroll To Top Button | Adam Bailey

CSS 库:Bootstrap V5.2

BackToTop.vue
vue
<template>
<div class=\"position-sticky bottom-0 end-0 w-100 d-flex justify-content-end b-0 pb-3 pe-5\">
<transition>
<button class=\"btn btn-secondary btn-sm\" v-show=\"isVisible\" @click=\"scrollToTop\" aria-label=\"scroll to top of the page\">
<img src=\"../assets/to-top.min.svg\" alt=\"an arrow point to top\" />
</button>
</transition>
</div>
</template>
<script lang=\"ts\">
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'
export default defineComponent({
name: 'BackToTop',
setup() {
const isVisible = ref(false)
const handleScroll = () => {
isVisible.value = window.scrollY > 0
}
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
onMounted(() => {
window.addEventListener('scroll', handleScroll)
})
onBeforeUnmount(() => {
window.removeEventListener('scroll', handleScroll)
})
return {
isVisible,
handleScroll,
scrollToTop
}
}
})
</script>
<style>
.v-enter-active,
.v-leave-active {
transition: opacity 0.2s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
to-top.min.svg
html
<svg width=\"20\" height=\"20\" viewBox=\"0 0 48 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">
<path
d=\"M24.008 14.1V42M12 26l12-12 12 12M12 6h24\"
stroke=\"#fff\"
stroke-width=\"4\"
stroke-linecap=\"round\"
stroke-linejoin=\"round\" />
</svg>

其他参考/实现方式:

题外话:BootStrap 的文档写得好烂

", "summary": "结合流畅的动画平滑滚动到页面顶部", "date_published": "2022-06-10T00:00:00.000Z", "date_modified": "2022-08-01T11:38:16.754Z", "tags": [ "Vue3", "BootStrap" ], "_indieweb": { "type": "article" } }, { "id": "2022-05-30-contabo-oss", "url": "https://seviche.cc/2022-05-30-contabo-oss", "title": "Contabo OSS + PicGo 图床配置", "content_html": "

1. 购买套餐

在官网购买 OSS 套餐,按月付费:Object Storage: S3-Compatible with Free Data Transfer

我没有修改设置,选的 250G 的容量,位于美国

2. 创建 Bucket

进入控制面板:Contabo Object Storage Panel,然后点击Create Bucket 创建存储桶

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/114bf9578ffa81fb28c0010cd1d159f1.png\"其中 Bucket Name 可以随便写,Select Region 不用选,默认是购买 OSS 时所选择的区域,如果换区域也可以另选\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/d7e982a6b2e8ff37b7354d3d9b49a415.png\"创建好后如图:(这个 Public Access 应该默认是红色的,也就是没有打开,我这里打开了)\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/dc654d35fe384d2e999f7a70d31cfd07.png\"

3. 安装 PicGo 插件

在插件里面搜s3,然后安装第一个:\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/86c9fbcaf0882da8ae5c342dcbae1cad.png\"

安装好后,在设置里选择打开,\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/e0e2ae2026b3182f65e9f6be6535050e.png\"

4. 配置插件

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/9ca1d257b19056becef07d604e388376.png\"

应用密钥 ID 和 应用密钥

打开Contabo Object Storage PanelAcount > Security & Access 面板,找到最下面的 S3 Object Storage Credentials

这里对应插件设置里的:

桶 / 自定义节点 /自定义域名

其他设置

打开这两项:\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/94d2791b84b8a9c9b801e4b39753a125.png\"

然后文件路径对应的是 Bucket 里面存储文件的路径,具体的设置可以参照:GitHub - wayjam/picgo-plugin-s3: PicGo S3 插件

5. 其他

Obsidian 中的图床设置

先安装这个Image auto upload Plugin插件:\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/567e38f40af9758fb155ff7408f9261f.png\"

然后在 PicGo 设置里面 → 设置 Server→ 打开 Server 开关\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/815df1bbba1e0183518e0d7bb04589e5.png\"

\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/bacd83f2375bdad673ce7ac98ebdfe96.png\"

然后 Obsidian 插件中这样设置:

PicGo Server :http://127.0.0.1:36677/upload

端口号不一定是 36677,只要一一对应就好。\"https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/05/eb921dee9017d358df5d3458cab43be6.png\"

设置好后,在后台保持 PicGo 开启,就可以在 Obsidian 里面粘贴一键上传图床了~

更多 PicGO 插件:

GitHub - PicGo/Awesome-PicGo: A collection of awesome projects using PicGo.

有图床备份、图片压缩、图床转移等插件,不过我都没有试过……

", "summary": "OSS自建图床笔记", "date_published": "2022-05-30T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.353Z", "tags": [ "OSS" ], "_indieweb": { "type": "article" } }, { "id": "2022-05-26-write-a-page-template", "url": "https://seviche.cc/2022-05-26-write-a-page-template", "title": "为博客写一个Project showcase 页面", "content_html": "

这两天为博客写了一个 Project 的页面用来放我的作品,这里记录一下我是怎么写(模仿)的,我对 Svelte 语法的了解不多,没有特别深入学习,只是在官方看了下文档和用了下他们的 交互式教程 ,编码的过程是一边学习一边模仿慢慢摸索的,虽然最终没有 merge 到 repo 中,但我觉得整个过程都蛮兴奋的。

既然有了博客,那我肯定是要写一下这个过程的。

1. 分析需求

我想要的是一个独立的 Page,而不是一个 Post 页面,最后把它放在导航栏里面。想要有以下这几个功能:

主要有这些信息的展示:

2. 画原型图

明确了需求后,参考了几个项目平台的布局,在 Whimsical 上画了原型图如下:\"https://s2.loli.net/2022/05/26/8kMa6LPrgUEC7Xb.png\"

目前还没有做上面 Tag 的分类功能,之后可能会做吧

2. 创建组件样式 CSS

为了统一风格,我在博客现有框架里四处搜寻可用的组件样式,想在这基础上修改,然后我找到了作者 藍 在 Tailwind Play 上的友链组件,感觉很适合,然后就直接在这个 Tailwind Play Demo 上进行了样式修改,不过此时填写的数据都是死数据,后面再进行修改。

因为我之前没有怎么用过 Tailwind,所以是一边对照 Tailwind 文档修改的,然后 Tailwind Play 上的代码提示功能真的很新手友好,hover CSS class 的时候会显示具体的 CSS 原始参数,很直观。

\"https://s2.loli.net/2022/05/27/lFwQ8T5YUcdjDfe.png\"最后我构建的 Demo 样式如下:Tailwind Play

\"https://s2.loli.net/2022/05/27/g5aYxD9mzlqpj6c.png\"

4. 编写组件代码

整个页面的构建跟 Friend 页面很像,我分析了 Friend 页面所涉及到的代码和结构,然后一点点模仿构建 Project 页面。

数据

首先根据需求确定传入的数据及其格式,以便后面使用 TypeScript 的提示

/src/lib/config/friends.ts
ts
export interface FriendOld {
// hCard+XFN
id: string // HTML id
rel?: string // XFN, contact / acquaintance / friend
link?: string // URL
html?: string // HTML
title?: string // 标题
descr?: string // 描述
avatar?: string // 头像
name?: string // backwards compatibility
}
 
export type Friend = {
id: string // HTML id
rel?: string // XHTML Friends Network
link?: string // URL
html?: string // Custom HTML
 
title?: string // 标题
name?: string // 人名
avatar?: string // 头像
descr?: string // 描述
class?: {
avatar?: string // 头像类名
img?: string // 图片类名
}
}
 
export const friends: Friend[] = [
{
id: 'id',
rel: '',
title: '',
name: '',
link: '',
descr: '',
avatar: ''
}
]
/src/lib/config/projects.ts
ts
export type Project = {
id: string
name?: string
tags?: string[]
feature?: string
description?: string
img?: string
link?: string
}
 
export const projects: Project[] = [
{
id: 'coach',
name: 'Find a Coach',
tags: ['Vue 3', 'Composition API'],
feature: 'Vue3',
description:
'既然如何, 问题的关键究竟为何? 要想清楚,科学和人文谁更有意义,到底是一种怎么样的存在。 普列姆昌德曾经提到过,希望的灯一旦熄灭,生活刹那间变成了一片黑暗。这启发了我, 那么, 我认为, 总结的来说,',
img: 'https://uneorange.oss-cn-guangzhou.aliyuncs.com/202205251801454.avif',
link: 'https://seviche.cc'
}
]

组件

将 CSS 复制进去,并注入数据

/src/lib/components/extra/friend.svelte
html
<script lang=\"ts\">
import type { Friend } from '$lib/config/friends'
import Footer from '$lib/components/footer.svelte'
export let item: unknown
let friend = item as unknown as Friend
</script>
{#if friend.id === 'footer'}
<footer rounded=\"{true}\" class=\"p-4 md:p-8\" />
{:else if friend.html}
<a id=\"{friend.id}\" rel=\"{friend.rel}\" href=\"{friend.link}\" class=\"h-card u-url\">{@html friend.html}</a>
{:else}
<a
id=\"{friend.id}\"
rel=\"{friend.rel}\"
href=\"{friend.link}\"
class=\"card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow h-card u-url\">
<div class=\"absolute text-4xl font-bold opacity-5 rotate-6 leading-tight top-4\">{friend.name ?? ''}{friend.title ?? ''}</div>
<div class=\"card-body p-4\">
<div class=\"flex items-center gap-4\">
{#if friend.avatar}
<div class=\"avatar {friend.class?.avatar} shrink-0 w-16 mb-auto\">
<img class=\"{friend.class?.img ?? 'rounded-xl'} u-photo\" src=\"{friend.avatar}\" alt=\"{friend.title}\" />
</div>
{:else}
<div class=\"avatar {friend.class?.avatar} placeholder mb-auto\">
<div class=\"{friend.class?.img ?? 'bg-neutral-focus text-neutral-content shadow-inner rounded-xl'} w-16\">
<span class=\"text-3xl\">{(friend.name ?? friend.title).charAt(0)}</span>
</div>
</div>
{/if}
<div class=\"card-title flex-col gap-0 flex-1 items-end\">
<span class=\"text-right p-name\">{friend.name ?? ''}</span>
<span class=\"opacity-50 text-right\">{friend.title}</span>
</div>
</div>
{#if friend.descr}
<div class=\"prose opacity-70 p-note\">{friend.descr}</div>
{/if}
</div>
</a>
{/if}

根据具体的页面效果修改了 Demo 中的 CSS 样式

/src/lib/components/extra/project.svelte
html
<script lang=\"ts\">
import type { Project } from '$lib/config/projects'
import Footer from '$lib/components/footer.svelte'
export let item: unknown
let project = item as unknown as Project
let tags = project.tags
</script>
{#if project.id === 'footer'}
<footer rounded=\"{true}\" class=\"max-w-4xl mx-auto p-4 md:p-8\" />
{:else}
<a
id=\"{project.id}\"
href=\"{project.link}\"
class=\"card mx-auto max-w-4xl bg-base-100 shadow-xl transition-shadow mb-7 h-card u-url hover:shadow-2xl\">
<div class=\"absolute text-5xl font-bold opacity-5 rotate-6 leading-tight top-2 right-0\">{project.feature}</div>
<div class=\"card-body p-4\">
<div class=\"flex flex-col md:flex-row items-start gap-4\">
<div class=\"mb-auto max-w-full shrink-0 md:max-w-xs\">
<img class=\"rounded-md\" src=\"{project.img}\" alt=\"{project.description}\" />
</div>
<div class=\"card-title flex-1 flex-col items-start gap-4\">
<div>
<h2 class=\"p-name text-left text-2xl mb-2\">{project.name}</h2>
<div class=\"mb-3 text-base font-normal\">
{#each tags as tag}
<span class=\"btn btn-sm btn-ghost normal-case border-dotted border-base-content/20 border-2 my-1 mr-1\">{tag}</span>
{/each}
</div>
</div>
<p class=\"text-left text-base font-normal opacity-70\">{@html project.description}</p>
</div>
</div>
</div>
</a>
{/if}

页面

/urara/friends/index.svelte
html
<script lang=\"ts\">
// @ts-nocheck
import Masonry from 'svelte-bricks'
import { Friend, friends as allFriends } from '$lib/config/friends'
import Head from '$lib/components/head.svelte'
import FriendComponent from '$lib/components/extra/friend.svelte'
const rnd = Math.random()
const fy = (a: Friend[], r = 0, c = a.length) => {
while (c) (r = (rnd * c--) | 0), ([a[c], a[r]] = [a[r], a[c]])
return a
}
let items: { id: string }[] = [...fy(allFriends as { id: string }[]), { id: 'footer' }]
let width: number, height: number
</script>
<head />
<Masonry
{items}
minColWidth=\"{384}\"
maxColWidth=\"{384}\"
gap=\"{32}\"
let:item
class=\"mx-4 sm:mx-8 md:my-4 lg:mx-16 lg:my-8 xl:mx-32 xl:my-16\"
bind:width
bind:height>
<FriendComponent {item} />
</Masonry>

Projects 页面

因为我没有用到瀑布流布局,所以删掉了一些组件和 function

/urara/projects/index.svelte
html
<script lang=\"ts\">
import { projects as allProjects } from '$lib/config/projects'
import Head from '$lib/components/head.svelte'
import ProjectComponent from '$lib/components/extra/projects.svelte'
let items: { id: string }[] = [...(allProjects as { id: string }[]), { id: 'footer' }]
</script>
<head />
{#each items as item}
<ProjectComponent {item} />
{/each}

响应式布局

参考 Tailwind 的响应式设计指南 ,修改了卡片flex 的方向,以及图片的宽度,以适应小尺寸屏幕。

5. 测试

其实有错误的话 pnpm dev 以及 pnpm build 的时候都会提醒,但我后面发现也可以用 pnpm check 来检查。过程中我好像没有遇到什么 Bug。

6. Pull request 到 Github

先看了一下 Repo 作者写的 contributing docs,了解 Contribute 的规范。

按照相应步骤做了之后,Google 了一下如何 pull request,照着 FreeCodeCamp 的这篇进行了操作:如何在 GitHub 提交第一个 pull request ,然后成功 Pull 了一个 Request,但后面发现有的文件没有改,造成了 bug,就删除了原 Request 重新 Pull。

最后终于创建成功了我的第一个 Pull request!链接:feat: ✨ add project page by Sevichecc · Pull Request #19 · importantimport/urara · GitHub

7. Last but not least

写一篇这样的博文,并发表到互联网。

好啦我知道这篇文章有点臭屁,但下次还敢……

", "summary": "第一次Pull Request的经历", "date_published": "2022-05-26T00:00:00.000Z", "date_modified": "2022-08-01T11:36:42.589Z", "tags": [ "Svelte", "Open Source" ], "_indieweb": { "type": "article" } }, { "id": "2022-05-25-git", "url": "https://seviche.cc/2022-05-25-git", "title": "Git · 常用操作笔记", "content_html": "

资料:

每次更新博客进行的操作

1. 追踪所有文件

(除了 gitignore 里面的),也可以单独加

git add -A

2. 提交上传信息

git commit -m '一些信息,如fixed something etc'

3. push 到 Github

git push origin main

等待一会儿就好了,如果不行,换个网或者关掉 VPN 看看

常用 Git 操作

命令作用
git config --global user.name 名字设置名字
git config --global user.email 邮箱设置邮件
git init初始化
git add -A追踪所有文件(除了 gitignore 里面的),也可以单独加
git commit -mm 代表信息,后面要写 commit 相关信息
git status查看 git 状态/信息
git log查看 commit 日志,按 Q 才可以退出
git reset hard (commit的id)回到特定版本
git reset hard --HEAD返回上一次改动 (还没有 commit)
git branch列出现在有的 branch,按 Q 退出
git branch (branch'name)创建新 branch
git merge (branch'name)合并 branch 到 main
git checkout (branch's name)切换 branch
git remote add origin https://github.com/用户名/仓库名.git链接到 remote repo
git pull拉更新
git push origin (branch'name)push 到 remote repo

常见问题

下面是一些我看过的文章

版本回滚

报错处理

博客相关

其他

", "summary": "每次更新博客进行的操作以及常见错误处理", "date_published": "2022-05-25T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.322Z", "tags": [ "Git" ], "_indieweb": { "type": "article" } }, { "id": "2022-05-25-hypothesis", "url": "https://seviche.cc/2022-05-25-hypothesis", "title": "Hypothesis 使用小记", "content_html": "

Hypothesis 太好用了,方便我满世界乱画写屁话(?),用它在网页上高亮,就像用荧光笔在纸上标注一样轻松 ,写标注就写在 3M 便利贴上,哪里都好贴,而且还支持用 Markdown 写,真的越用越顺手

它导出的笔记提供了导出的 API,可以轻松同步到 Obsidian / Logseq,真是平易近人呢!

我的使用例子:\"/2022-05-25-hypothesis/657e30351066c4a3.png\"

我在 Obsidian 中导出的笔记:\"/2022-05-25-hypothesis/0bf1f9770192c362.png\"

一些基础的部分我会省略掉,详情可以看这篇文章:开源、可定制的网页批注工具——Hypothesis

在下面这些操作之前,需要先注册一个 Hypotheis 账号,并安装浏览器扩展:

同步到 Logseq

Logseq 我用得不多,如果有写错的,欢迎给我提建议~

1. 打开插件系统开关

(如果打开了可以省略这个)

\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-14-43.png\"

如果在国内连接插件市场,最好设置一下代理,不然可能装不上,具体的设置需要看一下 VPN 端口号之类的。

2. 安装 Hypothesis 插件

打开 Logseq 的插件市场,找到如下名为Hypothesis的插件:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-20-20.png\"如果一直下载不了的话,可以直接在 Github 下载:logseq-hypothesis

然后在这里导入:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-22-41.png\"

3. 获取 API Token

在这里生成一个 API Token:Regenerate your API token

复制后点击 Logseq 右上角的这个 H 的标志:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-26-37.png\"

然后填入刚刚复制的 API Token 和用户名

\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-45-33.png\"

用户名跟 Hypothesis 这里显示的一样,比如我的就是 Sevicheeee\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-32-17.png\"

4. 同步笔记

点击 Fetch Latest Notes 会拉取最新的笔记

如果选择了指定页面,然后点Add page notes to graph,会自动生成一篇相应的笔记,比如:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-47-28.png\"

5. 修改笔记模板

如果想修改笔记模板的话,可以在setting中修改:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-48-58.png\"

同步到 Obsidian

1. 安装 Hypothesis 插件

如图所示:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-51-44.png\"

Github: obsidian-hypothesis-plugin

2. API 配置

打开插件设置,点击右上角的Connect, 输入你的 API Token 并保存,如果没有获取的话,请在这里获取:Regenerate your API token

\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-53-41.png\"

可以在这里选择笔记保存的位置:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-56-38.png\"

其中Use Domain Folders 是按域名来划分文件结构,如果没有打开的话就是默认一篇笔记一个 md 文档,如下图红框所示就是打开了这个,下面蓝色的就是没打开时的输出结构\"/2022-05-25-hypothesis/Snipaste_2022-05-25_21-58-17.png\"

3. 笔记模板配置

可以在右边的文本框内设置笔记输出格式\"/2022-05-25-hypothesis/Snipaste_2022-05-25_22-00-11.png\"说实话,我没有怎么看懂 Orz, 然后这是我的模板:

js
{% if is_new_article %}# {{title}}
## Metadata
{% if author %}- Author: [{{author}}]({{authorUrl}}){% endif %}
- Title: {{title}}
{% if url %}- Reference: {{url}}{% endif %}
- Category: #source/article🗞{% endif %}
- Tags:
{% if is_new_article -%}## Highlights{%- endif %}
{% for highlight in highlights -%}- {{highlight.text}}
{% if highlight.tags | length %} - Tags: {% for tag in highlight.tags -%}#{{tag| replace(\" \", \"-\")}} {%- endfor %}{%- endif %}
{% if highlight.annotation %} - Annotation: {{highlight.annotation}}{%- endif -%}{%- endfor -%}

效果:\"/2022-05-25-hypothesis/Snipaste_2022-05-25_22-03-03.png\"

4. 更新笔记

点击右边这个标志就可以更新笔记了~也可以在设置里打开启动时自动抓取的设置\"/2022-05-25-hypothesis/Snipaste_2022-05-25_22-04-44.png\"

订阅 Hypothesis 的 RSS

其他参考

", "summary": "Hypothesis同步到Obsidian / Logseq的方法", "date_published": "2022-05-25T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.353Z", "tags": [ "Logseq", "Obsidian" ], "_indieweb": { "type": "article" } }, { "id": "2022-05-23-win-sql", "url": "https://seviche.cc/2022-05-23-win-sql", "title": "SQL · 在 Windows 10 上安装 sqlite", "content_html": "

参考: Site Unreachable

1. 下载二进制文件

\"/2022-05-23-win-sql/2.png\"

2. 创建安装文件夹

3. 添加环境变量

参考:sqlite 在 windows 下载安装,配置环境变量

首先,打开控制面板,如果找不到的话,直接搜索,例如:\"/2022-05-23-win-sql/Bl1JjI2A6HnfQVZ.png\"

然后点击系统与安全,再点击 系统\"/2022-05-23-win-sql/q7RaUdAYHz2lpC3.png\"\"/2022-05-23-win-sql/V87O1wdLscPbvTC.png\"

在新窗口中点击高级系统设置 -> 环境变量\"/2022-05-23-win-sql/HSD6iy9nUxCEkcQ.png\"

\"/2022-05-23-win-sql/SnJePah46I7CyGF.png\"

如上图所示,在蓝色区域新建一个环境变量,此处填写一开始创建 sqlite 文件的路径,比如这里就是C:\\sqlite

4. 命令提示符中查看 sqlite3 版本

什么是命令提示符?

命令提示符是大多数 Windows 操作系统中可用的命令行解释器应用程序。

命令提示符用于执行输入的命令 。 大多数这些命令用于通过脚本和批处理文件自动执行任务,执行高级管理功能以及排除和解决某些类型的 Windows 问题。

命令提示符被正式称为Windows 命令处理器,但有时也被称为命令外壳程序cmd 提示符 ,甚至称其为文件名cmd.exe 。——命令提示符(它是什么以及如何使用它)

我的理解就是用代码的方式操作电脑系统

如何打开命令提示符:\"/2022-05-23-win-sql/oL7n6rta35UAODl.png\"

打开后输入sqlite3 ,出现下面的文字就是安装成功了\"/2022-05-23-win-sql/qNgBIvzLQJX126b.png\"

常用命令行

命令功能
ls(win 用 dir)展示当前文件夹文件
cd改变当前文件夹(change directory) 按 tab 可以自动填充
cd..去到上个层级的文件夹
clear清除命令行记录
mkdir创建新文件夹(make directory)
touch(win 用 edit)创建新文件(可以一次性创建多个)
mv移动文件,第一个参数是要移动的文件,第二个是目的地,如 mv index.html ../ 为移动到上一层级
rmdir删除空文件夹(remove directory)
rm -R删除文件夹(R-recursively)
pwd当前所在位置
rm(win 用 del)删除文件
", "summary": "写给计算机小白的 sqlite 安装笔记", "date_published": "2022-05-23T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.303Z", "tags": [ "SQL" ], "_indieweb": { "type": "article" } }, { "id": "2022-05-09-copywithin", "url": "https://seviche.cc/2022-05-09-copywithin", "title": "JavaScript · 数组中的copyWithin方法", "content_html": "

copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度

—— MDN

简介

以下部分内容来自 MDN

语法:

js
arr.copyWithin(target[, start[, end]])

参数

target

0 为基底的索引,复制序列到该位置。

如果是负数,target  将从末尾开始计算。如果 target 大于等于 arr.length,将会不发生拷贝。如果 targetstart 之后,复制的序列将被修改以符合 arr.length

start

0 为基底的索引,开始复制元素的起始位置。

如果是负数,start  将从末尾开始计算。如果  start  被忽略,copyWithin  将会从 0 开始复制。

end

0 为基底的索引,开始复制元素的结束位置。

copyWithin  将会拷贝到该位置,但不包括 end 这个位置的元素

如果是负数, end  将从末尾开始计算。如果 end 被忽略,copyWithin 方法将会一直复制至数组结尾(默认为 arr.length)。

例子

源码

js
const array1 = ['a', 'b', 'c', 'd', 'e']
// copy to index 0 the element at index 3
console.log(array1.copyWithin(0, 3, 4))
// expected output: Array [\"d\", \"b\", \"c\", \"d\", \"e\"]
// copy to index 1 all elements from index 3 to the end
console.log(array1.copyWithin(1, 3))
// expected output: Array [\"d\", \"d\", \"e\", \"d\", \"e\"]

可视化

上图将 target 位置用红色的部分表示,被复制的元素为蓝色。

  1. a = array1[0] ,所在位置是将要被替换的 target 位置。d = array1[3]是复制的起始元素,复制结束在array1[4]之前。复制后a的位置被 d所取代。
  2. b = array1[1] ,所在位置是将要被替换的 target 位置,d = array1[3]是复制的起始元素,复制没有指定结束位置,所以一直复制到数组末尾。复制后a的位置被 'd','e'所取代。
", "summary": "一种数组内元素复制的方法", "date_published": "2022-05-08T17:13:47.671Z", "date_modified": "2022-08-01T03:03:33.294Z", "tags": [ "JavaScript" ], "_indieweb": { "type": "article" } }, { "id": "2022-05-07-vps-init", "url": "https://seviche.cc/2022-05-07-vps-init", "title": "VPS 安全初始化", "content_html": "

前情提要:

前段时间我所购买的 VPS 服务商 Contabo 发邮件来说,我用 VPS 攻击了其他的服务器,让我快点停止这种行为,要是不改就罚我的钱,但是我并没有在上面装什么奇怪的东西,就只装了一个聊胜于无的 WordPress,手足无措之余在 Mastodon 哀嚎了一下,得到了很多热心网友的帮助,才发现原来我一直在裸奔使用 VPS,什么安全措施都没采取:(

鉴于 VPS 上本来就没有什么东西,我决定重新初始化机子,本文是初始化的笔记,我的系统是 Ubuntu 20.04,文中提到的 ufw 是内置的,没有额外安装, 有些步骤上有所省略,最好对照着提到的参考文章看。

(再次感谢 Allen Zhong、糖喵、南狐、shrik3 等朋友的热心指导 o(≧v≦)o!)

思路

下面这两点都是 Contabo 客服发给我的防护建议,用 Deepl 翻译了一下

日常防护

安全检查

  1. 保持定期备份。
  2. 保持你的整个系统一直是最新的,这意味着你必须定期安装使用的软件包和网络应用程序的更新和补丁。
  3. 安装并运行一个防病毒软件,如 ClamAV 或 ClamWin,以保持你的服务器不受恶意软件侵害。
  4. 设置一个防火墙,关闭所有你不需要的端口,并将 SSH 的 22 或 RDP 的 3389 等默认端口改为其他。
  5. 通过安装一个合适的软件,如 cPHulk 或 Fail2ban,阻止暴力攻击。
  6. 避免使用只在不安全的设置下工作的脚本。
  7. 不要点击电子邮件中的任何可疑附件或链接,或访问不安全的网站。
  8. 使用 SSH-Keys 而不是密码。

最后我将 VPS 里的内容全删了,从 0 出发,下面是具体的操作步骤:

1. 创建新用户

参考: VPS 建站新手上路 - YOLO

首先用 root 登陆,然后输入 adduser + 用户名 创建新用户,如添加用户jack

shell
adduser jack

接着输入两遍密码,其他信息可以按 Enter 留空

给这个用户 root 权限:

shell
sudo usermod -aG sudo jack

其他参考: 如何在 Ubuntu 上添加和删除用户 | myfreax

2. 配置 SSH-keys

参考: 给 VPS 配置 SSH 密钥免密登录 - P3TERX ZONE

本地生成 SSH 密钥对

文中提到可以在远端 VPS 上,也可以在本地,这里我选择在本地生成。

打开终端,输入 ssh-keygen ,连续按四次 Enter (密码设置为空),如果出现了 overwrite(y/n)? 就说明之前就有生成了,你可以选择 y 重新生成一个,或者就用已有的这个

比如:

bash
root@p3ter:~# ssh-keygen # 输入命令,按 Enter 键
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): # 保存位置,默认就行,按 Enter 键
Enter passphrase (empty for no passphrase): # 输入密钥密码,按 Enter 键。填写后每次都会要求输入密码,留空则实现无密码登录。
Enter same passphrase again: # 再次输入密钥密码,按 Enter 键
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:GYT9YqBV4gDIgzTYEWFs3oGZjp8FWXArBObfhPlPzIk root@p3ter
The key's randomart image is:
+---[RSA 2048]----+
|*OO%+ .+o |
|*=@.+++o. |
| *o=.=.... |
|. +.B + +o. |
| . + E *S. |
| o o |
| . |
| |
| |
+----[SHA256]-----+

出现那个神秘的矩形就是生成好了

安装公钥

在本地终端:

bash
ssh-copy-id -pport user@remote

user 为用户名,remote 为 IP 地址,port 为端口号。

也可以不加端口号:

bash
ssh-copy-id user@remote

然后按要求输入密码,如果是用 root 登陆的,就是用的初始密码,如果是用上面设置的新用户,那就跟之前设置的用户密码一样

修改权限

登入 VPS 后,在远程终端输入:

bash
chmod 600 .ssh/authorized_keys

修改 sshd 配置文件

打开配置文件:

bash
nano /etc/ssh/sshd_config

找到下面这两行,并改成这样:

bash
PermitRootLogin no
AllowUsers username #如果没有这一行就手动添加
RSAAuthentication yes #这一行我找不到就没有配置
PubkeyAuthentication yes

记得 username 要换成自己设置的名字,也就是上面配置的 jack

修改完按 Ctrl+o 保存,Enter 确认,Ctrl+X 退出编辑

重启 ssh 服务

bash
systemctl reload sshd

或者

bash
service sshd restart

禁用密码登陆和改端口

设置好后,试试看能不能用 ssh 登陆,如果可以,再 sudo nano /etc/ssh/sshd_config 修改配置,禁用密码登陆:

bash
PasswordAuthentication no

修改默认登陆端口

然后改默认登陆端口1,应该什么数都可以吧,什么 8080,9080,8888,3141……

找到 Port 22 这行,在下面加你要开的口

bash
Port 22
Port 8888

加完了之后重启

bash
sudo service sshd restart

打开防火墙并给你设置的端口放行

bash
sudo ufw allow 8888
sudo ufw enable

sudo ufw status 查看防火墙状态,比如:

bash
Status: active
To Action From
-- ------ ----
8888 ALLOW Anywhere
8888 (v6) ALLOW Anywhere (v6)

然后重新连接一下 VPS,用设置好的端口登陆看看,如果没问题的话重新 sudo nano /etc/ssh/sshd_config ,注释掉 Port 22 那一行

3. 安装 ClamAV

参考:

安装

bash
sudo apt update
sudo apt install clamav clamav-daemon -y

更新病毒数据库

先停止 clamav-freshclam 服务

bash
sudo systemctl stop clamav-freshclam

执行更新:

bash
sudo freshclam

启动clamav-freshclam 服务

bash
sudo systemctl start clamav-freshclam

开机启动

bash
sudo systemctl is-enabled clamav-freshclam

下载 ClamAV 数据库

先关掉 clamav-freshclam 再下载

bash
sudo systemctl stop clamav-freshclam
sudo freshclam

查看 clamav 的目录和文件的日期

bash
ls /var/lib/clamav/

限制 Clamscan CPU 使用率

nice:降低 clamscan 的优先级(限制相对 cpu 时间)。

bash
sudo nice -n 15 clamscan

cpulimit:限制绝对的 CPU 时间。安装 cpulimit

bash
sudo apt-get install cpulimit

使用 cpulimit 来限制 clamscan:

bash
cpulimit -z -e clamscan -l 20 & clamscan -ir /

常见 CLI

bash
clamscan /home/filename.docx #扫描特定目录或文件
clamscan --no-summary /home/ #扫描结束时不显示摘要
clamscan -i / #打印受感染的文件
clamscan --bell -i /home #警惕病毒检测
clamscan -r --remove /home/USER #删除受感染的文件

ClamAV 返回码

4. 安装 Fail2ban

安装 fail2ban 以阻止重复登录尝试

参考:准备你的机器 - Mastodon documentation

准备

更新软件包:

bash
sudo apt update
sudo apt upgrade -y

安装

参考:如何在 Ubuntu 20.04 上安装和配置 Fail2ban

bash
sudo apt install fail2ban

安装完后将自动启动,可以用sudo systemctl status fail2ban 查看运行状态

修改配置:

打开/etc/fail2ban/jail.local:

bash
sudo nano /etc/fail2ban/jail.local

写入下面的内容,修改邮箱,如果端口改了,也要记得相应修改

text
[DEFAULT]
destemail = your@email.here
sendername = Fail2Ban
[sshd]
enabled = true
port = 22
[sshd-ddos]
enabled = true
port = 22

重启 fail2ban:

bash
sudo systemctl restart fail2ban

5. SSL 证书相关

还没弄明白怎么回事,待更

参考:


  1. 更改 VPS 的默认 SSH 端口 22 – 托尼的博客
", "summary": "上次 VPS 被别人暴力破解了,一哭二闹三重装之后,有了本文", "date_published": "2022-05-06T00:00:00.000Z", "date_modified": "2022-08-01T03:03:33.291Z", "tags": [ "VPS", "Self hosted" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-29-12px", "url": "https://seviche.cc/2022-03-29-12px", "title": "CSS · 解决 Chrome 中小于12px的字体不显示的问题", "content_html": "

如设置字体大小为 10.2px

HTML

html
<p>
I am a frontend developer with a particular interest in making things simple and automating daily tasks. I try to keep up with
security and best practices, and am always looking for new things to learn.
</p>

CSS

css
p {
color: #dcdcdc;
/*缩小基准大小,也就是缩小后的字体应该是 10.2px=12px*0.85*/
font-size: 12px;
/* 缩小比例 10.2px/12px=0.85 */
transform: scale(0.85);
/*设置缩放中心*/
transform-origin: 0 0;
/*(1-0.85)+1,补上缩小的宽度,这里可以按视觉效果调整一点*/
width: 118%;
/*兼容IE*/
*font-size: 10.2px;
}

参考:

", "summary": "先用scale总体缩小再补上减少的宽度", "date_published": "2022-03-29T13:46:29.228Z", "date_modified": "2022-08-06T10:50:25.374Z", "tags": [ "CSS", "CSS Trick" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-11-miniflux-to-pocket", "url": "https://seviche.cc/2022-03-11-miniflux-to-pocket", "title": "Miniflux · 保存文章到 Pocket 以及 RSS", "content_html": "

将 Miniflux 上的文章到保存到 Pocket/Instapaper,以及 RSS 相关文章和资源

Miniflux 文档: Integration with External Services

1. 创建 Pocket Application

这里 创建一个 Pocket 应用,以获取 Consumer Key

我的设置如下:

\"https://s2.loli.net/2022/02/24/yfXLehkWrisS9Hb.png\"

2. 获取 Consumer Key 用户密钥

My Apps下面找到刚刚创建的应用,复制 Consumer Key:

\"https://s2.loli.net/2022/02/24/vO4jyhTfBaHRZ6n.png\"

在 Miniflux 后台,设置 → 集成 → Pocket → Pocket 用户密钥(第一栏)中 填入刚刚复制的 Consumer key

3. 获取 Access Token 访问密钥

填好后,通过通过点击下面的 连接您的 Pocket 账户 自动获取 Access Token(访问密钥):

\"https://s2.loli.net/2022/03/10/tzYeCNksmRaBIFj.png\"

点击链接后按 授权\"https://s2.loli.net/2022/03/11/sZOU8tBpjAJW3ol.png\"

这里可能会跳到 http://localhost/integration/pocket/callback 然后就无法访问页面了,解决办法很简单,把 localhost 改为你的服务器 ip 端口或者 miniflux 所在域名即可,如 http://miniflux.com/integration/pocket/callback,按回车会跳回到 miniflux 设置页面。

出现这个提醒就连接成功了:\"https://s2.loli.net/2022/03/11/ktoi3lOGjpQHP9B.png\"

然后就可以点击文章页面的保存测试看看。\"https://s2.loli.net/2022/03/11/uCBj6IAWxN149Xo.png\"

其他

1.为博客添加 Pocket 收藏按钮

此处复制需要的 Pocket 收藏按钮样式,添加到主题的 layout 里面(具体要看不同主题的设置,wordpress 似乎有内置这功能,我不确定,有三种效果。

2.用 Fever 同步到 Reeder

  1. 在 Miniflux 中创建 Fever 账户和密码
  2. 在 Reeder 中添加 Fever 账号,其中:

3.连接到 Instapaper

官网:Instapaper

用户名为 Instapaper 的登录邮箱,设置好更新下就可以了~

RSS 相关内容

来都来了,整理一下最近看过的相关内容,因为隐私问题,长毛象上的嘟文暂时不贴(除了我自己的

1. Miniflux 搭建

2. 其他选择

3. RSS 生成

4. 看什么

5. 关于 RSS

", "summary": "将 Miniflux 上的文章到保存到 Pocket/Instapaper,以及 RSS 相关文章和资源", "date_published": "2022-03-10T16:24:38.663Z", "date_modified": "2022-08-06T10:51:23.859Z", "tags": [ "RSS", "Miniflux" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-10-forty", "url": "https://seviche.cc/2022-03-10-forty", "title": "Forty页面仿写", "content_html": "

题目

来源:百度前端学院|浮动实战任务

通过 HTML 及 CSS 参考示例图实现页面开发,要求实现效果与示例图基本一致

示例图

\"https://s2.loli.net/2022/03/10/V9ZpjQYFvxEDGXJ.jpg\"

Demo

https://forty-seviche.netlify.app/

耗时:4 小时(还没有做自适应等很多东西……╮( ̄ ▽  ̄"")╭

", "summary": "完成 HTML、CSS 代码编写,暂无 JavaScript", "date_published": "2022-03-10T08:38:17.227Z", "date_modified": "2022-08-06T10:51:23.374Z", "tags": [ "CSS", "HTML" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-09-css-tab", "url": "https://seviche.cc/2022-03-09-css-tab", "title": "CSS · Tab选项卡", "content_html": "

一个纯 CSS 实现的 Tab 选项卡

原理

通过隐藏的 input 和与之关联的 label 点击 label 触发 inputchecked 状态触发的,再配合使用元素状态的伪类 :checked 样式就可以实现不同状态的切换,中间的过度效果还可以配合 CSS3 的 transition过度效果实现 1

代码

HTML

html
<div class=\"tab-frame\">
<!--标签页标题栏-->
<!-- 设置一个为check -->
<input type=\"radio\" name=\"tab\" id=\"tab1\" check />
<label for=\"tab1\">TAB1</label>
<input type=\"radio\" name=\"tab\" id=\"tab2\" />
<label for=\"tab2\">TAB2</label>
<input type=\"radio\" name=\"tab\" id=\"tab3\" />
<label for=\"tab3\">TAB2</label>
<!--Tab内容-->
<div class=\"tab-content\">
<p>THIS IS TAB1 CONTENT</p>
<p>Notice the gap between the content and tab after applying background cololr</p>
</div>
<div class=\"tab-content\">
<p>THIS IS TAB2 CONTENT</p>
<p>Notice the gap between the content and tab after applying background cololr</p>
</div>
<div class=\"tab-content\">
<p>THIS IS TAB3 CONTENT</p>
<p>Notice the gap between the content and tab after applying background cololr</p>
</div>
</div>

CSS

css
/* 隐藏input和tab内容 */
.tab-frame input,
.tab-content {
display: none;
}
/* 导航栏样式:未选中时 */
.tab-frame label {
color: #555;
padding: 10px 20px;
border-bottom: 1px solid #555;
cursor: pointer;
float: left;
}
/* 导航栏样式:选中时 */
.tab-frame input:checked + label {
color: #0f71aa;
border: 1px solid #555;
border-bottom: none;
border-radius: 4px 4px 0px 0px;
cursor: default;
}
/* Tab内容样式 */
.tab-frame .tab-content {
color: #0f71aa;
font-size: 1.5rem;
font-weight: bold;
padding-top: 40px;
clear: left;
}
/* 点击的时候显示tab内容,即input checked的时候显示label*/
.tab-frame input:nth-of-type(1):checked ~ .tab-content:nth-of-type(1),
.tab-frame input:nth-of-type(2):checked ~ .tab-content:nth-of-type(2),
.tab-frame input:nth-of-type(3):checked ~ .tab-content:nth-of-type(3) {
display: block;
}

参考

Demo:Tabs CSS & HTML, no JS & Jquery


  1. CSS tab 选项卡 (标签页) 切换
", "summary": "一个纯 CSS 实现的 Tab 选项卡", "date_published": "2022-03-09T07:42:25.299Z", "date_modified": "2022-08-06T10:50:54.161Z", "tags": [ "CSS" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-09-typewriter", "url": "https://seviche.cc/2022-03-09-typewriter", "title": "JavaScript · 打字机效果生成器", "content_html": "

题目

来源:百度前端学院

参照 打字机效果 DEMO (opens new window),实现一个打字机效果生成器

需求说明

html
<label>请输入文本:</label>
<input type=\"text\" />
<button onclick=\"generateTypeEffect()\">生成打字效果</button>
<h2 id=\"showText\"></h2>
<script>
function generateTypeEffect() {
//这里实现打字机效果
//将内容显示在h2中
}
</script>

解法

html
<label>请输入文本:</label>
<input type=\"text\" />
<button onclick=\"generateTypeEffect()\">生成打字效果</button>
<h2 id=\"showText\"></h2>
<script>
let i = 0
function generateTypeEffect() {
const output = document.getElementById('showText')
const input = document.querySelector('input').value
if (i < input.length) {
output.textContent += input[i]
setTimeout(generateTypeEffect, 200, ++i)
}
}
</script>

参考

How TO - Typing Effect

", "summary": "用 JavaScript 实现网页打字机效果", "date_published": "2022-03-08T16:19:05.137Z", "date_modified": "2022-08-06T10:50:29.470Z", "tags": [ "JavaScript" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-09-caesar-cipher", "url": "https://seviche.cc/2022-03-09-caesar-cipher", "title": "JavaScript · Caesar Cipher 凯撒加密", "content_html": "

题目

来源:操作字符串对象 | 百度前端技术学园

编码实现凯撒加密算法,根据输入的偏移量,实现对字符串的加密和解密.

恺撒加密(Caesar cipher),是一种最简单且最广为人知的替换加密技术。明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。

例如,当偏移量是 3 的时候,所有的字母 A 将被替换成 D,B 变成 E,以此类推。

需求说明

解法

HTML

html
<label>偏移:</label>
<input type=\"text\" name=\"offset\" size=\"5\" value=\"3\" />
<br />
<label>
明文:
<label></label>
<input type=\"text\" name=\"plain\" size=\"50\" value=\"This is a test.\" />
<br />
<label>密文:</label>
<input type=\"text\" name=\"enc\" size=\"50\" />
<br />
<input type=\"button\" value=\"加密\" onClick=\"encrypt()\" />
 
<input type=\"button\" value=\"解密\" onClick=\"decrypt()\" />
</label>

JS

js
let offsetInput = document.querySelector('input[name=offset]')
let plain = document.querySelector('input[name=plain')
let enc = document.querySelector('input[name=enc]')
// 加密
function encrypt() {
let offset = Number(offsetInput.value)
function conver(s) {
let charCode = s.charCodeAt(0)
// 替换大写字母 A-Z:65-90
if (charCode <= 90 && charCode >= 65) {
return String.fromCharCode(charCode + offset < 90 ? charCode + offset : charCode - offset)
} else {
//替换小写字母 a-z:97-122
return String.fromCharCode(charCode + offset < 122 ? charCode + offset : charCode - offset)
}
}
enc.value = plain.value.replace(/[A-Za-z]/g, conver)
// 替换大写字母 A-Z:65-90
// function transUpper(s) {
// let charCode = s.charCodeAt();
// return String.fromCharCode(
// charCode + offset <= 90 ? charCode + offset : charCode - offset
// );
// }
// //替换小写字母 a-z:97-122
// function transLower(s) {
// let charCode = s.charCodeAt();
// return String.fromCharCode(
// charCode + offset <= 122 ? charCode + offset : charCode - offset
// );
// }
// let encUpper = plain.replace(/[A-Z]/g, transUpper);
// enc.value = encUpper.replace(/[a-z]/g, transLower);
}
// 解密
function decrypt() {
let offset = Number(offsetInput.value)
function conver(s) {
let charCode = s.charCodeAt(0)
// 替换大写字母 A-Z:65-90
if (charCode <= 90 && charCode >= 65) {
return String.fromCharCode(charCode - offset < 65 ? charCode + offset : charCode - offset)
} else {
//替换小写字母 a-z:97-122
return String.fromCharCode(charCode - offset < 97 ? charCode + offset : charCode - offset)
}
}
plain.value = enc.value.replace(/[A-Za-z]/g, conver)
}

参考

", "summary": "用JavaScrpit编码实现凯撒加密算法", "date_published": "2022-03-08T16:01:08.850Z", "date_modified": "2022-08-06T10:50:48.659Z", "tags": [ "JavaScript" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-07-filter", "url": "https://seviche.cc/2022-03-07-filter", "title": "JavaScript · 字符串去重", "content_html": "

题目

来源:百度前端学院

js
/*
去掉字符串 str 中,连续重复的地方
*/
function removeRepetition(str) {
// do something
}
// 测试用例
console.log(removeRepetition('aaa')) // ->a
console.log(removeRepetition('abbba')) // ->aba
console.log(removeRepetition('aabbaabb')) // ->abab
console.log(removeRepetition('')) // ->
console.log(removeRepetition('abc')) // ->abc

解法

js
function removeRepetition(str) {
let strArr = [...str]
const result = strArr.filter((s, i, arr) => s !== arr[i + 1]).join('')
return result
}
console.log(removeRepetition('aaa')) // ->a
console.log(removeRepetition('abbba')) // ->aba
console.log(removeRepetition('aabbaabb')) // ->abab
console.log(removeRepetition('')) // ->
console.log(removeRepetition('abc')) // ->abc

如果没有限定条件说是“连续重复”,就可以用 Set

js
function removeRepetition(str) {
let strArr = [...new Set(str)]
return strArr.join('')
}
console.log(removeRepetition('aaa')) // ->a
console.log(removeRepetition('abbba')) // ->ab
console.log(removeRepetition('aabbaabb')) // ->ab
console.log(removeRepetition('')) // ->
console.log(removeRepetition('abc')) // ->abc

其他解法

", "summary": "编码实现字符串去重", "date_published": "2022-03-07T13:55:21.090Z", "date_modified": "2022-08-06T10:50:50.151Z", "tags": [ "JavaScript" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-07-obsidian-notes1", "url": "https://seviche.cc/2022-03-07-obsidian-notes1", "title": "Obsidian · 网课学习笔记整理", "content_html": "

最近在用 obsidian 做网课学习笔记,感觉还挺好用的。简单记一下我记笔记的一些方法(其实也不算什么方法)需要用到的插件:Image Auto Upload,用来传图片。

其实我的记笔记方法很简单,就是不断拆碎重组,方便后面查找。

上课时,先按时间顺序书写笔记,就像传统的笔记本一样,上完课后再将那一页笔记拆碎重组到知识结构中。方法论大概是 MOC?就是用索引去整理笔记结构,而不是所处文件夹的层次,这里我们先不做深入探讨。

下面以学习 JavaScript 为例子。

我近期的笔记目录页面(用 Logseq 发布):JavaScript

具体的方法

我把几乎所有的笔记都放在一个叫 Zone 的文件夹内,常用的会打上星标,或者移到最外层文件夹,新笔记默认放在 Zone 文件夹下。

Step1-构建地图

MOC 是 Map of Contents,也就是内容地图,所以我们会从构建一张地图出发。刚开始地图不需要太完美,很精细,因为一个不识路的人是没办法认路的,何况是指路、画地图,反正后面也要调整,可以随意一点。

我刚开始创建了一个叫 JavaScript 的索引页,里面用标题列了几项比较重要的内容,比如 OOP / DOM 之类的,然后在页面最上面列了几项常用的内容:\"https://s2.loli.net/2022/03/06/ybuxoSJmQGKcAV3.png\"

之后会以这一页内容为目录索引,不断补充和修改,构建自己的知识结构

Step2-写课堂笔记

首先需要创建一个空白页面。我用 Obsidian 里自带的插件 ZK 卡片 创建,可以自动生成时间戳标题,这个功能可以在设置里打开:

\"https://s2.loli.net/2022/03/06/ZXVqaIcS4xy6Evs.png\"

然后点击左边功能栏就可以创建并打开了

\"https://s2.loli.net/2022/03/06/VQZpj96GbzxhO7i.png\"

创建好之后,把这页笔记添加到索引页中,方便后面查找:

\"https://s2.loli.net/2022/03/06/Xr9CR7dfekTts5M.png\"

然后就可以写课堂笔记了,如果需要在笔记中插入图片,可以使用 Image Auto Upload 这个插件,配合 PicGo 客户端,可以在 Obsidian 里上传图片到图床,非常好用,直接粘贴图片到页面就可以了,具体可以看插件描述。

\"https://s2.loli.net/2022/03/06/V8SgsyWqONeYwjB.png\"

记笔记的过程没什么特别的,如果提到了一些我还不了解,以后还想深入的话题,我会用 [[ ]] 先标出来, 后面整理笔记的时候看到会留意下。

Step3-重组笔记

做完笔记后,将笔记重组。

布局

先打开三个窗口,布局如下:

\"https://s2.loli.net/2022/03/06/QwSIsWMHUlbZ71r.png\"

其中课堂笔记和索引页面需要锁定,这样新打开的窗口就会一直在右下角那个地方,将在这个区域编辑笔记内容。

结构编辑

浏览课堂笔记大纲,看下本节课的知识点应该放在索引里的哪里,知识点之间应该是怎样的关系,在索引里用 [[]] 都列出来,简而言之就是画思维导图。我的一个比较粗糙的整理:

\"https://s2.loli.net/2022/03/06/bHa4rFvRIB58jeT.png\"

这样就可以比较直观地看到哪些内容整理了,哪些没有整理。

拆分笔记

然后就可以将左边的笔记拆分整合到右边的索引中了,按住快捷键 CMD,鼠标点击索引里的链接打开新页面,然后在右下部分复制整理。写完一个知识点后可以不用关闭窗口,按住 CMD 然后点击链接,继续在右下窗口编辑笔记。

看到索引浅色链接(没有创建页面的)都没了,就基本整理完了,可以再看看课程笔记里有没有要补充的。

然后就整理完啦!之后继续补充索引页面就好了……^_^

", "summary": "拆分整合的过程", "date_published": "2022-03-06T16:23:33.118Z", "date_modified": "2022-08-06T10:50:46.655Z", "tags": [ "Obsidian" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-06-airtable", "url": "https://seviche.cc/2022-03-06-airtable", "title": "Airtable · 网页剪藏", "content_html": "

书签这回事

上回说到用 自建网页书签 Flare ,今天不小心把 SSH 链接弄坏了(也就是连不上了),因为搭载的服务不多,所以把整个服务器都重装了,Flare 网页书签也炸了。

其实搭建之后我没有用过(一次都没有),平时的书签管理主要靠搜索,各个浏览器之间的书签互相导入后,直接在搜索栏搜,如果是常用的网址,我用 Chorme 扩展 eesel 来解决,它可以列出最近用过的网页,按站点分类,查找起来很方便

\"essel

最近还推出了一个新功能,可以通过命令进行一下快捷操作,如创建新的 coda 文件、figma 文件等……有点像 Alfred

\"https://s2.loli.net/2022/03/06/7ZzGSUrWKR1vm6k.png\"

然后还有一个工具叫 Omni 可以做类似的事情,它还可以搜收藏夹,但不知道为什么我的 Chrome 用不了这个,所以也一直没用。

关于 Airtable

Airtable 是一个多功能的表格应用,它的表格跟 Notion 里的 Database 挺像的,不过功能更多,用来做网页收藏夹 Free Plan 完全够用。基本的操作可以看这个: 真· Airtable 3 分钟菜鸟入门 - 少数派 ,我没有什么要补充的。

从去年开始,我开始用 Airtable 整理我的一些收藏夹。原因如下:

  1. 可以分享的表格链接,移动端网页适配也很好
  2. 提供可嵌入网页(如博客)的 <iframe> 代码,样式也可以调整
  3. 方便的 Chrome 拓展,可以智能抓取网页标题和截图、描述
  4. 可以给收藏打 Tag,更好整理,也可以写补充描述/评分等
  5. 多种表格视图(Gallery/Calender/Kanban……)
  6. 美丽: D

当然这样做也有一些缺点,和其他专门做网页书签的应用不同,Airtable 只是一个「表格」,所以从表格到收藏的网页中去需要点两次,也就是需要打开条目再点一次链接。我把它定义为一个「收藏仓库」,而不是一个随用随取的「文具袋」,我会尽可能详细地描述收藏的条目,以备之后查找和辨识。

我的书签例子:

怎么用 Airtable 剪切网页

我的收藏夹示例: Airtable - About Coding

1. 创建表格

至少包含三项内容:

  1. URL:用来放网页的链接
  2. LongText:网页描述
  3. Attachment:放网页截图

如果需要打开 Markdown 格式支持,需要打开 Enable rich text formatting\"https://s2.loli.net/2022/03/06/7agleEFG5YyNSWU.png\"

也可以增加 Tag 和 Categories 分类等其他内容,下面是我建的示例文件:

\"https://s2.loli.net/2022/03/06/3IRug7QaOs46vBW.png\"

2. 创建 app

点击右上角的 App → 点击 App an app → 搜 Web clipper\"https://s2.loli.net/2022/03/06/ldpgQ9weHMJctUf.png\"

点击 add 添加应用

\"https://s2.loli.net/2022/03/06/v2TPpVXMnt4jYx8.png\"

然后按提示安装 Chrome 拓展,你可以直接在这里安装: Airtable web clipper

为剪切动作命名,如直接用表格名字:About Coding\"https://s2.loli.net/2022/03/06/69YEJzKCX5xntP7.png\"

然后点击 Add to Extension, 你会看到它出现在了 Web clipper 里面,不过现在先不用管,点击左上角关掉。\"https://s2.loli.net/2022/03/06/1tiLkpEXqTKJw3o.png\"

3. 配置剪切设置

在 Web clipper 的设置页面(如下),可以调整表格里面各个单元格对应的网页数据,可以按需设置

\"https://s2.loli.net/2022/03/06/6FMhjrZR2NSsqOG.png\"

其中:

我的设置是:

4. Web Clipper 剪切

配置好后就可以开始使用了。在你需要剪切的网页,打开 Airtable web clipper,也就是先前安装的浏览器拓展,点击相应动作,比如刚才创建的 About Coding(如果这个面板有挡到页面内容,可以用鼠标拖动到别的地方)

\"https://s2.loli.net/2022/03/06/RTu2xDNn5teqlQP.png\"

在 Attachment 里选择附加图片的来源:\"https://s2.loli.net/2022/03/06/tyU87WD4jsdBHiN.png\"

Description 里面的内容可以自己写,也可以在打开 Web clipper 之前先选中,打开后会自动填充进去,如图:\"https://s2.loli.net/2022/03/06/vPLMNaOlkotWV2Y.png\"

最后点击 Add record 就完成啦

", "summary": "Airtable Web Cilpper设置", "date_published": "2022-03-06T05:58:29.026Z", "date_modified": "2022-08-06T10:50:42.370Z", "tags": [ "实用技巧" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-04-decbin", "url": "https://seviche.cc/2022-03-04-decbin", "title": "JavaScript · 十进制数转二进制", "content_html": "

题目

来源:“如果”可以“重来” | 百度前端技术学园

验证工具:在线进制转换 | 进制转换器 — 在线工具

Task1

实现当点击转化按钮时,将输入的十进制数字转化为二进制,并显示在 resultp 标签内

Task2

解法

html
<input id=\"dec-number\" type=\"number\" placeholder=\"输入一个十进制非负整数\" />
<input id=\"bin-bit\" type=\"number\" placeholder=\"输入转化后二进制数字位数\" />
<button id=\"trans-btn\">转化为二进制</button>
<p id=\"result\">运算结果</p>
<script>
/////// Task 1
const btn = document.querySelector('#trans-btn')
const result = document.querySelector('#result')
function dec2bin() {
let decNumber = Number(document.querySelector('#dec-number').value)
// 判断输入必须为一个非负整数
if (decNumber < 0 || !Number.isInteger(decNumber)) {
alert('请输入一个非负整数!')
}
// 求余
let bin = []
let remainder
while (decNumber !== 0) {
remainder = decNumber % 2
decNumber = parseInt(decNumber / 2)
bin.unshift(remainder)
}
let binNumber = bin.join('')
////// Task2
let binBit = Number(document.querySelector('#bin-bit').value)
if (binNumber.length >= binBit) {
binNumber = binNumber.slice(0, binBit + 1)
} else {
binNumber = binNumber.padStart(binBit, '0')
}
result.innerHTML = `运算结果:${binNumber}`
}
btn.addEventListener('click', dec2bin)
</script>
", "summary": "用JavaScript将十进制数转二进制数", "date_published": "2022-03-04T14:57:48.683Z", "date_modified": "2022-08-06T10:50:40.572Z", "tags": [ "JavaScript" ], "_indieweb": { "type": "article" } }, { "id": "2022-03-03", "url": "https://seviche.cc/2022-03-03", "title": "JavaScript · 判断水仙花数", "content_html": "

题目来源: “如果”可以“重来” | 百度前端技术学园

题目

根据用户输入的数据,判断水仙花数(三位数),水仙花数是指一个 n 位数 (n≥3),它的每个位上的数字的 n 次幂之和等于它本身。

html
<label>请输需要判断的水仙花数(三位数):</label>
<input type=\"text\" />
<br />
<button>开始判断</button>
<script>
function numDaffodils(num) {
// 判断是否为水仙花数
}
</script>

需求说明

解法

html
<label>请输需要判断的水仙花数(三位数):</label>
<input type=\"text\" />
<br />
<button>开始判断</button>
<script>
const btn = document.querySelector('button')
function numDaffodils() {
let num = document.querySelector('input').value
//输入的是字符串
// console.log(typeof num); =>string
// 检查是否是有效数字
if (num.startsWith('0') || num.length !== 3 || isNaN(num)) alert('请输入三位有效数字')
// 拆分为数组
let numArr = num.split('')
//判断是不是水仙花数字!
const numCheck = numArr.reduce((acc, value) => acc + Math.pow(value, 3), 0)
numCheck == num ? alert('是水仙花数 ✅') : alert('不是水仙花数❗️')
}
btn.addEventListener('click', numDaffodils)
</script>

这里有一个坑:从 <input type="text"> 获取输入内容1,因为 type=text ,所以输出的是 string 而不是 number,不能直接用typeof ==='number'判断输入的是不是数字

参考


  1. HTML text input allow only numeric input
", "summary": "用JavaScript判断水仙花数", "date_published": "2022-03-03T15:07:14.533Z", "date_modified": "2022-08-06T10:50:38.994Z", "tags": [ "JavaScript" ], "_indieweb": { "type": "article" } }, { "id": "flare", "url": "https://seviche.cc/flare", "title": "自建网页书签Flare", "content_html": "

Flare 是一个自托管的网页书签导航 (个人理解),详细介绍可以看作者写的这篇:使用 Docker 搭建适用于 HomeLab 的书签导航

效果如图:\"https://uneorange.oss-cn-guangzhou.aliyuncs.com/202202152046471.png\"

这里分成了“应用”和“书签”两个栏目,但其实都是网页链接书签,看示例文档里的设置,应用里的是使用更为频繁的链接,书签栏则是一些参考链接/外链,或许“应用”命名为“常用”,“书签”则命名为“链接”或者“其他”更好一些。我之前还以为应用是本地应用……(我的问题╮( ̄ ▽  ̄"") ╭

项目仓库:GitHub - soulteary/docker-flare

1.创建 Flare 文件夹

此处我将文件夹命名为 flare

bash
mkdir ~/flare && cd ~/flare

2.下载包含示例的代码

bash
git clone https://github.com/soulteary/docker-flare.git
cd docker-flare

3.运行容器

方法 1:直接启动

bash
# 可以使用最新镜像
docker run --rm -it -p 5005:5005 -v `pwd`/app:/app soulteary/flare
# 也可以追求明确,使用固定版本
docker run --rm -it -p 5005:5005 -v `pwd`/app:/app soulteary/flare:0.2.10

方法 2:通过 docker composer

因为示例文件夹里面已经有一个 docker-compose.yml 文件了,所以我们不需要另外创建,如果需要修改的话可以用 nano docker-compose.yml 编辑 (如果需要设置用户登陆的话,需要在此修改)

启动容器:

bash
docker-compose up -d

这时我们可以通过 http://ip:5005 访问书签页面了。

4.修改书签内容

我用的是 Royal TSX 上的 File transfer 来查看文件夹内容。

配置文件的路径如下:\"https://uneorange.oss-cn-guangzhou.aliyuncs.com/202202152023587.png\"各个文件的功能如下:

作者贴心地内置了 @mdi/font 相关 icon,可以通过 http://ip:5005/resources/mdi-cheat-sheets/ 来访问图标列表,然后通过 Ctrl /CMD+ F 来进行页面内搜索。

在书签页面,可以通过左下角的齿轮图标调整主题和其他设置,和 config.yml 里的选项几乎一致:\"https://uneorange.oss-cn-guangzhou.aliyuncs.com/202202152044727.png\"

5.用 nginx 反代和设置域名

安装 Nginx 并打开 flare.conf 文件

bash
apt install nginx
cd /etc/nginx/sites-enabled/
rm rf default
cd /etc/nginx/sites-available/
nano flare.conf

flare.conf 的内容 (注意修改 example.com 为自己的域名)

server { listen 80; listen [::]:80; server_name example.com; location / { proxy_pass http://127.0.0.1:5005; }}

nginx -t 测试配置文件,如果最后有出现 suceessful,那测试就成功了。

然后为这个配置文件增加一个链接

bash
cd /etc/nginx/sites-enabled/
ln -s ../sites-available/flare.conf flare.conf

重启 nginx

bash
systemctl reload nginx

刚遇到一些问题,又回到了我的 Miniflux 主页^ ^,遂重启容器:

bash
cd ~/flare/docker-flare
docker-compose down
docker-compose up -d

6.SSL 证书和其他书签

安装证书,详情参考这篇:用 docker 安装 Halo 博客并用 Nginx 反代

bash
certbot --nginx -d example.com -d www.example.com

其他好用书签:

再次感谢云五的 WordPress 搭建教程,照葫芦画瓢套用了 Nginx 的设置。

", "summary": "通过Docker自建网页书签Flare", "date_published": "2022-02-20T14:04:17.000Z", "date_modified": "2022-08-06T10:49:53.655Z", "tags": [ "Docker", "Self-hosted" ], "_indieweb": { "type": "article" } }, { "id": "halo", "url": "https://seviche.cc/halo", "title": "用 docker 安装 Halo 博客", "content_html": "

系统:ubuntn 20.04

参考:

Halo 官网:Halo

建议大家先去Halo 官网主题仓库看看有没有喜欢的主题再决定要不要装,不然很可能像我一样装了之后又跑路了…… ^^

准备

解析域名

域名生效需要时间,为了避免申请 SSL 证书时屡次失败导致超过申请次数限制,最好提前添加域名解析1,我这里用的是子域名。

为域名添加一个 A 记录,指向服务器所在 IP。如 Namesile 中:\"https://uneorange.oss-cn-guangzhou.aliyuncs.com/202202151120134.png\"

安装 docker

在 Ubuntu 中安装 docker2

zsh
apt update
apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable\"
apt update

其他系统可参考:Plume | 利用 Docker-compose 搭建 Fedi 开源博客平台 – Zoe’s Dumpster.中的安装方式,或者查看官方文档。有的 VPS 在装系统的时候也可以预装 docker,如 vultr/contabo 都有,不过 docker-compose 就需要自己装(这里似乎没有用到 docker-compose。

检查是否装好,有版本就装好了:

zsh
docker -v

检查端口开放情况

参考:Ubuntu20.04 开放指定端口_哈-CSDN 博客

我不太懂端口是怎么开放的,自动开放还是用了就开放。这里用的是 ufw 来设置防火墙开放端口,用 netstat 查看端口占用状况。

Debian/Ubuntu 中安装 netstat3

zsh
apt install net-tools

检查端口占用:

bash
netstat -aptn

ufw 是 Ubuntu20.04 系统预装的 3,如未安装上,可以这样安装:

bash
sudo apt update
sudo apt install ufw

常用操作:

bash
ufw enable #打开防火墙
ufw disable #关闭防火墙
ufw status #查看防火墙规则
ufw allow 22 #打开22端口
ufw deny 22 #拒绝访问22端口

打开防火墙之前最好打开常用的端口,如 22,不然可能会连不上服务器。

安装 Halo

参考:使用 Docker 部署 Halo | Halo Documents

1.创建工作目录

创建一个文件夹存放 Halo,我这里命名为.halo,当然也可以叫其他的。

bash
mkdir ~/.halo && cd ~/.halo

2.下载示例配置文件到工作目录

bash
wget https://dl.halo.run/config/application-template.yaml -O ./application.yaml

3. 编辑配置文件,配置数据库或者端口

我不会用 vim 命令,所以这里用 nano 编辑

打开配置文件 application.yaml

bash
nano application.yaml

修改配置

配置参考 | Halo Documents这里已经有刚下载好的配置文件了,我们可以根据自己的需要修改,Halo 数据库支持 H2 和 Mysql 数据库,因为我已经安装了一个 Wordpress 博客占用了 Mysql 数据库,虽然不知道有无影响,但为了避免出错,最后选择了按示例里的配置,使用 H2 数据库(主要还是懒得改

我这里将端口放到 8090,因为原 80 端口已经被占用,大家可以选择其他开放端口,注意修改数据库用户名和密码。

yaml
server:
port: 8090
# Response data gzip.
compression:
enabled: false
spring:
datasource:
# H2 database configuration.
driver-class-name: org.h2.Driver
url: jdbc:h2:file:~/.halo/db/halo
username: admin #数据库用户名
password: 123456 #数据库密码
# MySQL database configuration.
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
# username: root
# password: 123456
# H2 database console configuration.
h2:
console:
settings:
web-allow-others: false
path: /h2-console
enabled: false
halo:
# Your admin client path is https://your-domain/{admin-path}
admin-path: admin
# memory or level
cache: memory

4. 拉取最新的 Halo 镜像

bash
docker pull halohub/halo:latest

5. 创建容器

bash
docker run -it -d --name halo -p 8090:8090 -v ~/.halo:/root/.halo --restart=unless-stopped halohub/halo:latest

6. 打开安装引导界面。

访问 http://服务器ip:端口号\"https://uneorange.oss-cn-guangzhou.aliyuncs.com/202202151243869.png\"

用 Nginx 反代

说实话,我现在还没懂 Nginx 是干嘛的,反代又是什么,但好像一般都要有,那就做一下吧。幸好 Halo 还有现成的配置4可以抄一下,结合云五的 Wordpress 搭建教程2,把里面 wordpress 的部分改为 halo 就可以了。

1.安装 Nginx

bash
apt install nginx
cd /etc/nginx/sites-enabled/
rm rf default
cd /etc/nginx/sites-available/
nano halo.conf

2.配置 halo.conf

注意修改www.yourdomain.com 为自己的域名

upstream halo { server 127.0.0.1:8090; } server { listen 80; listen [::]:80; server_name www.yourdomain.com; client_max_body_size 1024m; location / { proxy_pass http://127.0.0.1:8090; proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

nginx -t测试配置文件,如果最后有出现suceessful ,那测试就成功了。

然后为这个配置文件增加一个链接

bash
cd /etc/nginx/sites-enabled/
ln -s ../sites-available/halo.conf halo.conf

3.重启 nginx

bash
systemctl reload nginx

现在访问域名就可以到达 halo 博客主页了,如果不行,可以 ping 一下域名看是不是解析还没生效:如 ping exampl.com

我这里出现一个问题,是输入域名后到了我的 miniflux 主页,因为 halo.conf 里域名后缀输错了……

安装 certbot,为域名获取免费 SSL 证书

有 ssl 证书后,就不会被提示网站不安全了,也就是从 http->https

1.安装 certbot

bash
apt install certbot python3-certbot-nginx

2.配置证书

修改 example.com 为自己的域名:

bash
certbot --nginx -d example.com -d www.example.com

如果你不确定是否可以申请成功,或者还在修改/测试配置,可以在后面加--staging 测试,避免申请超过次数限制1

一些选项,这里最好选择 1,如果选择 2,就没办法用 http 访问域名了:

text
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.

然后同意条款后问是否暴露邮箱时选 No2

其他



  1. WordPress + VPS 建站教程 - 少数派
  2. 用 Nginx 反代实现 docker 安装 WordPress 与其他服务并存 – 沉默之沙
  3. 如何在 Linux 中安装 netstat 命令 - 云+社区 - 腾讯云
  4. 使用 Docker 部署 Halo | Halo Documents
", "summary": "", "date_published": "2022-02-16T14:04:17.000Z", "date_modified": "2022-08-06T10:49:49.221Z", "tags": [ "Docker", "Self-hosted" ], "_indieweb": { "type": "article" } }, { "id": "vscode", "url": "https://seviche.cc/vscode", "title": "VS Code 技巧合集", "content_html": "

关闭 VS Code 中的 popup 面板

如下图所示,在写 JavaScript 的时候,这个东西一直出来,还是蛮烦的。

\"https://s2.loli.net/2022/01/17/eElzfuJkH2Tcwv6.png\"

解决方法:在 setting 里搜 editor.parameterHints.enabled,取消勾

\"https://s2.loli.net/2022/01/17/3fwBTnNZXRUDF8d.png\"

参考来源:visual studio code - How do I get rid of this popup in VSCode? - Stack Overflow

修改字体

改了 VS Code 里面的字体,Obsidian 里 code block 的字体也一起变了,用了有连字符的字体,箭头变得好好看!

现在用的是 Fira Code: https://github.com/tonsky/FiraCode

VS Code 里面可以在 settings.json 里加这行打开连字符: “editor.fontLigatures”: true

字体推荐:

快捷键

  1. 按住 alt/option,可以在多个地方同时输入
  2. shift+option/alt+⬇️ 向下复制一行
  3. option/alt +⬇️/ ⬆️ 向下 / 上移动
  4. 在 html 中 输 lorem ,可以生成填充文本

插件

设置

其他

", "summary": "字体/插件/设置……", "date_published": "2022-01-27T13:40:28.920Z", "date_modified": "2022-08-06T10:49:29.477Z", "tags": [ "实用技巧" ], "_indieweb": { "type": "article" } }, { "id": "axure", "url": "https://seviche.cc/axure", "title": "两种免费发布Axure原型的方式", "content_html": "

好像很少看到有人提,这里简单记一下思路,我有用 Vercel 成功试验过,跟本地预览效果是一样的,还可以改域名。不过了解过 Netlify 之后,我觉得用 Netlify 更简单,有兴趣的可以再研究一下。

Netlify

需要了解 Netlify 的使用方式

  1. 在 Axure 中导出文档的 HTML 文件:发布>生成 HTML 文件
  2. 注册并托管到 Netlify
  3. 上传 HTML 文件:site -> 拖动 HTML 文件夹到下面的虚线区域

更新方式:再导入一次更新后的文件包

Vercel +Github

需要了解 Github 的基本使用方式和 Vercel

  1. 在 Axure 中导出文档的 HTML 文件:发布>生成 HTML 文件
  2. 创建一个 Github repo,并下载到本地
  3. 复制 HTML 文件到本地 Github repo 的文件夹中,并 commit->push 到云端
  4. 导入相应 repo 到 Vercel 中

更新方式:复制更新后的 HTML 文件夹到相应的 Github repo 文件夹中,覆盖原来的

", "summary": "通过Netlify和Vercel发布", "date_published": "2022-01-21T00:11:17.000Z", "date_modified": "2022-08-06T10:51:25.527Z", "tags": [ "实用技巧" ], "_indieweb": { "type": "article" } }, { "id": "argon", "url": "https://seviche.cc/argon", "title": "CSS · Argon主题的CSS修改", "content_html": "

然后已经不用 Wordpress 了,这个主题加载太慢了(也可能是我自己的问题

css
/*删掉tag图标*/
.fa-tags {
display: none;
}
/*左侧菜单居中*/
.leftbar-menu-item {
text-align: center;
}
/*删掉日历图标*/
.fa-calendar-o {
display: none;
}
/*说说字体统一大小*/
.shuoshuo-date-date {
font-size: 15px;
}
.shuoshuo-date-month {
font-size: 15px;
}
/*banner字体改为思源宋体*/
.banner-title.text-white {
font-family: 'Noto Serif SC', serif;
}
/*删掉左侧搜索栏*/
.card-body.text-center.leftbar-search-button {
display: none;
}
/*删掉左侧栏站点名称*/
.leftbar-banner.card-body {
display: none;
}
/*作者相关链接居中*/
.site-author-links a {
text-align: center;
}
/*去掉页脚卡片外形,并缩减边距*/
#footer.card {
background: none;
padding: 0;
box-shadow: none !important;
}
html.darkmode #footer {
background: none !important;
}
", "summary": "基于最近所学,对当前Argon主题做了一些微小的调整", "date_published": "2022-01-16T14:04:17.000Z", "date_modified": "2022-08-06T10:50:02.849Z", "tags": [ "CSS" ], "_indieweb": { "type": "article" } }, { "id": "hover", "url": "https://seviche.cc/hover", "title": "CSS · hover时图片放大的动效", "content_html": "

思路:

  1. 设置放大
  2. 隐藏溢出
css
.gallery-item {
overflow: hidden;
}
.gallery-item img:hover {
transform: scale(1.1);
}
.gallery-item img {
display: block;
width: 100%;
transition: all 0.4s;
}

效果:https://codepen.io/sevichee/pen/wvrRjjq

", "summary": "通过transform设置", "date_published": "2022-01-13T16:03:47.000Z", "date_modified": "2022-08-06T10:49:47.292Z", "tags": [ "CSS Trick", "CSS" ], "_indieweb": { "type": "article" } }, { "id": "css-layout", "url": "https://seviche.cc/css-layout", "title": "CSS · 三种布局方式", "content_html": "

课程:Build Responsive Real-World Websites with HTML and CSS

Overview

\"https://s2.loli.net/2022/01/08/SjgmE2nl7TkUAqM.png\"

Box-sizing

\"https://s2.loli.net/2022/01/09/yCScJXVD9dYIjqK.png\"

未定义之前:content-box

reset

css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

Float Layout

left

\"https://s2.loli.net/2022/01/08/HZlqu9dyfmG5QYV.png\"

css
.author-img {
float: left;
}

此时图片和文本不在同一个层面上

right

浮动到页面右边

\"https://s2.loli.net/2022/01/08/ESLcqpbH2VfuNXj.png\"

css
p {
float: right;
}

ABSOLUTE POSITIONING VS. FLOATS

\"https://s2.loli.net/2022/01/08/g6Zk2rDAWbxLeoI.png\"

Clear Float

方法 1: empty div

使用一个并列的空<div>元素来清除

html
<head>
<h1>title</h1>
<h2>title</h2>
<div class=\"clear\"></div>
</head>
css
/*清除两者*/
.clear {
clear: both;
}
/*清除左边*/
.clear {
clear: left;
}

方法 2:clearfix hack

原理和 empty div 的方式一样,但更简单,利用 pseudo element 在 parent element 后增加一个元素

html
<head class=\"clearfix\">
<h1>title</h1>
<h2>title</h2>
</head>
css
.clearfix::after {
clear: both;
content: '';
display: block;
}

Flexbox

\"https://s2.loli.net/2022/01/09/8HiwTDALGbCZQdf.png\"

\"https://s2.loli.net/2022/01/09/RMmiLHxa78n4Atw.png\"

加粗字体为默认状态

css
.container {
display: flex;
align-items: center; /*水平居中*/
justify-content: center; /*垂直居中*/
}

Flex container

常用:

css
.container {
display: flex;
align-items: stretch;
justify-content: flex-start;
}

Flex items

css
.items {
align-self: flex-start;
}

Propetities

order

数字越大越靠后

flex-grow

数字越大占比越大

flex-basis

item’s width

shorthand

flex:1 =

css
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;

CSS Grid

\"https://s2.loli.net/2022/01/09/6ovHUJOilxqtkjb.png\"

\"https://s2.loli.net/2022/01/09/1vLi2G6okjNzrut.png\"

\"https://s2.loli.net/2022/01/09/krsGJ3AgQpR2Uf9.png\"

grid container

css
.container {
display: grid;
grid-template-columns: 250px 200px; /*两列*/
grid-template-rows: 200px 200px; /*两行*/
}

通常不定义 rows

Gap

用 gap 而不是 margin:

css
.container {
/*gap:20px;*/
columns-gap: 30px;
row-gap: 20px;
}

align tracks inside containers

when the content is smaller than the grid

css
justify-content: center;
align-content: center;

grid items

css
.items: {
grid-column: 2/3; /*当前后数值相差只有1位时,可省去后面的数字*/
grid-row: 2/3;
/*占几个列/行时*/
grid-column: 1/3;
grid-row: 1/3;
/*或者*/
grid-column: 1 / span 3;
/*自动填充剩余空间*/
grid-column: 1/-1;
}

align items inside cells

css
.container {
align-items: center;
justify-items: center;
}
css
.item {
align-self: end;
}

fr

css
.container {
grid-template-columns: 2fr 1fr 1fr 1fr;
}
/*shorthand*/
.container {
grid-template-columns: repeat(4, 1fr);
}

implicit row

所定义的空间被用完后多出来的列

", "summary": "Float / Flexbox / Grid /", "date_published": "2022-01-13T10:07:00.000Z", "date_modified": "2022-08-06T10:49:54.807Z", "tags": [ "CSS" ], "_indieweb": { "type": "article" } }, { "id": "margin", "url": "https://seviche.cc/margin", "title": "CSS · Add margin to buttons", "content_html": "

用 helper class 为单个按钮加 margin,防止元素复用时产生不必要的 margin

HTML

html
<button class=\"btn helper\">Text</button>

CSS

css
.helper {
margin-right: 1.6rem;
}

为按钮增加内边距

如果设置 border 数值为负,周边空间则会收到影响,但用 box-shadow 就不会:

所以可以这样设置:

box-shadow: inset 0 0 0 3px #fff;

", "summary": "用helper class为单个按钮加margin,防止元素复用时产生不必要的margin", "date_published": "2022-01-13T10:04:21.000Z", "date_modified": "2022-08-06T10:49:40.186Z", "tags": [ "CSS Trick", "CSS" ], "_indieweb": { "type": "article" } }, { "id": "grid", "url": "https://seviche.cc/grid", "title": "CSS · Reusable Grid", "content_html": "
  1. 先设置一个 class 为 grid,并设定 gap
  2. 子元素中再设置具体的列数

如设一个上为 2 列和下为 3 列的栅格布局:

CSS:

css
.grid {
display: grid;
gap: 1.2rem;
}
.grid--2--cols {
grid-template-columns: repeat(2, 1fr);
}
.grid--3--cols {
grid-template-columns: repeat(3, 1fr);
}

HTML:

html
<section class=\"grid grid--2--cols\">
<div>Text1</div>
<div>Text2</div>
</section>
<section class=\"grid grid--3--cols\">
<div>Text1</div>
<div>Text2</div>
<div>Text3</div>
</section>
", "summary": "可复用的CSS Grid设置", "date_published": "2022-01-13T10:01:44.000Z", "date_modified": "2022-08-06T10:49:51.803Z", "tags": [ "CSS" ], "_indieweb": { "type": "article" } }, { "id": "rem-css", "url": "https://seviche.cc/rem-css", "title": "CSS · Rem in CSS", "content_html": "

默认为浏览器默认字体大小:

1rem=16px

改变 rem 为 10px:

1. set px

css
html {
font-size: 10px;
}

这种方法会让用户不能改变页面字体大小,不建议用

2. percentage

设置为用户浏览器字体的大小比例。

html{
/* 10px/16px =62.5%*/
font-size:62.5%;
}
", "summary": "CSS中的Rem是什么", "date_published": "2022-01-12T09:50:27.000Z", "date_modified": "2022-08-06T10:49:34.711Z", "tags": [ "CSS" ], "_indieweb": { "type": "article" } }, { "id": "material", "url": "https://seviche.cc/material", "title": "翻译 · Material Design3", "content_html": "

上个月参与了 Material Design 3 的中文版翻译,我和搭档一起翻译了 FAB (浮动操作按钮) 的部分内容。

这份翻译还存在一些问题,部分专有词汇还没统一翻译,但大概看看还是可以的。

相关内容:

", "summary": "部分翻译内容", "date_published": "2022-01-03T13:02:17.000Z", "date_modified": "2022-08-06T10:49:37.342Z", "tags": [ "翻译" ], "_indieweb": { "type": "article" } }, { "id": "用户体验要素3", "url": "https://seviche.cc/用户体验要素3", "title": "《用户体验要素》- 战略层(产品目标和用户需求)", "content_html": "

知道企业与用户双方对产品的期许和目标,有助于促进用户体验各方面战略的确立和制定

此处的关键词是“明确”

产品目标

用户需求

用户细分( user segmentation)

\"https://s2.loli.net/2022/01/02/aVewkFg5Cx1SHPq.png\"

用户研究(User Research)

团队角色和流程

", "summary": "第三章笔记", "date_published": "2022-01-02T13:08:29.000Z", "date_modified": "2022-08-06T10:50:03.925Z", "tags": [ "《用户体验要素》", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "css-fundamental", "url": "https://seviche.cc/css-fundamental", "title": "CSS · 基础笔记", "content_html": "

一些随堂笔记。

课程:Build Responsive Real-World Websites with HTML and CSS

css 是什么

分类

inline CSS

html
<p style=\"color:blue\">text</p>

最好不用

internal CSS

放在<head>里面的<style>,如:

html
<head>
<style>
h1 {
color: blue;
}
</style>
</head>

external CSS

html
<head>
<link href=\"style.css\" ref=\"stylesheet\" />
</head>

关于<link> ,可参考:MDN

组成

\"https://s2.loli.net/2022/01/06/Wvj8SKIwGrVaicJ.png\"

Selectors

descendent selector

css
footer p {
font-family: sans;
}
article header p {
color: blue;
}

line selector

css
h1,
h2,
h3 {
color: blue;
}

定义特定元素样式的两种方式:CSS ID、class attributes

id

给每个元素一个 id,仅能用一次,尽量不要用

在 HTML 中:

html
<p id=\"author\">text</p>

在 CSS 中:

css
#author {
font-family: sans;
}

class attributes

能无限复用

在 HTML 中:

html
<p class=\"author\">text</p>

在 CSS 中:

css
.author {
font-family: sans;
}

能无限复用

在 HTML 中:

html
<p class=\"author\">text</p>

在 CSS 中:

css
.author {
font-family: sans;
}

universal selector

css
* {
color: #1098ab;
}

body

优先级

conflicting between selectors

\"https://s2.loli.net/2022/01/07/Zl3GX9dhrO2nqMs.png\"

css
foot p {
color: green !important;
}

inheritance

颜色

RGB/RGBA

Hexadecimal Colors

Pseudo Class

用来指定特定元素

第一个元素/最后一个元素

css
li:first-child {
font-weight: bold;
}
li:last-child {
font-style: italic;
}

奇数/偶数 /特定次序

css
li:nth-child(odd) {
font-style: italic;
}
li:nth-child(even) {
font-style: italic;
}
li:nth-child(3) {
font-style: italic;
}

多种元素时

如下所示,当 HTML 中<p>并不是<article>里面的第一个元素时,不生效。

即当母元素(parent element)里有多种元素时(child elements),不宜使用伪类(pseudo class),可以在列中使用,如<li>

html
<article>
<head></head>
<p></p>
</article>
css
article p:fist-child {
font-family: sans;
}

Style hyperlinks

四个状态都应定义,并按顺序排列

link

不进行交互的预览下

css
a:link {
color: #1098ad;
}

visited

点击后

css
a:visited {
color: #777;
}

hover

悬停时

css
a:hover {
color: orangered;
font-weight: bold;
text-decoration: underline dotted orangered;
}

active

点击时(通常和 hover 同时出现

css
a:active {
background-color: black;
font-style: italic;
}

Pseudo Elements

any pseudo elements is actually an inline element

在 HTML 中并不真实存在,但仍可在 CSS 中选择,如行内第一个字符:在 HTML 中并不真实存在,但仍可在 CSS 中选择,如行内第一个字符:

css
h1::first-letter {
font-style: normal;
}

段内第一行:

css
p::first-line {
color: red;
}

adjacent sibiling selector

在同一 parent element 里,下面最临近的元素

如 h3 标题下的 p:

css
h3 + p::first-line {
color: red;
}

after

\"https://s2.loli.net/2022/01/08/YZaXGonBRELSuvH.png\"

css
h2::after {
content: 'TOP';
font-size: 16px;
font-weight: bold;
color: black;
padding: 5px 15px;
display: inline-block;
position: absolute;
top: -10px;
right: -25px;
background-color: #ffe70e;
}
h2 {
position: relative;
}

before

css
h2::before {
}

Box Model

\"https://s2.loli.net/2022/01/07/hM1AKQwbp5u8gSJ.png\"

\"https://s2.loli.net/2022/01/07/V9SoD43kBH1z5cp.png\"

\"https://s2.loli.net/2022/01/07/pUJWb7iMqgI213G.png\"

Type of boxes:

inline boxes

\"https://s2.loli.net/2022/01/08/HhPGpo83LnKMQCJ.png\"

block-level boxes

\"https://s2.loli.net/2022/01/08/kK4HD6YVsFIqx31.png\"

inline-block boxes

\"img其实是inline-block

img 其实是 inline-block box

padding

内边距

css
padding: 上下 左右;

reset margin ane padding

css
* {
margin: 0;
padding: 0;
}

不要用 body

margin

外边距

collapsing margins

Dimensions

width

position

\"https://s2.loli.net/2022/01/08/u9G7IJyYWzbdRF4.png\"

Normal Flow

Absolute Position

\"https://s2.loli.net/2022/01/08/NIga4hbQOHVfFCr.png\"

centering page

use the <div> element to create a container class, then set the margin-left and margin-right to auto

css
.container {
width: 800px;
margin: 0 auto;
}

基本操作

CSS 注释

Fix bugs

", "date_published": "2021-12-06T03:59:47.000Z", "date_modified": "2022-08-06T10:49:56.359Z", "tags": [ "CSS" ], "_indieweb": { "type": "article" } }, { "id": "html-basis", "url": "https://seviche.cc/html-basis", "title": "HTML · 基础笔记", "content_html": "

课程:Build Responsive Real-World Websites with HTML and CSS

HTML 是什么

HyperTextMarkupLanguageHTML 元素参考 - HTML(超文本标记语言) | MDN

基本组成

<p>Hello!</p>为例子:

  1. opening tag:<p>
  2. content : Hello!
  3. closing tag: </p>

框架

html
<!DOCTYPE html>
<html>
<head lang=\"语言代码\">
<meta charset=\"UTF-8\" />
<title>这是标题的位置,head里面的东西都看不见(一般来说)</title>
</head>
<body>
<h1>一级标题</h1>
</body>
</html>

attributes

attributes
srcsources
alt*alternative text (describe what the image are ) good for SEO/blind people
width宽度
height高度
href超链接 #表示回到页首
target在哪个窗口打开_blank 新窗口 _self 旧窗口 _top顶部 _parent 父级窗口,如果有窗口层级的话

tags

head在页面中不可见的元素,如页面标题、link to css files……
main文章的主要内容(HTML5 中新增)
body页面的主要内容
section区块(semeantic HTMl
h1标题,一个页面只能有一个 h1
p段落
span行内文本
<!— —>注释
b加粗,和<strong>不同,它没有语意,是过时的表述
strong加粗,表示是页面中重要的元素
i斜体,过时的表述,应用<em>
em斜体,emphasize
ol1. order list 数字排序
ul. unorder list
lilist item 列 ,用在 ol/ul 里面
img图片 特殊类型,不需要包含内容,需要 attributes( src/alt/width/height)
metadata about data meta charset=”UTF-8”
aanchor 超链接(attribute:href)
header页头(container
nav导航 ( container
menu菜单,web application 常用
article内容(container
footer页脚(container
div无意义内容区块
br/断行
aside次级信息/额外信息
figure常用于 feature cards/coding list……
figcaption图片脚注(只能在 figure 里面用)
form表单
input输入框
label表单输入标题(也许
table表格
thead表头
tbody表体
th表头单元格
trtable row
tdtable data
address地址
s删除
blockquote引言

HTML entity

特殊符号速查表

Glyphs | CSS-Tricks

semantic HTML

空格

 

当 div 中用 css 设置了图片时

html
<div class=\"image\" role=\"img\" arial-label=\"description about the image\"></div>

随堂练习

商品卡片

分页器 pagination

", "date_published": "2021-12-06T03:59:47.000Z", "date_modified": "2022-08-06T10:49:44.906Z", "tags": [ "HTML" ], "_indieweb": { "type": "article" } }, { "id": "用户体验要素2", "url": "https://seviche.cc/用户体验要素2", "title": "《用户体验要素》- 认识这些要素", "content_html": "

五个层面

表现层 (surface)

框架层 (skeleton)

结构层(structure)

笔记:相当于结构层是信息架构,框架层是信息架构所决定的布局。 地图和坐标?

范围层 (scope)

特性和功能就构成了网站的范围层( scope)

战略层 (strategy)

如何建设这五个层面?

自下而上地建设

\"https://s2.loli.net/2021/12/06/W53LrOJkURZT9S8.png\"

在上一层完成之前开始下一层

上下层之间不是互相隔断的让任何一个层面中的工作都不能在其下层面的工作完成之前结束

\"https://s2.loli.net/2021/12/06/PTR5yX9H6qUZ3F7.png\"

基本的双重性

什么是网页基本的双重性质?

网站既不能干脆地分类到应用程序,也不能分类到信息资源

什么是信息型的媒介类产品?

什么是功能型的平台类产品

用户体验的基本要素是什么?(五个层次、两种类别的产品)

\"https://s2.loli.net/2021/12/06/6xo2IgZcqNm8au1.png\"

", "summary": "第二章笔记", "date_published": "2021-12-06T03:59:47.000Z", "date_modified": "2022-08-06T10:50:05.032Z", "tags": [ "读书笔记", "《用户体验要素》" ], "_indieweb": { "type": "article" } }, { "id": "用户体验要素1", "url": "https://seviche.cc/用户体验要素1", "title": "《用户体验要素》- 用户体验为何如此重要", "content_html": "

在 MarginNote 里读了本书,现将其重新梳理一遍。本书共有 8 个章节,预计分 6 个文章写完,这是第一篇。\"https://i.loli.net/2021/11/29/KYZ2nPxXcCurWyL.png\"

什么是用户体验

用户体验并不是指一件产品本身是如何工作的

用户体验是指“产品如何与外界发生联系并发挥作用”,也就是人们如何“接触”和“使用”它。

用户体验设计通常要解决的是应用环境的综合问题

为体验而设计:使用第一

“设计一个用户体验良好的产品”作为明确的目标,意味着不仅仅是功能成外观那么简单。

为什么小而美的产品,更容易成功?

产品越复杂,确定如何向用户提供良好的使用体验就越困难。在使用产品的过程中,每一个新增的特性、功能或步骤, 都增加了导致用户体验失败的机会

用户体验和商机

为什么在网站上,用户体验比别的产品更重要?

不管用户访问的是什么类型的网站,它都是一个“自助式”的产品。没有可以事先阅读的说明书、没有任何操作培训或讨论会、没有客户服务代表来帮助用户了解这个网站。

用户所能依靠的只有自己的智慧和经验,来独自面对这个网站的信息一一那么这个网站的主要目标之,就是尽可能有效地传达那些信息

\"https://i.loli.net/2021/11/29/RgLjEZAiOQCax8U.png\"

为什么用户体验很重要?有什么作用吗

商机、竞争优势

提供优质的用户体验是一个重要的、可持续的竞争优势一一不仅仅对网站是这样, 对所有类型的产品和服务也是如此。

用户体验形成了客户对企业的整体印象,界定了企业和争对手的差异,并且决定定了客户是否还会再次光顾

什么是转化率?有什么用?

衡量用户体验效果的指标\"https://i.loli.net/2021/11/29/scDoiWbHGtNIr4k.png\"

转化率通过跟踪有百分之多少的用户被你“转化”到了下一个步骤,就能衡量你的网站在达到“商业目的”方面的效率有多高

改善用户体验,是为了什么?通过什么方式达到?

任何在用户体验上所做的努力,目的都是为了提高效率。

  1. “帮助人们工作得更快
  2. “减少他们犯错的几率

以用户为中心的设计

什么是“以用户为中心”的设计?

创建吸引人的、高效的用户体验的方法称为“以用户为中心的设计(user- centered design)”。

怎么做?

在开发产品的每一个步骤中,都要把用户列入考虑范围。

", "summary": "第一章笔记", "date_published": "2021-11-29T03:06:10.000Z", "date_modified": "2022-08-06T10:50:06.494Z", "tags": [ "读书笔记", "《用户体验要素》" ], "_indieweb": { "type": "article" } }, { "id": "2021-11-18-ux5", "url": "https://seviche.cc/2021-11-18-ux5", "title": "Evaluation", "content_html": "

评估

分类

形成性评估

总结性评估

如何评估

数据收集

衡量

可学习性

可记忆性

主观情感评估

\"/2021-11-18-ux5/1.png\"

相关资源

  1. usability evaluation
  2. 15. Usability Evaluation
  3. more than ease of use
  4. Measuring Usability: Are Effectiveness, Efficiency, and Satisfaction Really Correlated?
  5. usability 101|introduction to usability/

推荐教材

Trade Book

Text Books

参考网站

", "summary": "Introduction to User Experience Design|Week5", "date_published": "2021-11-18T14:09:49.000Z", "date_modified": "2022-08-06T10:47:38.841Z", "tags": [ "UX" ], "_indieweb": { "type": "article" } }, { "id": "2021-11-18-ux4", "url": "https://seviche.cc/2021-11-18-ux4", "title": "Prototyping", "content_html": "

关于 Prototyping

什么是原型开发

原型开发的作用

水平原型

\"/2021-11-18-ux4/1.png\"涵盖功能的广度,整合所含的功能(大概有什么)

垂直原型

\"/2021-11-18-ux4/2.png\"对一些特征进行深入的建模(多层级)

低保真 Lofi Prototype

低保真和高保真的区别

注意事项

三种形式

1. 草图

表明用户场景,痛点是怎么产生的(场景)\"/2021-11-18-ux4/3.png\"

2. 故事版

\"/2021-11-18-ux4/4.png\"

3.卡片原型

\"/2021-11-18-ux4/5.png\"

其他原型方式

绿野仙踪/奥茨法师

Wizard of Oz

\"/2021-11-18-ux4/6.png\"

概念验证视频

Proof of Concept Video

隐喻技术

Metaphor Development

相关资源

Resources and Tools for Protoyping 1

  1. prototyping.html
  2. high fidelity prototype/
  3. high fidelity vs low fidelity prototyping web design and app development
  4. Social Mirror

Tools

  1. Uxrecorder
  2. Invision
  3. Marvel
  4. Axure

Resources for Prototyping 2

  1. What a prototype is and is not
  2. the skeptics guide to low fidelity prototyping
  3. low fidelity prototype
  4. why every consumer internet startup should do more low fidelity prototyping
  5. lofi vs hifi prototyping how real does the real thing have to be
  6. prototyping types of prototypes
  7. horizontal and vertical prototypes
  8. introduction sketch ui design/
  9. the messy art of ux sketching/
  10. how to draw quick useful ui sketches
  11. User interface sketching tips part-1
  12. storyboarding in the software design process
  13. the 8 steps to creating a great storyboard
  14. users story ux storyboarding
", "summary": "Introduction to User Experience Design|Week4", "date_published": "2021-11-18T13:26:41.000Z", "date_modified": "2022-08-06T10:49:10.016Z", "tags": [ "UX" ], "_indieweb": { "type": "article" } }, { "id": "2021-11-14-ux3", "url": "https://seviche.cc/2021-11-14-ux3", "title": "Design Alternatives", "content_html": "

Review of Design Goals 审查设计目标

用户体验的核心目标

“用户使用界面来完成任务 ”

通过理解用户以及他们要完成的任务 以便设计出最好的界面(interface)

前提是,最好的界面 只能在我们了解用户和他们想要完成的任务之后才能被设计出

设计的定义

一种创新的开发用来满足一些需求。

设计的目标

设计时,需考虑的三个方面

\"/2021-11-14-ux3/1.png\"

Design Alternatives 设计替代方案

目标

开发一个比现在更能满足用户需求的界面(基于设计的问题空间)

关键

使用屏幕实践将设计基础概念化

Road map 路线

", "summary": "Introduction to User Experience Design|Week3", "date_published": "2021-11-14T03:18:45.000Z", "date_modified": "2022-08-06T10:49:15.281Z", "tags": [ "UX" ], "_indieweb": { "type": "article" } }, { "id": "2021-11-09-ux2", "url": "https://seviche.cc/2021-11-09-ux2", "title": "Requirement Gathering", "content_html": "

Overview

Types of Users and Types of Data

数据类型

定量

定性

设计师通常结合使用两种数据,这种方法称为 混合方法(Mixed method approach)

将用户视为利益相关者的三个类型

1.主要 Primary

主要利益相关者是直接使用设计的人员。这些是设计者最常与之互动的用户,他们被称为最终用户

2.二级 Secondary

不直接使用设计,但可以间接地使用,因为他们从中可得到某种输出

3.三级 Tertiary

可能根本不使用设计 ,但直接受设计的影响, 无论是消极还是积极的方式

了解利益相关者的意义

考虑二级和三级利益相关者,也可以帮助我们创造具有创新性的设计,并为我们的客户提供竞争优势。从这个意义上讲了解利益相关者会带来更好的用户体验设计

Discovery Technique Overview

四种调研方法

  1. 自然观察 (Naturalistic observation)
  2. 问卷法( Surveys)
  3. 焦点小组( Focus groups)
  4. 访谈 (Interviews)

交互程度

\"/2021-11-09-ux2/1.png\"

进行的场景

\"/2021-11-09-ux2/2.png\"

在设计周期中迭代

\"/2021-11-09-ux2/3.png\"

Naturalistic Observation 自然观察

是什么

是在用户自己的环境中,观察用户的行为,不需要询问用户何时何地以及怎样完成既定任务的 设计师会亲自到用户完成任务的地点,并观察他们的行为。

如何做

数据

定性、定量

优点

缺点

道德局限

需要注意用户隐私、匿名化

在设计流程中

基于收集到的数据,进行下一步调研,如问卷、焦点小组等,使问题域更聚焦,探寻真正的问题所在,解释用户行为的原因

Survey 问卷

是什么

如何做

数据

1.定性-封闭性问题

Closed-ended questions

2.定量-开放性问题

Open-ended questions

目的是获取用户简短的回答,包括他们的观点、偏好、态度等

优点

缺点

在设计流程中

\"下一步组织焦点小组或自然观察,深入洞察\"

Focus group 焦点小组

是什么

如何做

组成

数据

优点

缺点

在设计流程中

\"/2021-11-09-ux2/6.png\"

Interviews

是什么

目的是深度收集用户信息

如何做

数据

定性>定量

优点

缺点

在设计流程中

\"/2021-11-09-ux2/7.png\"

User Results 用户数据处理

假设:已对定性和对定量数据进行了适当的分析

描述性统计信息

Descriptive statistics

描述性统计允许我们总结定量信息 这包括数据集的范围、平均值和中值

  1. 范围
  2. 平均值
  3. 中值 (减少极端数据的影响)

用户特征表

User characteristics table

特征表以简单的形式提供了我们所有数据的快速总结,包括定量数据和定性数据

\"/2021-11-09-ux2/8.png\"

人物画像 Persona

Presenting Task Findings

Scenario

场景

场景使得我们能了解用户如何使用系统。

\"Scenario例子\"

是什么

场景提供了定性和定量数据的描述。

作用

Essential Case Study

基本案例场景

帮助理解  用户的活动以及系统的要求

\"案例\"

三个要素

1.用户的目标

2. 用户意向

3.系统的责任

Hierarchical Task Analysis

分层任务分析

最常见的任务分析技术。  使我们考虑用户当前如何完成任务。关键点是可观察的行为

\"/2021-11-09-ux2/11.png\"

Current UI Critique

当前 UI 评估

\"/2021-11-09-ux2/12.png\"

\"/2021-11-09-ux2/13.png\"

步骤

1.确定用户任务

对于每个 UI 对应的每个 UI ,你想要确定任务或者用户使用的目的是什么

2. 测定任务完成时间

要客观的测定所需时间去 响应这个客户提交的任务

在这个例子中 我选择了一些点击,它会让我使用这个应用程序,或者网站,或者通过终端完成任务。

3. 评估操作

4. 改进方法

相关资源

Resources for Requirement Gathering

  1. 25 Useful Brainstorming Techniques
  2. Non-Functional Requirements - Minimal Checklist
  3. Differentiating between Functional and Nonfunctional Requirements
  4. Facilitated Brainstorming
  5. 5 powerful ways to brainstorming with teams/
  6. ips for Structuring Better Brainstorming Sessions
  7. Collaborative Brainstorming for Better UX Workshop
  8. WHAT IS AN AFFINITY DIAGRAM?
  9. Affinity Diagrams
  10. Affinity diagramming
", "summary": "Introduction to User Experience Design|Week2", "date_published": "2021-11-09T14:26:49.000Z", "date_modified": "2022-08-06T10:49:17.439Z", "tags": [ "UX" ], "_indieweb": { "type": "article" } }, { "id": "2021-11-08-ux1", "url": "https://seviche.cc/2021-11-08-ux1", "title": "Overview of User Experience Design", "content_html": "

课程简介

这是一门在 Coursera 上的 UX 课程,共有五个星期的内容,讲了 UX 的基本概念和实际操作流程,主要侧重用户研究方面,也讲了很多和设计心理学有关的内容,很适合 UX 入门。

我将按照课程内容的划分,分别发布五个 weeks 的笔记,这是第一篇。

原笔记是在 Notion 上写的,我喜欢用 Notion 里面的 toggle 效果,用“提问”-“答案”的形式来启发思考,但是博客这里用折叠文本比较麻烦,所以直接列出来了

Week 1 笔记

什么是 ux?

User Experience design is design that is user centered。The goal is to design artifacts that allow the users to meet their needs in the most effective efficient and satisfying manner。

以用户为中心的设计。目标是轻松高效地满足用户的需求。

用户体验设计的核心概念

“用户使用界面来完成任务”

通过理解用户以及他们要完成的任务,以便设计出最好的界面 (interface)

什么是用户

这里的“用户”是指使用一些技术来达到目的的个体

什么是界面

有输入、输出、系统,于此同时,每个输入都会导致一个期望的输出:\"有输入、输出、系统,于此同时,每个输入都会导致一个期望的输出\"个人使用界面的能力与个人特征、群体、社会有关\"个人使用界面的能力与个人特征、群体、社会有关\"

用户体验设计的目标

\"/2021-11-08-ux1/3.png\"

设计“能用”并且“好用”的界面

界面设计环 (四个步骤)

\"/2021-11-08-ux1/4.png\"

Step1:Requirements Gathering 收集需求

Step 2:Design Alternatives 设计方案

Step 3:Prototyping 原型

Step 4:Evaluation 评估测试

什么样的界面是“实用”的?

“实用性”指的是有效、高效并且让用户觉得满意的。

如果用户能够理解怎样的输入 (Input) 会带来所需的输出 (Output) 那么这个界面就是实用的

好设计的三个特征

\"https://i.loli.net/2021/11/08/MtmwlyuXHGgbJPZ.png\"

1 - Affordance (示能)

指一个物理对象与人之间的关系

(无论是动物还是人类,甚至机器和机器人,他们之间发生的任何交互作用)

是指可以感知到的事物的实际属性,主要是那些决定事物如何被使用的基本属性 (看看你的智能手机,我们会看到很多种功能,例如按键,可以感知到可以被按。)

2 - Signifiers (意符)

能告诉人们正确操作方式的任何可感知的标记或声音。

示能决定可能进行哪些操作,意符则点名操作的位置。

3 - Feedback (反馈)

一些让你知道系统正在处理你的要求的方式

反馈需要将用户信息发送回来其中包括哪些系统输入的信息已发生它跟我们沟通操作结果

如何与“用户”接洽

注意礼貌、着装

三个步骤

1 - 介绍

2 - 交流

3 - 结束

", "summary": "Introduction to User Experience Design|Week1", "date_published": "2021-11-08T02:54:40.000Z", "date_modified": "2022-08-06T10:49:20.887Z", "tags": [ "UX" ], "_indieweb": { "type": "article" } }, { "id": "设计心理学1-7", "url": "https://seviche.cc/设计心理学1-7", "title": "全球商业化中的设计", "content_html": "

章前写了对本书的总结和本章的概要,提到了功能主义以及两种创新方式。

1.竞争压力

产品设计会受公司商业竞争压力的影响,这很可能会让设计偏离初衷,使市场产品变得同质化。设计受竞争压力影响的一个表现就是“功能蔓延”,它指的是为产品增加更多功能的倾向。

面对竞争压力,好的策略是“有所为,有所不为”,专心于已经占据优势的地方,让强者更强。然后聚焦于市场和宣传,大力推广已有的优势。不要盲目跟随潮流,增加无用功能。其中最重要的是关注真正在使用产品的用户的需求。

2.新技术推动变革

科技是巨大的变革动力,技术的发展改变了人们做事的方式,但人们的基本需求没有改变。

3.新产品上市需要多长时间

那些对生活有重大影响的创新,从概念到上市通常需要数十年,而且有很多的创新在上市之前就已经失败。这是因为这些创新会影响到现有的产品,会产生冲突和矛盾,用户适应新的产品也需要时间,而时间的拉长则会消耗预算和资源。“从概念到成功的产品,好点子需要旷日持久地跨越漫漫长途。”

4.创新的两种形式:渐进式和颠覆式

两种都需要

5. 设计心理学:1988 ~ 2038

6.书籍的未来

新技术的出现,让书籍、视频、音频等交互媒体的制作更加简单,这让入门变得简单,让人们更容易分享、获得信息,但这也会带来信息的泛滥,高技术水平的信息内容仍需要专业的制作。

7. 设计的道义和责任

设计应承担社会责任。作者批判了产品的废止制度,还提到可以采取订阅的方式可持续发展。

8. 设计思维和思考设计

", "summary": "《设计心理学 1》第七章——本书的总结", "date_published": "2021-11-03T03:59:18.000Z", "date_modified": "2022-08-06T10:50:07.505Z", "tags": [ "设计心理学1", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "设计心理学1-6", "url": "https://seviche.cc/设计心理学1-6", "title": "设计思维", "content_html": "

这一章和第七章读得有些仓促,加上是比较熟悉的内容,所以笔记比较简短。之后梳理概念的时候再仔细写写。

1. 解决正确的问题

设计思维的关键是要解决真正的、根本的问题。可以通过“五个为什么”等方法去确定问题。“以人为本的设计”则是可以应用于解决问题的原则之一,它强调采用满足用户需求和能力的恰当方式去解决问题,这主要是通过四个步骤:观察->创意->打样和测试。

2. 双钻设计模式

描述了设计的两个阶段:找到正确的问题,和满足用户需求

四个步骤(发散->聚焦)

  1. 发现
  2. 定义
  3. 开发
  4. 交付

3. 以人为本的设计流程

可以将以人为本的设计原则嵌入到双钻模型中,于是有“以人为本的设计流程\"https://raw.githubusercontent.com/norevi/image/main/img202110272332820.png\"

1.观察

2. 激发创意(构思)

有很多激发创意的方法,核心的原则是:

3.打样(原型)

奥兹向导”(Wizard of Oz):

4.测试

汇集小部分目标用户进行原型测试,作者一般一次单独测试 5 个人,有测试结果后改进方案,接着选择另外五个人再次测试。

另外一些提到的概念:A/B 测试、以活动为中心的设计、关键点评审

4.我刚告诉你什么?那根本行不通

实践双钻模式和以人为本的设计理念时,会遇到很多现实的冲突。比较好的解决方法是组建跨部门的联合团队,并正视挑战,精心规划设计流程。

5.设计的挑战

提到了现在设计过程中常遇到的一些问题,比如有多个互相冲突的需求、缺少团队沟通的设计改动等。还提到了包容性设计/通用设计/无障碍设计的概念和意义。

6. 复杂是好事,混乱惹麻烦

复杂跟混乱是不一样的,复杂是“内容多,但有条理”,混乱是“内容多,但没条理”。驯服复杂性最重要的原则之一,是建立一个良好的概念模型。

7.标准化和技术

建立统一的标准可以提升产品的易用性,降低人们的学习成本。但建立标准是困难的,在技术没有完善之前建立标准,会让标准容易过时,而过迟建立标准,则很难达成一致。

8.故意制作困难

在需要设计限制的产品,比如安全系统、危险设备等,需要故意制造一些使用上的困难。但这并不意味着完全放弃产品的易用性,设计需要基于具体的任务来分析,在需要限制的地方反向利用优良设计的原则,在不需要限制的地方仍然遵循原则。

  1. 隐藏关键的部位
  2. 在任务执行阶段利用不自然的匹配关系
  3. 增加操作的物理难度。
  4. 要求把握非常精确的操作时机和操作步骤。
  5. 不提供任何反馈信息。
  6. 在任务评估阶段利用不自然的匹配关系,使用户难以了解系统状态。

9. 设计:为了人类发展科技

设计是个非凡的学科

", "summary": "《设计心理学 1》第六章——设计需要探究问题的来源,而不只是停留在表面", "date_published": "2021-11-03T03:49:10.353Z", "date_modified": "2022-08-06T10:50:08.975Z", "tags": [ "设计心理学1", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "设计心理学1-5", "url": "https://seviche.cc/设计心理学1-5", "title": "人为差错?不,拙劣的设计", "content_html": "

1.何以出错

出错的原因

解决错误的方法

差错之外:故意犯错

本章主要讨论的是“无意识的犯错”

2.差错的两种类型:失误和错误

差错

差错和行动的七个阶段

\"https://uneorange.oss-cn-guangzhou.aliyuncs.com/202110232335757.png\"

失误

行动失误

1. 撷取性失误
2. 描述相似性失误
3. 功能状态失误

记忆失误

原文又称:记忆失效性失误

3.错误的分类

错误

1. 基于规则的错误(违反规则)

在复杂的情况下,太多的信息就是问题所在:信息,既支持决策,也会排斥它

2. 基于知识的错误(缺乏知识)

3. 记忆失效的错误

4.社会和习俗压力

压力

社会和习俗压力影响大,但却难观测。好的方法是“奖励安全“、培训等等

永远不要低估社会压力对个人行为的影响力量,它可能促使原本理智的人们去做他们即使知道是错误或可能危险的事情。

社会压力不断出现。它们通常很难被记录下来,因为大多数人和组织都不愿承认这些因素,所以即使在事故调查中发现有社会压力的因素,其结果也往往隐匿不见,得不到公众的仔细监督

我们需要不同的培训;我们需要奖励安全,并将其置于经济压力之上

检查清单

检查清单是个功能强大的工具,经过验证,它可以增加行为的准确性和减少差错,特别是失误和记忆失效

5、差错报告

减少差错的唯一方法就是直面差错,承认差错存在,并为减少差错而作出改变。三个案例:

自动化(JIDOKA)

来源于汽车生产系统

防呆(POKA-YOKE)

航空安全报告体系

主要是讲如何降低人们报告差错时的心理负担

  1. 匿名提交差错报告
  2. 差错真实则豁免处罚
  3. 通过第三方机构提交差错报告检查

6.甄别差错

为什么甄别差错是困难的

甄别失误

甄别错误

记忆失效性(失误/错误)

区别:

为错误辩解

人们常常忽略单一的异常情况,并试图为其辩解。但他们的辩解是基于过去经验的,可能已不适用于现状,这些“简单处理”的辩解会让他们错失挽救错误的良机。

事故分析要置身于真实情境

事故发生时

事故发生后

事故分析应

7.为差错设计

基本原则

  1. 了解差错的根本原因,通过设计以尽量减少这些诱因。
  2. 进行合理性检验。检查操作行为是否能够通过“一般性常识”的测试
  3. 设计出可以“撤销”操作的功能——“返回”以前操作,或者如果操作不能“返回”,则增加该操作的难度。
  4. 让人们易于发现一定会出的差错,以便容易纠正。
  5. 不要把操作看成是一种差错;相反,帮助操作者正确地完成动作。应该将操作认为近似于预期目的。

记忆中断

警示信号存在的问题

为差错设计的方法

研究差错

增加约束

撤销

差错信息确认

合理性检查

电子系统可以更方便的定位和确认不合理的操作,但用户并不一定能即时发现错误,在用户进行不合常规的操作时,给用户提醒、确认。

如:大额转账金额确认

减小失误

防范失误最好的办法是对正在实施的动作的特性,提供可以感受到的反馈,越是灵敏的反馈越能体现新的结果和状态,再伴之以能够撤销差错的机制

从差错到事故——瑞士奶酪模型

\"https://raw.githubusercontent.com/norevi/image/main/img202110260102695.png\"

我们应该好好思考系统,思考所有可能导致人为失误,进而酿成事故的交互因素,然后,策划出从总体上改进系统,使之更加可靠的方案。

8.良好的设计还不够

良好的设计还是难以防范人们故意犯错,差错并不全都因为设计

9.修补回复工程

resilience engineering

是什么

如何做

10.自动化的悖论

故障时结果难以估计

原因:

当自动化系统发生故障时,经常没有警告,人需要时间去注意问题、评估分析、解决问题。

悖论

能够执行那些枯燥乏味、令人厌烦的工作,但是不能做太复杂的工作。

11.应对差错的设计原则

人和机器协同应工作

“人为差错”,往往只是一种人类特性与技术需求不相符的行动

关键设计原则

", "summary": "《设计心理学 1》第五章——设计师经常严重误解人心理的局限性,要求人像机器一样工作", "date_published": "2021-10-27T12:22:17.000Z", "date_modified": "2022-08-06T10:50:11.112Z", "tags": [ "设计心理学1", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "设计心理学1-4", "url": "https://seviche.cc/设计心理学1-4", "title": "知晓:约束、可视性和反馈", "content_html": "

章前

外界的知识:

头脑中的知识

1. 四种约束因素:物理、文化、语义和逻辑

物理约束

文化约束

语义约束

逻辑约束

2. 示能、意符和约束在日常用品设计中的应用

门的问题

开关的问题

确定两类基本问题:

  1. 要控制的设备
  2. 映射

任务分析

通过对实际任务的细致观察,进行设计过程,从而设计出与实际任务最贴切的方法

以用户为中心的设计

基于对实际任务的细致观察,进行的设计过程

以活动为中心的控制

3. 引导行为的约束力

强制功能

是什么

一种物理约束,是较强约束的极端情况

作用

例子

需要用钥匙才可以开车

三种强制方式

互锁

自锁

反锁

4. 惯例、约束和示能

5. 水龙头:关于设计的历史案例

6. 利用声音作为意符

为什么

应提供什么信息:

如何提供

例子

汽车的声音是重要的意符

“拟真”(skeuomorphic)

", "summary": "《设计心理学 1》第四章——遇到一个新的设备或状况,设计师应如何提供重要信息,以便人们知道如何操作", "date_published": "2021-10-22T20:21:44.000Z", "date_modified": "2022-08-06T10:50:12.946Z", "tags": [ "设计心理学1", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "设计心理学1-3", "url": "https://seviche.cc/设计心理学1-3", "title": "头脑中的知识和外界知识", "content_html": "

并非精确行为需要的所有知识都得储存在头脑里。它可以分布在不同地方——部分在头脑里,部分在外部世界,还有一部分存在于外界约束因素之中。

1. 含糊的知识引导精确的行为

人们不需要完全精确的知识来支撑引导他们的行为。

原因:

两种类型的知识:

  1. 陈述性(是什么)->未必是真的
  2. 程序性(怎么做)->通过练习来学习

2. 记忆是储存在头脑中的知识

复杂的密码增加了人们记忆的难度,于是人们采用简单粗暴的方式来记下密码,这反而让密码不安全了。更好的方式是使用多种标识符。

3. 记忆的结构

两种记忆类型

1. 短时记忆(工作记忆)

2. 长时记忆(LTM /long-term memory)


4.近似模型:现实世界里的记忆

5.头脑中的知识

外界知识是帮助记忆的有力工具,关键是要在合适的场合、时间

前瞻记忆

仅仅指记住在未来某个时间要从事的一些活动这个记忆任务。(记住未来的某一件事)

未来记忆

指规划能力,想象未来的能力

提醒的两个层面:

  1. 信号:有件事要做
  2. 信息:这件事是什么

理想的提醒方法应兼顾两个层面

6.外界知识和头脑中知识的此消彼长

外界的知识

查找低效、初次使用时易用性高->设计可优化信息查找效率

头脑中的知识

查找高效、初次使用时易用性低(需要学习)->设计可构建合理概念模型,简化学习过程

\"https://raw.githubusercontent.com/norevi/image/main/img202110202356370.png\"

7.多个大脑里和多个设备中的记忆

8.自然映射

映射是结合外部世界与头脑里知识的最佳案例

什么是自然映射

作用于控制与被控制对象之间的,显而易见的映射关系,

三个层次的映射

  1. 最佳映射:直接对应
  2. 次好映射:位置靠近
  3. 第三好的映射:空间分布一至

糟糕的映射 VS 好的映射

9.文化与设计:自然映射随文化而异

", "summary": "《设计心理学 1》第三章——并非精确行为需要的所有知识都得储存在头脑里", "date_published": "2021-10-20T17:08:47.371Z", "date_modified": "2022-08-06T10:50:14.322Z", "tags": [ "设计心理学1", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "设计心理学1-2", "url": "https://seviche.cc/设计心理学1-2", "title": "日常行为心理学", "content_html": "

1.人们如何做事:执行与评估的鸿沟

当人们使用物品时,会面对两个心理鸿沟:执行的鸿沟、评估的鸿沟

\"https://raw.githubusercontent.com/norevi/image/main/img202110141357427.png\"

执行的鸿沟:

评估的鸿沟:

如何消除这两个心理鸿沟:

2.行动的七个阶段

行动的两个步骤

执行动作-> 评估结果(解释)

七个阶段:

\"https://raw.githubusercontent.com/norevi/image/main/img202110141358613.png\"

执行桥 (目标->外部世界)

  1. 计划
  2. 确认
  3. 执行

评估桥(外部世界->目标)

  1. 对比
  2. 诠释
  3. 感知

大部分行动不需要按顺序经历所有阶段,事件行动之间相互影响,可能会有很多行动分支

行动的分类:

日常行动中许多是机会主义的行动,没有明确的目标

“根本原因分析”

反复思索,追问背后的真实原因,如 5w

3.人的思想:潜意识主导

\"https://raw.githubusercontent.com/norevi/image/main/img202110141357967.png\"

潜意识

有意识的思维

认知和情感

4.人的认知和情感

三个层次:本能、行为、反思

本能层

行为层

反思层

高层次的反思认知可以触发低层次的情绪。低层次的情绪会引发更高层次的反思认知。

5.行动的七个阶段和大脑的三个层次

\"https://raw.githubusercontent.com/norevi/image/main/img202110202321409.png\"

心流

是什么:完全沉浸在行动中的情感

会如何:人们会忽略外部时间和空间,有身临其境的感觉

特点

6.自说自话

人们喜欢为事情建立因果关系

概念模式

7.责备错误之事

给用户有保留的预测:

习得性无助(自责循环)

积极心理学

即一种正面思考的并且自我感觉良好的文化

tips:

8.不当的自责

自责的原因

系统差错被归因为人为差错

设计师应如何

人擅长灵活的工作和创造,机器擅长准确的工作。

9.行动的七个阶段:七个基本设计原则

\"https://raw.githubusercontent.com/norevi/image/main/img202110202327901.png\"

前馈

有助于回答执行类(做)的信息->如何做?

反馈

有助于理解发生了什么的信息->发生了什么?

设计的七个基本原则

可见性、反馈、概念模型、示能、意符、映射、约束

", "summary": "《设计心理学 1》第二章——人们如何做事?当事情出错了怎么办?怎么知道要做什么?", "date_published": "2021-10-20T16:14:47.371Z", "date_modified": "2022-08-06T10:50:15.315Z", "tags": [ "设计心理学1", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "设计心理学1-1", "url": "https://seviche.cc/设计心理学1-1", "title": "日用品心理学", "content_html": "

最近参加了一个知识星球的读书打卡活动,每天需要阅读三个小节并归纳。于是第二次翻开这本书,不过这次决定在微信读书上阅读,划线标注之后再在 flomo 里写打卡内容,等读完了再放到 obsidian 中进行概念梳理。

以下在 flomo 中总结的内容

1.复杂的现代设备

  1. 工程师思维强调逻辑而容易忽略用户
  2. 需要同时考虑人和机器
  3. 不同的设计学科有不同的侧重点,列举了工业设计、体验设计、交互设计

2.以人为本的设计

  1. 以人为本的设计是一种设计理念,不是一门学科,和专业设计角色不同。
  2. 以人为本的设计通过分析用户的需求、能力和行为,用设计来满足用户的需求、能力和行为方式。
  3. 以人为本的设计需要通过观察来理解用户
  4. 快速测试可以方便地找到设计的问题所在。

3.交互设计的基本原则

示能

意符

约束

映射

反馈

4.系统映像

  1. 是指设计师提供给用户的适用信息组合,如(说明书、操作说明视频等)

  2. 是一种沟通中介,设计师通过“系统映像”给用户传递产品预设的心理模型,并期望用户基于此建立一致的心理模型。

5.科技的悖论

产品的复杂性增加,更难学难用。作者认为最好的方式是建立统一的标准。

6.设计的挑战

设计需要跨学科合作,设计管理很重要,需要兼顾平衡多方目标

", "summary": "《设计心理学 1》第一章——好设计有两个特征可视性和易通性", "date_published": "2021-10-20T12:28:00.000Z", "date_modified": "2022-08-06T10:50:16.843Z", "tags": [ "设计心理学1", "读书笔记" ], "_indieweb": { "type": "article" } }, { "id": "insight", "url": "https://seviche.cc/insight", "title": "什么是设计洞察 · What is Insight", "content_html": "

原文: thrivethinking.com

insight 不是什么

是什么

  1. 是揭示本质的

An unrecognized fundamental human truth that reveals the inner nature of things

一个未被承认的人类基本真理,揭示了事物的内在本质

  1. 是一种看待世界的新方式

A new way of viewing the world that causes us to reexamine existing conventions and challenge the status quo.

一种看待世界的新方式,使我们重新审视现有的惯例并挑战现状

  1. 是对人类行为的深入观察

A penetrating observation about human behavior thaWhat Is Insight- The 5 Principles of Insight Definitiont results in seeing consumers from a fresh perspective.

对人类行为的深入观察,导致从一个全新的角度看待消费者。

  1. 是一种对人们潜在行为的发现

A discovery about the underlying motivations that drive people’s actions.

对驱动人们行动的潜在动机的发现。

洞察(Insight)让我们去思考人们为什么做事,是什么阻碍了人们做事情,它更深入问题根源,而不是停留在表面

五大要素

语境/困境/动机/原因/设想\"https://thrivethinking.com/wp-content/uploads/2016/03/WhatIsInsight_5.jpg\"

定义 Insight 的步骤

1.设定语境/场景

A simple observation of how people behave in a given situation, what they think, what they feel, but most importantly explain what they are doing and trying to achieve

一个关于人们在给定的情境内如何想、如何做、如何感受的观察陈述。重要的是要说明他们在做什么以及想做什么。

2.沟通困境

understanding the barriers that are stopping consumers from achieving what they want to achieve with a given product, service or experience

了解消费者通过特定产品、服务或体验实现其愿望时遇到的障碍。

3. 阐明原因

An insight statement is a discovery of understanding and the identification of unmet needs to explain why something is happening the way it is

洞察力声明是对理解的发现和对未满足需求的识别,以解释为什么事情会以这样的方式发生.

You must know the reason a consumer is behaving in a particular way, and why it is happening if you are to develop a product or service that can in some way augment the behavior or change it.

你必须知道消费者以特定方式行事的原因,以及为什么会发生这种情况,如果你要开发一种产品或服务,以某种方式增强这种行为或改变它的话。

4. 捕捉动机

Discovering the underlying motivations that drive people’s actions

发现推动人们行动的潜在动机

End-users of a product or service are motivated to change by the tensions that exist in their lives.

产品或服务的终端用户被他们生活中存在的紧张关系所激励而改变

Look for tensions in four key areas: the physiological, the emotional, the cognitive and the environmental to inform your insight statements.

寻找四个关键领域的紧张关系:生理的、情感的、认知的和环境的,

5.构想理想

It is important to describe the desired end-state or situation the consumer is seeking.

描述消费者所追求的理想的最终状态或情况是很重要的

The key here is not to define a solution but clearly convey how the consumer would like the world to look and feel, what the ideal experience should be.

此处的关键在于不需要定义一个清晰的解决方案,而需要清楚地传达消费者希望世界看起来和感觉如何,理想的体验应该是什么

An excellent way to articulate this is to start with the statement “I wish there was,

一个很好的表达方式是以 “我希望有 “开始

如何表达

Think of insight definition as a three sentence journey that takes the reader through the consumer’s situation, frustration, and future desires.

将洞察定义视为一个三句话的旅程,带领读者了解消费者的情况、挫折和未来的愿望。

第一句话——描述现状和用户行为。

Having pictures around that instantly remind me of special moments and people, makes me feel good.”

“身边有照片,能让我立即想起特别的时刻和人物,让我感觉很好。”

第二句话——描述用户的困境,并说明造成困境的原因。

“But I find that pictures from my digital camera often stay hidden on my devices because I never have time to print them.”

“但是我发现我的数码相机里的照片经常被藏在我的设备里,因为我从来没有时间去打印它们。”

第三句话——描述用户期望的最终理想状况。

“I wish there was a way to enjoy them everyday without having to actively play them on my TV or computer.”

“我希望有一种方法可以每天欣赏它们,而不必在电视或电脑上主动播放它们。”

Tips

什么是一个好的’Insight’

Insight->How Might We?

需要把 insight(洞察)转变为 HMW(我们如何?)

例如:

“我们可能如何每天享受我们的记忆,而不必花时间在设备上主动播放它们?”

后记

", "date_published": "2021-10-15T15:07:14.533Z", "date_modified": "2022-08-06T10:49:42.689Z", "tags": [ "Design Thinking", "翻译" ], "_indieweb": { "type": "article" } } ] }