Commit 5017070c authored by 周远喜's avatar 周远喜

ok

parent 42b80d5b
import request from '@/plugins/request';
export function AccountLogin (data) {
return request({
url: '/api/login',
method: 'post',
data
});
}
export function AccountRegister (data) {
return request({
url: '/api/register',
method: 'post',
data
});
}
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1457px" height="651px" viewBox="0 0 1457 651" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 54 (76480) - https://sketchapp.com -->
<title>bg</title>
<desc>Created with Sketch.</desc>
<g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="body">
<rect id="矩形" fill="#E0E3F5" transform="translate(1111.405592, 620.405592) rotate(45.000000) translate(-1111.405592, -620.405592) " x="1089.90559" y="598.905592" width="43" height="43"></rect>
<rect id="矩形" fill="#DEEFF6" transform="translate(127.254834, 230.254834) rotate(40.000000) translate(-127.254834, -230.254834) " x="95.254834" y="198.254834" width="64" height="64"></rect>
<circle id="Oval-7" fill="#C9CFED" fill-rule="nonzero" opacity="0.45" cx="1286.5" cy="27.5" r="23.5"></circle>
<circle id="Oval-7" fill="#E7E6E6" fill-rule="nonzero" opacity="0.45" cx="191" cy="51" r="18"></circle>
<circle id="Oval-7" fill="#EFEFEF" fill-rule="nonzero" opacity="0.45" cx="888" cy="499" r="18"></circle>
<ellipse id="Oval-7" stroke="#E4E4F2" stroke-width="10" fill-opacity="0" fill="#FFFFFF" fill-rule="nonzero" opacity="0.45" cx="1373.5" cy="331.5" rx="78.5" ry="79.5"></ellipse>
<ellipse id="Oval-7" stroke="#CEECF8" stroke-width="10" fill-opacity="0" fill="#FFFFFF" fill-rule="nonzero" opacity="0.45" cx="235" cy="481.5" rx="44" ry="44.5"></ellipse>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#2d8cf0" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="49" height="10"></rect>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1559281361864" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2715" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#FBD971" p-id="2716"></path><path d="M512 877.714286c-161.328762 0-292.571429-131.242667-292.571429-292.571429a24.380952 24.380952 0 0 1 48.761905 0c0 134.436571 109.372952 243.809524 243.809524 243.809524s243.809524-109.372952 243.809524-243.809524a24.380952 24.380952 0 0 1 48.761905 0c0 161.328762-131.242667 292.571429-292.571429 292.571429z" fill="#F0C419" p-id="2717"></path><path d="M390.095238 414.47619a24.380952 24.380952 0 0 1-24.380952-24.380952c0-40.326095-32.816762-73.142857-73.142857-73.142857s-73.142857 32.816762-73.142858 73.142857a24.380952 24.380952 0 0 1-48.761904 0c0-67.218286 54.686476-121.904762 121.904762-121.904762s121.904762 54.686476 121.904761 121.904762a24.380952 24.380952 0 0 1-24.380952 24.380952zM828.952381 414.47619a24.380952 24.380952 0 0 1-24.380952-24.380952c0-40.326095-32.816762-73.142857-73.142858-73.142857s-73.142857 32.816762-73.142857 73.142857a24.380952 24.380952 0 0 1-48.761904 0c0-67.218286 54.686476-121.904762 121.904761-121.904762s121.904762 54.686476 121.904762 121.904762a24.380952 24.380952 0 0 1-24.380952 24.380952z" fill="#F29C1F" p-id="2718"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1559281374840" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3076" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#FBD971" p-id="3077"></path><path d="M780.190476 877.714286a24.380952 24.380952 0 0 1-24.380952-24.380953c0-134.436571-109.372952-243.809524-243.809524-243.809523s-243.809524 109.372952-243.809524 243.809523a24.380952 24.380952 0 1 1-48.761905 0c0-161.328762 131.242667-292.571429 292.571429-292.571428s292.571429 131.242667 292.571429 292.571428a24.380952 24.380952 0 0 1-24.380953 24.380953z" fill="#F0C419" p-id="3078"></path><path d="M390.095238 414.47619a24.380952 24.380952 0 0 1-24.380952-24.380952c0-40.326095-32.816762-73.142857-73.142857-73.142857s-73.142857 32.816762-73.142858 73.142857a24.380952 24.380952 0 1 1-48.761904 0c0-67.218286 54.686476-121.904762 121.904762-121.904762s121.904762 54.686476 121.904761 121.904762a24.380952 24.380952 0 0 1-24.380952 24.380952zM828.952381 414.47619a24.380952 24.380952 0 0 1-24.380952-24.380952c0-40.326095-32.816762-73.142857-73.142858-73.142857s-73.142857 32.816762-73.142857 73.142857a24.380952 24.380952 0 1 1-48.761904 0c0-67.218286 54.686476-121.904762 121.904761-121.904762s121.904762 54.686476 121.904762 121.904762a24.380952 24.380952 0 0 1-24.380952 24.380952z" fill="#F29C1F" p-id="3079"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543867554" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16276" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M253.44 577.536c2.56 18.432 5.12 36.352 7.68 55.296 15.36-11.776 30.208-23.04 45.056-34.304-1.536-2.56-43.008-18.944-52.736-20.992zM610.304 577.536c2.56 18.432 5.12 36.352 7.68 55.296 15.36-11.776 30.208-23.04 45.056-34.304-2.048-2.56-43.008-18.944-52.736-20.992z" fill="#DD6DA6" p-id="16277"></path><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0z m-29.184 444.928h16.896c3.072 0 4.096 1.024 4.096 4.608v53.76c-6.656-0.512-13.824-0.512-20.992-1.024V444.928z m-115.712-6.656c2.048 0 3.072 1.024 3.584 3.584 1.536 17.92 3.584 35.328 5.12 53.76-6.656 1.024-13.312 1.024-20.48 1.536-2.56-19.456-5.12-38.912-7.168-58.368 6.144-0.512 12.8-0.512 18.944-0.512z m-27.648 3.072c2.56-0.512 2.048 2.048 2.56 3.584 0.512 6.656 1.536 12.8 2.048 19.456 1.024 10.24 2.56 19.968 3.584 30.208v4.096l-15.872 2.56c-3.584-18.944-6.656-37.376-10.24-56.832 5.632-1.024 11.776-2.56 17.92-3.072z m-3.072 184.32c-2.56 8.192-8.192 14.848-15.36 20.48-16.896 13.312-34.304 25.088-54.784 31.744-14.336 5.12-29.184 8.192-44.032 10.752-19.456 3.072-39.424 4.096-59.392 6.144-4.096 0.512-8.192 0-12.288 0-2.048 0-2.56-1.024-2.56-3.072 0-8.704-1.024-17.92-1.536-27.136-1.024-11.776-2.048-23.04-3.584-34.816-2.048-16.384-4.096-32.256-5.632-48.64-2.048-16.896-4.096-34.304-6.144-51.2-2.048-16.896-3.584-33.28-5.632-49.664-2.56-18.944-5.12-37.376-8.192-56.832-3.072-22.016-7.168-44.544-12.8-66.56-0.512-1.024 0.512-3.072 1.024-3.584 19.456-7.168 38.4-14.848 57.856-23.04 6.144-2.56 6.656-3.584 6.656 4.608 0 34.304 0 68.608 0.512 103.424 0.512 17.92 1.024 35.84 2.56 53.76 1.536 21.504 4.096 43.008 5.632 65.024 0 1.024 0.512 1.536 0.512 2.56 7.168-0.512 14.848-1.024 22.016-1.024 30.72-1.024 60.416 3.584 89.088 14.848 13.824 5.632 27.648 12.288 39.936 20.992 9.216 7.68 11.776 17.408 6.144 31.232z m26.112 44.544c-10.24-50.688-19.968-100.352-29.696-151.04 8.192-1.024 15.872-2.56 23.552-3.584 5.632-1.024 11.264-1.024 16.896-1.536 3.072-0.512 5.12 1.024 5.632 4.096 1.024 7.68 2.56 15.36 3.584 23.552 1.536 13.312 3.072 26.624 4.096 39.936 1.536 11.776 2.56 23.04 4.096 34.816 1.536 12.288 3.072 24.576 4.608 37.376 0.512 3.584 1.024 7.168 1.536 11.264-11.776 2.048-22.528 3.584-34.304 5.12zM445.44 655.36c-10.24-1.024-19.968-1.536-30.72-2.56-9.216-92.672-16.896-185.856-34.304-279.04 5.632-0.512 11.264-1.536 17.408-2.048 6.656-0.512 13.312-1.024 20.48-1.536 5.632-0.512 7.68 1.024 8.192 6.656l3.072 44.032c1.536 19.968 3.072 39.936 4.096 59.392 1.024 14.336 1.536 28.672 3.072 43.008 1.536 15.872 3.072 31.232 4.096 47.104 1.536 11.264 2.56 23.04 3.584 34.304 1.536 12.288 2.56 24.576 3.584 37.376l1.024 10.752c-0.512 2.56-1.536 3.072-3.584 2.56z m27.136-210.944c2.56 0 3.584 0.512 3.584 3.584-0.512 7.68 0 15.872 0 23.552v29.696c-5.632 0.512-11.264 1.024-16.384 1.536-1.536-18.944-3.072-37.888-4.608-58.368h17.408z m2.56 228.864c-1.024 0-2.56-1.536-2.56-2.56l-4.608-50.688c-1.536-15.36-3.072-31.744-4.096-47.104-1.536-15.872-3.072-32.256-4.096-48.64 0-1.024 0-2.048-0.512-3.584 4.096-1.024 7.168-1.536 11.264-1.536 10.24 0 20.48 0 30.72 0.512 2.56 0 4.096 2.048 4.096 4.096 0 5.12 0.512 10.24 0.512 15.36 0 23.552-0.512 48.128 0 71.68 0.512 19.456 1.024 37.888 1.536 57.344 0 1.024 0 2.56 0.512 4.608-11.264 0.512-22.016 1.024-32.768 0.512zM896 444.928h16.896c3.072 0 4.096 1.024 4.096 4.608v53.76c-6.656-0.512-13.824-0.512-20.992-1.024V444.928z m-116.224-6.656c2.048 0 3.072 1.024 3.584 3.584 1.536 17.92 3.584 35.328 5.12 53.76-6.656 1.024-13.312 1.024-20.48 1.536-2.56-19.456-5.12-38.912-7.168-58.368 6.656-0.512 12.8-0.512 18.944-0.512z m-27.648 3.072c2.56-0.512 2.048 2.048 2.56 3.584 0.512 6.656 1.536 12.8 2.048 19.456 1.024 10.24 2.56 19.968 3.584 30.208v4.096l-15.872 2.56c-3.584-18.944-6.656-37.376-10.24-56.832 5.632-1.024 11.776-2.56 17.92-3.072z m-3.072 183.808c-2.56 8.192-8.192 14.848-15.36 20.48-16.896 13.312-34.304 25.088-54.784 31.744-14.336 5.12-29.184 8.192-44.032 10.752-19.456 3.072-39.424 4.096-59.392 6.144-4.096 0.512-8.192 0-12.288 0-2.048 0-2.56-1.024-2.56-3.072 0-8.704-1.024-17.92-1.536-27.136-1.024-11.776-2.048-23.04-3.584-34.816-2.048-16.384-4.096-32.256-5.632-48.64-2.048-16.896-4.096-34.304-6.144-51.2-2.048-16.896-3.584-33.28-5.632-49.664-2.56-18.944-5.12-37.376-8.192-56.832-3.072-22.016-7.168-44.544-12.8-66.56 0-1.024 1.024-3.072 1.536-3.584 19.456-7.168 38.4-14.848 57.856-23.04 6.144-2.56 6.656-3.584 6.656 4.608 0 34.304 0 68.608 0.512 103.424 0.512 17.92 1.024 35.84 2.56 53.76 1.536 21.504 4.096 43.008 5.632 65.024 0 1.024 0.512 1.536 0.512 2.56 7.168-0.512 14.848-1.024 22.016-1.024 30.72-1.024 60.416 3.584 89.088 14.848 13.824 5.632 27.648 12.288 39.936 20.992 9.216 8.192 11.264 17.92 5.632 31.232z m26.112 45.056c-10.24-50.688-19.968-100.352-29.696-151.04 8.192-1.024 15.872-2.56 23.552-3.584 5.632-1.024 11.264-1.024 16.896-1.536 3.072-0.512 5.12 1.024 5.632 4.096 1.024 7.68 2.56 15.36 3.584 23.552 1.536 13.312 3.072 26.624 4.096 39.936 1.536 11.776 2.56 23.04 4.096 34.816 1.536 12.288 3.072 24.576 4.608 37.376 0.512 3.584 1.024 7.168 1.536 11.264-11.264 2.048-22.528 3.584-34.304 5.12z m83.456-14.848c-10.24-1.024-19.968-1.536-30.72-2.56-9.216-92.672-16.896-185.856-34.304-279.04 5.632-0.512 11.264-1.536 17.408-2.048 6.656-0.512 13.312-1.024 20.48-1.536 5.632-0.512 7.68 1.024 8.192 6.656l3.072 44.032c1.536 19.968 3.072 39.936 4.096 59.392 1.024 14.336 1.536 28.672 3.072 43.008 1.536 15.872 3.072 31.232 4.096 47.104 1.536 11.264 2.56 23.04 3.584 34.304 1.536 12.288 2.56 24.576 3.584 37.376l1.024 10.752c-1.024 2.56-1.536 3.072-3.584 2.56z m26.624-210.944c2.56 0 3.584 0.512 3.584 3.584-0.512 7.68 0 15.872 0 23.552v29.696c-5.632 0.512-11.264 1.024-16.384 1.536-1.536-18.944-3.072-37.888-4.608-58.368h17.408z m3.072 228.864c-1.024 0-2.56-1.536-2.56-2.56l-4.608-50.688c-1.536-15.36-3.072-31.744-4.096-47.104-1.536-15.872-3.072-32.256-4.096-48.64 0-1.024 0-2.048-0.512-3.584 4.096-1.024 7.168-1.536 11.264-1.536 10.24 0 20.48 0 30.72 0.512 2.56 0 4.096 2.048 4.096 4.096 0 5.12 0.512 10.24 0.512 15.36 0 23.552-0.512 48.128 0 71.68 0.512 19.456 1.024 37.888 1.536 57.344 0 1.024 0 2.56 0.512 4.608-11.776 0.512-22.528 1.024-32.768 0.512z" fill="#DD6DA6" p-id="16278"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543870835" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16387" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 1024C228.894118 1024 0 795.105882 0 512S228.894118 0 512 0s512 228.894118 512 512-228.894118 512-512 512" fill="#3296FA" p-id="16388"></path><path d="M733.866667 381.490196c-19.07451-8.031373-158.619608-58.227451-213.835294-79.309804C404.580392 261.019608 272.062745 197.772549 250.980392 187.733333c-3.011765-2.007843-7.027451-1.003922-10.039216 1.003922-3.011765 2.007843-3.011765 4.015686-4.015686 6.023529-7.027451 45.176471 12.047059 122.478431 49.192157 155.607843 18.070588 16.062745 253.992157 89.34902 253.992157 89.34902S291.137255 383.498039 286.117647 383.498039c-4.015686 0-6.023529 0-9.035294 3.011765-2.007843 2.007843-3.011765 5.019608-3.011765 9.035294 9.035294 50.196078 56.219608 115.45098 98.384314 123.482353 34.133333 7.027451 170.666667 2.007843 170.666667 2.007843s-165.647059 23.090196-169.662745 24.094118c-4.015686 1.003922-6.023529 3.011765-7.027451 5.019608-1.003922 2.007843-2.007843 5.019608 0 9.035294 7.027451 15.058824 29.113725 37.145098 29.113725 37.145098 45.176471 46.180392 94.368627 40.156863 106.415686 38.149019 12.047059-2.007843 44.172549-12.047059 59.231373-20.078431l-27.105882 99.388235 82.321568 1.003922-49.192157 157.615686 178.698039-225.882353h-87.341176S740.894118 510.996078 765.992157 461.803922c7.027451-13.05098 10.039216-32.12549 5.019608-46.180393-6.023529-14.054902-16.062745-25.098039-37.145098-34.133333" fill="#FFFFFF" p-id="16389"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543877388" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16609" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512.2 0.6C229.6 0.6 0.5 229.7 0.5 512.3S229.6 1024 512.2 1024s511.7-229.1 511.7-511.7S794.8 0.6 512.2 0.6z m132.7 341h-48.2c-17.6 0-29.5 3.7-35.6 11.1-6.1 7.4-9.2 18.5-9.2 33.2v58h89.9l-12 90.9h-78v233h-94v-233h-78.3v-90.9h78.3V377c0-38.1 10.6-67.6 31.9-88.6s49.6-31.5 85-31.5h0.2c30.1 0 53.4 1.2 70 3.7v81z" fill="#3162A2" p-id="16610"></path><path d="M551.7 534.8h0.1v233h-0.1zM561 352.7c-6.1 7.4-9.2 18.4-9.2 33.2v58h89.9l-12 90.9h0.1l12-90.9h-89.9v-58c0-14.7 3.1-25.8 9.1-33.2 6.2-7.4 18.1-11.1 35.7-11.1h-0.1c-17.6 0-29.5 3.7-35.6 11.1zM574.9 256.9c30 0 53.3 1.2 69.8 3.7v81h0.2v-81c-16.6-2.5-39.9-3.7-70-3.7z" fill="#FFFFFF" p-id="16611"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543883733" class="icon" style="" viewBox="0 0 1272 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16831" xmlns:xlink="http://www.w3.org/1999/xlink" width="496.875" height="400"><defs><style type="text/css"></style></defs><path d="M729.64116345 165.27693991L634.32650881 90.125l-99.5625 78.52693991-5.17887981 4.16056009 104.74137981 83.50215546 105.09051682-83.50215546-9.77586218-7.53556009z m361.21228445 291.47198236l-456.78879245 360.19396555-456.49784537-359.99030128L110.125 511.12715547l523.93965546 413.11745671 524.23060335-413.35021555-67.44181091-54.14547436z m-456.78879245 29.21120673L385.4784479 290.00646554 318.06573237 344.12284454l315.96982771 249.16810336 316.28987101-249.40086136-67.41271555-54.14547436-248.84806008 196.21551682z" fill="#006cff" p-id="16832"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543886845" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16941" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#18ACFC" p-id="16942"></path><path d="M500.113 228.39c118.396-1.518 178.924 61.004 201 156 3.497 15.048 0.15 34.807 0 50 27.142999999999997 5.682 33.087 60.106 10 75v1h1c8.26 14.329999999999998 19.04 28.125 26 44 7.332 16.723 9.305999999999997 35.16 14 55 4.024 17.01-2.287 51.505-10 57-0.7709999999999998 0.683-2.231 1.312-3 2-14.601-3.016-30.377-16.865-38-27-3.065-4.074-5.275-9.672-10-12-0.395 21.568-12.503 41.15-22 55-3.5139999999999993 5.123-14.073 13.216999999999999-14 18 3.690999999999999 2.836 8.305 2.955999999999999 13 5 10.513 4.577 25.449 13.168 32 22 2.333999999999999 3.146 5.548 7.554999999999999 7 11 16.193 38.414-36.527 48.314-63 54-27.185 5.839-77.818-10.224-92-19-8.749-5.414-16.863-18.573-29-19-3.666 2.3889999999999993-14.438 1.1319999999999997-20 1-16.829 32.804-101.913 47.867999999999995-148 31-14.061-5.146-43.398-17.695-38-40 4.437-18.327 19.947-29.224 35-37 5.759-2.975 18.915-4.419 22-10-13.141-8.988-24.521-28.659-31-44-3.412-8.077-4.193-25.775-9-32-7.789 12.245-32.097 36.91-52 33-3.071-4.553-7.213-9.097-9-15-4.792-15.835-1.81-40.379 2-54 8.117-29.02 16.965-50.62299999999998 32-72 4.672-6.643 11.425-12.135 16-19-8.945-9.733-6.951-37.535999999999994-1-49 4.002-7.709 9.701-7.413 10-20-1.9199999999999997-3.022-0.071-8.604-1-13-4.383-20.75 3.2729999999999997-47.552 9-63 19.8-53.420999999999985 53.712-90.466 105-112 11.986-5.033 25.833-7.783 39-11 5.322-1.3 11.969 0.518 16-2z" fill="#FFFFFF" p-id="16943"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543874130" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16498" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M511.8 0.6C229.2 0.6 0.1 229.7 0.1 512.3S229.2 1024 511.8 1024s511.7-229.1 511.7-511.7S794.4 0.6 511.8 0.6z m264.9 375.3c-0.1 0.1-0.1 0.2-0.2 0.3h0.2c-9 14.3-21.2 28.8-34.2 39.2-5.2 4.2-10.5 8.3-15.7 12.5v0.5c0.3 22.9-0.3 44.9-4.7 64.2-25.2 113.1-91.9 189.9-197.4 222.8-37.9 11.8-99.1 16.7-142.6 5.9-21.5-5.4-41-11.4-59.3-19.3-10.2-4.5-19.6-9.3-28.5-14.7l-8.8-5.3h0.5l-0.5-0.3c9.8 0.2 21.3 2.9 32.2 1.2 9.8-1.6 19.6-1.2 28.7-3.2 22.7-5 43-11.6 60.4-21.8 8.3-4.8 20.9-10.6 27-17.6-11.2 0.2-21.4-2.4-29.7-5.4-32.6-11.5-51.6-32.7-63.9-64.4h0.1c0-0.1-0.1-0.2-0.1-0.3 9.7 1.1 37.3 3.5 44.6-1.7-12.3-0.8-24.1-7.9-32.5-13.2-26.2-16.4-47.6-43.9-47.4-86.2v-0.3c3.5 1.7 6.9 3.3 10.4 4.9 6.6 2.8 13.3 4.3 21.1 5.9 3.1 0.7 9.3 2.4 13.2 1.4-5.1-5.8-13.2-9.7-18.4-16-14.7-18.3-28.7-45.3-25.2-77.4 0.5-4.7 1.3-9.4 2.6-14.3 2.5-9.7 6.5-18.3 10.8-26.2 0.2 0.1 0.4 0.2 0.5 0.3 2 4.2 6.4 7.2 9.1 10.6 8.6 10.7 19.2 20.3 30 28.7 36.7 28.7 69.9 46.4 123.1 59.5 13.5 3.3 29 5.9 45.1 5.9-1.9-5.8-2.7-13-2.7-20.5 0-9.7 1.3-19.6 3.3-26.8 9-32.1 28.5-55.2 57-67.6 6.9-3 14.4-5.2 22.4-6.9 4.1-0.6 8.2-1.1 12.3-1.6 39-0.7 59.8 13.5 79.6 31.6 16.8-1.4 38.7-10.8 51.6-17.4l12.6-6.9c0 0.1-0.1 0.3-0.1 0.4l0.1-0.1c-7.3 19.9-17.4 35.5-32.7 47.3-3.1 2.4-6.3 5.6-10 7.4 21.5-0.4 39.3-10 56.1-15.4v0.3z" fill="#2EB1EB" p-id="16499"></path><path d="M719.7 391.1s0.1 0 0.1-0.1c0 0-0.1 0-0.1 0.1zM726.8 428.4v-0.5 0.5zM336.4 479.9c3.3 0.7 9.9 2.7 13.8 1.2h-0.5l-0.1-0.1c-3.9 1-10.1-0.7-13.2-1.4-7.8-1.6-14.5-3.1-21.1-5.9-3.5-1.6-6.9-3.2-10.4-4.9v0.3c3.4 1.6 6.9 3.2 10.4 4.9 6.6 2.8 13.3 4.3 21.1 5.9zM719.6 391.4v0.2c21.9-0.2 39.8-10.1 56.9-15.4 0.1-0.1 0.1-0.2 0.2-0.3v-0.3c-16.9 5.3-34.6 14.9-56.1 15.4-0.4 0.1-0.7 0.3-1 0.4zM584.8 337.5c6.9-3 14.4-5.1 22.4-6.9 4.1-0.6 8.2-1 12.3-1.6 39-0.7 59.8 13.5 79.6 31.6 16.8-1.4 38.7-10.8 51.6-17.4 4.2-2.3 8.3-4.5 12.5-6.8 0-0.1 0.1-0.3 0.1-0.4l-12.6 6.9c-12.9 6.6-34.8 16-51.6 17.4-19.8-18.1-40.6-32.3-79.6-31.6-4.1 0.5-8.2 1-12.3 1.6-8 1.7-15.5 3.9-22.4 6.9-28.5 12.4-48 35.5-57 67.6-2 7.2-3.4 17.2-3.3 26.8 0-9.6 1.3-19.4 3.3-26.5 9-32.1 28.5-55.2 57-67.6zM385.2 568.5h-0.4c-7.3 5.2-34.9 2.8-44.6 1.7 0 0.1 0.1 0.2 0.1 0.3 10 1.1 38.2 3.6 44.9-2zM319.4 347.4c0.1 0.1 0.3 0.1 0.5 0.3 2 4.1 6.3 7.1 9.1 10.6 8.6 10.6 19.2 20.2 30 28.7 36.8 28.7 69.9 46.4 123.1 59.5 13.5 3.3 29.1 5.9 45.2 5.9 0-0.1-0.1-0.2-0.1-0.3-16.1 0-31.6-2.6-45.1-5.9-53.2-13.1-86.4-30.8-123.1-59.5-10.8-8.4-21.4-18-30-28.7-2.7-3.4-7.1-6.4-9.1-10.6-0.1-0.1-0.3-0.2-0.5-0.3-4.3 7.9-8.3 16.5-10.8 26.2-1.3 4.9-2.1 9.6-2.6 14.3 0.5-4.6 1.3-9.2 2.6-14 2.5-9.7 6.5-18.3 10.8-26.2zM317.7 683.2c9.9-1.6 19.6-1.2 28.7-3.2 22.8-5 43-11.6 60.4-21.8 8.4-4.9 21.3-10.8 27.3-17.9h-0.3c-6.1 7-18.7 12.8-27 17.6-17.4 10.2-37.7 16.8-60.4 21.8-9.1 2-18.9 1.6-28.7 3.2-10.9 1.7-22.4-1-32.2-1.2l0.5 0.3c9.7 0.4 21 2.9 31.7 1.2z" fill="#FFFFFF" p-id="16500"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543880869" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16720" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 1024C229.228089 1024 0 794.771911 0 512S229.228089 0 512 0s512 229.228089 512 512-229.228089 512-512 512z m-87.813689-750.933333C314.1632 273.066667 221.866667 351.744 221.866667 444.7232c0 57.230222 31.9488 103.708444 78.085689 135.896178h3.549866l-21.2992 60.802844 74.5472-39.344355h3.549867c21.2992 10.729244 42.587022 14.301867 63.886222 14.301866h14.199467c-3.549867-14.301867-7.099733-28.603733-7.099734-42.9056 0-85.833956 81.635556-157.354667 184.570312-157.354666h7.099733C608.756622 337.442133 527.132444 273.066667 424.186311 273.066667z m370.676622 306.722133c0-78.654578-78.791111-143.018667-171.906844-143.018667-96.688356 0-171.895467 64.364089-171.895467 143.018667s78.791111 143.018667 171.895467 143.018667c17.908622 0 35.817244-3.584 53.725867-7.156623h3.572622l60.882489 32.176356-17.908623-53.623467c46.557867-28.603733 71.634489-67.925333 71.634489-114.414933z" fill="#5FC948" p-id="16721"></path><path d="M358.4 420.977778a28.444444 28.444444 0 1 1 0-56.888889 28.444444 28.444444 0 0 1 0 56.888889z m204.8 136.533333a28.444444 28.444444 0 1 1 0-56.888889 28.444444 28.444444 0 0 1 0 56.888889z m-68.266667-136.533333a28.444444 28.444444 0 1 1 0-56.888889 28.444444 28.444444 0 0 1 0 56.888889z m193.422223 136.533333a28.444444 28.444444 0 1 1 0-56.888889 28.444444 28.444444 0 0 1 0 56.888889z" fill="#5FC948" p-id="16722"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543890412" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17052" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#F1DF55" p-id="17053"></path><path d="M455.876923 589.784615c-6.892308-2.953846-15.753846 0.984615-19.692308 7.876923-3.938462 6.892308-1.969231 14.769231 4.923077 17.723077 6.892308 2.953846 16.738462 0 20.676923-7.876923 2.953846-6.892308 0.984615-14.769231-5.907692-17.723077zM410.584615 608.492308c-18.707692-7.876923-42.338462 0-53.16923 17.723077-11.815385 17.723077-5.907692 39.384615 11.815384 47.261538 18.707692 8.861538 43.323077 0 54.153846-17.723077 10.830769-17.723077 4.923077-39.384615-12.8-47.261538z" fill="#FFFFFF" p-id="17054"></path><path d="M426.338462 466.707692C307.2 478.523077 217.6 551.384615 225.476923 629.169231c7.876923 77.784615 110.276923 131.938462 229.415385 120.123077 119.138462-11.815385 208.738462-84.676923 200.861538-162.461539-7.876923-78.769231-110.276923-131.938462-229.415384-120.123077zM530.707692 649.846154c-24.615385 55.138462-93.538462 83.692308-152.615384 64.984615-57.107692-18.707692-81.723077-74.830769-56.123077-126.030769 24.615385-50.215385 88.615385-77.784615 144.738461-63.015385 58.092308 15.753846 88.615385 70.892308 64 124.061539z" fill="#FFFFFF" p-id="17055"></path><path d="M692.184615 490.338462c-9.846154-2.953846-16.738462-4.923077-11.815384-18.707693 11.815385-28.553846 12.8-54.153846 0-71.876923-23.630769-33.476923-86.646154-31.507692-160.492308-0.984615 0 0-22.646154 9.846154-16.738461-7.876923 10.830769-36.430769 9.846154-65.969231-7.876924-83.692308-39.384615-39.384615-144.738462 1.969231-235.323076 91.569231-66.953846 67.938462-106.338462 139.815385-106.338462 201.846154 0 118.153846 151.630769 191.015385 301.292308 191.015384 194.953846 0 324.923077-113.230769 324.923077-202.830769-0.984615-55.138462-46.276923-85.661538-87.63077-98.461538zM454.892308 748.307692c-119.138462 11.815385-221.538462-42.338462-229.415385-120.123077-7.876923-77.784615 82.707692-150.646154 200.861539-162.461538 119.138462-11.815385 221.538462 42.338462 229.415384 120.123077 7.876923 78.769231-82.707692 151.630769-200.861538 162.461538z" fill="#D52B2A" p-id="17056"></path><path d="M822.153846 272.738462c-47.261538-52.184615-116.184615-71.876923-181.169231-58.092308-14.769231 2.953846-24.615385 17.723077-20.676923 32.492308 2.953846 14.769231 17.723077 24.615385 32.492308 20.676923 45.292308-9.846154 94.523077 4.923077 128 41.353846 33.476923 37.415385 42.338462 87.630769 28.553846 131.938461-4.923077 14.769231 2.953846 29.538462 17.723077 34.461539s29.538462-2.953846 34.461539-17.723077c19.692308-62.030769 6.892308-132.923077-39.384616-185.107692z" fill="#E89214" p-id="17057"></path><path d="M738.461538 444.061538c12.8 3.938462 25.6-2.953846 29.538462-14.76923 9.846154-30.523077 3.938462-64.984615-19.692308-90.584616-22.646154-25.6-57.107692-35.446154-87.630769-28.553846-12.8 2.953846-20.676923 15.753846-17.723077 28.553846 2.953846 12.8 15.753846 20.676923 27.569231 17.723077 15.753846-2.953846 31.507692 1.969231 43.323077 13.784616 10.830769 12.8 13.784615 29.538462 9.846154 44.307692-3.938462 11.815385 2.953846 25.6 14.76923 29.538461z" fill="#E89214" p-id="17058"></path><path d="M466.707692 526.769231c-56.123077-14.769231-120.123077 13.784615-144.738461 63.015384-24.615385 51.2-0.984615 107.323077 56.123077 126.03077 59.076923 18.707692 128.984615-9.846154 152.615384-64.984616 24.615385-54.153846-5.907692-109.292308-64-124.061538zM423.384615 655.753846c-11.815385 18.707692-36.430769 26.584615-54.153846 17.723077-18.707692-7.876923-23.630769-29.538462-11.815384-47.261538 11.815385-17.723077 35.446154-25.6 53.16923-17.723077 17.723077 7.876923 23.630769 29.538462 12.8 47.261538z m37.415385-48.246154c-3.938462 6.892308-13.784615 10.830769-20.676923 7.876923-6.892308-2.953846-8.861538-10.830769-4.923077-17.723077s12.8-9.846154 19.692308-7.876923c7.876923 2.953846 9.846154 10.830769 5.907692 17.723077z" fill="#040000" p-id="17059"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543863835" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16165" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 1024C229.236364 1024 0 794.763636 0 512S229.236364 0 512 0s512 229.236364 512 512-229.236364 512-512 512z m-129.861818-756.48s-36.212364 2.094545-48.989091 24.482909c-12.8 22.365091-54.318545 137.378909-54.318546 137.378909s13.847273 6.376727 37.28291-10.658909c23.435636-17.035636 30.882909-46.848 30.882909-46.848l42.589091-2.117818 1.070545 121.390545s-73.495273-1.070545-88.413091 0c-14.894545 1.047273-23.412364 40.448-23.412364 40.448h111.825455s-9.588364 67.095273-38.353455 116.084364c-28.741818 48.989091-83.060364 87.319273-83.060363 87.319273s39.424 15.965091 77.730909-6.4c38.353455-22.341818 66.629818-120.692364 66.629818-120.692364l89.925818 110.056727s8.192-52.386909-1.466182-67.188363c-9.658182-14.778182-62.208-74.286545-62.208-74.286546l-22.946909 20.247273 16.337455-65.117091h97.954909s0-38.353455-19.153455-40.494545c-19.176727-2.094545-78.801455 0-78.801454 0V371.898182h88.389818s-1.070545-39.400727-18.106182-39.400727h-143.755636l22.341818-64.954182z m169.984 61.184v358.562909h36.002909l13.102545 45.009455 63.348364-45.009455h89.064727V328.704h-201.518545z" fill="#0f84fd" p-id="16166"></path><path d="M594.781091 368.64h117.899636v277.876364h-41.890909l-53.364363 40.261818-11.636364-40.261818h-11.008V368.64z" fill="#0f84fd" p-id="16167"></path></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<template>
<GlobalFooter class="i-copyright" :links="links" :copyright="copyright" />
</template>
<script>
export default {
name: 'i-copyright',
data () {
return {
links: [
{
title: '官网',
key: '官网',
href: 'https://iview.design',
blankTarget: true
},
{
title: '社区',
key: '社区',
href: 'https://dev.iviewui.com',
blankTarget: true
},
{
title: '专业版',
key: '专业版',
href: 'https://pro.iviewui.com',
blankTarget: true
}
],
copyright: 'Copyright © 2019 北京视图更新科技有限公司'
}
}
}
</script>
<style lang="less">
.i-copyright{
flex: 0 0 auto;
}
</style>
<template>
<a
:href="linkUrl"
:target="target"
class="i-link"
:class="{ 'i-link-color': !linkColor }"
@click.exact="handleClickItem($event, false)"
@click.ctrl="handleClickItem($event, true)"
@click.meta="handleClickItem($event, true)"
><slot></slot></a>
</template>
<script>
import mixinsLink from 'view-design/src/mixins/link';
export default {
name: 'i-link',
mixins: [ mixinsLink ],
props: {
disabled: {
type: Boolean,
default: false
},
// 开启后,链接颜色为默认的蓝色,默认关闭为继承效果
linkColor: {
type: Boolean,
default: false
}
},
methods: {
handleClickItem (event, new_window = false) {
if (this.disabled) return;
this.handleCheckClick(event, new_window);
}
}
}
</script>
<style lang="less">
.i-link{
cursor: pointer;
&-color{
&, &:hover, &:active{
color: inherit;
}
}
}
</style>
<template>
<div class="i-mde" :class="classes">
<textarea ref="mde"></textarea>
</div>
</template>
<script>
import SimpleMDE from 'simplemde';
import 'simplemde/dist/simplemde.min.css';
export default {
name: 'i-mde',
props: {
value: {
type: String,
default: ''
},
border: {
type: Boolean,
default: false
},
// 配置参数
config: {
type: Object,
default: () => ({})
}
},
data () {
return {
// 编辑器实例
mde: null,
// 编辑器默认参数
// 详见 https://github.com/sparksuite/simplemde-markdown-editor#configuration
defaultConfig: {
}
}
},
computed: {
classes () {
return [
{
'i-mde-no-border': !this.border
}
];
}
},
methods: {
// 初始化
init () {
// 合并参数
const config = Object.assign({}, this.defaultConfig, this.config);
// 初始化
this.mde = new SimpleMDE({
...config,
// 初始值
initialValue: this.value,
// 挂载元素
element: this.$refs.mde
});
this.mde.codemirror.on('change', () => {
this.$emit('input', this.mde.value());
this.$emit('on-change', this.mde.value());
});
},
// 增加内容
add (val) {
if (this.mde) {
this.mde.value(this.value + val);
}
},
// 替换内容
replace (val) {
if (this.mde) {
this.mde.value(val);
}
}
},
mounted () {
// 初始化
this.init();
},
beforeDestroy () {
// 在组件销毁后销毁实例
this.mde = null;
}
}
</script>
<style lang="less">
.i-mde{
.editor-toolbar.fullscreen, .CodeMirror-fullscreen, .editor-preview-side{
z-index: 100;
border-radius: 0;
}
&-no-border{
.editor-toolbar{
border: none;
border-bottom: 1px solid #e8eaec;
}
.CodeMirror{
border: none;
}
.editor-preview-side{
border: none;
border-left: 1px solid #e8eaec;
}
}
}
</style>
<template>
<div class="i-quill" :class="classes">
<div ref="editor" :style="styles"></div>
</div>
</template>
<script>
import Quill from 'quill';
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
export default {
name: 'i-quill',
props: {
value: {
type: String,
default: ''
},
border: {
type: Boolean,
default: false
},
height: {
type: Number
},
minHeight: {
type: Number
}
},
data () {
return {
Quill: null,
currentValue: '',
options: {
theme: 'snow',
bounds: document.body,
debug: 'warn',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'color': [] }, { 'background': [] }],
['blockquote', 'code-block'],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
// [{ 'script': 'sub' }, { 'script': 'super' }],
[{ 'indent': '-1' }, { 'indent': '+1' }],
[{ 'align': [] }],
[{ 'direction': 'rtl' }],
// [{ 'font': [] }],
['clean'],
['link', 'image']
]
},
placeholder: '内容...',
readOnly: false
}
}
},
computed: {
classes () {
return [
{
'i-quill-no-border': !this.border
}
];
},
styles () {
let style = {};
if (this.minHeight) {
style.minHeight = `${this.minHeight}px`;
}
if (this.height) {
style.height = `${this.height}px`;
}
return style;
}
},
watch: {
value: {
handler (val) {
if (val !== this.currentValue) {
this.currentValue = val;
if (this.Quill) {
this.Quill.pasteHTML(this.value);
}
}
},
immediate: true
}
},
methods: {
init () {
const editor = this.$refs.editor;
// 初始化编辑器
this.Quill = new Quill(editor, this.options);
// 默认值
this.Quill.pasteHTML(this.currentValue);
// 绑定事件
this.Quill.on('text-change', (delta, oldDelta, source) => {
const html = this.$refs.editor.children[0].innerHTML;
const text = this.Quill.getText();
const quill = this.Quill;
// 更新内部的值
this.currentValue = html;
// 发出事件 v-model
this.$emit('input', html);
// 发出事件
this.$emit('on-change', { html, text, quill });
});
// 将一些 quill 自带的事件传递出去
this.Quill.on('text-change', (delta, oldDelta, source) => {
this.$emit('on-text-change', delta, oldDelta, source);
});
this.Quill.on('selection-change', (range, oldRange, source) => {
this.$emit('on-selection-change', range, oldRange, source);
});
this.Quill.on('editor-change', (eventName, ...args) => {
this.$emit('on-editor-change', eventName, ...args);
});
}
},
mounted () {
this.init();
},
beforeDestroy () {
// 在组件销毁后销毁实例
this.Quill = null;
}
}
</script>
<style lang="less">
.i-quill-no-border{
.ql-toolbar.ql-snow{
border: none;
border-bottom: 1px solid #e8eaec;
}
.ql-container.ql-snow{
border: none;
}
}
</style>
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import store from '@/store/index';
import Languages from '@/i18n/locale';
store.dispatch('admin/i18n/getLocale');
const locale = store.state.admin.i18n.locale;
Vue.use(VueI18n);
export default new VueI18n({
locale,
messages: Languages
});
// 导入自有语言包
import zhCN from './locale/zh-CN';
import enUS from './locale/en-US';
// 导入 iView 语言包
import zhCNiView from 'view-design/dist/locale/zh-CN';
import enUSiView from 'view-design/dist/locale/en-US';
// 导入布局语言包
import layoutLocale from '@/layouts/basic-layout/i18n';
// 合并语言包
export default {
'zh-CN': Object.assign(zhCN, zhCNiView, layoutLocale['zh-CN']),
'en-US': Object.assign(enUS, enUSiView, layoutLocale['en-US'])
};
export default {
locale: 'en-US',
language: 'English',
menu: {
i18n: 'Internationalization'
},
page: {
login: {
title: 'Login',
remember: 'Remember me',
forgot: 'Forgot your password?',
submit: 'Login',
other: 'Sign in with',
signup: 'Sign up'
},
register: {
title: 'Register',
submit: 'Register',
other: 'Already have an account?'
},
exception: {
e403: 'Sorry, you don\'t have access to this page.',
e404: 'Sorry, the page you visited does not exist.',
e500: 'Sorry, the server is reporting an error.',
btn: 'Back to home'
},
i18n: {
content: 'Hello, nice to meet you!'
}
}
}
export default {
locale: 'zh-CN',
language: '简体中文',
menu: {
i18n: '多语言'
},
page: {
login: {
title: '登录',
remember: '自动登录',
forgot: '忘记密码',
submit: '登录',
other: '其它登录方式',
signup: '注册账户'
},
register: {
title: '注册',
submit: '注册',
other: '使用已有账户登录'
},
exception: {
e403: '抱歉,你无权访问该页面',
e404: '抱歉,你访问的页面不存在',
e500: '抱歉,服务器出错了',
btn: '返回首页'
},
i18n: {
content: '你好,很高兴认识你!'
}
}
}
<template>
<Breadcrumb class="i-layout-header-breadcrumb" v-if="!isLimit" ref="breadcrumb">
<BreadcrumbItem>
<i-menu-head-title :item="topItem" :hide-icon="!showBreadcrumbIcon" />
</BreadcrumbItem>
<BreadcrumbItem v-for="item in items" :key="item.path">
<i-menu-head-title :item="item" :hide-icon="!showBreadcrumbIcon" />
</BreadcrumbItem>
<BreadcrumbItem>
<i-menu-head-title :item="siderMenuObject[activePath]" :hide-icon="!showBreadcrumbIcon" />
</BreadcrumbItem>
</Breadcrumb>
</template>
<script>
import { mapState } from 'vuex';
import menuSider from '@/menu/sider';
import { flattenSiderMenu } from '@/libs/system';
import iMenuHeadTitle from '../menu-head/title';
import { on, off } from 'view-design/src/utils/dom';
import { findComponentUpward, getStyle } from 'view-design/src/utils/assist';
import { throttle } from 'lodash';
export default {
name: 'iHeaderBreadcrumb',
components: { iMenuHeadTitle },
computed: {
...mapState('admin/layout', [
'showBreadcrumbIcon',
'menuCollapse'
]),
...mapState('admin/menu', [
'openNames',
'activePath',
'header',
'headerName'
]),
siderMenuObject () {
let obj = {};
this.allSiderMenu.forEach(item => {
if ('path' in item) {
obj[item.path] = item;
}
});
return obj;
},
items () {
let items = [...this.openNames];
let newItems = [];
items.forEach(i => {
newItems.push(this.siderMenuObject[i]);
});
return newItems;
},
// 第一级,默认是 menu/header.js 中的第一项
topItem () {
return this.header.find(item => item.name === this.headerName);
}
},
data () {
return {
// 得到所有侧边菜单,并转为平级,查询图标及显示对应内容
allSiderMenu: flattenSiderMenu(menuSider, []),
handleResize: () => {},
isLimit: false,
maxWidth: 560,
breadcrumbWidth: 0
}
},
methods: {
handleCheckWidth () {
const $header = findComponentUpward(this, 'Header');
if ($header) {
const headerWidth = parseInt(getStyle($header.$el, 'width'));
this.$nextTick(() => {
this.isLimit = headerWidth - this.maxWidth <= this.breadcrumbWidth;
});
}
},
handleGetWidth () {
this.isLimit = false;
this.$nextTick(() => {
const $breadcrumb = this.$refs.breadcrumb;
if ($breadcrumb) {
this.breadcrumbWidth = parseInt(getStyle($breadcrumb.$el, 'width'));
}
});
}
},
watch: {
topItem: {
handler () {
this.handleGetWidth();
this.handleCheckWidth();
},
deep: true
},
items: {
handler () {
this.handleGetWidth();
this.handleCheckWidth();
},
deep: true
},
activePath: {
handler () {
this.handleGetWidth();
this.handleCheckWidth();
},
deep: true
}
},
mounted () {
this.handleResize = throttle(this.handleCheckWidth, 100, { leading: false });
on(window, 'resize', this.handleResize);
this.handleGetWidth();
this.handleCheckWidth();
},
beforeDestroy () {
off(window, 'resize', this.handleResize);
}
}
</script>
<template>
<span class="i-layout-header-trigger" :class="{ 'i-layout-header-trigger-min': showReload }" @click="handleToggleMenuSide">
<Icon custom="i-icon i-icon-menu-unfold" v-show="menuCollapse || isMobile" />
<Icon custom="i-icon i-icon-menu-fold" v-show="!menuCollapse && !isMobile" />
</span>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
name: 'iHeaderCollapse',
computed: {
...mapState('admin/layout', [
'isMobile',
'isTablet',
'isDesktop',
'menuCollapse',
'showReload'
])
},
methods: {
...mapMutations('admin/layout', [
'updateMenuCollapse'
]),
// 展开/收起侧边栏
handleToggleMenuSide (state) {
if (this.isMobile) {
this.updateMenuCollapse(false);
this.$emit('on-toggle-drawer', state);
} else {
this.updateMenuCollapse(!this.menuCollapse);
}
}
},
watch: {
// 切换页面时,在移动端自动收起侧边栏
// 强行传参 false 是因为有的路由不是在菜单栏发生的,toggle 会使其显示
'$route' () {
if (this.isMobile) this.handleToggleMenuSide(false);
},
// 在平板时自动收起菜单
isTablet (state) {
if (!this.isMobile && state) this.updateMenuCollapse(true);
},
// 在桌面时自动展开菜单
isDesktop (state) {
if (!this.isMobile && state) this.updateMenuCollapse(false);
}
}
}
</script>
<template>
<span class="i-layout-header-trigger i-layout-header-trigger-min" @click="toggleFullscreen">
<Icon custom="i-icon i-icon-full-screen" v-show="!isFullscreen" />
<Icon custom="i-icon i-icon-exit-full-screen" v-show="isFullscreen" />
</span>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
name: 'iHeaderFullscreen',
computed: {
...mapState('admin/layout', [
'isFullscreen'
])
},
methods: {
...mapActions('admin/layout', [
'toggleFullscreen'
])
}
}
</script>
<template>
<span class="i-layout-header-trigger i-layout-header-trigger-min">
<Dropdown :trigger="isMobile ? 'click' : 'hover'" class="i-layout-header-i18n" :class="{ 'i-layout-header-user-mobile': isMobile }" @on-click="handleClick">
<Icon type="md-globe" />
<DropdownMenu slot="list">
<DropdownItem v-for="(item, key) in languages" :key="key" :name="key" :selected="locale === key">
<span>{{ item.language }}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</span>
</template>
<script>
import Languages from '@/i18n/locale';
import { mapState, mapActions } from 'vuex';
export default {
name: 'iHeaderI18n',
data () {
return {
languages: Languages
}
},
computed: {
...mapState('admin/i18n', [
'locale'
]),
...mapState('admin/layout', [
'isMobile'
])
},
methods: {
...mapActions('admin/i18n', [
'setLocale'
]),
handleClick (locale) {
if (locale === this.locale) return;
this.setLocale({ locale, vm: this });
}
}
}
</script>
<template>
<Tooltip :content="tooltipContent" transfer>
<span class="i-layout-header-trigger i-layout-header-trigger-min" @click="handleOpenLog">
<Badge :count="lengthError === 0 ? null : lengthError" :overflow-count="99" :dot="showDot" :offset="showDot ? [26, 2] : [20, 0]">
<Icon custom="i-icon i-icon-record" />
</Badge>
</span>
</Tooltip>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'iHeaderLog',
computed: {
...mapGetters('admin/log', [
'length',
'lengthError'
]),
showDot () {
return !!this.length && this.lengthError === 0;
},
tooltipContent () {
if (!this.length) {
return '没有日志或异常';
} else {
let text = `${this.length} 条日志`;
if (this.lengthError) text += ` | 包含 ${this.lengthError} 个异常`;
return text;
}
}
},
methods: {
handleOpenLog () {
this.$router.push({
name: 'log'
});
}
}
}
</script>
<template>
<i-link class="i-layout-header-logo" :class="{ 'i-layout-header-logo-stick': !isMobile }" to="/">
<img src="@/assets/images/logo-small.png" v-if="isMobile">
<img src="@/assets/images/logo.png" v-else-if="headerTheme === 'light'">
<img src="@/assets/images/logo-dark.png" v-else>
</i-link>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'iHeaderLogo',
computed: {
...mapState('admin/layout', [
'isMobile',
'headerTheme'
])
}
}
</script>
<template>
<span class="i-layout-header-trigger i-layout-header-trigger-min i-layout-header-trigger-in">
<Notification
:wide="isMobile"
:badge-props="badgeProps"
class="i-layout-header-notice"
:class="{ 'i-layout-header-notice-mobile': isMobile }">
<Icon slot="icon" custom="i-icon i-icon-notification" />
<NotificationTab title="通知">
</NotificationTab>
<NotificationTab title="消息">
</NotificationTab>
<NotificationTab title="待办">
</NotificationTab>
</Notification>
</span>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'iHeaderNotice',
data () {
return {
badgeProps: {
offset: [20, 0]
}
}
},
computed: {
...mapState('admin/layout', [
'isMobile'
])
}
}
</script>
<template>
<span class="i-layout-header-trigger" :class="{ 'i-layout-header-trigger-min': showSiderCollapse }" @click="handleReload">
<Icon custom="i-icon i-icon-refresh" />
</span>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'iHeaderReload',
computed: {
...mapState('admin/layout', [
'isMobile',
'showSiderCollapse'
])
},
methods: {
handleReload () {
this.$emit('on-reload');
}
}
}
</script>
<template>
<span v-if="isDesktop" class="i-layout-header-trigger i-layout-header-trigger-min i-layout-header-trigger-in i-layout-header-trigger-nohover">
<input class="i-layout-header-search" type="text" :placeholder="$t('basicLayout.search.placeholder')">
</span>
<span v-else class="i-layout-header-trigger i-layout-header-trigger-min">
<Dropdown trigger="click" class="i-layout-header-search-drop" ref="dropdown">
<Icon type="ios-search" />
<DropdownMenu slot="list">
<div class="i-layout-header-search-drop-main">
<Input size="large" prefix="ios-search" type="text" :placeholder="$t('basicLayout.search.placeholder')" />
<span class="i-layout-header-search-drop-main-cancel" @click="handleCloseSearch">{{ $t('basicLayout.search.cancel') }}</span>
</div>
</DropdownMenu>
</Dropdown>
</span>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'iHeaderSearch',
computed: {
...mapState('admin/layout', [
'isDesktop',
'headerMenu'
])
},
methods: {
handleCloseSearch () {
this.$refs.dropdown.handleClick();
}
}
}
</script>
This diff is collapsed.
<template>
<span class="i-layout-header-trigger i-layout-header-trigger-min">
<Dropdown :trigger="isMobile ? 'click' : 'hover'" class="i-layout-header-user" :class="{ 'i-layout-header-user-mobile': isMobile }" @on-click="handleClick">
<Avatar size="small" :src="info.avatar" v-if="info.avatar" />
<span class="i-layout-header-user-name" v-if="!isMobile">{{ info.name }}</span>
<DropdownMenu slot="list">
<i-link to="/setting/user">
<DropdownItem>
<Icon type="ios-contact-outline" />
<span>{{ $t('basicLayout.user.center') }}</span>
</DropdownItem>
</i-link>
<i-link to="/setting/account">
<DropdownItem>
<Icon type="ios-settings-outline" />
<span>{{ $t('basicLayout.user.setting') }}</span>
</DropdownItem>
</i-link>
<DropdownItem divided name="logout">
<Icon type="ios-log-out" />
<span>{{ $t('basicLayout.user.logOut') }}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</span>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
name: 'iHeaderUser',
computed: {
...mapState('admin/user', [
'info'
]),
...mapState('admin/layout', [
'isMobile',
'logoutConfirm'
])
},
methods: {
...mapActions('admin/account', [
'logout'
]),
handleClick (name) {
if (name === 'logout') {
this.logout({
confirm: this.logoutConfirm,
vm: this
});
}
}
}
}
</script>
// 默认布局使用的多语言
export default {
'zh-CN': {
basicLayout: {
search: {
placeholder: '搜索...',
cancel: '取消'
},
user: {
center: '个人中心',
setting: '设置',
logOut: '退出登录'
},
logout: {
confirmTitle: '退出登录确认',
confirmContent: '您确定退出登录当前账户吗?打开的标签页和个人设置将会保存。'
},
tabs: {
left: '关闭左侧',
right: '关闭右侧',
other: '关闭其它',
all: '全部关闭'
}
}
},
'en-US': {
basicLayout: {
search: {
placeholder: 'Search...',
cancel: 'Cancel'
},
user: {
center: 'My home',
setting: 'Setting',
logOut: 'Log out'
},
logout: {
confirmTitle: 'Logout confirmation',
confirmContent: 'Are you sure you are logged out of your current account? Open tabs and personal settings will be saved.'
},
tabs: {
left: 'Close left',
right: 'Close right',
other: 'Close other',
all: 'Close all'
}
}
}
}
This diff is collapsed.
<template>
<div class="i-layout-menu-head" :class="{ 'i-layout-menu-head-mobile': isMobile }">
<Menu mode="horizontal" :active-name="headerName" v-if="!isMobile && !isMenuLimit" ref="menu">
<MenuItem v-for="item in filterHeader" :to="item.path" :replace="item.replace" :target="item.target" :name="item.name" :key="item.path">
<i-menu-head-title :item="item" />
</MenuItem>
</Menu>
<div class="i-layout-header-trigger i-layout-header-trigger-min i-layout-header-trigger-in i-layout-header-trigger-no-height" v-else>
<Dropdown trigger="click" :class="{ 'i-layout-menu-head-mobile-drop': isMobile }">
<Icon type="ios-apps" />
<DropdownMenu slot="list">
<i-link v-for="item in filterHeader" :to="item.path" :replace="item.replace" :target="item.target" :key="item.path">
<DropdownItem>
<i-menu-head-title :item="item" />
</DropdownItem>
</i-link>
</DropdownMenu>
</Dropdown>
</div>
</div>
</template>
<script>
import iMenuHeadTitle from './title';
import { mapState, mapGetters } from 'vuex';
import { getStyle } from 'view-design/src/utils/assist';
import { on, off } from 'view-design/src/utils/dom';
import { throttle } from 'lodash';
export default {
name: 'iMenuHead',
components: { iMenuHeadTitle },
computed: {
...mapState('admin/layout', [
'isMobile'
]),
...mapState('admin/menu', [
'headerName'
]),
...mapGetters('admin/menu', [
'filterHeader'
])
},
data () {
return {
handleResize: () => {},
isMenuLimit: false,
menuMaxWidth: 0 // 达到这个值后,menu 就显示不下了
}
},
methods: {
handleGetMenuHeight () {
const menuWidth = parseInt(getStyle(this.$el, 'width'));
const $menu = this.$refs.menu;
if ($menu) {
const menuHeight = parseInt(getStyle(this.$refs.menu.$el, 'height'));
if (menuHeight > 64) {
if (!this.isMenuLimit) {
this.menuMaxWidth = menuWidth;
}
this.isMenuLimit = true;
}
} else if (menuWidth >= this.menuMaxWidth) {
this.isMenuLimit = false;
}
}
},
watch: {
filterHeader () {
this.handleGetMenuHeight();
},
isMobile () {
this.handleGetMenuHeight();
}
},
mounted () {
this.handleResize = throttle(this.handleGetMenuHeight, 100, { leading: false });
on(window, 'resize', this.handleResize);
this.handleGetMenuHeight();
},
beforeDestroy () {
off(window, 'resize', this.handleResize);
}
}
</script>
<template>
<div class="i-layout-menu-head-title">
<span class="i-layout-menu-head-title-icon" v-if="(item.icon || item.custom || item.img) && !hideIcon">
<Icon :type="item.icon" v-if="item.icon" />
<Icon :custom="item.custom" v-else-if="item.custom" />
<img :src="item.img" v-else-if="item.img" />
</span>
<span class="i-layout-menu-head-title-text">{{ tTitle(item.title) }}</span>
</div>
</template>
<script>
/**
* 该组件除了 Menu,也被 Breadcrumb 使用过
* */
import tTitle from '../mixins/translate-title';
export default {
name: 'iMenuHeadTitle',
mixins: [ tTitle ],
props: {
item: {
type: Object,
default () {
return {}
}
},
hideIcon: {
type: Boolean,
default: false
}
}
}
</script>
<template>
<div>
<div class="i-layout-sider-logo" :class="{ 'i-layout-sider-logo-dark': siderTheme === 'dark' }">
<transition name="fade-quick">
<i-link to="/" v-show="!hideLogo">
<img src="@/assets/images/logo-small.png" v-if="menuCollapse">
<img src="@/assets/images/logo.png" v-else-if="siderTheme === 'light'">
<img src="@/assets/images/logo-dark.png" v-else>
</i-link>
</transition>
</div>
<Menu
ref="menu"
class="i-layout-menu-side i-scrollbar-hide"
:theme="siderTheme"
:accordion="menuAccordion"
:active-name="activePath"
:open-names="openNames"
width="auto">
<template v-if="!menuCollapse" v-for="(item, index) in filterSider">
<i-menu-side-item v-if="item.children === undefined || !item.children.length" :menu="item" :key="index" />
<i-menu-side-submenu v-else :menu="item" :key="index" />
</template>
<template v-else>
<Tooltip :content="tTitle(item.title)" placement="right" v-if="item.children === undefined || !item.children.length" :key="index">
<i-menu-side-item :menu="item" hide-title />
</Tooltip>
<i-menu-side-collapse v-else :menu="item" :key="index" top-level />
</template>
</Menu>
</div>
</template>
<script>
import iMenuSideItem from './menu-item';
import iMenuSideSubmenu from './submenu';
import iMenuSideCollapse from './menu-collapse';
import tTitle from '../mixins/translate-title';
import { mapState, mapGetters } from 'vuex';
export default {
name: 'iMenuSide',
mixins: [ tTitle ],
components: { iMenuSideItem, iMenuSideSubmenu, iMenuSideCollapse },
props: {
hideLogo: {
type: Boolean,
default: false
}
},
computed: {
...mapState('admin/layout', [
'siderTheme',
'menuAccordion',
'menuCollapse'
]),
...mapState('admin/menu', [
'activePath',
'openNames'
]),
...mapGetters('admin/menu', [
'filterSider'
])
},
watch: {
'$route': {
handler () {
this.handleUpdateMenuState();
},
immediate: true
},
// 在展开/收起侧边菜单栏时,更新一次 menu 的状态
menuCollapse () {
this.handleUpdateMenuState();
}
},
methods: {
handleUpdateMenuState () {
this.$nextTick(() => {
if (this.$refs.menu) {
this.$refs.menu.updateActiveName();
if (this.menuAccordion) this.$refs.menu.updateOpened();
}
});
}
}
}
</script>
<template>
<Dropdown placement="right-start" :class="dropdownClasses">
<li :class="menuItemClasses" v-if="topLevel">
<i-menu-side-title :menu="menu" hide-title />
</li>
<DropdownItem v-else>
<i-menu-side-title :menu="menu" :selected="openNames.indexOf(menu.path) >= 0" />
<Icon type="ios-arrow-forward" class="i-layout-menu-side-arrow" />
</DropdownItem>
<DropdownMenu slot="list">
<div class="i-layout-menu-side-collapse-title" v-if="showCollapseMenuTitle">
<i-menu-side-title :menu="menu" />
</div>
<template v-for="(item, index) in menu.children">
<i-link :to="item.path" :target="item.target" v-if="item.children === undefined || !item.children.length" :key="index">
<DropdownItem :divided="item.divided" :class="{ 'i-layout-menu-side-collapse-item-selected': item.path === activePath }">
<i-menu-side-title :menu="item" />
</DropdownItem>
</i-link>
<i-menu-side-collapse v-else :menu="item" :key="index" />
</template>
</DropdownMenu>
</Dropdown>
</template>
<script>
import iMenuSideTitle from './menu-title';
import { mapState } from 'vuex';
export default {
name: 'iMenuSideCollapse',
components: { iMenuSideTitle },
props: {
menu: {
type: Object,
default () {
return {}
}
},
// 是否是第一级,区分在于左侧和展开侧
topLevel: {
type: Boolean,
default: false
}
},
computed: {
...mapState('admin/layout', [
'siderTheme',
'showCollapseMenuTitle'
]),
...mapState('admin/menu', [
'activePath',
'openNames'
]),
dropdownClasses () {
return {
'i-layout-menu-side-collapse-top': this.topLevel,
'i-layout-menu-side-collapse-dark': this.siderTheme === 'dark'
}
},
menuItemClasses () {
return [
'ivu-menu-item i-layout-menu-side-collapse-top-item',
{
'ivu-menu-item-selected ivu-menu-item-active': this.openNames.indexOf(this.menu.path) >= 0 // -active 在高亮时,有背景
}
]
}
}
}
</script>
<template>
<div>
<MenuItem :to="menu.path" :replace="menu.replace" :target="menu.target" :name="menu.path">
<i-menu-side-title :menu="menu" :hide-title="hideTitle" />
</MenuItem>
</div>
</template>
<script>
import iMenuSideTitle from './menu-title';
export default {
name: 'iMenuSideItem',
components: { iMenuSideTitle },
props: {
menu: {
type: Object,
default () {
return {}
}
},
hideTitle: {
type: Boolean,
default: false
}
}
}
</script>
<template>
<span class="i-layout-menu-side-title">
<span class="i-layout-menu-side-title-icon" :class="{ 'i-layout-menu-side-title-icon-single': hideTitle }" v-if="menu.icon || menu.custom || menu.img">
<Icon :type="menu.icon" v-if="menu.icon" />
<Icon :custom="menu.custom" v-else-if="menu.custom" />
<img :src="menu.img" v-else-if="menu.img" />
</span>
<span class="i-layout-menu-side-title-text" :class="{ 'i-layout-menu-side-title-text-selected': selected }" v-if="!hideTitle">{{ tTitle(menu.title) }}</span>
</span>
</template>
<script>
import tTitle from '../mixins/translate-title';
export default {
name: 'iMenuSideTitle',
mixins: [ tTitle ],
props: {
menu: {
type: Object,
default () {
return {}
}
},
hideTitle: {
type: Boolean,
default: false
},
// 用于侧边栏收起 Dropdown 当前高亮
selected: {
type: Boolean,
default: false
}
}
}
</script>
<template>
<Submenu :name="menu.path">
<template slot="title">
<i-menu-side-title :menu="menu" />
</template>
<template v-for="(item, index) in menu.children">
<i-menu-side-item v-if="item.children === undefined || !item.children.length" :menu="item" :key="index" />
<i-menu-side-submenu v-else :menu="item" :key="index" />
</template>
</Submenu>
</template>
<script>
import iMenuSideItem from './menu-item';
import iMenuSideTitle from './menu-title';
export default {
name: 'iMenuSideSubmenu',
components: { iMenuSideItem, iMenuSideTitle },
props: {
menu: {
type: Object,
default () {
return {}
}
}
}
}
</script>
export default {
methods: {
tTitle (title) {
if (title && title.indexOf('$t:') === 0) {
return this.$t(title.split('$t:')[1]);
} else {
return title;
}
}
}
}
<template>
<div class="i-layout-tabs" :class="classes" :style="styles">
<div class="i-layout-tabs-main">
<Tabs
type="card"
:value="current"
:animated="false"
closable
@on-click="handleClickTab"
@on-tab-remove="handleClickClose"
>
<TabPane
v-for="page in opened"
:key="page.fullPath"
:label="(h) => tabLabel(h, page)"
:name="page.fullPath" />
</Tabs>
<Dropdown class="i-layout-tabs-close" @on-click="handleClose">
<div class="i-layout-tabs-close-main">
<Icon type="ios-arrow-down" />
</div>
<DropdownMenu slot="list">
<DropdownItem name="left">
<Icon type="md-arrow-back" />
{{ $t('basicLayout.tabs.left') }}
</DropdownItem>
<DropdownItem name="right">
<Icon type="md-arrow-forward" />
{{ $t('basicLayout.tabs.right') }}
</DropdownItem>
<DropdownItem name="other">
<Icon type="md-close" />
{{ $t('basicLayout.tabs.other') }}
</DropdownItem>
<DropdownItem name="all">
<Icon type="md-close-circle" />
{{ $t('basicLayout.tabs.all') }}
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import menuSider from '@/menu/sider';
import tTitle from '../mixins/translate-title';
import Setting from '@/setting';
import { getAllSiderMenu } from '@/libs/system';
export default {
name: 'iTabs',
mixins: [ tTitle ],
computed: {
...mapState('admin/page', [
'opened',
'current'
]),
...mapState('admin/layout', [
'showTabsIcon',
'tabsFix',
'headerFix',
'headerStick',
'isMobile',
'menuCollapse'
]),
...mapGetters('admin/menu', [
'hideSider'
]),
classes () {
return {
'i-layout-tabs-fix': this.tabsFix
}
},
isHeaderStick () {
return this.hideSider;
},
styles () {
let style = {};
if (this.tabsFix && !this.headerFix) {
style.top = `${64 - this.scrollTop}px`;
}
const menuWidth = this.isHeaderStick ? 0 : this.menuCollapse ? 80 : Setting.menuSideWidth;
if (!this.isMobile && this.tabsFix) {
style.width = `calc(100% - ${menuWidth}px)`;
style.left = `${menuWidth}px`;
}
return style;
}
},
data () {
return {
// 得到所有侧边菜单,并转为平级,查询图标用
allSiderMenu: getAllSiderMenu(menuSider),
scrollTop: 0
}
},
methods: {
...mapActions('admin/page', [
'close',
'closeLeft',
'closeRight',
'closeOther',
'closeAll'
]),
tabLabel (h, page) {
const title = h('span', this.tTitle(page.meta.title) || '未命名');
let slot = [];
if (this.showTabsIcon) {
const fullPathWithoutQuery = page.fullPath.indexOf('?') >= 0 ? page.fullPath.split('?')[0] : page.fullPath;
const currentMenu = this.allSiderMenu.find(menu => menu.path === fullPathWithoutQuery) || {};
let icon;
if (currentMenu.icon) {
icon = h('Icon', {
props: {
type: currentMenu.icon
}
});
} else if (currentMenu.custom) {
icon = h('Icon', {
props: {
custom: currentMenu.custom
}
});
} else if (currentMenu.img) {
icon = h('img', {
attrs: {
src: currentMenu.img
}
});
}
if (icon) slot.push(icon);
slot.push(title);
} else {
slot.push(title);
}
return h('div', {
class: 'i-layout-tabs-title'
}, slot);
},
handleClickTab (tabName) {
const page = this.opened.find(page => page.fullPath === tabName);
const { name, params, query } = page;
if (page) this.$router.push({ name, params, query }, () => {});
},
handleClickClose (tagName) {
this.close({
tagName
});
},
handleScroll () {
if (this.tabsFix && !this.headerFix) {
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop;
this.scrollTop = scrollTop > 64 ? 64 : scrollTop;
}
},
handleClose (name) {
const params = {
pageSelect: this.current
};
switch (name) {
case 'left':
this.closeLeft(params);
break;
case 'right':
this.closeRight(params);
break;
case 'other':
this.closeOther(params);
break;
case 'all':
this.closeAll();
break;
}
}
},
mounted () {
document.addEventListener('scroll', this.handleScroll, { passive: true });
this.handleScroll();
},
beforeDestroy () {
document.removeEventListener('scroll', this.handleScroll);
}
}
</script>
<template>
<div>
<nuxt />
</div>
<Main/>
</template>
<style>
html {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: border-box;
margin: 0;
}
.button--green {
display: inline-block;
border-radius: 4px;
border: 1px solid #3b8070;
color: #3b8070;
text-decoration: none;
padding: 10px 30px;
}
.button--green:hover {
color: #fff;
background-color: #3b8070;
}
.button--grey {
display: inline-block;
border-radius: 4px;
border: 1px solid #35495e;
color: #35495e;
text-decoration: none;
padding: 10px 30px;
margin-left: 15px;
<script>
import Main from "./basic-layout/index.vue"
export default {
components:{
Main
}
}
</script>
.button--grey:hover {
color: #fff;
background-color: #35495e;
<style lang="less">
html,body,#__layout,#__nuxt{
height: 100%;
}
</style>
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
// 生成随机字符串
export default function (len = 32) {
const $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
const maxPos = $chars.length;
let str = '';
for (let i = 0; i < len; i++) {
str += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return str;
}
/**
* 系统内置方法集,正常情况下您不应该修改或移除此文件
* */
import { cloneDeep } from 'lodash';
/**
* @description 根据当前路由,找打顶部菜单名称
* @param {String} currentPath 当前路径
* @param {Array} menuList 所有路径
* */
function getHeaderName (currentPath, menuList) {
const allMenus = [];
menuList.forEach(menu => {
const headerName = menu.header || '';
const menus = transferMenu(menu, headerName);
allMenus.push({
path: menu.path,
header: headerName
});
menus.forEach(item => allMenus.push(item));
});
const currentMenu = allMenus.find(item => item.path === currentPath);
return currentMenu ? currentMenu.header : null;
}
function transferMenu (menu, headerName) {
if (menu.children && menu.children.length) {
return menu.children.reduce((all, item) => {
all.push({
path: item.path,
header: headerName
});
const foundChildren = transferMenu(item, headerName);
return all.concat(foundChildren);
}, []);
} else {
return [menu];
}
}
export { getHeaderName };
/**
* @description 根据当前顶栏菜单 name,找到对应的二级菜单
* @param {Array} menuList 所有的二级菜单
* @param {String} headerName 当前顶栏菜单的 name
* */
function getMenuSider (menuList, headerName = '') {
if (headerName) {
return menuList.filter(item => item.header === headerName);
} else {
return menuList;
}
}
export { getMenuSider };
/**
* @description 根据当前路由,找到其所有父菜单 path,作为展开侧边栏 open-names 依据
* @param {String} currentPath 当前路径
* @param {Array} menuList 所有路径
* */
function getSiderSubmenu (currentPath, menuList) {
const allMenus = [];
menuList.forEach(menu => {
const menus = transferSubMenu(menu, []);
allMenus.push({
path: menu.path,
openNames: []
});
menus.forEach(item => allMenus.push(item));
});
const currentMenu = allMenus.find(item => item.path === currentPath);
return currentMenu ? currentMenu.openNames : [];
}
function transferSubMenu (menu, openNames) {
if (menu.children && menu.children.length) {
const itemOpenNames = openNames.concat([menu.path]);
return menu.children.reduce((all, item) => {
all.push({
path: item.path,
openNames: itemOpenNames
});
const foundChildren = transferSubMenu(item, itemOpenNames);
return all.concat(foundChildren);
}, []);
} else {
return [menu].map(item => {
return {
path: item.path,
openNames: openNames
}
});
}
}
export { getSiderSubmenu };
/**
* @description 递归获取所有子菜单
* */
function getAllSiderMenu (menuList) {
let allMenus = [];
menuList.forEach(menu => {
if (menu.children && menu.children.length) {
const menus = getMenuChildren(menu);
menus.forEach(item => allMenus.push(item));
} else {
allMenus.push(menu);
}
});
return allMenus;
}
function getMenuChildren (menu) {
if (menu.children && menu.children.length) {
return menu.children.reduce((all, item) => {
const foundChildren = getMenuChildren(item);
return all.concat(foundChildren);
}, []);
} else {
return [menu];
}
}
export { getAllSiderMenu };
/**
* @description 将菜单转为平级
* */
function flattenSiderMenu (menuList, newList) {
menuList.forEach(menu => {
let newMenu = {};
for (let i in menu) {
if (i !== 'children') newMenu[i] = cloneDeep(menu[i]);
}
newList.push(newMenu);
menu.children && flattenSiderMenu(menu.children, newList);
});
return newList;
}
export { flattenSiderMenu };
/**
* @description 判断列表1中是否包含了列表2中的某一项
* 因为用户权限 access 为数组,includes 方法无法直接得出结论
* */
function includeArray (list1, list2) {
let status = false;
list2.forEach(item => {
if (list1.includes(item)) status = true;
});
return status;
}
export { includeArray };
import Cookies from 'js-cookie';
import Setting from '@/setting';
const cookies = {};
/**
* @description 存储 cookie 值
* @param {String} name cookie name
* @param {String} value cookie value
* @param {Object} cookieSetting cookie setting
*/
cookies.set = function (name = 'default', value = '', cookieSetting = {}) {
let currentCookieSetting = {
expires: Setting.cookiesExpires
};
Object.assign(currentCookieSetting, cookieSetting);
Cookies.set(`admin-${name}`, value, currentCookieSetting);
};
/**
* @description 拿到 cookie 值
* @param {String} name cookie name
*/
cookies.get = function (name = 'default') {
return Cookies.get(`admin-${name}`);
};
/**
* @description 拿到 cookie 全部的值
*/
cookies.getAll = function () {
return Cookies.get();
};
/**
* @description 删除 cookie
* @param {String} name cookie name
*/
cookies.remove = function (name = 'default') {
return Cookies.remove(`admin-${name}`);
};
export default cookies;
import low from 'lowdb';
import LocalStorage from 'lowdb/adapters/LocalStorage';
const adapter = new LocalStorage('admin');
const db = low(adapter);
db
.defaults({
sys: {},
database: {}
})
.write();
export default db;
import cookies from './util.cookies';
import log from './util.log';
import db from './util.db';
import Setting from '@/setting';
const util = {
cookies,
log,
db
};
function tTitle (title) {
if (window && window.$t) {
if (title.indexOf('$t:') === 0) {
return window.$t(title.split('$t:')[1]);
} else {
return title;
}
} else {
return title;
}
}
/**
* @description 更改标题
* @param {Object} title 标题
* @param {Object} count 未读消息数提示(可视情况选择使用或不使用)
*/
util.title = function ({ title, count }) {
title = tTitle(title);
let fullTitle = title ? `${title} - ${Setting.titleSuffix}` : Setting.titleSuffix;
if (count) fullTitle = `(${count}条消息)${fullTitle}`;
window.document.title = fullTitle;
};
function requestAnimation (task) {
if ('requestAnimationFrame' in window) {
return window.requestAnimationFrame(task);
}
setTimeout(task, 16);
}
export { requestAnimation };
export default util;
const log = {};
/**
* @description 返回这个样式的颜色值
* @param {String} type 样式名称 [ primary | success | warning | error | text ]
*/
function typeColor (type = 'default') {
let color = '';
switch (type) {
case 'default': color = '#515a6e'; break;
case 'primary': color = '#2d8cf0'; break;
case 'success': color = '#19be6b'; break;
case 'warning': color = '#ff9900'; break;
case 'error': color = '#ed4014'; break;
default:; break
}
return color;
}
/**
* @description 打印一个 [ title | text ] 样式的信息
* @param {String} title title text
* @param {String} info info text
* @param {String} type style
*/
log.capsule = function (title, info, type = 'primary') {
console.log(
`%c ${title} %c ${info} %c`,
'background:#35495E; padding: 1px; border-radius: 3px 0 0 3px; color: #fff;',
`background:${typeColor(type)}; padding: 1px; border-radius: 0 3px 3px 0; color: #fff;`,
'background:transparent'
);
};
/**
* @description 打印彩色文字
*/
log.colorful = function (textArr) {
console.log(
`%c${textArr.map(t => t.text || '').join('%c')}`,
...textArr.map(t => `color: ${typeColor(t.type)};`)
);
};
/**
* @description 打印 default 样式的文字
*/
log.default = function (text) {
log.colorful([{ text }]);
};
/**
* @description 打印 primary 样式的文字
*/
log.primary = function (text) {
log.colorful([{ text, type: 'primary' }]);
};
/**
* @description 打印 success 样式的文字
*/
log.success = function (text) {
log.colorful([{ text, type: 'success' }]);
};
/**
* @description 打印 warning 样式的文字
*/
log.warning = function (text) {
log.colorful([{ text, type: 'warning' }]);
};
/**
* @description 打印 error 样式的文字
*/
log.error = function (text) {
log.colorful([{ text, type: 'error' }]);
};
export default log;
// 菜单,顶栏
export default [
{
path: '/',
title: '首页',
icon: 'md-home',
hideSider: false,
name: 'home'
},
{
path: '/log',
title: '日志',
icon: 'md-locate',
hideSider: true,
name: 'system'
}
];
const pre = '/dashboard/';
export default {
path: '/dashboard',
title: 'Dashboard',
header: 'home',
icon: 'md-speedometer',
children: [
{
path: `${pre}console`,
title: '主控台'
}
]
}
export default {
path: '/log',
title: '前端日志',
header: 'system',
icon: 'md-locate'
}
// 菜单,侧边栏
import dashboard from './modules/dashboard';
// 系统
import log from './modules/log';
export default [
dashboard,
log
];
/**
* 通用混合
* */
export default {
methods: {
// 当 $route 更新时触发
appRouteChange (to, from) {
}
}
}
import Mock from 'mockjs';
import qs from 'qs';
import withCredentials from './patch/withCredentials';
/* 补丁 */
withCredentials(Mock);
/* Mock 默认配置 */
Mock.setup({ timeout: '200-300' });
/* 扩展 [生成器] */
const Generator = (prop, template) => {
const obj = {};
obj[prop] = [template];
return Mock.mock(obj);
};
/* 扩展 [循环] */
const Repeat = (num, itemTemplate) => Generator(`data|${num}`, itemTemplate).data;
const CustomExtends = {
Generator,
Repeat,
Mock,
Random: Mock.Random
};
const extend = (prop, value) => {
CustomExtends[prop] = value;
};
/* 装配配置组 */
const wired = ({ url, type, body }) => ({
method: type,
params: qs.parse(url.split('?').length > 1 ? url.split('?')[1] : ''),
body: JSON.parse(body),
url: qs.parse(url.split('?')[0]),
...CustomExtends
});
const setup = (path, method, handle) => {
Mock.mock(
RegExp(path),
method,
typeof handle === 'function' ? o => handle(wired(o)) : handle
)
};
const load = (collection) => {
collection.map(({ path, method, handle }) => {
if (method === '*') {
method = [
'get',
'head',
'post',
'put',
'delete',
'connect',
'options',
'trace',
'patch'
]
}
if (typeof method === 'string' && method.indexOf('|') > -1) method = method.split('|')
if (method instanceof Array) {
method.map(item => setup(path, item, handle))
} else {
setup(path, method, handle)
}
})
};
export default { setup, load, extend };
export default function (Mock) {
// http://cnine.me/note/FrontEnd/mock-lose-cookies-dbg.html
Mock.XHR.prototype.__send = Mock.XHR.prototype.send;
Mock.XHR.prototype.send = function () {
if (this.custom.xhr) this.custom.xhr.withCredentials = this.withCredentials || false;
this.__send.apply(this, arguments);
}
}
const userDB = [
{
username: 'admin',
password: 'admin',
uuid: 'admin-uuid',
info: {
name: 'Aresn',
avatar: 'https://dev-file.iviewui.com/userinfoPDvn9gKWYihR24SpgC319vXY8qniCqj4/avatar',
access: ['admin']
}
}
];
export default [
{
path: '/api/login',
method: 'post',
handle ({ body }) {
const user = userDB.find(e => e.username === body.username && e.password === body.password);
if (user) {
return {
code: 0,
msg: '登录成功',
data: {
...user,
token: 'A68NUPaXVBJYRStwvd9frcUn8rlf30h6'
}
}
} else {
return {
code: 401,
msg: '用户名或密码错误',
data: {}
}
}
}
},
{
path: '/api/register',
method: 'post',
handle ({ body }) {
return {
code: 0,
msg: '注册成功',
data: {
username: 'admin',
uuid: 'admin-uuid',
info: {
name: 'Aresn',
avatar: 'https://dev-file.iviewui.com/userinfoPDvn9gKWYihR24SpgC319vXY8qniCqj4/avatar',
access: ['admin']
},
token: 'A68NUPaXVBJYRStwvd9frcUn8rlf30h6'
}
}
}
}
]
import adminMock from './admin-mock';
const req = context => context.keys().map(context);
const options = req(require.context('./api/', true, /\.js$/))
.filter(e => e.default)
.map(e => e.default);
options.forEach(option => {
adminMock.load(option);
});
......@@ -32,7 +32,8 @@ export default {
** Plugins to load before mounting the App
*/
plugins: [
'@/plugins/iview'
'@/plugins/iview',
'@/plugins/admin'
],
/*
** Nuxt.js dev-modules
......
......@@ -12,11 +12,25 @@
},
"dependencies": {
"@nuxtjs/axios": "^5.3.6",
"nuxt": "^2.0.0",
"view-design": "^4.0.2",
"js-cookie": "^2.2.1",
"nuxt": "^2.0.0",
"less": "^3.10.3",
"less-loader": "^5.0.0"
"less-loader": "^5.0.0",
"better-scroll": "^1.12.1",
"dayjs": "^1.8.9",
"echarts": "^4.1.0",
"view-design": "^4.0.0",
"js-cookie": "^2.2.0",
"lodash": "^4.17.10",
"lowdb": "^1.0.0",
"marked": "^0.3.9",
"mockjs": "^1.0.1-beta3",
"qs": "^6.6.0",
"quill": "^1.3.6",
"screenfull": "^4.0.0",
"simplemde": "^1.11.2",
"ua-parser-js": "^0.7.18",
"vue-i18n": "^7.8.1"
},
"devDependencies": {}
}
<template>
<div class="page-account">
<div v-if="showI18n" class="page-account-header">
<i-header-i18n />
</div>
<div class="page-account-container">
<div class="page-account-top">
<div class="page-account-top-logo">
<img src="@/assets/images/logo.png" alt="logo">
</div>
<div class="page-account-top-desc">iView Admin Pro 企业级中台前端/设计解决方案</div>
</div>
<Login @on-submit="handleSubmit">
<UserName name="username" value="admin" />
<Password name="password" value="admin" enter-to-submit />
<div class="page-account-auto-login">
<Checkbox v-model="autoLogin" size="large">{{ $t('page.login.remember') }}</Checkbox>
<a href="">{{ $t('page.login.forgot') }}</a>
</div>
<Submit>{{ $t('page.login.submit') }}</Submit>
</Login>
<div class="page-account-other">
<span>{{ $t('page.login.other') }}</span>
<img src="@/assets/svg/icon-social-wechat.svg" alt="wechat">
<img src="@/assets/svg/icon-social-qq.svg" alt="qq">
<img src="@/assets/svg/icon-social-weibo.svg" alt="weibo">
<router-link class="page-account-register" :to="{ name: 'register' }">{{ $t('page.login.signup') }}</router-link>
</div>
</div>
<i-copyright />
</div>
</template>
<script>
import iCopyright from '@/components/copyright';
import { mapActions } from 'vuex';
import mixins from '../mixins';
export default {
mixins: [ mixins ],
components: { iCopyright },
data () {
return {
autoLogin: true
}
},
methods: {
...mapActions('admin/account', [
'login'
]),
/**
* @description 登录
* 表单校验已有 iView Pro 自动完成,如有需要修改,请阅读 iView Pro 文档
*/
handleSubmit (valid, values) {
if (valid) {
const { username, password } = values;
this.login({
username,
password
})
.then(() => {
// 重定向对象不存在则返回顶层路径
this.$router.replace(this.$route.query.redirect || '/');
});
}
}
}
};
</script>
import iHeaderI18n from '@/layouts/basic-layout/header-i18n';
import { mapState } from 'vuex';
export default {
components: { iHeaderI18n },
computed: {
...mapState('admin/layout', [
'showI18n'
])
}
}
<template>
<div class="page-account">
<div v-if="showI18n" class="page-account-header">
<i-header-i18n />
</div>
<div class="page-account-container">
<div class="page-account-top">
<div class="page-account-top-logo">
<img src="@/assets/images/logo.png" alt="logo">
</div>
<div class="page-account-top-desc">iView Admin Pro 企业级中台前端/设计解决方案</div>
</div>
<Login ref="form" @on-submit="handleSubmit">
<Email name="mail" />
<Poptip trigger="focus" placement="right" width="240">
<Password name="password" :rules="passwordRule" placeholder="至少6位密码,区分大小写" @on-change="handleChangePassword" />
<div slot="content" class="page-account-register-tip">
<div class="page-account-register-tip-title" :class="passwordTip.class">
强度:{{ passwordTip.strong }}
</div>
<Progress :percent="passwordTip.percent" hide-info :stroke-width="6" :stroke-color="passwordTip.color" />
<div class="page-account-register-tip-desc">
请至少输入 6 个字符。请不要使用容易被猜到的密码。
</div>
</div>
</Poptip>
<Password name="passwordConfirm" :rules="passwordConfirmRule" placeholder="确认密码" />
<Mobile name="mobile" />
<Captcha name="captcha" :field="['mobile']" enter-to-submit @on-get-captcha="handleGetCaptcha" />
<Submit>{{ $t('page.register.submit') }}</Submit>
</Login>
<div class="page-account-to-login">
<router-link :to="{ name: 'login' }">{{ $t('page.register.other') }}</router-link>
</div>
</div>
<i-copyright />
</div>
</template>
<script>
import iCopyright from '@/components/copyright';
import { mapActions } from 'vuex';
import mixins from '../mixins';
export default {
mixins: [ mixins ],
components: { iCopyright },
data () {
// 二次校验密码
// 因为 iView Pro 的表单控件省去了对数据的绑定,因此需要通过 ref 从 Login 组件中获取数据
// 下面的 formValidate.password 中的 password,指的是给 <Password> 组件设置的 name="password"
const validatePassCheck = (rule, value, callback) => {
if (value !== this.$refs.form.formValidate.password) {
callback(new Error('两次输入的密码不匹配!'));
} else {
callback();
}
};
return {
passwordRule: [
{
required: true, message: '密码不能为空!', trigger: 'change'
},
{
min: 6, message: '密码不能少于6位!', trigger: 'change'
}
],
passwordConfirmRule: [
{
required: true, message: '确认密码不能为空!', trigger: 'change'
},
{ validator: validatePassCheck, trigger: 'change' }
],
// 密码长度,在密码强度提示时作为判断依据
passwordLen: 0
}
},
computed: {
// 密码强度提示文案等
passwordTip () {
let strong = '强';
let className = 'strong';
let percent = this.passwordLen > 10 ? 10 : this.passwordLen;
let color = '#19be6b';
if (this.passwordLen < 6) {
strong = '太短';
className = 'low';
color = '#ed4014';
} else if (this.passwordLen < 10) {
strong = '中';
className = 'medium';
color = '#ff9900';
}
return {
strong,
class: `page-account-register-tip-${className}`,
percent: percent * 10,
color
}
}
},
methods: {
...mapActions('admin/account', [
'register',
'login'
]),
handleChangePassword (val) {
this.passwordLen = val.length;
},
/**
* @description 注册
* 表单校验已有 iView Pro 自动完成,如有需要修改,请阅读 iView Pro 文档
*/
handleSubmit (valid, values) {
if (valid) {
if (valid) {
const { mail, password, mobile, captcha } = values;
this.register({
mail,
password,
mobile,
captcha
})
.then(() => {
this.$router.replace({ name: 'register-result' });
});
}
}
},
/**
* @description 获取验证码
* */
handleGetCaptcha () {
}
}
};
</script>
<template>
<div class="page-account">
<div class="page-account-container page-account-container-result">
<div class="page-account-top">
<div class="page-account-top-logo">
<img src="@/assets/images/logo.png" alt="logo">
</div>
<div class="page-account-top-desc">iView Admin Pro 企业级中台前端/设计解决方案</div>
</div>
<Result type="success" :title="title" :desc="desc">
<div slot="actions">
<Button size="large" type="primary" to="https://mail.qq.com" target="_blank">查看邮箱</Button>
<Button size="large" to="/">返回首页</Button>
</div>
</Result>
</div>
<i-copyright />
</div>
</template>
<script>
import iCopyright from '@/components/copyright';
export default {
components: { iCopyright },
data () {
return {
title: '你的账户:admin 注册成功',
desc: '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。'
}
}
};
</script>
<template>
<div>
欢迎使用 iView Admin Pro
</div>
</template>
<script>
export default {
name: 'dashboard-console'
}
</script>
<template>
<div>
<Exception type="403" img-color :desc="$t('page.exception.e403')" :back-text="$t('page.exception.btn')" />
</div>
</template>
<script>
</script>
<template>
<div>
<Exception type="404" img-color :desc="$t('page.exception.e404')" :back-text="$t('page.exception.btn')" />
</div>
</template>
<script>
</script>
<template>
<div>
<Exception type="500" img-color :desc="$t('page.exception.e500')" :back-text="$t('page.exception.btn')" />
</div>
</template>
<script>
</script>
<template>
<div class="i-table-no-border">
<Card :bordered="false" dis-hover>
<div slot="title">
<Avatar icon="md-locate" size="small" v-color="'#2f54eb'" v-bg-color="'#f0f5ff'" />
<span class="ivu-pl-8">前端日志</span>
</div>
<div slot="extra">
<Tooltip content="清空日志" placement="top">
<Button type="text" @click="clean">
<Icon type="md-trash" size="16" />
</Button>
</Tooltip>
</div>
<Table :columns="columns" :data="log">
<template slot-scope="{ row }" slot="page">
{{ get(row, 'meta.url') }}
</template>
<template slot-scope="{ row }" slot="type">
<Tag color="blue" v-if="row.type === 'info'">info</Tag>
<Tag color="green" v-if="row.type === 'success'">success</Tag>
<Tag color="orange" v-if="row.type === 'warning'">warning</Tag>
<Tag color="red" v-if="row.type === 'error'">error</Tag>
</template>
<template slot-scope="{ row }" slot="more">
<Button type="primary" @click="handleMore(row)">查看</Button>
</template>
</Table>
</Card>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import { get } from 'lodash';
export default {
name: 'log',
data () {
return {
columns: [
{
title: '时间',
key: 'time',
width: 180
},
{
title: '信息',
key: 'message',
minWidth: 300
},
{
title: '触发页面',
slot: 'page',
minWidth: 300
},
{
title: '类型',
width: 100,
slot: 'type'
},
{
title: '详细信息',
width: 100,
slot: 'more'
}
]
}
},
computed: {
...mapState('admin/log', [
'log'
])
},
methods: {
...mapMutations('admin/log', [
'clean'
]),
get,
handleMore (log) {
this.$Notice.info({
title: '提示',
desc: '请在浏览器控制台查看完整日志'
});
this.$log.capsule('iView Admin', '完整日志内容', 'primary');
console.group('完整日志');
console.log('message ', log.message);
console.log('time: ', log.time);
console.log('type: ', log.type);
console.log('meta: ', log.meta);
console.groupEnd();
}
}
}
</script>
// Vue
import Vue from 'vue';
// import App from './App';
// 配置
// import Setting from './setting';
// 混合
// import mixinApp from '@/mixins/app';
// 插件
import plugins from '@/plugins';
// store
// import store from '@/store/index';
// iView 和 iView Pro
import ViewUI from 'view-design';
import iViewPro from '@/libs/iview-pro/iview-pro.min.js';
// 菜单和路由
// import router from './router';
import menuHeader from '@/menu/header';
import menuSider from '@/menu/sider';
import { frameInRoutes } from '@/router/routes';
// 多语言
import i18n from '@/i18n';
// 方法
import { getHeaderName, getMenuSider, getSiderSubmenu } from '@/libs/system';
// 内置组件
import iLink from '@/components/link';
// 使用样式,修改主题可以在 styles 目录下创建新的主题包并修改 iView 默认的 less 变量
// 参考 https://www.iviewui.com/docs/guide/theme
import '@/styles/index.less';
import '@/libs/iview-pro/iview-pro.css';
if (window) window.$t = (key, value) => i18n.t(key, value);
Vue.use(plugins);
// Vue.use(ViewUI, {
// i18n: (key, value) => i18n.t(key, value)
// });
Vue.use(iViewPro);
Vue.component('i-link', iLink);
\ No newline at end of file
/**
* @description 鉴权指令
* 当传入的权限当前用户没有时,会移除该组件
* 用例:<Tag v-auth="['admin']">text</Tag>
* */
import store from '@/store';
import { includeArray } from '@/libs/system';
export default {
inserted (el, binding, vnode) {
const { value } = binding;
const access = store.state.admin.user.info.access;
if (value && value instanceof Array && value.length && access && access.length) {
const isPermission = includeArray(value, access);
if (!isPermission) {
el.parentNode && el.parentNode.removeChild(el);
}
}
}
}
import store from '@/store';
import util from '@/libs/util';
export default {
install (Vue, options) {
Vue.config.errorHandler = function (error, instance, info) {
Vue.nextTick(() => {
// store 追加 log
store.dispatch('admin/log/push', {
message: `${info}: ${error.message}`,
type: 'error',
meta: {
error
// instance
}
});
// 只在开发模式下打印 log
if (process.env.NODE_ENV === 'development') {
util.log.capsule('iView Admin', 'ErrorHandler', 'error');
util.log.error('>>>>>> 错误信息 >>>>>>');
console.log(info);
util.log.error('>>>>>> Vue 实例 >>>>>>');
console.log(instance);
util.log.error('>>>>>> Error >>>>>>');
console.log(error)
}
})
}
}
}
/**
* 插件
* */
// 错误捕获
import pluginError from '@/plugins/error';
// 日志插件
import pluginLog from '@/plugins/log';
// 鉴权指令
import directiveAuth from '@/plugins/auth';
export default {
async install (Vue, options) {
// 插件
Vue.use(pluginError);
Vue.use(pluginLog);
// 指令
Vue.directive('auth', directiveAuth);
}
}
import store from '@/store';
import util from '@/libs/util';
export default {
install (Vue, options) {
// 快速打印 log
Vue.prototype.$log = {
...util.log,
push (data) {
if (typeof data === 'string') {
// 如果传递来的数据是字符串
// 赋值给 message 字段
// 为了方便使用
// eg: this.$log.push('foo text')
store.dispatch('admin/log/push', {
message: data
});
} else if (typeof data === 'object') {
// 如果传递来的数据是对象
store.dispatch('admin/log/push', data);
}
}
}
}
}
import store from '@/store';
import axios from 'axios';
import util from '@/libs/util';
import Setting from '@/setting';
import { Message, Notice } from 'view-design';
// 创建一个错误
function errorCreate (msg) {
const err = new Error(msg);
errorLog(err);
throw err;
}
// 记录和显示错误
function errorLog (err) {
// 添加到日志
store.dispatch('admin/log/push', {
message: '数据请求异常',
type: 'error',
meta: {
error: err
}
});
// 打印到控制台
if (process.env.NODE_ENV === 'development') {
util.log.error('>>>>>> Error >>>>>>');
console.log(err);
}
// 显示提示,可配置使用 iView 的 $Message 还是 $Notice 组件来显示
if (Setting.errorModalType === 'Message') {
Message.error({
content: err.message,
duration: Setting.modalDuration
});
} else if (Setting.errorModalType === 'Notice') {
Notice.error({
title: '提示',
desc: err.message,
duration: Setting.modalDuration
});
}
}
// 创建一个 axios 实例
const service = axios.create({
baseURL: Setting.apiBaseURL,
timeout: 5000 // 请求超时时间
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 在请求发送之前做一些处理
const token = util.cookies.get('token');
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
config.headers['X-Token'] = token;
return config;
},
error => {
// 发送失败
console.log(error);
Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
// dataAxios 是 axios 返回数据中的 data
const dataAxios = response.data;
// 这个状态码是和后端约定的
const { code } = dataAxios;
// 根据 code 进行判断
if (code === undefined) {
// 如果没有 code 代表这不是项目后端开发的接口
return dataAxios;
} else {
// 有 code 代表这是一个后端接口 可以进行进一步的判断
switch (code) {
case 0:
// [ 示例 ] code === 0 代表没有错误
return dataAxios.data;
case 'xxx':
// [ 示例 ] 其它和后台约定的 code
errorCreate(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`);
break;
default:
// 不是正确的 code
errorCreate(`${dataAxios.msg}: ${response.config.url}`);
break;
}
}
},
error => {
if (error && error.response) {
switch (error.response.status) {
case 400: error.message = '请求错误'; break;
case 401: error.message = '未授权,请登录'; break;
case 403: error.message = '拒绝访问'; break;
case 404: error.message = `请求地址出错: ${error.response.config.url}`; break;
case 408: error.message = '请求超时'; break;
case 500: error.message = '服务器内部错误'; break;
case 501: error.message = '服务未实现'; break;
case 502: error.message = '网关错误'; break;
case 503: error.message = '服务不可用'; break;
case 504: error.message = '网关超时'; break;
case 505: error.message = 'HTTP版本不受支持'; break;
default: break;
}
}
errorLog(error);
return Promise.reject(error);
}
);
export default service;
import Vue from 'vue';
import VueRouter from 'vue-router';
import iView from 'view-design';
import util from '@/libs/util'
import Setting from '@/setting';
import store from '@/store/index';
// 路由数据
import routes from './routes';
// Vue.use(VueRouter);
// 导出路由 在 main.js 里使用
// const router = new VueRouter({
// routes,
// mode: Setting.routerMode
// });
// const router=Vue.$nuxt.$router
const router={}
console.warn("初始化router",Vue.$nuxt)
/**
* 路由拦截
* 权限验证
*/
// router.beforeEach((to, from, next) => {
// if (Setting.showProgressBar) iView.LoadingBar.start();
// // 判断是否需要登录才可以进入
// if (to.matched.some(_ => _.meta.auth)) {
// // 这里依据 token 判断是否登录,可视情况修改
// const token = util.cookies.get('token');
// if (token && token !== 'undefined') {
// next();
// } else {
// // 没有登录的时候跳转到登录界面
// // 携带上登陆成功之后需要跳转的页面完整路径
// next({
// name: 'login',
// query: {
// redirect: to.fullPath
// }
// });
// }
// } else {
// // 不需要身份校验 直接通过
// next();
// }
// });
// router.afterEach(to => {
// if (Setting.showProgressBar) iView.LoadingBar.finish();
// // 多页控制 打开新的页面
// store.dispatch('admin/page/open', to);
// // 更改标题
// util.title({
// title: to.meta.title
// });
// // 返回页面顶端
// window.scrollTo(0, 0);
// });
export default router;
import BasicLayout from '@/layouts/basic-layout';
const meta = {
auth: true
};
const pre = 'dashboard-';
export default {
path: '/dashboard',
name: 'dashboard',
redirect: {
name: `${pre}console`
},
meta,
component: BasicLayout,
children: [
{
path: 'console',
name: `${pre}console`,
meta: {
...meta,
title: '主控台'
},
component: () => import('@/pages/dashboard/console')
}
]
};
import dashboard from './modules/dashboard';
import BasicLayout from '@/layouts/basic-layout';
/**
* 在主框架内显示
*/
const frameIn = [
{
path: '/',
redirect: {
name: 'dashboard-console'
},
component: BasicLayout,
children: [
{
path: 'index',
name: 'index',
redirect: {
name: 'dashboard-console'
}
},
{
path: 'log',
name: 'log',
meta: {
title: '前端日志',
auth: true
},
component: () => import('@/pages/system/log')
},
// 刷新页面 必须保留
{
path: 'refresh',
name: 'refresh',
hidden: true,
component: {
beforeRouteEnter (to, from, next) {
next(instance => instance.$router.replace(from.fullPath));
},
render: h => h()
}
},
// 页面重定向 必须保留
{
path: 'redirect/:route*',
name: 'redirect',
hidden: true,
component: {
beforeRouteEnter (to, from, next) {
next(instance => instance.$router.replace(JSON.parse(from.params.route)));
},
render: h => h()
}
}
]
},
dashboard
];
/**
* 在主框架之外显示
*/
const frameOut = [
// 登录
{
path: '/login',
name: 'login',
meta: {
title: '$t:page.login.title'
},
component: () => import('@/pages/account/login')
},
// 注册
{
path: '/register',
name: 'register',
meta: {
title: '$t:page.register.title'
},
component: () => import('@/pages/account/register')
},
// 注册结果
{
path: '/register/result',
name: 'register-result',
meta: {
auth: true,
title: '注册结果'
},
component: () => import('@/pages/account/register/result')
}
];
/**
* 错误页面
*/
const errorPage = [
{
path: '/403',
name: '403',
meta: {
title: '403'
},
component: () => import('@/pages/system/error/403')
},
{
path: '/500',
name: '500',
meta: {
title: '500'
},
component: () => import('@/pages/system/error/500')
},
{
path: '*',
name: '404',
meta: {
title: '404'
},
component: () => import('@/pages/system/error/404')
}
];
// 导出需要显示菜单的
export const frameInRoutes = frameIn;
// 重新组织后导出
export default [
...frameIn,
...frameOut,
...errorPage
];
/**
* iView Admin Pro 开发配置
* */
const env = process.env.NODE_ENV;
const Setting = {
// 是否使用 Mock 的数据,默认 开发环境为 true,生产环境为 false
isMock: env === 'development',
// 部署应用包时的基本 URL
publicPath: env === 'development' ? '/' : '/',
// 生产环境构建文件的目录名
outputDir: 'dist',
// 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
assetsDir: '',
// 开发环境每次保存时 lint 代码,会将 lint 错误输出为编译警告
// true || false || error
lintOnSave: true,
// iView Loader 的选项
// 详见 https://www.iviewui.com/docs/guide/iview-loader
iviewLoaderOptions: {
prefix: false
}
};
module.exports = Setting;
/**
* iView Admin Pro 业务配置
* */
const env = process.env.NODE_ENV;
const Setting = {
/**
* 基础配置
* */
// 网页标题的后缀
titleSuffix: 'iView Admin Pro',
// 路由模式,可选值为 history 或 hash
routerMode: 'history',
// 页面切换时,是否显示模拟的进度条
showProgressBar: true,
// 接口请求地址
apiBaseURL: env === 'development' ? '/' : '/',
// 接口请求返回错误时,弹窗的持续时间,单位:秒
modalDuration: 3,
// 接口请求返回错误时,弹窗的类型,可选值为 Message 或 Notice
errorModalType: 'Message',
// Cookies 默认保存时间,单位:天
cookiesExpires: 1,
/**
* 多语言配置
* */
i18n: {
// 默认语言
default: 'zh-CN',
// 是否根据用户电脑配置自动设置语言(仅第一次有效)
auto: false
},
/**
* 布局配置
* */
// 侧边菜单宽度,单位 px,不可动态修改,需与 setting.less 的 @menuSideWidth 保持一致
menuSideWidth: 256,
layout: {
// 侧边栏风格,可选值为 dark 或 light
siderTheme: 'dark',
// 顶栏风格,可选值为 light、dark 或 primary
headerTheme: 'light',
// 顶栏是否置顶,开启后会覆盖侧边栏,需开启 headerFix
headerStick: false,
// 是否开启多 Tabs 页签
tabs: true,
// 多 Tabs 页签是否显示图标,开启 tabs 时有效
showTabsIcon: true,
// 是否固定多 Tabs 多页签
tabsFix: true,
// 是否固定侧边栏
siderFix: true,
// 是否固定顶栏
headerFix: true,
// 是否在下滑时隐藏顶栏,需开启 headerFix,如果开启了 tabsFix,Tabs 也会被隐藏
headerHide: false,
// 是否显示顶部菜单栏
// 一般来说,侧边的菜单栏足以满足大部分业务,如需动态切换侧边栏,可开启此选项启用顶部一级菜单,此时侧边栏将作为二级菜单
headerMenu: false,
// 侧边菜单栏是否开启手风琴模式
menuAccordion: true,
// 是否显示折叠侧边栏按钮,移动端下会自动强制开启
showSiderCollapse: true,
// 侧边菜单栏是否默认折起
menuCollapse: false,
// 侧边菜单折起时,是否在子菜单前显示父级菜单名称
showCollapseMenuTitle: false,
// 是否显示重载按钮
showReload: true,
// 是否显示搜索
showSearch: true,
// 是否显示通知
showNotice: true,
// 是否显示全屏
showFullscreen: true,
// 在手机访问时,是否在顶部显示小尺寸 logo
showMobileLogo: true,
// 是否显示全局面包屑,开启 headerMenu 时不可用
showBreadcrumb: true,
// 全局面包屑是否显示图标,开启 showBreadcrumb 时有效
showBreadcrumbIcon: false,
// 是否显示日志入口,开启与否,不影响日志记录,如不希望用户看到可关闭
showLog: true,
// 是否显示多语言
showI18n: true,
// 是否支持动态修改布局配置,移动端下会自动强制关闭
enableSetting: true,
// 退出登录时,是否二次确认
logoutConfirm: true
},
/**
* 多页 Tabs
* */
page: {
// 默认打开的页签
opened: []
},
/**
* 功能配置
* */
// 相同路由,不同参数间进行切换,是否强力更新
sameRouteForceUpdate: false,
// 是否使用动态侧边菜单
dynamicSiderMenu: false
};
export default Setting;
import Vue from 'vue';
import Vuex from 'vuex';
import admin from './modules/admin'
Vue.use(Vuex);
const store=()=> new Vuex.Store({
modules: {
admin
}
})
export default store
/**
* 该文件启用 `@/store/index.js` 导入所有 vuex 模块。
* 这个文件是一次性创建的,不应该被修改。
*/
const files = require.context('./modules', false, /\.js$/);
const modules = {};
files.keys().forEach(key => {
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
});
export default {
namespaced: true,
modules
};
/**
* 注册、登录、注销
* */
import util from '@/libs/util';
import router from '@/router';
import { AccountLogin, AccountRegister } from '@/api/account';
import { Modal } from 'view-design';
export default {
namespaced: true,
actions: {
/**
* @description 登录
* @param {Object} param context
* @param {Object} param username {String} 用户账号
* @param {Object} param password {String} 密码
* @param {Object} param route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
*/
login ({ dispatch }, {
username = '',
password = ''
} = {}) {
return new Promise((resolve, reject) => {
// 开始请求登录接口
AccountLogin({
username,
password
})
.then(async res => {
// 设置 cookie 一定要存 uuid 和 token 两个 cookie
// 整个系统依赖这两个数据进行校验和存储
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
// token 代表用户当前登录状态 建议在网络请求中携带 token
// 如有必要 token 需要定时更新,默认保存一天,可在 setting.js 中修改
// 如果你的 token 不是通过 cookie 携带,而是普通字段,也可视情况存储在 localStorage
util.cookies.set('uuid', res.uuid);
util.cookies.set('token', res.token);
// 设置 vuex 用户信息
await dispatch('admin/user/set', res.info, { root: true });
// 用户登录后从持久化数据加载一系列的设置
await dispatch('load');
// 结束
resolve();
})
.catch(err => {
// console.log('err: ', err);
reject(err);
})
})
},
/**
* @description 退出登录
* */
logout ({ commit, dispatch }, { confirm = false, vm } = {}) {
async function logout () {
// 删除cookie
util.cookies.remove('token');
util.cookies.remove('uuid');
// 清空 vuex 用户信息
await dispatch('admin/user/set', {}, { root: true });
// 跳转路由
router.push({
name: 'login'
});
}
if (confirm) {
Modal.confirm({
title: vm.$t('basicLayout.logout.confirmTitle'),
content: vm.$t('basicLayout.logout.confirmContent'),
onOk () {
logout();
}
});
} else {
logout();
}
},
/**
* @description 注册
* @param {Object} param context
* @param {Object} param mail {String} 邮箱
* @param {Object} param password {String} 密码
* @param {Object} param mobile {String} 手机号码
* @param {Object} param captcha {String} 验证码
*/
register ({ dispatch }, {
mail = '',
password = '',
mobile = '',
captcha = ''
} = {}) {
return new Promise((resolve, reject) => {
// 开始请求登录接口
AccountRegister({
mail,
password,
mobile,
captcha
})
.then(async res => {
// 注册成功后,完成与登录一致的操作
// 注册也可视情况不返还 uuid、token 等数据,在注册完成后,由前端自动执行一次登录逻辑
util.cookies.set('uuid', res.uuid);
util.cookies.set('token', res.token);
// 设置 vuex 用户信息
await dispatch('admin/user/set', res.info, { root: true });
// 用户登录后从持久化数据加载一系列的设置
await dispatch('load');
// 结束
resolve();
})
.catch(err => {
// console.log('err: ', err);
reject(err);
})
})
},
/**
* @description 用户登录后从持久化数据加载一系列的设置
* @param {Object} state vuex state
* @param {Object} dispatch vuex dispatch
*/
load ({ state, dispatch }) {
return new Promise(async resolve => {
// 加载用户登录信息
await dispatch('admin/user/load', null, { root: true });
// 持久化数据加载上次退出时的多页列表
await dispatch('admin/page/openedLoad', null, { root: true });
// end
resolve();
})
}
}
};
/**
* 持久化存储
* 一般情况下,您无需修改此文件
* */
import util from '@/libs/util';
import router from '@/router';
import { cloneDeep } from 'lodash';
/**
* @description 检查路径是否存在 不存在的话初始化
* @param {Object} dbName {String} 数据库名称
* @param {Object} path {String} 路径
* @param {Object} user {Boolean} 区分用户
* @param {Object} validator {Function} 数据校验钩子 返回 true 表示验证通过
* @param {Object} defaultValue {*} 初始化默认值
* @returns {String} 可以直接使用的路径
*/
function pathInit ({
dbName = 'database',
path = '',
user = true,
validator = () => true,
defaultValue = ''
}) {
const uuid = util.cookies.get('uuid') || 'ghost-uuid';
const currentPath = `${dbName}.${user ? `user.${uuid}` : 'public'}${path ? `.${path}` : ''}`;
const value = util.db.get(currentPath).value();
if (!(value !== undefined && validator(value))) {
util.db.set(currentPath, defaultValue).write();
}
return currentPath;
}
export { pathInit };
export default {
namespaced: true,
actions: {
/**
* @description 将数据存储到指定位置 | 路径不存在会自动初始化
* @description 效果类似于取值 dbName.path = value
* @param context context
* @param {Object} dbName {String} 数据库名称
* @param {Object} path {String} 存储路径
* @param {Object} value {*} 需要存储的值
* @param {Object} user {Boolean} 是否区分用户
*/
set (context, {
dbName = 'database',
path = '',
value = '',
user = false
}) {
util.db.set(pathInit({
dbName,
path,
user
}), value).write()
},
/**
* @description 获取数据
* @description 效果类似于取值 dbName.path || defaultValue
* @param context context
* @param {Object} dbName {String} 数据库名称
* @param {Object} path {String} 存储路径
* @param {Object} defaultValue {*} 取值失败的默认值
* @param {Object} user {Boolean} 是否区分用户
*/
get (context, {
dbName = 'database',
path = '',
defaultValue = '',
user = false
}) {
return new Promise(resolve => {
resolve(cloneDeep(util.db.get(pathInit({
dbName,
path,
user,
defaultValue
})).value()))
})
},
/**
* @description 获取存储数据库对象
* @param {Object} context context
* @param {Object} user {Boolean} 是否区分用户
*/
database (context, {
user = false
} = {}) {
return new Promise(resolve => {
resolve(util.db.get(pathInit({
dbName: 'database',
path: '',
user,
defaultValue: {}
})))
})
},
/**
* @description 清空存储数据库对象
* @param {Object} context context
* @param {Object} user {Boolean} 是否区分用户
*/
databaseClear (context, {
user = false
} = {}) {
return new Promise(resolve => {
resolve(util.db.get(pathInit({
dbName: 'database',
path: '',
user,
validator: () => false,
defaultValue: {}
})))
})
},
/**
* @description 获取存储数据库对象 [ 区分页面 ]
* @param {Object} context context
* @param {Object} basis {String} 页面区分依据 [ name | path | fullPath ]
* @param {Object} user {Boolean} 是否区分用户
*/
databasePage (context, {
basis = 'fullPath',
user = false
} = {}) {
return new Promise(resolve => {
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${router.app.$route[basis]}`,
user,
defaultValue: {}
})))
})
},
/**
* @description 清空存储数据库对象 [ 区分页面 ]
* @param {Object} context context
* @param {Object} basis {String} 页面区分依据 [ name | path | fullPath ]
* @param {Object} user {Boolean} 是否区分用户
*/
databasePageClear (context, {
basis = 'fullPath',
user = false
} = {}) {
return new Promise(resolve => {
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${router.app.$route[basis]}`,
user,
validator: () => false,
defaultValue: {}
})))
})
},
/**
* @description 快速将页面当前的数据 ( $data ) 持久化
* @param {Object} context context
* @param {Object} instance {Object} vue 实例
* @param {Object} basis {String} 页面区分依据 [ name | path | fullPath ]
* @param {Object} user {Boolean} 是否区分用户
*/
pageSet (context, {
instance,
basis = 'fullPath',
user = false
}) {
return new Promise(resolve => {
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${router.app.$route[basis]}.$data`,
user,
validator: () => false,
defaultValue: cloneDeep(instance.$data)
})))
})
},
/**
* @description 快速获取页面快速持久化的数据
* @param {Object} context context
* @param {Object} instance {Object} vue 实例
* @param {Object} basis {String} 页面区分依据 [ name | path | fullPath ]
* @param {Object} user {Boolean} 是否区分用户
*/
pageGet (context, {
instance,
basis = 'fullPath',
user = false
}) {
return new Promise(resolve => {
resolve(cloneDeep(util.db.get(pathInit({
dbName: 'database',
path: `$page.${router.app.$route[basis]}.$data`,
user,
defaultValue: cloneDeep(instance.$data)
})).value()))
})
},
/**
* @description 清空页面快照
* @param {Object} context context
* @param {Object} basis {String} 页面区分依据 [ name | path | fullPath ]
* @param {Object} user {Boolean} 是否区分用户
*/
pageClear (context, {
basis = 'fullPath',
user = false
}) {
return new Promise(resolve => {
resolve(util.db.get(pathInit({
dbName: 'database',
path: `$page.${router.app.$route[basis]}.$data`,
user,
validator: () => false,
defaultValue: {}
})))
})
}
}
};
/**
* 多语言
* */
import Languages from '@/i18n/locale';
import Setting from '@/setting';
import util from '@/libs/util';
import { pathInit } from '@/store/modules/admin/modules/db';
const savedLocaleKey = 'i18n-locale';
export default {
namespaced: true,
state: {
locale: ''
},
actions: {
/**
* @description 获取当前语言
* */
getLocale ({ state }) {
let locale;
const db = util.db.get(pathInit({
dbName: 'database',
path: '',
user: true,
defaultValue: {}
}));
const savedLocale = db.get(savedLocaleKey).value();
// 先判断本地存储是否已有语言选择
if (savedLocale) {
locale = savedLocale;
} else {
// 判断是否开启自动识别语言
if (Setting.i18n.auto) {
// 如果自动识别的语言,本地没有该语言包,则设置为默认语言
const navLang = navigator.language;
if (Languages[navLang]) {
locale = navLang;
} else {
locale = Setting.i18n.default;
}
} else {
locale = Setting.i18n.default;
}
// 将初次的语言保存在本地
db.set(savedLocaleKey, locale).write();
}
state.locale = locale;
},
/**
* @description 设置当前语言
* */
setLocale ({ state }, { locale = Setting.i18n.default, vm }) {
const db = util.db.get(pathInit({
dbName: 'database',
path: '',
user: true,
defaultValue: {}
}));
// 将语言保存在本地
db.set(savedLocaleKey, locale).write();
// 设置当前语言
state.locale = locale;
// 设置 vue-i18n 的语言
vm.$i18n.locale = locale;
// 更新网页标题
util.title({
title: vm.$route.meta.title
});
}
}
};
/**
* 布局配置
* */
import screenfull from 'screenfull';
import Setting from '@/setting';
export default {
namespaced: true,
state: {
...Setting.layout,
isMobile: false, // 是否为手机
isTablet: false, // 是否为平板
isDesktop: true, // 是否为桌面
isFullscreen: false // 是否切换到了全屏
},
mutations: {
/**
* @description 设置设备类型
* @param {Object} state vuex state
* @param {String} type 设备类型,可选值为 Mobile、Tablet、Desktop
*/
setDevice (state, type) {
state.isMobile = false;
state.isTablet = false;
state.isDesktop = false;
state[`is${type}`] = true;
},
/**
* @description 修改 menuCollapse
* @param {Object} state vuex state
* @param {Boolean} collapse 折叠状态
* */
updateMenuCollapse (state, collapse) {
state.menuCollapse = collapse;
},
/**
* @description 设置全屏状态
* @param {Object} state vuex state
* @param {Boolean} isFullscreen vuex
* */
setFullscreen (state, isFullscreen) {
state.isFullscreen = isFullscreen;
},
/**
* @description 更改指定布局配置
* @param {Object} state vuex state
* @param {Object} key layout 名称,对应 Setting.layout
* @param {Object} value layout 值
* */
updateLayoutSetting (state, { key, value }) {
state[key] = value;
}
},
actions: {
/**
* @description 初始化监听全屏状态
*/
listenFullscreen ({ commit }) {
return new Promise(resolve => {
if (screenfull.enabled) {
screenfull.on('change', () => {
if (!screenfull.isFullscreen) {
commit('setFullscreen', false)
}
})
}
// end
resolve();
});
},
/**
* @description 切换全屏
*/
toggleFullscreen ({ commit }) {
return new Promise(resolve => {
if (screenfull.isFullscreen) {
screenfull.exit();
commit('setFullscreen', false);
} else {
screenfull.request();
commit('setFullscreen', true);
}
// end
resolve();
});
}
}
};
import dayjs from 'dayjs';
import { get } from 'lodash';
import util from '@/libs/util.js';
export default {
namespaced: true,
state: {
/**
* @description 错误日志,单条属性:
* message: 必填,日志信息
* type: 非必填,类型,可选值为 info(默认值)| success | warning | error,其中 error 会以具体数目强调显示,其它以点轻量显示
* time: 必填,日志记录时间
* meta: 非必填,其它携带信息
* */
log: []
},
getters: {
/**
* @description 返回现存 log (all) 的条数
* @param {*} state vuex state
*/
length (state) {
return state.log.length;
},
/**
* @description 返回现存 log (error) 的条数
* @param {*} state vuex state
*/
lengthError (state) {
return state.log.filter(log => log.type === 'error').length;
}
},
actions: {
/**
* @description 添加一个日志
* @param {String} param message {String} 信息
* @param {String} param type {String} 类型
* @param {Object} param meta {Object} 附带的信息
*/
push ({ rootState, commit }, { message, type = 'info', meta }) {
commit('push', {
message,
type,
time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
meta: {
// 当前用户信息
user: rootState.admin.user.info,
// 当前用户的 uuid
uuid: util.cookies.get('uuid'),
// 当前的 token
token: util.cookies.get('token'),
// 当前地址
url: get(window, 'location.href', ''),
// 用户设置
...meta
}
});
}
},
mutations: {
/**
* @description 添加日志
* @param {Object} state vuex state
* @param {Object} log data
*/
push (state, log) {
state.log.push(log);
},
/**
* @description 清空日志
* @param {Object} state vuex state
*/
clean (state) {
state.log = [];
}
}
}
/**
* 菜单
* */
import { cloneDeep } from 'lodash';
import { includeArray } from '@/libs/system';
// 根据 menu 配置的权限,过滤菜单
function filterMenu (menuList, access, lastList) {
menuList.forEach(menu => {
let menuAccess = menu.auth;
if (!menuAccess || includeArray(menuAccess, access)) {
let newMenu = {};
for (let i in menu) {
if (i !== 'children') newMenu[i] = cloneDeep(menu[i]);
}
if (menu.children && menu.children.length) newMenu.children = [];
lastList.push(newMenu);
menu.children && filterMenu(menu.children, access, newMenu.children);
}
});
return lastList;
}
export default {
namespaced: true,
state: {
// 顶部菜单
header: [],
// 侧栏菜单
sider: [],
// 当前顶栏菜单的 name
headerName: '',
// 当前所在菜单的 path
activePath: '',
// 展开的子菜单 name 集合
openNames: []
},
getters: {
/**
* @description 根据 user 里登录用户权限,对侧边菜单进行鉴权过滤
* */
filterSider (state, getters, rootState) {
const userInfo = rootState.admin.user.info;
// @权限
const access = userInfo.access;
if (access && access.length) {
return filterMenu(state.sider, access, []);
} else {
return filterMenu(state.sider, [], []);
}
},
/**
* @description 根据 user 里登录用户权限,对顶栏菜单进行鉴权过滤
* */
filterHeader (state, getters, rootState) {
const userInfo = rootState.admin.user.info;
// @权限
const access = userInfo.access;
if (access && access.length) {
return state.header.filter(item => {
let state = true;
if (item.auth && !includeArray(item.auth, access)) state = false;
return state;
});
} else {
return state.header.filter(item => {
let state = true;
if (item.auth && item.auth.length) state = false;
return state;
});
}
},
/**
* @description 当前 header 的全部信息
* */
currentHeader (state) {
return state.header.find(item => item.name === state.headerName);
},
/**
* @description 在当前 header 下,是否隐藏 sider(及折叠按钮)
* */
hideSider (state, getters) {
let visible = false;
if (getters.currentHeader && 'hideSider' in getters.currentHeader) visible = getters.currentHeader.hideSider;
return visible;
}
},
mutations: {
/**
* @description 设置侧边栏菜单
* @param {Object} state vuex state
* @param {Array} menu menu
*/
setSider (state, menu) {
state.sider = menu;
},
/**
* @description 设置顶栏菜单
* @param {Object} state vuex state
* @param {Array} menu menu
*/
setHeader (state, menu) {
state.header = menu;
},
/**
* @description 设置当前顶栏菜单 name
* @param {Object} state vuex state
* @param {Array} name headerName
*/
setHeaderName (state, name) {
state.headerName = name;
},
/**
* @description 设置当前所在菜单的 path,用于侧栏菜单高亮当前项
* @param {Object} state vuex state
* @param {Array} path fullPath
*/
setActivePath (state, path) {
state.activePath = path;
},
/**
* @description 设置当前所在菜单的全部展开父菜单的 names 集合
* @param {Object} state vuex state
* @param {Array} names openNames
*/
setOpenNames (state, names) {
state.openNames = names;
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment