feat: 添加分页组件和配置

- 新增 PageConfig 组件用于分页设置
- 新增 Paging 组件用于显示分页信息
- 添加自定义样式和布局
This commit is contained in:
陈昱达
2025-03-03 11:04:50 +08:00
parent 1151a89aa9
commit f6f5d82c0d
33 changed files with 6585 additions and 68 deletions

3
.eslintignore Normal file
View File

@@ -0,0 +1,3 @@
/src/fonts
/public

50
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,50 @@
module.exports = {
root: true,
env: {
node: true
},
plugins: ['vue'],
extends: ['plugin:vue/vue3-essential'],
parserOptions: {
parser: '@babel/eslint-parser'
},
rules: {
'global-require': 0,
'import/prefer-default-export': 0,
'no-console': 0,
'vue/no-deprecated-slot-attribute': 0,
'no-plusplus': 0,
'no-param-reassign': 0,
'vue/no-use-v-if-with-v-for': 0,
'max-len': [
'error',
{
code: 120,
tabWidth: 2,
ignoreStrings: true,
ignoreUrls: true,
ignoreRegExpLiterals: true,
ignoreTemplateLiterals: true
}
],
'import/extensions': 0,
eqeqeq: 0,
'vue/no-deprecated-slot-scope-attribute': 0,
'no-underscore-dangle': 0,
'consistent-return': 0,
'linebreak-style': [0, 'error', 'windows'],
'vue/no-parsing-error': 0,
'vue/multi-word-component-names': 0,
'vue/custom-event-name-casing': 0,
'vue/no-ref-as-operand': 0,
'operator-linebreak': ['error', 'before', { overrides: { '=': 'none' } }]
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
env: {
mocha: true
}
}
]
};

8
.prettierrc.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": true,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

4
auto-imports.d.ts vendored
View File

@@ -5,6 +5,4 @@
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}
declare global {}

14
package-lock.json generated
View File

@@ -17,7 +17,8 @@
"uuid": "^11.1.0",
"vant": "^4.9.17",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
"vue-router": "^4.3.3",
"vuex": "^4.1.0"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
@@ -10510,6 +10511,17 @@
"typescript": ">=5.0.0"
}
},
"node_modules/vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.1.0.tgz",
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",

View File

@@ -22,7 +22,8 @@
"uuid": "^11.1.0",
"vant": "^4.9.17",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
"vue-router": "^4.3.3",
"vuex": "^4.1.0"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",

View File

