diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..9f11b755
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea/
diff --git a/admin/.gitignore b/admin/.gitignore
new file mode 100644
index 00000000..78a752d8
--- /dev/null
+++ b/admin/.gitignore
@@ -0,0 +1,23 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+
+package-lock.json
+yarn.lock
diff --git a/admin/src/assets/imgs/pc1.jpg b/admin/src/assets/imgs/pc1.jpg
new file mode 100644
index 00000000..b5d77f25
Binary files /dev/null and b/admin/src/assets/imgs/pc1.jpg differ
diff --git a/admin/src/views/maintain/picture/index.vue b/admin/src/views/maintain/picture/index.vue
new file mode 100644
index 00000000..a44649ab
--- /dev/null
+++ b/admin/src/views/maintain/picture/index.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/tests/unit/.eslintrc.js b/admin/tests/unit/.eslintrc.js
new file mode 100644
index 00000000..958d51ba
--- /dev/null
+++ b/admin/tests/unit/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ env: {
+ jest: true
+ }
+}
diff --git a/admin/tests/unit/components/Hamburger.spec.js b/admin/tests/unit/components/Hamburger.spec.js
new file mode 100644
index 00000000..01ea303a
--- /dev/null
+++ b/admin/tests/unit/components/Hamburger.spec.js
@@ -0,0 +1,18 @@
+import { shallowMount } from '@vue/test-utils'
+import Hamburger from '@/components/Hamburger/index.vue'
+describe('Hamburger.vue', () => {
+ it('toggle click', () => {
+ const wrapper = shallowMount(Hamburger)
+ const mockFn = jest.fn()
+ wrapper.vm.$on('toggleClick', mockFn)
+ wrapper.find('.hamburger').trigger('click')
+ expect(mockFn).toBeCalled()
+ })
+ it('prop isActive', () => {
+ const wrapper = shallowMount(Hamburger)
+ wrapper.setProps({ isActive: true })
+ expect(wrapper.contains('.is-active')).toBe(true)
+ wrapper.setProps({ isActive: false })
+ expect(wrapper.contains('.is-active')).toBe(false)
+ })
+})
diff --git a/admin/tests/unit/components/SvgIcon.spec.js b/admin/tests/unit/components/SvgIcon.spec.js
new file mode 100644
index 00000000..31467a9f
--- /dev/null
+++ b/admin/tests/unit/components/SvgIcon.spec.js
@@ -0,0 +1,22 @@
+import { shallowMount } from '@vue/test-utils'
+import SvgIcon from '@/components/SvgIcon/index.vue'
+describe('SvgIcon.vue', () => {
+ it('iconClass', () => {
+ const wrapper = shallowMount(SvgIcon, {
+ propsData: {
+ iconClass: 'test'
+ }
+ })
+ expect(wrapper.find('use').attributes().href).toBe('#icon-test')
+ })
+ it('className', () => {
+ const wrapper = shallowMount(SvgIcon, {
+ propsData: {
+ iconClass: 'test'
+ }
+ })
+ expect(wrapper.classes().length).toBe(1)
+ wrapper.setProps({ className: 'test' })
+ expect(wrapper.classes().includes('test')).toBe(true)
+ })
+})
diff --git a/admin/tests/unit/utils/formatTime.spec.js b/admin/tests/unit/utils/formatTime.spec.js
new file mode 100644
index 00000000..d07e414f
--- /dev/null
+++ b/admin/tests/unit/utils/formatTime.spec.js
@@ -0,0 +1,29 @@
+import { formatTime } from '@/utils/index.js'
+describe('Utils:formatTime', () => {
+ const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+ const retrofit = 5 * 1000
+
+ it('ten digits timestamp', () => {
+ expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
+ })
+ it('test now', () => {
+ expect(formatTime(+new Date() - 1)).toBe('刚刚')
+ })
+ it('less two minute', () => {
+ expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
+ })
+ it('less two hour', () => {
+ expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
+ })
+ it('less one day', () => {
+ expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
+ })
+ it('more than one day', () => {
+ expect(formatTime(d)).toBe('7月13日17时54分')
+ })
+ it('format', () => {
+ expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+ expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+ expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+ })
+})
diff --git a/admin/tests/unit/utils/parseTime.spec.js b/admin/tests/unit/utils/parseTime.spec.js
new file mode 100644
index 00000000..bc61d1ac
--- /dev/null
+++ b/admin/tests/unit/utils/parseTime.spec.js
@@ -0,0 +1,32 @@
+import { parseTime } from '@/utils/index.js'
+describe('Utils:parseTime', () => {
+ const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+ it('timestamp', () => {
+ expect(parseTime(d)).toBe('2018-07-13 17:54:01')
+ })
+
+ it('timestamp string', () => {
+ expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
+ })
+
+ it('ten digits timestamp', () => {
+ expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
+ })
+ it('new Date', () => {
+ expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
+ })
+ it('format', () => {
+ expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+ expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+ expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+ })
+ it('get the day of the week', () => {
+ expect(parseTime(d, '{a}')).toBe('五') // 星期五
+ })
+ it('get the day of the week', () => {
+ expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
+ })
+ it('empty argument', () => {
+ expect(parseTime()).toBeNull()
+ })
+})
diff --git a/admin/tests/unit/utils/validate.spec.js b/admin/tests/unit/utils/validate.spec.js
new file mode 100644
index 00000000..ef2efe61
--- /dev/null
+++ b/admin/tests/unit/utils/validate.spec.js
@@ -0,0 +1,28 @@
+import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets } from '@/utils/validate.js'
+describe('Utils:validate', () => {
+ it('validUsername', () => {
+ expect(validUsername('admin')).toBe(true)
+ expect(validUsername('editor')).toBe(true)
+ expect(validUsername('xxxx')).toBe(false)
+ })
+ it('validURL', () => {
+ expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false)
+ })
+ it('validLowerCase', () => {
+ expect(validLowerCase('abc')).toBe(true)
+ expect(validLowerCase('Abc')).toBe(false)
+ expect(validLowerCase('123abc')).toBe(false)
+ })
+ it('validUpperCase', () => {
+ expect(validUpperCase('ABC')).toBe(true)
+ expect(validUpperCase('Abc')).toBe(false)
+ expect(validUpperCase('123ABC')).toBe(false)
+ })
+ it('validAlphabets', () => {
+ expect(validAlphabets('ABC')).toBe(true)
+ expect(validAlphabets('Abc')).toBe(true)
+ expect(validAlphabets('123aBC')).toBe(false)
+ })
+})