@@ -41,12 +41,11 @@
}
}
*,
*::before,
*::after {
box-sizing: border-box;
* {
/* box-sizing: border-box; */
margin: 0;
font-weight: normal;
/* font-weight: normal; */
}
body {
@@ -74,4 +73,8 @@ body {
text-rendering: optimizelegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
& div {
font-size: 14px;
}
}

View File

@@ -1,5 +1,6 @@
@import './base.css';
@import '../font/iconfont.css';
@import '../fonts/iconfont.css';
a,
.green {

View File

@@ -11,7 +11,7 @@ import { RouterView } from 'vue-router';
<style scoped lang="scss">
.common-layout {
height: calc(100vh);
min-height: calc(100vh);
background-color: #E9EEF3;
color: #333;
text-align: center;

File diff suppressed because it is too large Load Diff

44
src/style/box.scss Normal file
View File

@@ -0,0 +1,44 @@
.flex {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex-column {
flex-direction: column;
}
.flex-start,
.flex.flex-start {
display: flex;
align-items: center;
justify-content: flex-start;
}
.flex-end,
.flex.flex-end {
display: flex;
align-items: center;
justify-content: flex-end;
}
.flex-auto {
flex: auto;
}
.flex-none {
flex: none;
}
.full-width {
width: 100%;
}
.full-height {
height: 100%;
}
.full-size {
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,36 @@
.quiz-config-content {
width: 100%;
border-radius: 6px;
background-color: #fff;
}
.title {
margin-bottom: 12px;
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.text {
margin-right: 16px;
}
}
.flex-none {
flex: none;
}
.w138 {
width: 138px;
}
.w125 {
width: 125px;
}
.w56 {
width: 56px;
}

View File

@@ -0,0 +1,155 @@
/**
* 自定义按钮样式,为 a-button 添加 custom-button 类,并传入相应的 typesizedisabled
* 默认尺寸按钮size="default" 大尺寸按钮size="large"
* 置灰状态按钮disabled
* 无边框按钮: type="text" class="custom-button"
* 灰色边框按钮: type="default" class="custom-button"
* 蓝色边框按钮: type="default" class="custom-button btn-outline"
* 蓝色背景按钮: type="primary" class="custom-button"
*/
.custom-modal .ant-modal-footer .ant-btn,
.custom-button,
.ant-btn.custom-button {
padding-right: 20px;
padding-left: 20px;
border-radius: 4px;
letter-spacing: 0;
word-spacing: 0;
//font-family: PingFangSC-Regular, PingFang SC, sans-serif;
cursor: pointer;
&.ant-btn-default {
&:not([disabled]) {
border-color: #d9d9d9;
color: #262626;
svg path {
fill: #262626;
}
&:hover {
border-color: $yili-default-color;
color: $yili-default-color;
svg path {
fill: $yili-default-color;
}
}
}
&[disabled] {
border-color: #d9d9d9;
background-color: #fff;
color: #bfbfbf;
svg path {
fill: #bfbfbf;
}
}
}
&.btn-outline {
&:not([disabled]) {
border-color: $yili-btn-check-color;
color: $yili-default-color;
svg path {
fill: $yili-default-color;
}
&:hover {
border-color: $yili-btn-check-color;
color: $yili-btn-check-color;
svg path {
fill: $yili-btn-check-color;
}
}
}
&[disabled] {
border-color: $yili-disabled-color;
background-color: #fff;
color: $yili-disabled-color;
svg path {
fill: $yili-disabled-color;
}
}
}
&.ant-btn-primary {
&:not([disabled]) {
border-color: $yili-default-color;
background-color: $yili-default-color;
svg path {
fill: #fff;
}
&:hover {
border-color: $yili-btn-check-color;
background-color: $yili-btn-check-color;
svg path {
fill: #fff;
}
}
}
&[disabled] {
border-color: $yili-disabled-color;
background-color: $yili-disabled-color;
color: #fff;
svg path {
fill: #fff;
}
}
}
&.ant-btn-text {
&:not([disabled]) {
color: $yili-default-color;
svg path {
fill: $yili-default-color;
}
&:hover {
background-color: #fff;
color: $yili-btn-check-color;
svg path {
fill: $yili-btn-check-color;
}
}
}
&[disabled] {
color: $yili-disabled-color;
svg path {
fill: $yili-disabled-color;
}
}
}
&.ant-btn-link {
color: $yili-default-color;
}
&.ant-btn-lg {
height: 42px;
border-radius: 6px;
font-size: 16px;
}
&:not(.ant-btn-lg) ::v-deep {
> .anticon + span,
> span + .anticon {
margin-left: 6px;
}
}
}

View File

@@ -0,0 +1,96 @@
/**
* 自定义 checkbox 样式,为 a-checkbox 添加类
* 小尺寸custom-checkbox
* 大尺寸custom-checkbox-large
*
* 自定义 checkbox-group 样式,为 a-checkbox-group 添加类
* 小尺寸custom-checkbox-group
* 大尺寸custom-checkbox-group-large
*/
.custom-checkbox-group .ant-checkbox-wrapper,
.custom-checkbox {
.ant-checkbox,
.ant-checkbox-input,
.ant-checkbox-inner {
width: 16px;
height: 16px;
border-radius: 4px;
}
.ant-checkbox + span {
color: #262626;
}
&.ant-checkbox-wrapper-disabled {
.ant-checkbox-checked.ant-checkbox-disabled {
.ant-checkbox-inner {
background-color: #d9d9d9;
&::after {
border-color: #fff;
}
}
}
}
.ant-checkbox-checked {
&::after {
border-color: $yili-default-color;
border-radius: 4px;
}
.ant-checkbox-inner {
border-color: $yili-default-color;
background-color: $yili-default-color;
}
}
}
.custom-checkbox-group-large .ant-checkbox-wrapper,
.custom-checkbox-large {
.ant-checkbox,
.ant-checkbox-input,
.ant-checkbox-inner {
width: 20px;
height: 20px;
border-radius: 6px;
}
.ant-checkbox + span {
color: #262626;
}
&.ant-checkbox-wrapper-disabled {
.ant-checkbox-checked.ant-checkbox-disabled {
.ant-checkbox-inner {
background-color: #d9d9d9;
&::after {
border-color: #fff;
}
}
}
}
.ant-checkbox-checked {
&::after {
border-color: $yili-default-color;
border-radius: 6px;
}
.ant-checkbox-inner {
border-color: $yili-default-color;
background-color: $yili-default-color;
}
}
.ant-checkbox-inner {
&::after {
top: 47%;
left: 26%;
width: 6.5px;
height: 10.1px;
border-width: 3px !important;
}
}
}

View File

@@ -0,0 +1,17 @@
.custom-date-picker.ant-calendar-picker {
.ant-calendar-picker-input {
border-radius: 4px;
box-shadow: none;
outline: none;
&[disabled] {
color: #8c8c8c;
&::placeholder {
color: #bfbfbf;
font-weight: 400;
font-size: 14px;
}
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* 自定义 form 表单样式,为 a-form 添加类 custom-form
* 自定义 form-item 样式,为 a-form-item 添加 custom-form 类
*/
.custom-form {
&.ant-form-vertical .ant-form-item-label {
padding-bottom: 4px;
}
}
.custom-form .ant-form-item,
.custom-form-item.ant-form-item {
.ant-form-item-label {
padding-bottom: 4px;
}
.ant-form-item-no-colon {
margin-left: 4px;
}
&.ant-form-item-has-error {
.ant-input,
.ant-input-affix-wrapper,
.ant-input:hover,
.ant-input-affix-wrapper:hover {
border-color: #ff374f;
}
.ant-form-item-explain.ant-form-item-explain-error {
color: #ff374f;
}
}
}

View File

@@ -0,0 +1,11 @@
@import './button';
@import './input';
@import './form';
@import './select';
@import './checkbox';
@import './radio';
@import './switch';
@import './modal';
@import './date-picker';
@import './rate';
@import './tinymce';

View File

@@ -0,0 +1,198 @@
/**
* 自定义 input 样式,为 a-input 添加 custom-input 类
*/
.custom-form .ant-form-item .ant-input,
.custom-form-item.ant-form-item .ant-input,
.custom-form .ant-form-item .ant-input-affix-wrapper,
.custom-form-item.ant-form-item .ant-input-affix-wrapper,
.custom-form .ant-form-item .ant-input-group-wrapper,
.custom-form-item.ant-form-item .ant-input-group-wrapper,
.custom-input {
box-shadow: none;
outline: none;
font-family: PingFangSC-Regular, 'PingFang SC', sans-serif;
&:not(.ant-input-group-wrapper) {
padding-right: 12px;
padding-left: 12px;
}
&.ant-input-affix-wrapper .ant-input {
padding-right: 0;
padding-left: 0;
border-radius: 0;
font-size: 14px;
}
/* <=1024的设备 */
@media (max-width: 1024px) {
&.ant-input-affix-wrapper .ant-input {
padding-right: 0;
padding-left: 0;
border-radius: 0;
font-size: 16px;
}
}
/* <=600的设备 */
@media (max-width: 600px) {
&.ant-input-affix-wrapper .ant-input {
padding-right: 0;
padding-left: 0;
border-radius: 0;
font-size: 18px;
}
}
/**
* 禁用输入框
*/
&.ant-input.ant-input-disabled,
.ant-input.ant-input-disabled {
&[disabled] {
color: #8c8c8c;
font-size: 16px;
&::placeholder {
color: #bfbfbf;
font-weight: 400;
font-size: 14px;
}
}
}
/**
* 最简输入框
*/
&.ant-input {
border-color: #dfe0e3;
border-radius: 4px;
color: #262626;
&:focus {
border-color: $yili-default-color;
box-shadow: none;
outline: none;
}
}
/**
* 带清除按钮
*/
&.ant-input-affix-wrapper {
border-color: #dfe0e3;
border-radius: 4px;
color: #262626;
&.ant-input-affix-wrapper-focused {
border-color: $yili-default-color;
box-shadow: none;
outline: none;
}
}
/*
* 带有 addon-before addon-after 的 input
*/
&.ant-input-group-wrapper {
.ant-input-group-addon {
border-radius: 4px 0 0 4px;
}
.ant-input + .ant-input-group-addon,
.ant-input-affix-wrapper + .ant-input-group-addon {
border-radius: 0 4px 4px 0;
}
.ant-input,
.ant-input-affix-wrapper {
border-radius: 0;
}
*:last-child {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
}
/**
* 自定义 input-number 样式,为 a-input-number 添加 custom-input-number 类
*/
.custom-form .ant-form-item .ant-input-number,
.custom-form-item.ant-form-item .ant-input-number,
.custom-input-number.ant-input-number {
border-color: #dfe0e3;
border-radius: 4px;
color: #262626;
&.ant-input-number-focused {
border-color: $yili-default-color;
box-shadow: none;
outline: none;
}
.ant-input-number-handler-wrap {
width: 18px;
height: 22px;
border: none;
opacity: 1;
transform: translate(-6px, 4px);
}
.ant-input-number-handler,
.ant-input-number-handler-up,
.ant-input-number-handler-down {
position: relative;
height: 50% !important;
border-color: transparent;
.anticon {
position: absolute;
right: 6px;
width: 6px;
height: 5px;
margin: 0;
background-position: center center;
background-size: 6px 5px;
background-repeat: no-repeat;
transform: translate(0);
}
svg {
display: none;
}
}
.ant-input-number-handler-up {
.ant-input-number-handler-up-inner {
top: unset;
bottom: 1px;
background-image: url('~@/assets/img/customize/num_up.png');
&:hover {
background-image: url('~@/assets/img/customize/num_up_hover.png');
}
}
}
.ant-input-number-handler-down {
.ant-input-number-handler-down-inner {
top: 1px;
bottom: unset;
background-image: url('~@/assets/img/customize/num_down.png');
&:hover {
background-image: url('~@/assets/img/customize/num_down_hover.png');
}
}
}
}
.custom-form .ant-form-item textarea.ant-input,
.custom-form-item.ant-form-item textarea.ant-input,
textarea.custom-textarea.ant-input {
&[no-resize] {
resize: none;
}
}

View File

@@ -0,0 +1,124 @@
/**
* 自定义 modal 样式,为 a-modal 添加 wrapClassName 属性值 custom-modal
* 通知图标:添加 wrapClassName 属性值 custom-modal-title-notice
* 下载图标:添加 wrapClassName 属性值 custom-modal-title-download
*/
.custom-modal {
//font-family: PingFangSC-Semibold, PingFang SC, sans-serif;
&.custom-modal-title-notice,
&.custom-modal-title-confirm-notice,
&.custom-modal-title-download {
.ant-modal-header,
.ant-modal-confirm-title {
&::before {
content: '';
width: 24px;
height: 24px;
margin-right: 12px;
background-position: center center;
background-size: 24px 24px;
background-repeat: no-repeat;
}
}
.ant-modal-confirm-title::before {
display: inline-block;
}
}
&.custom-modal-title-download .ant-modal-header::before {
background-image: url('~@/assets/img/customize/title_download.png');
}
&.custom-modal-title-notice .ant-modal-header::before,
&.custom-modal-title-notice .ant-modal-confirm-title::before,
&.custom-modal-title-confirm-notice .ant-modal-confirm-title::before {
background-image: url('~@/assets/img/customize/title_notice.png');
}
&.custom-modal-title-notice .ant-modal-confirm-title,
&.custom-modal-title-confirm-notice .ant-modal-confirm-title {
display: flex;
align-items: center;
justify-content: flex-start;
line-height: normal;
}
&.hide-ant-icon .ant-modal-confirm-body span.anticon {
display: none;
}
.ant-modal-content {
overflow: hidden;
border-radius: 6px;
background: #fff;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
.ant-btn {
border-radius: 4px;
}
}
&.custom-modal-title-notice .ant-modal-confirm-body {
span.anticon.anticon-info-circle {
display: none;
}
}
.ant-modal-confirm-content {
padding-left: 38px;
}
span + span + .ant-modal-confirm-content {
padding-left: 0;
}
.ant-modal-header {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 32px 32px 0;
border-bottom: none;
color: #262626;
font-weight: 600;
font-size: 16px;
}
.ant-modal-footer {
padding: 0 32px 24px;
border-top: none;
.ant-btn {
margin-left: 12px;
padding-right: 14px;
letter-spacing: 6px;
}
}
.ant-modal-body {
overflow-y: auto;
max-height: 60vh;
padding-right: 32px;
padding-left: 32px;
}
&.custom-modal-height-70vh .ant-modal-body {
max-height: 70vh;
}
}
/**
* 全屏 modal 样式,为 a-modal 添加 wrapClassName 属性值 fullscreen-modal
*/
.fullscreen-modal {
overflow: hidden !important;
width: 100vw;
height: 100vh;
.ant-modal-body {
width: 100vw;
height: 100vh;
padding: 0;
}
}

View File

@@ -0,0 +1,91 @@
/**
* 自定义 radio 样式,为 a-checkbox 添加类
* 小尺寸custom-radio
* 大尺寸custom-radio-large
*
* 自定义 radio-group 样式,为 a-radio-group 添加类
* 小尺寸custom-radio-group
* 大尺寸custom-radio-group-large
*/
.custom-radio-group .ant-radio-wrapper,
.custom-radio {
.ant-radio,
.ant-radio-input,
.ant-radio-inner {
width: 16px;
height: 16px;
border-radius: 16px;
}
.ant-radio + span {
color: #262626;
}
&.ant-radio-wrapper-disabled {
.ant-radio-checked.ant-radio-disabled {
.ant-radio-inner {
background-color: #fff;
&::after {
background-color: #d9d9d9;
}
}
}
}
.ant-radio-checked {
.ant-radio-inner {
border-color: $yili-btn-border-color;
background-color: #fff;
&::after {
border-color: $yili-default-color;
background-color: $yili-default-color;
}
}
}
}
.custom-radio-group-large .ant-radio-wrapper,
.custom-radio-large {
.ant-radio,
.ant-radio-input,
.ant-radio-inner {
width: 20px;
height: 20px;
border-radius: 20px;
}
.ant-radio + span {
color: #262626;
}
&.ant-radio-wrapper-disabled {
.ant-radio-checked.ant-radio-disabled {
.ant-radio-inner {
background-color: #fff;
&::after {
background-color: #d9d9d9;
}
}
}
}
.ant-radio-checked {
.ant-radio-inner {
border-color: $yili-btn-border-color;
background-color: #fff;
&::after {
top: 2px;
left: 2px;
width: 14px;
height: 14px;
border-color: $yili-default-color;
border-radius: 14px;
background-color: $yili-default-color;
}
}
}
}

View File

@@ -0,0 +1,35 @@
.custom-rate.ant-rate {
text-align: left;
.ant-rate-star {
.ant-rate-star-first,
.ant-rate-star-second {
font-size: 28px;
}
&:not(:last-child) {
margin-right: 21px;
}
.icon.iconfont {
font-size: 23px;
}
}
}
.custom-rate-mob.ant-rate {
.ant-rate-star {
.ant-rate-star-first,
.ant-rate-star-second {
font-size: 20px;
}
&:not(:last-child) {
margin-right: 12px;
}
.icon.iconfont {
font-size: 20px;
}
}
}

View File

@@ -0,0 +1,100 @@
/**
* 自定义 select 样式,为 a-select 添加 custom-select 类
*/
.custom-form .ant-form-item .ant-select,
.custom-form-item.ant-form-item .ant-select,
.custom-select {
border-radius: 4px;
&.ant-select .ant-select-selector {
padding-right: 12px;
}
&.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector {
border-color: $yili-default-color;
box-shadow: none;
outline: none;
}
&.ant-select:not(.ant-select-customize-input) .ant-select-selector {
border-radius: 4px;
}
.ant-select-arrow .ant-select-suffix {
&::after {
content: '';
display: inline-block;
width: 12px;
height: 10px;
background-image: url('~@/assets/img/customize/arrow_down_black.png');
background-position: center center;
background-size: 100% 100%;
background-repeat: no-repeat;
}
svg {
display: none;
}
}
&.ant-select-disabled {
.ant-select-selection-placeholder {
color: #bfbfbf;
font-weight: 400;
font-size: 14px;
}
.ant-select-selection-item {
color: #8c8c8c;
}
.ant-select-arrow .ant-select-suffix {
&::after {
background-image: url('~@/assets/img/customize/arrow_down_gray.png');
}
}
}
/**
* 多选
*/
.ant-select-selection-overflow-item .ant-select-selection-item {
border-color: #f5f5f5;
border-radius: 4px;
.ant-select-selection-item-content {
color: #262626;
}
.ant-select-selection-item-remove {
display: inline-flex;
align-items: center;
justify-content: center;
.anticon-close {
display: inline-flex;
flex: none;
align-items: center;
justify-content: center;
width: 12px;
height: 12px;
border-radius: 12px;
background-color: #bfbfbf;
line-height: normal;
svg {
font-size: 7px;
path {
fill: #f0f0f0;
stroke: #f0f0f0;
}
}
&:hover {
background-color: #8c8c8c;
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
/**
* 自定义 switch 样式,为 a-switch 添加 custom-switch 类
*/
.custom-form .ant-form-item .ant-switch,
.custom-form-item.ant-form-item .ant-switch,
.custom-switch.ant-switch {
width: 38px;
min-width: 38px;
height: 20px;
background-color: #bfbfbf;
line-height: 18px;
opacity: 1;
&.ant-switch-checked {
background-color: $yili-default-color;
&.ant-switch-disabled {
background-color: #d5ebc3;
}
}
&.ant-switch-disabled {
background-color: #f5f5f5;
}
&::after {
width: 16px;
height: 16px;
}
}

View File

@@ -0,0 +1,82 @@
.tox .tox-edit-area__iframe {
background: #f5f5f5 !important;
}
.tox .tox-edit-area {
border: none !important;
}
.tox-silver-sink .tox-pop__dialog {
border: none !important;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2) !important;
}
.tox .tox-pop.tox-pop--top::after {
display: none !important;
}
.tox .tox-pop.tox-pop--top::before {
display: none !important;
}
.tox .tox-pop.tox-pop--left::after {
display: none !important;
}
.tox .tox-pop.tox-pop--left::before {
display: none !important;
}
.tox .tox-pop.tox-pop--right::after {
display: none !important;
}
.tox .tox-pop.tox-pop--right::before {
display: none !important;
}
.tox .tox-pop.tox-pop--bottom::after {
display: none !important;
}
.tox .tox-pop.tox-pop--bottom::before {
display: none !important;
}
.tox.tox-tbtn:focus {
background: #fff !important;
}
.tox .tox-tbtn:hover {
background: #fff !important;
}
.tox .tox-tbtn:hover svg {
fill: $yili-default-color !important;
}
.tox .tox-tbtn:active {
background: #fff !important;
}
.tox .tox-tbtn:active svg {
fill: $yili-default-color !important;
}
.tox .tox-tbtn--enabled,
.tox .tox-tbtn--enabled:hover {
background: #fff !important;
}
.tox .tox-tbtn--enabled svg,
.tox .tox-tbtn--enabled:hover svg {
fill: $yili-default-color !important;
}
.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) {
color: #222f3e !important;
}
.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg {
fill: $yili-default-color !important;
}

54
src/style/font.scss Normal file
View File

@@ -0,0 +1,54 @@
.fs-8 {
font-size: 8px !important;
}
.fs-10 {
font-size: 10px !important;
}
.fs-12 {
font-size: 12px !important;
}
.fs-14 {
font-size: 14px !important;
}
.fs-16 {
font-size: 16px !important;
}
.fw-bold {
font-weight: bold;
}
.fw-bolder {
font-weight: bolder;
}
.fw-400 {
font-weight: 400;
}
.fw-500 {
font-weight: 500;
}
.fw-600 {
font-weight: 600;
}
.fw-700 {
font-weight: 700;
}
.fw-800 {
font-weight: 800;
}
.fw-900 {
font-weight: 900;
}

44
src/style/scroller.scss Normal file
View File

@@ -0,0 +1,44 @@
.scroller-x {
overflow-x: auto;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: #fff;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: #d8d8d8;
}
}
.scroller-y {
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: #fff;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: #d8d8d8;
}
}
.scroller-xy {
overflow: auto;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: #fff;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: #d8d8d8;
}
}

23
src/style/utils.scss Normal file
View File

@@ -0,0 +1,23 @@
/*
覆盖全局样式
*/
@import './box';
.ant-select-item-option-selected {
font-weight: normal !important;
}
.ant-select-selector {
border-radius: 4px;
}
.ant-select-dropdown.ant-select-dropdown-placement-bottomLeft {
border-radius: 4px;
}
.ant-table-column-sorter {
position: relative;
top: -2px;
}

View File

@@ -0,0 +1,5 @@
$yili-default-color: #70b936;
$yili-disabled-color: rgba(112, 185, 54, 0.3);
$yili-btn-check-color: #8ec75a;
$yili-btn-border-color: #74dd1d;
$yili-bkg-color: rgba(112, 185, 54, 0.11);

View File

@@ -10,11 +10,22 @@
>
<template #item="{ element, index }">
<choose-question
:element="element" :index="index" :chooseQuestionId="chooseQuestionId"
:element="element"
:index="index"
:chooseQuestionId="chooseQuestionId"
@get-choose-question-id="getChooseQuestionId"
>
<base-select v-if="element.question_type === 1" :element="element"></base-select>
</choose-question>
<!-- {{ element.question_type }}-->
<!-- {{questionInfo.survey.pages.length}}-->
<paging
v-if="!element.question_type && questionInfo.survey.pages.length > 1"
:info="element"
:index="index"
:active="pageIsActive(activeIndex, questionInfo.questions, element.page)"
@click.stop=""
/>
</template>
</draggable>
</div>
@@ -37,9 +48,58 @@ const BaseSelect = defineAsyncComponent(() => {
const ChooseQuestion = defineAsyncComponent(() => {
return import('./components/ChooseQuestion.vue');
});
const questionInfo = ref({ questions: [] });
const chooseQuestionId = ref('');
const Paging = defineAsyncComponent(() => {
return import('./components/Questions/paging/Paging.vue');
});
const chooseQuestionId = ref('');
const questionInfo = ref(storeToRefs(counterStore).questionsInfo);
/**
* 工具函数
*/
function util() {
/** 通过id找到数组中对应的下标 */
const getIndexById = (arr, id) => arr.findIndex((i) => i.id === id);
/** 通过分页找到数组中对应的下标 */
const getIndexByPage = (arr, page) => arr.findIndex((i) => i.page === page);
const quesIsActive = (activeIndex, questionList, eleId) => {
return activeIndex === getIndexById(questionList, eleId);
};
const pageIsActive = (activeIndex, questionList, elePage) => {
return activeIndex === getIndexByPage(questionList, elePage);
};
const quesIsDisabled = (questionIndex, disabledQuestionIndex) => {
return (disabledQuestionIndex || []).includes(questionIndex);
};
const pagingDisabled = (index, question, disabledQuestionIndex) => {
if (disabledQuestionIndex.includes(question?.[index - 1]?.questionIndex)) {
return true;
}
for (let i = 1; i < question.length - 1; i++) {
if (question?.[index + i]?.page) {
continue;
}
if (disabledQuestionIndex.includes(question?.[index + i]?.questionIndex)) {
return true;
}
}
return false;
};
/** 工具函数复制store中的数据避免直接更改store */
const copyStoreContent = (store) => {
return JSON.parse(JSON.stringify(store.state.common));
};
return {
getIndexById,
getIndexByPage,
quesIsActive,
pageIsActive,
quesIsDisabled,
pagingDisabled,
copyStoreContent
};
}
const { pageIsActive } = util();
// 获取选中的题目的ID
const getChooseQuestionId = (questionItem) => {
chooseQuestionId.value = questionItem.id;
@@ -47,8 +107,5 @@ const getChooseQuestionId = (questionItem) => {
onMounted(() => {
questionInfo.value = store.questionsInfo.value;
});
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -1,10 +1,20 @@
<template>
<div class="choose-question-container">
<div :class="chooseQuestionId===element.id ? 'choose-question-active':''" @click="chooseItem">
<div
class="choose-question-context"
:class="chooseQuestionId === element.id ? 'choose-question-active' : ''"
@click="chooseItem"
>
<slot></slot>
<!-- 题目操作-->
<div v-if="chooseQuestionId===element.id" class="choose-question-active-container">
<div v-for="item in questionAction" :key="item.key" class="" :class="item.class?item.class:''" @click="itemAction(item)">
<div v-if="chooseQuestionId === element.id" class="choose-question-active-container">
<div
v-for="item in questionAction"
:key="item.key"
class=""
:class="item.class ? item.class : ''"
@click="itemAction(item)"
>
<i class="icon iconfont choose-question-active-container-icon" v-html="item.icon"></i>
<div class="choose-question-active-container-name">
{{ item.name }}
@@ -79,24 +89,28 @@ const chooseItem = () => {
const itemAction = (item) => {
switch (item.key) {
case 'edit':
break;
case 'copy':
break;
case 'moveUp':
break;
case 'moveDown':
break;
case 'delete':
break;
case 'edit':
break;
case 'copy':
break;
case 'moveUp':
break;
case 'moveDown':
break;
case 'delete':
break;
}
};
</script>
<style scoped lang="scss">
.choose-question-container {
padding: 5px;
& .choose-question-context {
overflow: hidden;
border-radius: 5px;
}
& .choose-question-active {
outline: 1px dashed #409eff;
}
@@ -118,5 +132,4 @@ const itemAction = (item) => {
}
}
}
</style>

View File

@@ -2,9 +2,12 @@
<van-field
v-model="element.stem"
:label="element.stem"
:required="element.config.is_required=== 1"
:required="element.config.is_required === 1"
label-align="top"
>
<template #label>
<div v-html="element.stem"></div>
</template>
<template #input>
<van-checkbox-group>
<template v-for="item in element.options">
@@ -40,6 +43,4 @@ const props = defineProps({
});
const element = ref(props.element);
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,195 @@
<template>
<div>
<ConfigTitle title="分页设置" :quiz-index="quizIndex" />
<ConfigBaseItem class="line-item">
<div class="title">
<span>最短时间</span>
<a-tooltip :mouseEnterDelay="0.2" :overlayStyle="{ width: '248px' }">
<template #title>
<div style="padding: 0 4px; font-size: 12px">
设置最短时间后用户即使填写内容未到规定时间也不可以点击进入下一页
</div>
</template>
<i class="iconfont use-min-icon">&#xe72b;</i>
</a-tooltip>
</div>
<div class="row">
<a-switch
:checked="!!+copyConfig.is_short_time"
class="custom-switch"
@change="switchChanged('is_short_time', $event)"
/>
</div>
</ConfigBaseItem>
<template v-if="copyConfig.is_short_time">
<ConfigBaseItem class="line-item">
<div class="title">本页至少显示</div>
<div class="row">
<a-input-number
ref="shortTimeRef"
v-model:value="copyConfig.short_time"
:formatter="shortTimeFormatter"
:parser="(val) => (val === '不限' ? 0 : val || 0)"
:min="0"
placeholder="不限"
class="custom-input-number display-count"
@focus="inputSelectAll(shortTimeRef)"
@blur="numberChange('short_time', copyConfig.short_time)"
/>
<span>S</span>
</div>
</ConfigBaseItem>
<ConfigBaseItem class="line-item">
<div class="title">显示计时器</div>
<div class="row">
<a-switch
:checked="!!+copyConfig.is_show"
class="custom-switch"
@change="switchChanged('is_show', $event)"
/>
</div>
</ConfigBaseItem>
<ConfigBaseItem class="line-item">
<div class="title">批量应用</div>
</ConfigBaseItem>
<a-radio-group
v-model:value="copyConfig.use_type"
@change="radioChange('use_type', copyConfig.use_type)"
>
<ConfigBaseItem v-for="item in useTypes" :key="item.id" class="line-item">
<div class="title">
<a-radio :value="item.id">{{ item.name }}</a-radio>
</div>
</ConfigBaseItem>
</a-radio-group>
<ConfigBaseItem v-if="[2].includes(copyConfig.use_type)" class="line-item">
<span></span>
<a-select
v-model:value="copyConfig.pages"
:get-pupup-container="(el) => el.parentNode"
mode="multiple"
placeholder="请选择页码"
class="custom-select select-page"
@blur="selectChanged('pages', copyConfig.pages)"
>
<a-select-option v-for="(item, index) in pages" :key="index + 1" :value="index + 1">
{{ index + 1 }}
</a-select-option>
</a-select>
<span>页</span>
</ConfigBaseItem>
</template>
</div>
</template>
<script setup>
import { computed, defineEmits, defineProps, reactive, ref } from 'vue';
import { useStore } from 'vuex';
import ConfigTitle from '../../components/config/ConfigTitle.vue';
import ConfigBaseItem from '../../components/config/ConfigBaseItem.vue';
const store = useStore();
const emits = defineEmits(['update']);
const props = defineProps({
config: { type: Object, default: Object.create(null) }
});
const copyConfig = computed(() => {
return reactive({
is_short_time: 0,
short_time: 0,
is_show: 0,
use_type: 0,
pages: [],
...JSON.parse(JSON.stringify(props.config || {}))
});
});
const quizIndex = computed(() => `第${copyConfig.value.page}页`);
const pages = computed(() => store.state.common.questionInfo.survey?.pages || []);
const useTypes = ref([
{ id: 0, name: '应用到当前页' },
{ id: 1, name: '应用到所有页' },
{ id: 2, name: '应用到指定页' }
]);
const shortTimeRef = ref(null);
function inputSelectAll(elRef) {
const inputEl = elRef?.$el?.querySelector('input');
if (inputEl.select) {
inputEl.select();
}
}
function shortTimeFormatter(val) {
const num = `${val}`.replace(/[^0-9]/g, '');
return !+num ? '不限' : num;
}
function switchChanged(field, value) {
update({ [field]: +value });
}
function numberChange(field, value) {
update({ [field]: +value });
}
function radioChange(field, value) {
const info = { [field]: +value };
if (field === 'use_type' && +value !== 2) {
info.pages = [];
}
update(info);
}
function selectChanged(field, value) {
update({ [field]: value });
}
function update(values) {
emits('update', {
...(copyConfig.value || {}),
...(values || {})
});
}
</script>
<style scoped lang="scss">
.line-item {
height: 48px;
padding-top: 0;
padding-bottom: 0;
border-bottom: none !important;
.title,
.row {
margin-top: 0;
margin-bottom: 0;
}
}
.use-min-icon {
margin-left: 8px;
color: #bfbfbf;
font-size: 14px;
}
.display-count {
margin-right: 16px;
}
.select-page {
flex: 1 1 1px;
margin-right: 4px;
margin-left: 4px;
}
</style>

View File

@@ -0,0 +1,125 @@
<template>
<div class="page" :class="{ active: active }">
<div class="page-text">
<div class="page-text-line"></div>
<span class="page-text-content">
{{ info.page }}/{{ info.total }} {{ info.first_title }}-{{ info.last_title }}
</span>
<div class="page-text-line"></div>
<div v-if="!isOneQuestionPerPage" class="page-icon">
<i
class="iconfont active-icon"
:style="{ marginRight: isLastPage ? '0' : '16px' }"
@click="activePage"
>&#xe86c;</i
>
<template v-if="!isLastPage">
<i class="iconfont moverQues" style="margin-right: 16px">&#xe71b;</i>
<i class="iconfont" @click="deleteHandle">&#xe6c5;</i>
</template>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
// Props 定义
const props = defineProps({
info: {
type: Object,
default: () => ({})
},
index: {
type: Number,
default: 0
},
active: {
type: Boolean,
default: false
}
});
// 使用 Pinia Store
const counterStore = useCounterStore();
const { questionsInfo } = storeToRefs(counterStore);
// 计算属性
const isOneQuestionPerPage = computed(() => {
return !!questionsInfo.value?.survey?.is_one_page_one_question;
});
const isLastPage = computed(() => {
return props.index === (questionsInfo.value?.questions?.length - 1 || -1);
});
// 自定义事件
const emit = defineEmits(['activePage', 'removePage']);
function activePage() {
emit('activePage');
}
function deleteHandle() {
emit('removePage');
}
</script>
<style lang="scss" scoped>
@import '@/style/whole/whole';
.page {
&.active {
color: $yili-default-color;
.page-text-line {
border-color: $yili-default-color;
}
.active-icon {
color: $yili-default-color;
}
}
.page-text {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 38px;
border-radius: 6px;
}
.page-text-line {
flex: 1;
height: 1px;
border: 1px dashed #bfbfbf;
}
.page-text-content {
margin: 0 3px;
cursor: default;
}
.page-icon {
display: flex;
align-items: center;
padding-right: 18px;
padding-left: 20px;
}
.iconfont {
width: 24px;
height: 24px;
color: #dadada;
cursor: pointer;
&:hover {
color: $yili-default-color;
}
}
}
</style>